ALSA Sequencer Interim checkin.

This commit is contained in:
Robin E. R. Davies
2025-06-28 18:41:44 -04:00
parent acb668853b
commit 862d5b6315
17 changed files with 801 additions and 434 deletions
+375 -49
View File
@@ -26,11 +26,83 @@
#include <string>
#include <regex>
#include "ss.hpp"
#include <alsa/asoundlib.h>
#include <alsa/seq.h>
#include <stdexcept>
#include <poll.h>
#include <cstring>
#include "Finally.hpp"
#include "Lv2Log.hpp"
#include <mutex>
// enumerate alsa sequencer ports
namespace pipedal
{
namespace impl
{
enum class ConnectAction
{
Subscribe,
Unsubscribe
};
class AlsaSequencerImpl : public AlsaSequencer
{
public:
AlsaSequencerImpl();
public:
~AlsaSequencerImpl();
virtual void ConnectPort(int clientId, int portId) override;
virtual void ConnectPort(const std::string &name) override;
// Read a single MIDI message from the sequencer input port. A timeout of -1 blocks indefinitely.
// A timeout of 0 returns immediately.
virtual bool ReadMessage(AlsaMidiMessage &message, int timeoutMs = -1) override;
// Get current real-time from the queue (useful for calculating precise timing)
virtual bool GetQueueRealtime(uint64_t *sec, uint32_t *nsec) override;
virtual void RemoveAllConnections() override;
private:
void ModifyConnection(int clientId, int portId, ConnectAction action);
// Get the current queue ID (returns -1 if no queue is active)
int GetQueueId() const { return queueId; }
bool WaitForMessage(int timeoutMs);
// Create an ALSA input queue with real-time timestamps for the given client/port
int CreateRealtimeInputQueue();
struct Connection
{
int clientId;
int portId;
};
int myClientId = -1;
std::mutex connectionsMutex;
std::vector<Connection> connections;
std::vector<struct pollfd> pollFds; // For polling input events
snd_seq_t *seqHandle = nullptr;
int inPort = -1;
int queueId = -1; // Queue for real-time timestamps
};
};
using namespace impl;
AlsaSequencer::ptr AlsaSequencer::Create()
{
return std::make_shared<AlsaSequencerImpl>();
}
std::vector<AlsaSequencerPort> AlsaSequencer::EnumeratePorts()
{
std::vector<AlsaSequencerPort> ports;
@@ -149,7 +221,7 @@ namespace pipedal
return ports;
}
AlsaSequencer::AlsaSequencer()
AlsaSequencerImpl::AlsaSequencerImpl()
{
seqHandle = nullptr;
queueId = -1;
@@ -173,14 +245,29 @@ namespace pipedal
// convert rc to message
throw std::runtime_error(SS("Failed to open ALSA sequencer:" << snd_strerror(inPort)));
}
CreateRealtimeInputQueue();
snd_seq_nonblock(seqHandle, 1); // Set sequencer to non-blocking mode
// Get our client and port numbers for reference
myClientId = snd_seq_client_id(seqHandle);
if (myClientId < 0)
{
throw std::runtime_error(SS("Failed to get client ID: " << snd_strerror(myClientId)));
}
AlsaSequencer::~AlsaSequencer()
}
void AlsaSequencerImpl::RemoveAllConnections()
{
for (const auto &connection : connections)
{
snd_seq_disconnect_from(seqHandle, inPort, connection.clientId, connection.portId);
}
connections.clear();
}
AlsaSequencerImpl::~AlsaSequencerImpl()
{
RemoveAllConnections();
if (queueId >= 0)
{
snd_seq_free_queue(seqHandle, queueId);
@@ -198,25 +285,17 @@ namespace pipedal
}
}
void AlsaSequencer::ConnectPort(int clientId, int portId)
void AlsaSequencerImpl::ConnectPort(int clientId, int portId)
{
CreateRealtimeInputQueue();
// Connect this input port to the target client:port
int rc = snd_seq_connect_from(seqHandle, inPort, clientId, portId);
if (rc < 0)
{
throw std::runtime_error(SS("Failed to connect to ALSA port: " << snd_strerror(rc)));
}
this->connections.push_back({clientId, portId});
ModifyConnection(clientId, portId, ConnectAction::Subscribe);
}
void AlsaSequencer::ConnectPort(const std::string &name)
void AlsaSequencerImpl::ConnectPort(const std::string &id)
{
auto ports = EnumeratePorts();
for (const auto &port : ports)
{
if (port.name == name)
if (port.id == id)
{
ConnectPort(port.client, port.port);
return;
@@ -225,33 +304,63 @@ namespace pipedal
throw std::runtime_error("ALSA port not found");
}
void AlsaSequencer::WaitForMessage() {
bool AlsaSequencerImpl::WaitForMessage(int timeoutMs)
{
while (true)
{
auto fdCount = snd_seq_poll_descriptors_count(seqHandle, POLLIN);
if (fdCount == 0) return;
if (fdCount == 0)
return true;
this->pollFds.resize(fdCount);
snd_seq_poll_descriptors(seqHandle, pollFds.data(), fdCount, POLLIN);
poll(pollFds.data(), fdCount, -1); // Wait indefinitely for input
int rc = poll(pollFds.data(), fdCount, timeoutMs);
if (rc == 0)
{
return false; // timed out.
}
bool AlsaSequencer::ReadMessage(AlsaMidiMessage &message, bool block)
else if (rc < 0)
{
if (errno == EINTR)
{
continue;
}
else
{
throw std::runtime_error(SS("ALSA sequencer poll error: " << strerror(errno)));
}
}
else
{
return true;
}
}
}
bool AlsaSequencerImpl::ReadMessage(AlsaMidiMessage &message, int timeoutMs)
{
// Event loop
snd_seq_event_t *event = nullptr;
while (true) {
while (true)
{
bool success = false;
int rc = snd_seq_event_input(seqHandle, &event);
if (rc < 0) {
if (rc == -EAGAIN) {
if (!block) {
if (rc < 0)
{
if (rc == -EAGAIN)
{
if (!WaitForMessage(timeoutMs))
{
return false;
}
WaitForMessage();
} else {
}
else
{
// Handle other errors
throw std::runtime_error(SS("ALSA sequencer input error: " << snd_strerror(rc)));
}
} else if (event)
}
else if (event)
{
success = true;
// Extract timestamp information
@@ -263,58 +372,198 @@ namespace pipedal
switch (event->type)
{
case SND_SEQ_EVENT_NOTEON:
message.cc0 = 0x90 | event->data.note.channel; // channel
message.cc1 = event->data.note.note; // note
message.cc2 = event->data.note.velocity; // velocity
message.Set(
(uint8_t)(0x90 | event->data.note.channel),
(uint8_t)(event->data.note.note), // note
(uint8_t)(event->data.note.velocity)); // velocity
break;
case SND_SEQ_EVENT_NOTEOFF:
// handle note-off
message.cc0 = 0x80 | event->data.note.channel; // channel
message.cc1 = event->data.note.note; // note
message.cc2 = event->data.note.off_velocity; // off velocity
message.Set(
uint8_t(0x80 | event->data.note.channel),
uint8_t(event->data.note.note),
uint8_t(event->data.note.off_velocity)); // off velocity
break;
case SND_SEQ_EVENT_KEYPRESS:
message.cc0 = 0xA0 | event->data.note.channel; // polyphonic key pressure
message.cc1 = event->data.note.note; // note
message.cc2 = event->data.note.velocity; // pressure
message.Set(
(uint8_t)(0xA0 | event->data.note.channel), // polyphonic key pressure
(uint8_t)(event->data.note.note), // note
(uint8_t)(event->data.note.velocity)); // pressure
break;
case SND_SEQ_EVENT_CONTROLLER:
message.cc0 = 0xB0 | event->data.control.channel; // control change
message.cc1 = event->data.control.param; // controller number
message.cc2 = event->data.control.value; // controller value
message.Set(
(uint8_t)(0xB0 | event->data.control.channel), // control change
(uint8_t)(event->data.control.param), // controller number
(uint8_t)(event->data.control.value)); // controller value
break;
case SND_SEQ_EVENT_PGMCHANGE:
message.cc0 = 0xC0 | event->data.control.channel; // program change
message.cc1 = event->data.control.value; // program number
message.cc2 = 0; // unused
message.Set(
(uint8_t)(0xC0 | event->data.control.channel), // program change
(uint8_t)(event->data.control.value));
break;
case SND_SEQ_EVENT_CHANPRESS:
message.cc0 = 0xD0 | event->data.control.channel; // channel pressure
message.cc1 = event->data.control.value; // pressure value
message.cc2 = 0; // unused
message.Set(
uint8_t(0xD0 | event->data.control.channel),
uint8_t(event->data.control.value));
break;
case SND_SEQ_EVENT_PITCHBEND:
message.cc0 = 0xE0 | event->data.control.channel; // pitch bend
message.cc1 = (event->data.control.value >> 7) & 0x7F; // MSB
message.cc2 = event->data.control.value & 0x7F; // LSB
message.Set(uint8_t(0xE0 | event->data.control.channel),
uint8_t((event->data.control.value >> 7) & 0x7F),
uint8_t(event->data.control.value & 0x7F));
break;
case SND_SEQ_EVENT_CONTROL14:
message.size = 6;
message.data = message.fixedBuffer;
message.fixedBuffer[0] = uint8_t(0xB0 | event->data.control.channel); // Control Change 14-bit
message.fixedBuffer[1] = uint8_t(event->data.control.param); // MSB
message.fixedBuffer[2] = uint8_t(event->data.control.param >> 7) & 0x7F; // MSB
message.fixedBuffer[3] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[4] = uint8_t(event->data.control.value + 0x20);
message.fixedBuffer[5] = uint8_t(event->data.control.value) & 0x7F; // MSB value
break;
case SND_SEQ_EVENT_NONREGPARAM:
message.size = 12;
message.data = message.fixedBuffer;
message.fixedBuffer[0] = uint8_t(0xB0 | event->data.control.channel); // Non-registered parameter
message.fixedBuffer[1] = 0x63; // MSB
message.fixedBuffer[2] = uint8_t(event->data.control.param >> 7) & 0x7F; // MSB
message.fixedBuffer[3] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[4] = 0x62; // LSB
message.fixedBuffer[5] = uint8_t(event->data.control.param) & 0x7F; // LSB
message.fixedBuffer[6] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[7] = uint8_t(0x06); // Non-registered parameter value MSB
message.fixedBuffer[8] = uint8_t(event->data.control.value >> 7) & 0x7F; // MSB value
message.fixedBuffer[9] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[10] = uint8_t(0x26); // Non-registered parameter value LSB
message.fixedBuffer[11] = uint8_t(event->data.control.value) & 0x7F; // LSb
break;
case SND_SEQ_EVENT_REGPARAM:
message.size = 12;
message.data = message.fixedBuffer;
message.fixedBuffer[0] = uint8_t(0xB0 | event->data.control.channel); // Registered parameter
message.fixedBuffer[1] = uint8_t(0x65); // MSB
message.fixedBuffer[2] = uint8_t(event->data.control.param >> 7) & 0x7F; // MSB
message.fixedBuffer[3] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[4] = uint8_t(0x64); // LSB
message.fixedBuffer[5] = uint8_t(event->data.control.param) & 0x7F; // LSB
message.fixedBuffer[6] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[7] = uint8_t(0x6); // Registered parameter value MSB
message.fixedBuffer[8] = uint8_t(event->data.control.value >> 7) & 0x7F; // MSB value
message.fixedBuffer[9] = uint8_t(0xB0 | event->data.control.channel);
message.fixedBuffer[10] = uint8_t(0x26); // Registered parameter value LSB
message.fixedBuffer[11] = uint8_t(event->data.control.value) & 0x7F; // LSB value
break;
case SND_SEQ_EVENT_SONGPOS:
message.Set(
0xF2,
(uint8_t)((event->data.control.value >> 7) & 0x7F), // MSB
(uint8_t)(event->data.control.value & 0x7F) // LSB
);
break;
case SND_SEQ_EVENT_SONGSEL:
message.Set(
0xF3,
(uint8_t)((event->data.control.value >> 7) & 0x7F), // MSB
(uint8_t)(event->data.control.value & 0x7F) // LSB
);
break;
case SND_SEQ_EVENT_QFRAME:
message.Set(
0xF1,
(uint8_t)((event->data.control.value >> 7) & 0x7F), // MSB
(uint8_t)(event->data.control.value & 0x7F) // LSB
);
break;
case SND_SEQ_EVENT_START:
message.Set(0xFA); // MIDI Real Time Start
break;
case SND_SEQ_EVENT_CONTINUE:
message.Set(0xFB); // MIDI Real Time Continue
break;
case SND_SEQ_EVENT_STOP:
message.Set(0xFC); // MIDI Real Time Stop
break;
case SND_SEQ_EVENT_TICK:
message.Set(0xF8); // MIDI Real Time Clock Tick
break;
case SND_SEQ_EVENT_SENSING:
message.Set(0xFE); // MIDI Real Time Active Sensing
break;
case SND_SEQ_EVENT_RESET:
message.Set(0xFF); // MIDI Real Time System Reset
break;
case SND_SEQ_EVENT_SYSEX:
// Handle SysEx messages
if (event->data.ext.len > 0 && event->data.ext.len + 2 <= sizeof(message.fixedBuffer))
{
message.size = event->data.ext.len + 1; // +1 for SysEx
message.data = message.fixedBuffer;
message.fixedBuffer[0] = 0xF0; // Start of SysEx
memcpy(message.fixedBuffer + 1, event->data.ext.ptr, event->data.ext.len);
message.fixedBuffer[event->data.ext.len + 1] = 0xF7; // End of SysEx
}
else
{
message.size = event->data.ext.len;
message.data = (uint8_t *)event->data.ext.ptr;
if (message.data[0] != 0xF0)
{
throw std::logic_error("Invalid SysEx message: does not start with 0xF0");
}
}
break;
case SND_SEQ_EVENT_SETPOS_TICK:
message.size = 3 + sizeof(event->data.queue.param.value); // Tempo events are usually 3 bytes
message.data = message.fixedBuffer;
message.fixedBuffer[0] = 0xFF; // Meta event type for tempo
message.fixedBuffer[1] = (uint8_t)MetaEventType::SetPositionTick; // Meta event subtype for tempo
message.fixedBuffer[2] = (uint8_t)(event->data.queue.queue); // MSB
memcpy(message.fixedBuffer + 3, &event->data.queue.param.value, sizeof(event->data.queue.param.value));
break;
case SND_SEQ_EVENT_SETPOS_TIME:
message.size = 3 + sizeof(event->data.queue.param.time); // Tempo events are usually 3 bytes
message.data = message.fixedBuffer;
message.fixedBuffer[0] = 0xFF; // Meta event type for tempo
message.fixedBuffer[1] = (uint8_t)MetaEventType::SetPositionTime; // Meta event subtype for tempo
message.fixedBuffer[2] = (uint8_t)(event->data.queue.queue);
memcpy(message.fixedBuffer + 3, &event->data.queue.param.time, sizeof(event->data.queue.param.time));
break;
case SND_SEQ_EVENT_TEMPO:
// Handle tempo events
message.size = sizeof(event->data.queue.param.value) + 3; // Tempo events are usually 3 bytes
if (message.size > sizeof(message.fixedBuffer))
{
throw std::logic_error("Tempo event size exceeds fixed buffer size");
}
message.data = message.fixedBuffer;
message.fixedBuffer[0] = 0xFF; // Meta event type for tempo
message.fixedBuffer[1] = (uint8_t)MetaEventType::Tempo; // Meta event subtype for tempo
message.fixedBuffer[2] = (uint8_t)(event->data.queue.queue);
memcpy(message.fixedBuffer + 3, &event->data.queue.param.value, sizeof(event->data.queue.param.value));
break;
case SND_SEQ_EVENT_CLOCK:
message.Set(0xF8); // MIDI Real Time Clock Tick
break;
case SND_SEQ_EVENT_KEYSIGN:
case SND_SEQ_EVENT_TIMESIGN:
// and a PASSEL of others!
default:
success = false;
break;
}
snd_seq_free_event(event);
}
if (success) {
if (success)
{
return true;
}
}
}
int AlsaSequencer::CreateRealtimeInputQueue()
int AlsaSequencerImpl::CreateRealtimeInputQueue()
{
if (!seqHandle)
{
@@ -381,7 +630,7 @@ namespace pipedal
return queueId;
}
bool AlsaSequencer::GetQueueRealtime(uint64_t *sec, uint32_t *nsec)
bool AlsaSequencerImpl::GetQueueRealtime(uint64_t *sec, uint32_t *nsec)
{
if (!seqHandle || queueId < 0)
{
@@ -418,4 +667,81 @@ namespace pipedal
return {};
}
void AlsaSequencerImpl::ModifyConnection(int clientId, int portId, ConnectAction action)
{
std::lock_guard<std::mutex> lock(connectionsMutex);
// use our own seq handle so that we can do this free-threaded.
snd_seq_t *seq;
if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
{
throw std::runtime_error("Failed to open ALSA sequencer for connection");
}
Finally seq_finally([seq]() {
snd_seq_close(seq);
});
snd_seq_addr_t sender, dest;
dest.client = myClientId;
dest.port = 0;
sender.client = clientId;
sender.port = portId;
snd_seq_port_subscribe_t *subs;
int queue = this->queueId;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_port_subscribe_set_sender(subs, &sender);
snd_seq_port_subscribe_set_dest(subs, &dest);
snd_seq_port_subscribe_set_queue(subs, queue);
snd_seq_port_subscribe_set_exclusive(subs, 0);
if (action == ConnectAction::Unsubscribe)
{
if (snd_seq_get_port_subscription(seq, subs) < 0)
{
Lv2Log::warning(
"Failed to disconnect ALSA sequencer port %d:%d. Subscripton not found.",
(int)clientId,
(int)portId);
} else
{
int rc = snd_seq_unsubscribe_port(seq, subs);
if (rc < 0) {
Lv2Log::warning(
"Failed to disconnect ALSA sequencer port %d:%d. (%s)",
(int)clientId,
(int)portId,
snd_strerror(rc));
}
}
for (auto it = this->connections.begin(); it != this->connections.end();++it)
{
if (it->clientId == clientId && it->portId == portId)
{
it = this->connections.erase(it);
break;
}
}
}
else
{
if (snd_seq_get_port_subscription(seq, subs) == 0)
{
Lv2Log::warning("ALSA sequencer port %d:%d is already subscribed.",(int)clientId,(int)portId);
return;
}
int rc = snd_seq_subscribe_port(seq, subs);
if (rc < 0)
{
Lv2Log::error("Failed to connect ALSA sequencer port %d:%d. (%s)",
(int)clientId,(int)portId,
snd_strerror(rc));
return;
}
this->connections.push_back({clientId, portId});
}
}
} // namespace pipedal
+1
View File
@@ -41,6 +41,7 @@ message(STATUS "NMPIPEDAL CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
# Use the sdbus-c++ target in SDBusCpp namespace
add_library(PiPedalCommon STATIC
include/Finally.hpp
include/dbus/org.freedesktop.NetworkManager.Device.Statistics.hpp
include/dbus/org.freedesktop.NetworkManager.Settings.Connection.hpp
include/dbus/org.freedesktop.NetworkManager.IP6Config.hpp
+187 -38
View File
@@ -25,12 +25,23 @@
#include <vector>
#include <string>
#include <alsa/asoundlib.h>
#include <alsa/seq.h>
#include <stdexcept>
#include <memory>
namespace pipedal
{
// event messages starting with 0xFF are meta events.
// data not in midi format, is variable length, and private and the second byte is the MetaEventType.
enum class MetaEventType
{
None,
Tempo,
TimeSignature,
KeySignature,
SetPositionTick,
SetPositionTime
};
struct AlsaSequencerPort
{
std::string id;
@@ -61,12 +72,162 @@ namespace pipedal
struct AlsaMidiMessage
{
AlsaMidiMessage()
{
size = 0;
data = fixedBuffer;
}
AlsaMidiMessage(uint8_t cc0)
{
size = 1;
data = fixedBuffer;
fixedBuffer[0] = cc0;
}
AlsaMidiMessage(uint8_t cc0, uint8_t cc1)
{
size = 2;
data = fixedBuffer;
fixedBuffer[0] = cc0;
fixedBuffer[1] = cc1;
}
AlsaMidiMessage(uint8_t cc0, uint8_t cc1, uint8_t cc2)
{
size = 3;
data = fixedBuffer;
fixedBuffer[0] = cc0;
fixedBuffer[1] = cc1;
fixedBuffer[2] = cc2;
}
AlsaMidiMessage(uint8_t *data, size_t size)
{
this->size = size;
this->data = data;
}
AlsaMidiMessage(const AlsaMidiMessage &other)
{
this->timestamp = other.timestamp;
this->realtime_sec = other.realtime_sec;
this->realtime_nsec = other.realtime_nsec;
if (other.size > 3)
{
this->size = other.size;
this->data = other.data;
}
else
{
this->size = other.size;
this->data = fixedBuffer;
for (size_t i = 0; i < other.size; ++i)
{
fixedBuffer[i] = other.fixedBuffer[i];
}
}
}
AlsaMidiMessage &operator=(const AlsaMidiMessage &other)
{
this->timestamp = other.timestamp;
this->realtime_sec = other.realtime_sec;
this->realtime_nsec = other.realtime_nsec;
if (other.size > 3)
{
this->size = other.size;
this->data = other.data;
}
else
{
this->size = other.size;
this->data = fixedBuffer;
for (size_t i = 0; i < other.size; ++i)
{
fixedBuffer[i] = other.fixedBuffer[i];
}
}
return *this;
}
AlsaMidiMessage(AlsaMidiMessage &&other)
{
this->timestamp = other.timestamp;
this->realtime_sec = other.realtime_sec;
this->realtime_nsec = other.realtime_nsec;
if (other.size > 3)
{
this->size = other.size;
this->data = other.data;
}
else
{
this->size = other.size;
this->data = fixedBuffer;
for (size_t i = 0; i < other.size; ++i)
{
fixedBuffer[i] = other.fixedBuffer[i];
}
}
}
AlsaMidiMessage &operator=(AlsaMidiMessage &&other)
{
this->timestamp = other.timestamp;
this->realtime_sec = other.realtime_sec;
this->realtime_nsec = other.realtime_nsec;
if (other.size > 3)
{
this->size = other.size;
this->data = other.data;
}
else
{
this->size = other.size;
this->data = fixedBuffer;
for (size_t i = 0; i < other.size; ++i)
{
fixedBuffer[i] = other.fixedBuffer[i];
}
}
return *this;
}
void Set(uint8_t cc0)
{
size = 1;
data = fixedBuffer;
fixedBuffer[0] = cc0;
}
void Set(uint8_t cc0, uint8_t cc1)
{
size = 2;
data = fixedBuffer;
fixedBuffer[0] = cc0;
fixedBuffer[1] = cc1;
}
void Set(uint8_t cc0, uint8_t cc1, uint8_t cc2)
{
size = 3;
data = fixedBuffer;
fixedBuffer[0] = cc0;
fixedBuffer[1] = cc1;
fixedBuffer[2] = cc2;
}
void Set(uint8_t *data, size_t size)
{
this->size = size;
this->data = data;
}
uint8_t cc0() const { return size > 0 ? data[0] : 0; }
uint8_t cc1() const { return size > 1 ? data[1] : 0; }
uint8_t cc2() const { return size > 2 ? data[2] : 0; }
uint32_t timestamp; // in microseconds (tick time if using queue)
uint64_t realtime_sec; // real-time timestamp seconds
uint32_t realtime_nsec; // real-time timestamp nanoseconds
uint8_t cc0;
uint8_t cc1;
uint8_t cc2;
size_t size = 0;
uint8_t *data = fixedBuffer;
uint8_t fixedBuffer[12];
};
/*
@@ -84,46 +245,34 @@ namespace pipedal
* printf("MIDI event at %lu.%09u seconds\n", message.realtime_sec, message.realtime_nsec);
* }
*/
class AlsaSequencer
{
protected:
AlsaSequencer() {}
public:
virtual ~AlsaSequencer() = default;
using self = AlsaSequencer;
using ptr = std::shared_ptr<self>;
static ptr Create();
static std::vector<AlsaSequencerPort> EnumeratePorts();
AlsaSequencer();
~AlsaSequencer();
public:
virtual void ConnectPort(int clientId, int portId) = 0;
virtual void ConnectPort(const std::string &id) = 0;
void ConnectPort(int clientId, int portId);
void ConnectPort(const std::string&name);
// Read a single MIDI message from the sequencer input port. A timeout of -1 blocks indefinitely.
// A timeout of 0 returns immediately.
virtual bool ReadMessage(AlsaMidiMessage &message, int timeoutMs = -1) = 0;
// Read a single MIDI message from the sequencer input port
bool ReadMessage(AlsaMidiMessage &message, bool block = true);
// currently non-functional
virtual bool GetQueueRealtime(uint64_t *sec, uint32_t *nsec) = 0;
// Get current real-time from the queue (useful for calculating precise timing)
bool GetQueueRealtime(uint64_t* sec, uint32_t* nsec);
// Get the current queue ID (returns -1 if no queue is active)
int GetQueueId() const { return queueId; }
private:
void WaitForMessage();
// Create an ALSA input queue with real-time timestamps for the given client/port
int CreateRealtimeInputQueue();
struct Connection
{
int clientId;
int portId;
};
std::vector<Connection> connections;
std::vector<struct pollfd> pollFds; // For polling input events
snd_seq_t *seqHandle = nullptr;
int inPort = -1;
int queueId = -1; // Queue for real-time timestamps
// Example: open an ALSA sequencer input port and read MIDI events continuously
void ReadMidiFromPort(int clientId, int portId);
virtual void RemoveAllConnections() = 0;
};
std::string RawMidiIdToSequencerId(const std::vector<AlsaSequencerPort> &seqDevices, const std::string &rawMidiId);
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) 2024 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <functional>
namespace pipedal {
class Finally {
public:
Finally(std::function<void(void)> &&fn)
:fn (std::move(fn))
{
}
~Finally() {
fn();
}
private:
std::function<void(void)> fn;
};
}
+40 -289
View File
@@ -144,8 +144,6 @@ namespace pipedal
return false;
}
struct AudioFormat
{
char name[40];
@@ -304,6 +302,7 @@ namespace pipedal
AlsaDriverImpl(AudioDriverHost *driverHost)
: driverHost(driverHost)
{
midiEventMemoryIndex = 0;
midiEventMemory.resize(MAX_MIDI_EVENT * MAX_MIDI_EVENT_SIZE);
midiEvents.resize(MAX_MIDI_EVENT);
for (size_t i = 0; i < midiEvents.size(); ++i)
@@ -415,14 +414,7 @@ namespace pipedal
snd_pcm_sw_params_free(playbackSwParams);
playbackSwParams = nullptr;
}
for (auto &midiState : this->midiDevices)
{
if (midiState)
{
midiState->Close();
}
}
midiDevices.resize(0);
this->alsaSequencer = nullptr;
}
std::string discover_alsa_using_apps()
@@ -1084,7 +1076,6 @@ namespace pipedal
open = true;
try
{
OpenMidi(jackServerSettings, channelSelection);
OpenAudio(jackServerSettings, channelSelection);
std::atomic_thread_fence(std::memory_order::release);
}
@@ -1534,13 +1525,37 @@ namespace pipedal
void ReadMidiData(uint32_t audioFrame)
{
for (size_t i = 0; i < midiDevices.size(); ++i)
AlsaMidiMessage message;
midiEventCount = 0;
while(alsaSequencer->ReadMessage(message,0))
{
size_t nRead = midiDevices[i]->ReadMidiEvents(
this->midiEvents,
midiEventCount,
audioFrame);
midiEventCount += nRead;
size_t messageSize = message.size;
if (messageSize == 0)
{
continue;
}
if (midiEventMemoryIndex + messageSize >= this->midiEventMemory.size()) {
continue;
}
if (midiEventCount >= this->midiEvents.size()) {
midiEvents.resize(midiEventCount*2);
}
// for now, prevent META event messages from propagating.
if (message.data[0] == 0xFF && message.size > 1) {
continue;
}
MidiEvent *pEvent = midiEvents.data() + midiEventCount++;
pEvent->time = audioFrame;
pEvent->size = messageSize;
pEvent->buffer = midiEventMemory.data() + midiEventMemoryIndex;
memcpy(
midiEventMemory.data() + midiEventMemoryIndex,
message.data,
message.size);
midiEventMemoryIndex += messageSize;
}
}
@@ -1671,16 +1686,17 @@ namespace pipedal
pBuffer[j] = 0;
}
}
try {
try
{
while (!terminateAudio())
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// zero out input buffers.
this->driverHost->OnProcess(this->bufferSize);
}
} catch (const std::exception &e)
}
catch (const std::exception &e)
{
}
}
this->driverHost->OnAudioTerminated();
@@ -1772,282 +1788,17 @@ namespace pipedal
size_t midiEventCount = 0;
std::vector<MidiEvent> midiEvents;
size_t midiEventMemoryIndex = 0;
std::vector<uint8_t> midiEventMemory;
AlsaSequencer::ptr alsaSequencer;
public:
class AlsaMidiDeviceImpl
{
private:
snd_rawmidi_t *hIn = nullptr;
snd_rawmidi_params_t *hInParams = nullptr;
std::string deviceName;
// running status state.
uint8_t runningStatus = 0;
int dataLength = 0;
int dataIndex = 0;
size_t statusBytesRemaining = 0;
size_t data0 = 0;
size_t data1 = 0;
bool inputProcessingSysex = false;
size_t inputSysexBufferCount = 0;
std::vector<uint8_t> inputSysexBuffer;
uint8_t readBuffer[1024];
void checkError(int result, const char *message)
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
{
if (result < 0)
{
throw PiPedalStateException(SS("Unexpected error: " << message << " (" << this->deviceName));
}
}
public:
AlsaMidiDeviceImpl()
{
inputSysexBuffer.resize(1024);
}
~AlsaMidiDeviceImpl() {
Close();
}
void Open(const AlsaMidiDeviceInfo &device)
{
runningStatus = 0;
inputProcessingSysex = false;
inputSysexBufferCount = 0;
dataIndex = 0;
dataLength = 0;
this->deviceName = device.description_;
int err = snd_rawmidi_open(&hIn, nullptr, device.name_.c_str(), SND_RAWMIDI_NONBLOCK);
if (err < 0)
{
throw PiPedalStateException(SS("Can't open midi device " << deviceName << ". (" << snd_strerror(err)));
}
err = snd_rawmidi_params_malloc(&hInParams);
checkError(err, "snd_rawmidi_params_malloc failed.");
err = snd_rawmidi_params_set_buffer_size(hIn, hInParams, 2048);
checkError(err, "snd_rawmidi_params_set_buffer_size failed.");
err = snd_rawmidi_params_set_no_active_sensing(hIn, hInParams, 1);
checkError(err, "snd_rawmidi_params_set_no_active_sensing failed.");
}
void Close()
{
if (hIn)
{
snd_rawmidi_close(hIn);
hIn = nullptr;
}
if (hInParams)
{
snd_rawmidi_params_free(hInParams);
hInParams = 0;
}
}
int GetDataLength(uint8_t cc)
{
static int sDataLength[] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, -1};
return sDataLength[cc >> 4];
}
void MidiPut(uint8_t cc, uint8_t d0, uint8_t d1)
{
if (cc == 0)
return;
// check for overrun.
if (inputEventBufferIndex >= pInputEventBuffer->size())
{
return;
}
auto &event = (*pInputEventBuffer)[inputEventBufferIndex];
event.time = inputSampleFrame;
event.size = dataLength + 1;
assert(dataLength + 1 <= MAX_MIDI_EVENT_SIZE);
event.buffer[0] = cc;
event.buffer[1] = d0;
event.buffer[2] = d1;
++inputEventBufferIndex;
}
void FillInputBuffer()
{
while (true)
{
ssize_t nRead = snd_rawmidi_read(hIn, readBuffer, sizeof(readBuffer));
if (nRead == -EAGAIN)
return;
if (nRead < 0)
{
checkError(nRead, SS(this->deviceName << "MIDI event read failed. (" << snd_strerror(nRead)).c_str());
}
ProcessInputBuffer(readBuffer, nRead); // expose write to test code.
}
}
uint32_t inputSampleFrame = -1;
size_t inputEventBufferIndex;
std::vector<MidiEvent> *pInputEventBuffer = nullptr;
size_t ReadMidiEvents(
std::vector<MidiEvent> &outputBuffer,
size_t startIndex,
uint32_t sampleFrame)
{
inputSampleFrame = sampleFrame;
inputEventBufferIndex = startIndex;
pInputEventBuffer = &outputBuffer;
FillInputBuffer();
pInputEventBuffer = nullptr;
return inputEventBufferIndex - startIndex;
}
void FlushSysex()
{
if (inputProcessingSysex)
{
// just discard it. :-/
// if (this->eventCount != MAX_MIDI_EVENT)
// {
// auto *event = &(events[eventCount++]);
// event->size = this->bufferCount - sysexStartIndex;
// event->buffer = &(this->buffer[this->sysexStartIndex]);
// event->time = 0;
// }
// sysexStartIndex = -1;
}
inputProcessingSysex = false;
}
int GetSystemCommonLength(uint8_t cc)
{
static int sizes[] = {-1, 1, 2, 1, -1, -1, 0, 0};
return sizes[(cc >> 4) & 0x07];
}
void ProcessInputBuffer(uint8_t *readBuffer, size_t nRead)
{
for (ssize_t i = 0; i < nRead; ++i)
{
uint8_t v = readBuffer[i];
if (v >= 0x80)
{
if (v >= 0xF0)
{
if (v == 0xF0)
{
inputProcessingSysex = true;
inputSysexBufferCount = 0;
inputSysexBuffer[inputSysexBufferCount++] = 0xF0;
runningStatus = 0; // discard subsequent data.
dataLength = -2; // indefinitely.
dataIndex = -1;
}
else if (v >= 0xF8)
{
// don't overwrite running status.
// don't break sysexes on a running status message.
// LV2 standard is ambiguous how realtime messages are handled, so just discard them.
continue;
}
else
{
FlushSysex();
int length = GetSystemCommonLength(v);
if (length == -1)
break; // ignore illegal messages.
runningStatus = v;
dataLength = length;
dataIndex = 0;
}
}
else
{
FlushSysex();
int dataLength = GetDataLength(v);
runningStatus = v;
if (dataLength == -1)
{
this->dataLength = dataLength;
dataIndex = -1;
}
else
{
this->dataLength = dataLength;
dataIndex = 0;
}
}
}
else
{
if (inputProcessingSysex)
{
if (inputSysexBufferCount != inputSysexBuffer.size())
{
inputSysexBuffer[inputSysexBufferCount++] = v;
}
}
else
{
switch (dataIndex)
{
default:
// discard.
break;
case 0:
data0 = v;
dataIndex = 1;
break;
case 1:
data1 = v;
dataIndex = 2;
break;
}
}
}
if (dataIndex == dataLength && dataLength >= 0 && runningStatus != 0)
{
MidiPut(runningStatus, data0, data1);
dataIndex = 0;
}
}
}
};
std::vector<std::unique_ptr<AlsaMidiDeviceImpl>> midiDevices;
void OpenMidi(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{
const auto &devices = channelSelection.GetInputMidiDevices();
midiDevices.reserve(devices.size());
for (size_t i = 0; i < devices.size(); ++i)
{
try
{
const auto &device = devices[i];
auto midiDevice = std::make_unique<AlsaMidiDeviceImpl>();
midiDevice->Open(device);
midiDevices.push_back(std::move(midiDevice));
}
catch (const std::exception &e)
{
Lv2Log::error(e.what());
}
}
this->alsaSequencer = alsaSequencer;
}
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); }
+2 -2
View File
@@ -25,7 +25,7 @@
#include "JackConfiguration.hpp"
#include <functional>
#include "AlsaSequencer.hpp"
@@ -74,7 +74,7 @@ namespace pipedal {
virtual float*GetOutputBuffer(size_t channe) = 0;
virtual void Open(const JackServerSettings & jackServerSettings,const JackChannelSelection &channelSelection) = 0;
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) = 0;
virtual void Activate() = 0;
virtual void Deactivate() = 0;
virtual void Close() = 0;
+7 -1
View File
@@ -21,6 +21,7 @@
#include "util.hpp"
#include <lv2/atom/atom.h>
#include "SchedulerPriority.hpp"
#include "AlsaSequencer.hpp"
#include "Lv2Log.hpp"
@@ -388,6 +389,8 @@ bool SystemMidiBinding::IsMatch(const MidiEvent &event)
class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchWriterCallback
{
private:
AlsaSequencer::ptr alsaSequencer;
void OnWritePatchPropertyBuffer(
PatchPropertyWriter::Buffer *);
@@ -599,6 +602,7 @@ private:
this->outputRingBuffer.reset();
audioDriver = nullptr;
alsaSequencer = nullptr;
}
void ZeroBuffer(float *buffer, size_t nframes)
@@ -1248,6 +1252,7 @@ public:
lv2_atom_forge_init(&inputWriterForge, pHost->GetMapFeature().GetMap());
cpuTemperatureMonitor = CpuTemperatureMonitor::Get();
this->alsaSequencer = AlsaSequencer::Create();
}
virtual ~AudioHostImpl()
{
@@ -1637,6 +1642,7 @@ public:
{
this->isDummyAudioDriver = true;
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
this->audioDriver->SetAlsaSequencer(this->alsaSequencer);
}
else
{
@@ -1666,7 +1672,7 @@ public:
try
{
audioDriver->Open(jackServerSettings, this->channelSelection);
audioDriver->SetAlsaSequencer(this->alsaSequencer);
this->sampleRate = audioDriver->GetSampleRate();
this->overrunGracePeriodSamples = (uint64_t)(((uint64_t)this->sampleRate) * OVERRUN_GRACE_PERIOD_S);
-1
View File
@@ -217,7 +217,6 @@ set (PIPEDAL_SOURCES
Lv2PluginChangeMonitor.cpp Lv2PluginChangeMonitor.hpp
WebServerConfig.cpp WebServerConfig.hpp
Locale.hpp Locale.cpp
Finally.hpp
ZipFile.cpp ZipFile.hpp
TemporaryFile.cpp TemporaryFile.hpp
FilePropertyDirectoryTree.cpp FilePropertyDirectoryTree.hpp
+5
View File
@@ -122,6 +122,7 @@ namespace pipedal
}
JackServerSettings jackServerSettings;
AlsaSequencer::ptr alsaSequencer;
unsigned int periods = 0;
@@ -190,6 +191,10 @@ namespace pipedal
throw;
}
}
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
{
this->alsaSequencer = alsaSequencer;
}
virtual std::string GetConfigurationDescription()
{
std::string result = SS(
+19 -1
View File
@@ -23,6 +23,7 @@
#include "AlsaDriver.hpp"
#include "Lv2Log.hpp"
#include "PiPedalException.hpp"
#include "AlsaSequencer.hpp"
#if JACK_HOST
@@ -106,6 +107,23 @@ namespace pipedal {
#endif
}
static std::vector<AlsaMidiDeviceInfo> GetAlsaSequencers() {
std::vector<AlsaMidiDeviceInfo> result;
try {
auto sequencer = AlsaSequencer::Create();
if (sequencer)
{
std::vector<AlsaSequencerPort> ports = sequencer->EnumeratePorts();
for (const auto &port : ports)
{
result.push_back(AlsaMidiDeviceInfo(port.id.c_str(), port.name.c_str()));
}
}
} catch (const std::exception& e) {
Lv2Log::error("Failed to enumerate ALSA sequencers: %s", e.what());
}
return result;
}
void JackConfiguration::AlsaInitialize(
const JackServerSettings &jackServerSettings)
{
@@ -116,7 +134,7 @@ void JackConfiguration::AlsaInitialize(
{
this->inputMidiDevices_.clear();
} else {
this->inputMidiDevices_ = GetAlsaMidiInputDevices();
this->inputMidiDevices_ = GetAlsaSequencers(); // NB: Sequencers, not rawmidi devices, anymore.
}
if (jackServerSettings.IsValid())
{
+4
View File
@@ -111,6 +111,10 @@ namespace pipedal
{
return inputMidiDevices_;
}
std::vector<AlsaMidiDeviceInfo>& GetInputMidiDevices()
{
return inputMidiDevices_;
}
JackChannelSelection RemoveInvalidChannels(const JackConfiguration&config) const;
static JackChannelSelection MakeDefault(const JackConfiguration&config);
+2 -2
View File
@@ -433,11 +433,11 @@ static std::vector<AlsaMidiDeviceInfo> OldGetAlsaDevices(const char *devname, co
return result;
}
std::vector<AlsaMidiDeviceInfo> pipedal::GetAlsaMidiInputDevices()
std::vector<AlsaMidiDeviceInfo> pipedal::LegacyGetAlsaMidiInputDevices()
{
return GetAlsaDevices("rawmidi", "Input");
}
std::vector<AlsaMidiDeviceInfo> pipedal::GetAlsaMidiOutputDevices()
std::vector<AlsaMidiDeviceInfo> pipedal::LegacyGetAlsaMidiOutputDevices()
{
return GetAlsaDevices("rawmidi", "Output");
}
+5 -2
View File
@@ -53,6 +53,7 @@ namespace pipedal {
std::string name_;
std::string description_;
// non-serialized.
int card_ = -1;
int device_ = -1;
int subdevice_ = -1;
@@ -74,7 +75,9 @@ namespace pipedal {
std::vector<AlsaDeviceInfo> GetAlsaDevices();
};
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiInputDevices();
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiOutputDevices();
// we use ALSA sequencers now instead of ALSA rawmidi devices.
// Used by test suite to verify migration behaviour.
std::vector<AlsaMidiDeviceInfo> LegacyGetAlsaMidiInputDevices();
std::vector<AlsaMidiDeviceInfo> LegacyGetAlsaMidiOutputDevices();
}
+10 -11
View File
@@ -38,10 +38,8 @@ static void DiscoveryTest()
auto result = devices.GetAlsaDevices();
std::cout << result.size() << " ALSA devices found." << std::endl;
auto midiInputDevices = GetAlsaMidiInputDevices();
std::cout << midiInputDevices.size() << " ALSA MIDI input devices found." << std::endl;
auto midiOutputDevices = GetAlsaMidiOutputDevices();
std::cout << midiOutputDevices.size() << " ALSA MIDI output devices found." << std::endl;
auto midiInputDevices = AlsaSequencer::EnumeratePorts();
std::cout << midiInputDevices.size() << " ALSA MIDI input sequencers found." << std::endl;
}
void EnumerateSequencers()
@@ -86,18 +84,19 @@ void EnumerateSequencers()
void ReadFromsequencerTest()
{
cout << "--- Reading from ALSA Sequencer" << endl;
AlsaSequencer sequencer;
sequencer.ConnectPort("V25 V25 In");
AlsaSequencer::ptr sequencer = AlsaSequencer::Create();
sequencer->ConnectPort("V25 V25 In");
AlsaMidiMessage message;
while (true)
{
if (sequencer.ReadMessage(message,true))
if (sequencer->ReadMessage(message,true))
{
cout << "Received MIDI message: "
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc0)
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc1)
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc2)
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc0())
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc1())
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc2())
<< " Timestamp: " << setw(8) << dec << message.timestamp
<< std::endl;
}
@@ -109,7 +108,7 @@ void TestConfigMigration()
cout << "--- Testing ALSA Config Migration" << endl;
// older versions of PiPedal uses ALSA rawmidi.
// this code test coversion of rawmidi device IDs to ALSA Sequencer IDs.
auto rawDevices = GetAlsaMidiInputDevices();
auto rawDevices = LegacyGetAlsaMidiInputDevices();
auto seqDevices = AlsaSequencer::EnumeratePorts();
for (const auto&seqDevice: seqDevices) {
+44 -1
View File
@@ -22,6 +22,7 @@
#include "Storage.hpp"
#include "AudioConfig.hpp"
#include "PiPedalException.hpp"
#include "AlsaSequencer.hpp"
#include <stdexcept>
#include <sstream>
#include "json.hpp"
@@ -734,6 +735,48 @@ JackChannelSelection Storage::GetJackChannelSelection(const JackConfiguration &j
return jackChannelSelection.RemoveInvalidChannels(jackConfiguration);
}
static void MigrateRawMidiToAlsaSequencer(JackChannelSelection &channelSelection)
{
try {
auto sequencerPorts = AlsaSequencer::EnumeratePorts();
// Migrate raw MIDI devices to ALSA sequencer ports.
std::vector<AlsaMidiDeviceInfo>& selectedDevices = channelSelection.GetInputMidiDevices();
for (auto i = selectedDevices.begin(); i != selectedDevices.end(); ++i)
{
if (i->name_.starts_with("hw:")) {
bool migrated = false;
for (const auto &port: sequencerPorts)
{
if (i->name_ == port.rawMidiDevice)
{
// Found a matching sequencer port.
i->name_ = port.rawMidiDevice;
i->description_ = port.name;
migrated = true;
break;
}
}
if (!migrated) {
i = selectedDevices.erase(i);
if (i == selectedDevices.end())
{
break;
}
--i;
}
}
}
} catch (const std::exception&e) {
Lv2Log::error(SS("Failed to migrate MIDI settings. " << e.what()));
// ick.
channelSelection.GetInputMidiDevices().clear();
}
}
void Storage::LoadChannelSelection()
{
auto fileName = this->GetChannelSelectionFileName();
@@ -745,11 +788,11 @@ void Storage::LoadChannelSelection()
json_reader reader(s);
reader.read(&this->jackChannelSelection);
this->isJackChannelSelectionValid = true;
MigrateRawMidiToAlsaSequencer(this->jackChannelSelection);
}
catch (const std::exception &e)
{
Lv2Log::error("I/O error reading %s: %s", fileName.c_str(), e.what());
throw PiPedalStateException("Unexpected error reading Jack settings file.");
}
}
}
+29 -11
View File
@@ -48,6 +48,7 @@
#include "AudioFiles.hpp"
#include <systemd/sd-daemon.h>
#include "AlsaSequencer.hpp"
using namespace pipedal;
@@ -81,9 +82,9 @@ static bool isJackServiceRunning()
return std::filesystem::exists(path);
}
#if ENABLE_BACKTRACE
void segvHandler(int sig) {
void segvHandler(int sig)
{
void *array[10];
// Get void*'s for all entries on the stack
@@ -98,20 +99,34 @@ void segvHandler(int sig) {
_exit(EXIT_FAILURE);
}
static void EnableBacktrace()
{
signal(SIGSEGV, segvHandler);
}
#endif
static bool TryGetLogLevel(const std::string &strLogLevel, LogLevel *result)
{
if (strLogLevel == "debug") {*result= LogLevel::Debug; return true;}
if (strLogLevel == "info") {*result= LogLevel::Info; return true;}
if (strLogLevel == "warning") {*result= LogLevel::Warning; return true;}
if (strLogLevel == "error") { *result= LogLevel::Error; return true;}
if (strLogLevel == "debug")
{
*result = LogLevel::Debug;
return true;
}
if (strLogLevel == "info")
{
*result = LogLevel::Info;
return true;
}
if (strLogLevel == "warning")
{
*result = LogLevel::Warning;
return true;
}
if (strLogLevel == "error")
{
*result = LogLevel::Error;
return true;
}
*result = LogLevel::Info;
return false;
}
@@ -223,8 +238,9 @@ int main(int argc, char *argv[])
if (TryGetLogLevel(logLevel, &lv2LogLevel))
{
Lv2Log::log_level(lv2LogLevel);
} else {
}
else
{
Lv2Log::error(SS("Invalid log level: " << logLevel));
return EXIT_SUCCESS; // indicate to systemd that we don't want a restart.
}
@@ -237,7 +253,8 @@ int main(int argc, char *argv[])
// clean up orphaned temporary files.
const std::filesystem::path webTempDirectory = "/var/pipedal/web_temp";
if (!webTempDirectory.empty()) {
if (!webTempDirectory.empty())
{
std::filesystem::remove_all(webTempDirectory); //// user must belong to the pipedald grop when debugging.
std::filesystem::create_directories(webTempDirectory);
}
@@ -273,6 +290,7 @@ int main(int argc, char *argv[])
return EXIT_SUCCESS; // indiate to systemd that we don't want a restart.
}
try
{
{
+7 -1
View File
@@ -1,4 +1,10 @@
check filetime conversion in gcc 12.2.
Route the midi connections.
Midi handling in the dummy audio connector.
PiPedal sequencer not visible. Is it missing duplex somewhere?
check filetime conversion in gcc 12.2! (missing c++ 20 time conversion functions)
AudioFiles.cpp fileTimeToInt64