ALSA Sequencer Interim checkin.
This commit is contained in:
@@ -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,22 +72,172 @@ namespace pipedal
|
||||
|
||||
struct AlsaMidiMessage
|
||||
{
|
||||
uint32_t timestamp; // in microseconds (tick time if using queue)
|
||||
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];
|
||||
};
|
||||
|
||||
/*
|
||||
* AlsaSequencer - ALSA MIDI sequencer interface with real-time timestamp support
|
||||
*
|
||||
*
|
||||
* Example usage with real-time timestamps:
|
||||
*
|
||||
*
|
||||
* AlsaSequencer sequencer;
|
||||
* sequencer.connect(clientId, portId);
|
||||
*
|
||||
*
|
||||
* AlsaMidiMessage message;
|
||||
* while (sequencer.ReadMessage(&message)) {
|
||||
* // message.realtime_sec and message.realtime_nsec contain precise real-time timestamps
|
||||
@@ -84,48 +245,36 @@ 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);
|
||||
std::string RawMidiIdToSequencerId(const std::vector<AlsaSequencerPort> &seqDevices, const std::string &rawMidiId);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user