Browse Source

MIDI input working in juce drivers

tags/1.9.4
falkTX 11 years ago
parent
commit
ababc77459
2 changed files with 355 additions and 58 deletions
  1. +310
    -12
      source/backend/engine/CarlaEngineJuce.cpp
  2. +45
    -46
      source/backend/engine/CarlaEngineRtAudio.cpp

+ 310
- 12
source/backend/engine/CarlaEngineJuce.cpp View File

@@ -40,7 +40,7 @@ CARLA_BACKEND_START_NAMESPACE
static const char** gRetNames = nullptr; static const char** gRetNames = nullptr;
static OwnedArray<AudioIODeviceType> gJuceDeviceTypes; static OwnedArray<AudioIODeviceType> gJuceDeviceTypes;


struct JuceCleanup {
struct JuceCleanup : public DeletedAtShutdown {
JuceCleanup() noexcept {} JuceCleanup() noexcept {}
~JuceCleanup() ~JuceCleanup()
{ {
@@ -62,7 +62,6 @@ struct JuceCleanup {


static void initJuceDevicesIfNeeded() static void initJuceDevicesIfNeeded()
{ {
static const JuceCleanup sJuceCleanup;
static AudioDeviceManager sDeviceManager; static AudioDeviceManager sDeviceManager;
static bool needsInit = true; static bool needsInit = true;


@@ -70,6 +69,7 @@ static void initJuceDevicesIfNeeded()
return; return;


needsInit = false; needsInit = false;
new JuceCleanup();


sDeviceManager.createAudioDeviceTypes(gJuceDeviceTypes); sDeviceManager.createAudioDeviceTypes(gJuceDeviceTypes);


@@ -87,7 +87,8 @@ static void initJuceDevicesIfNeeded()
// Juce Engine // Juce Engine


class CarlaEngineJuce : public CarlaEngine, class CarlaEngineJuce : public CarlaEngine,
public AudioIODeviceCallback
public AudioIODeviceCallback,
public MidiInputCallback
{ {
public: public:
CarlaEngineJuce(AudioIODeviceType* const devType) CarlaEngineJuce(AudioIODeviceType* const devType)
@@ -211,6 +212,28 @@ public:
fDevice = nullptr; fDevice = nullptr;
} }


for (LinkedList<MidiInPort>::Itenerator it = fMidiIns.begin(); it.valid(); it.next())
{
MidiInPort& inPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);

inPort.port->stop();
delete inPort.port;
}

for (LinkedList<MidiOutPort>::Itenerator it = fMidiOuts.begin(); it.valid(); it.next())
{
MidiOutPort& outPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);

outPort.port->stopBackgroundThread();
delete outPort.port;
}

fMidiIns.clear();
fMidiOuts.clear();
fMidiInEvents.clear();

return !hasError; return !hasError;
} }


@@ -307,7 +330,47 @@ public:
callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, RACK_GRAPH_GROUP_AUDIO_OUT, static_cast<int>(i), PATCHBAY_PORT_TYPE_AUDIO|PATCHBAY_PORT_IS_INPUT, 0.0f, outputNames[i].toRawUTF8()); callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, RACK_GRAPH_GROUP_AUDIO_OUT, static_cast<int>(i), PATCHBAY_PORT_TYPE_AUDIO|PATCHBAY_PORT_IS_INPUT, 0.0f, outputNames[i].toRawUTF8());
} }


// TODO - MIDI
// MIDI In
{
StringArray midiIns(MidiInput::getDevices());

callback(ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, RACK_GRAPH_GROUP_MIDI_IN, PATCHBAY_ICON_HARDWARE, -1, 0.0f, "Readable MIDI ports");

for (int i=0, count=midiIns.size(); i<count; ++i)
{
String portName(midiIns[i]);

std::snprintf(strBuf, STR_MAX, "Readable MIDI ports:%s", portName.toRawUTF8());

PortNameToId portNameToId;
portNameToId.setData(RACK_GRAPH_GROUP_MIDI_IN, static_cast<uint>(i), portName.toRawUTF8(), strBuf);

callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, portNameToId.group, static_cast<int>(portNameToId.port), PATCHBAY_PORT_TYPE_MIDI, 0.0f, portNameToId.name);

rack->midi.ins.append(portNameToId);
}
}

// MIDI Out
{
StringArray midiOuts(MidiOutput::getDevices());

callback(ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, RACK_GRAPH_GROUP_MIDI_OUT, PATCHBAY_ICON_HARDWARE, -1, 0.0f, "Writable MIDI ports");

for (int i=0, count=midiOuts.size(); i<count; ++i)
{
String portName(midiOuts[i]);

std::snprintf(strBuf, STR_MAX, "Writable MIDI ports:%s", portName.toRawUTF8());

PortNameToId portNameToId;
portNameToId.setData(RACK_GRAPH_GROUP_MIDI_OUT, static_cast<uint>(i), portName.toRawUTF8(), strBuf);

callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, portNameToId.group, static_cast<int>(portNameToId.port), PATCHBAY_PORT_TYPE_MIDI|PATCHBAY_PORT_IS_INPUT, 0.0f, portNameToId.name);

rack->midi.outs.append(portNameToId);
}
}


// Connections // Connections
rack->audio.mutex.lock(); rack->audio.mutex.lock();
@@ -374,7 +437,39 @@ public:


rack->audio.mutex.unlock(); rack->audio.mutex.unlock();


// TODO - MIDI
for (LinkedList<MidiInPort>::Itenerator it=fMidiIns.begin(); it.valid(); it.next())
{
const MidiInPort& inPort(it.getValue());

const uint portId(rack->midi.getPortId(true, inPort.name));
CARLA_SAFE_ASSERT_CONTINUE(portId < rack->midi.ins.count());

ConnectionToId connectionToId;
connectionToId.setData(++(rack->connections.lastId), RACK_GRAPH_GROUP_MIDI_IN, portId, RACK_GRAPH_GROUP_CARLA, RACK_GRAPH_CARLA_PORT_MIDI_IN);

std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", connectionToId.groupA, connectionToId.portA, connectionToId.groupB, connectionToId.portB);

callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf);

rack->connections.list.append(connectionToId);
}

for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin(); it.valid(); it.next())
{
const MidiOutPort& outPort(it.getValue());

const uint portId(rack->midi.getPortId(false, outPort.name));
CARLA_SAFE_ASSERT_CONTINUE(portId < rack->midi.outs.count());

ConnectionToId connectionToId;
connectionToId.setData(++(rack->connections.lastId), RACK_GRAPH_GROUP_CARLA, RACK_GRAPH_CARLA_PORT_MIDI_OUT, RACK_GRAPH_GROUP_MIDI_OUT, portId);

std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", connectionToId.groupA, connectionToId.portA, connectionToId.groupB, connectionToId.portB);

callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf);

rack->connections.list.append(connectionToId);
}
} }


void patchbayRefreshPatchbay() noexcept void patchbayRefreshPatchbay() noexcept
@@ -395,6 +490,8 @@ protected:
if (! pData->audio.isReady) if (! pData->audio.isReady)
return runPendingRtEvents(); return runPendingRtEvents();


const uint32_t nframes(static_cast<uint32_t>(numSamples));

// initialize juce output // initialize juce output
for (int i=0; i < numOutputChannels; ++i) for (int i=0; i < numOutputChannels; ++i)
FloatVectorOperations::clear(outputChannelData[i], numSamples); FloatVectorOperations::clear(outputChannelData[i], numSamples);
@@ -402,7 +499,37 @@ protected:
// initialize input events // initialize input events
carla_zeroStruct<EngineEvent>(pData->events.in, kMaxEngineEventInternalCount); carla_zeroStruct<EngineEvent>(pData->events.in, kMaxEngineEventInternalCount);


// TODO - get events from juce
if (fMidiInEvents.mutex.tryLock())
{
uint32_t engineEventIndex = 0;
fMidiInEvents.splice();

for (LinkedList<RtMidiEvent>::Itenerator it = fMidiInEvents.data.begin(); it.valid(); it.next())
{
const RtMidiEvent& midiEvent(it.getValue());
EngineEvent& engineEvent(pData->events.in[engineEventIndex++]);

if (midiEvent.time < pData->timeInfo.frame)
{
engineEvent.time = 0;
}
else if (midiEvent.time >= pData->timeInfo.frame + nframes)
{
carla_stderr("MIDI Event in the future!, %i vs %i", engineEvent.time, pData->timeInfo.frame);
engineEvent.time = static_cast<uint32_t>(pData->timeInfo.frame) + nframes - 1;
}
else
engineEvent.time = static_cast<uint32_t>(midiEvent.time - pData->timeInfo.frame);

engineEvent.fillFromMidiData(midiEvent.size, midiEvent.data);

if (engineEventIndex >= kMaxEngineEventInternalCount)
break;
}

fMidiInEvents.data.clear();
fMidiInEvents.mutex.unlock();
}


if (pData->graph.isRack) if (pData->graph.isRack)
{ {
@@ -439,23 +566,135 @@ protected:


// ------------------------------------------------------------------- // -------------------------------------------------------------------


bool connectRackMidiInPort(const char* const /*portName*/) override
void handleIncomingMidiMessage(MidiInput* /*source*/, const MidiMessage& message) override
{ {
return false;
if (! pData->audio.isReady)
return;

const int messageSize(message.getRawDataSize());

if (messageSize <= 0 || messageSize > EngineMidiEvent::kDataSize)
return;

const uint8_t* const messageData(message.getRawData());

RtMidiEvent midiEvent;
midiEvent.time = 0; // TODO

midiEvent.size = static_cast<uint8_t>(messageSize);

int i=0;
for (; i < messageSize; ++i)
midiEvent.data[i] = messageData[i];
for (; i < EngineMidiEvent::kDataSize; ++i)
midiEvent.data[i] = 0;

fMidiInEvents.appendNonRT(midiEvent);
} }


bool connectRackMidiOutPort(const char* const /*portName*/) override
// -------------------------------------------------------------------

bool connectRackMidiInPort(const char* const portName) override
{ {
return false;
CARLA_SAFE_ASSERT_RETURN(portName != nullptr && portName[0] != '\0', false);
carla_debug("CarlaEngineJuce::connectRackMidiInPort(\"%s\")", portName);

RackGraph* const rack(pData->graph.rack);
CARLA_SAFE_ASSERT_RETURN(rack->midi.ins.count() > 0, false);

StringArray midiIns(MidiInput::getDevices());

if (! midiIns.contains(portName))
return false;

MidiInput* const juceMidiIn(MidiInput::openDevice(midiIns.indexOf(portName), this));
juceMidiIn->start();

MidiInPort midiPort;
midiPort.port = juceMidiIn;

std::strncpy(midiPort.name, portName, STR_MAX);
midiPort.name[STR_MAX] = '\0';

fMidiIns.append(midiPort);
return true;
} }


bool disconnectRackMidiInPort(const char* const /*portName*/) override
bool connectRackMidiOutPort(const char* const portName) override
{ {
CARLA_SAFE_ASSERT_RETURN(portName != nullptr && portName[0] != '\0', false);
carla_debug("CarlaEngineJuce::connectRackMidiOutPort(\"%s\")", portName);

RackGraph* const rack(pData->graph.rack);
CARLA_SAFE_ASSERT_RETURN(rack->midi.ins.count() > 0, false);

StringArray midiOuts(MidiOutput::getDevices());

if (! midiOuts.contains(portName))
return false;

MidiOutput* const juceMidiOut(MidiOutput::openDevice(midiOuts.indexOf(portName)));
juceMidiOut->startBackgroundThread();

MidiOutPort midiPort;
midiPort.port = juceMidiOut;

std::strncpy(midiPort.name, portName, STR_MAX);
midiPort.name[STR_MAX] = '\0';

fMidiOuts.append(midiPort);
return true;
}

bool disconnectRackMidiInPort(const char* const portName) override
{
CARLA_SAFE_ASSERT_RETURN(portName != nullptr && portName[0] != '\0', false);
carla_debug("CarlaEngineRtAudio::disconnectRackMidiInPort(\"%s\")", portName);

RackGraph* const rack(pData->graph.rack);
CARLA_SAFE_ASSERT_RETURN(rack->midi.ins.count() > 0, false);

for (LinkedList<MidiInPort>::Itenerator it=fMidiIns.begin(); it.valid(); it.next())
{
MidiInPort& inPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);

if (std::strcmp(inPort.name, portName) != 0)
continue;

inPort.port->stop();
delete inPort.port;

fMidiIns.remove(it);
return true;
}

return false; return false;
} }


bool disconnectRackMidiOutPort(const char* const /*portName*/) override
bool disconnectRackMidiOutPort(const char* const portName) override
{ {
CARLA_SAFE_ASSERT_RETURN(portName != nullptr && portName[0] != '\0', false);
carla_debug("CarlaEngineRtAudio::disconnectRackMidiOutPort(\"%s\")", portName);

RackGraph* const rack(pData->graph.rack);
CARLA_SAFE_ASSERT_RETURN(rack->midi.outs.count() > 0, false);

for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin(); it.valid(); it.next())
{
MidiOutPort& outPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);

if (std::strcmp(outPort.name, portName) != 0)
continue;

outPort.port->stopBackgroundThread();
delete outPort.port;

fMidiOuts.remove(it);
return true;
}

return false; return false;
} }


@@ -465,6 +704,65 @@ private:
ScopedPointer<AudioIODevice> fDevice; ScopedPointer<AudioIODevice> fDevice;
AudioIODeviceType* const fDeviceType; AudioIODeviceType* const fDeviceType;


struct MidiInPort {
MidiInput* port;
char name[STR_MAX+1];
};

struct MidiOutPort {
MidiOutput* port;
char name[STR_MAX+1];
};

LinkedList<MidiInPort> fMidiIns;
LinkedList<MidiOutPort> fMidiOuts;

struct RtMidiEvent {
uint64_t time; // needs to compare to internal time
uint8_t size;
uint8_t data[EngineMidiEvent::kDataSize];
};

struct RtMidiEvents {
CarlaMutex mutex;
RtLinkedList<RtMidiEvent>::Pool dataPool;
RtLinkedList<RtMidiEvent> data;
RtLinkedList<RtMidiEvent> dataPending;

// FIXME - 32, 512 + append_sleepy? check plugin code
RtMidiEvents()
: dataPool(512, 512),
data(dataPool),
dataPending(dataPool) {}

~RtMidiEvents()
{
clear();
}

void appendNonRT(const RtMidiEvent& event)
{
mutex.lock();
dataPending.append(event);
mutex.unlock();
}

void clear()
{
mutex.lock();
data.clear();
dataPending.clear();
mutex.unlock();
}

void splice()
{
dataPending.spliceAppendTo(data);
}
};

RtMidiEvents fMidiInEvents;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineJuce) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineJuce)
}; };




+ 45
- 46
source/backend/engine/CarlaEngineRtAudio.cpp View File

@@ -192,8 +192,6 @@ public:
CARLA_SAFE_ASSERT(fAudioOutCount == 0); CARLA_SAFE_ASSERT(fAudioOutCount == 0);
CARLA_SAFE_ASSERT(fLastEventTime == 0); CARLA_SAFE_ASSERT(fLastEventTime == 0);
carla_debug("CarlaEngineRtAudio::~CarlaEngineRtAudio()"); carla_debug("CarlaEngineRtAudio::~CarlaEngineRtAudio()");

fUsedMidiPorts.clear();
} }


// ------------------------------------- // -------------------------------------
@@ -343,21 +341,23 @@ public:
fAudio.closeStream(); fAudio.closeStream();
} }


for (LinkedList<MidiPort>::Itenerator it = fMidiIns.begin(); it.valid(); it.next())
for (LinkedList<MidiInPort>::Itenerator it = fMidiIns.begin(); it.valid(); it.next())
{ {
MidiPort& port(it.getValue());
RtMidiIn* const midiInPort((RtMidiIn*)port.rtmidi);
MidiInPort& inPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);


midiInPort->cancelCallback();
delete midiInPort;
inPort.port->cancelCallback();
inPort.port->closePort();
delete inPort.port;
} }


for (LinkedList<MidiPort>::Itenerator it = fMidiOuts.begin(); it.valid(); it.next())
for (LinkedList<MidiOutPort>::Itenerator it = fMidiOuts.begin(); it.valid(); it.next())
{ {
MidiPort& port(it.getValue());
RtMidiOut* const midiOutPort((RtMidiOut*)port.rtmidi);
MidiOutPort& outPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);


delete midiOutPort;
outPort.port->closePort();
delete outPort.port;
} }


fAudioInCount = 0; fAudioInCount = 0;
@@ -365,12 +365,10 @@ public:
fLastEventTime = 0; fLastEventTime = 0;


fDeviceName.clear(); fDeviceName.clear();
fUsedMidiPorts.clear();
fMidiInEvents.clear();
//fMidiOutEvents.clear();

fMidiIns.clear(); fMidiIns.clear();
fMidiOuts.clear(); fMidiOuts.clear();
fMidiInEvents.clear();
//fMidiOutEvents.clear();


return !hasError; return !hasError;
} }
@@ -402,8 +400,6 @@ public:
{ {
CARLA_SAFE_ASSERT_RETURN(pData->audio.isReady, false); CARLA_SAFE_ASSERT_RETURN(pData->audio.isReady, false);


fUsedMidiPorts.clear();

if (pData->graph.isRack) if (pData->graph.isRack)
patchbayRefreshRack(); patchbayRefreshRack();
else else
@@ -572,11 +568,11 @@ public:


rack->audio.mutex.unlock(); rack->audio.mutex.unlock();


for (LinkedList<MidiPort>::Itenerator it=fMidiIns.begin(); it.valid(); it.next())
for (LinkedList<MidiInPort>::Itenerator it=fMidiIns.begin(); it.valid(); it.next())
{ {
const MidiPort& midiPort(it.getValue());
const MidiInPort& inPort(it.getValue());


const uint portId(rack->midi.getPortId(true, midiPort.name));
const uint portId(rack->midi.getPortId(true, inPort.name));
CARLA_SAFE_ASSERT_CONTINUE(portId < rack->midi.ins.count()); CARLA_SAFE_ASSERT_CONTINUE(portId < rack->midi.ins.count());


ConnectionToId connectionToId; ConnectionToId connectionToId;
@@ -589,11 +585,11 @@ public:
rack->connections.list.append(connectionToId); rack->connections.list.append(connectionToId);
} }


for (LinkedList<MidiPort>::Itenerator it=fMidiOuts.begin(); it.valid(); it.next())
for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin(); it.valid(); it.next())
{ {
const MidiPort& midiPort(it.getValue());
const MidiOutPort& outPort(it.getValue());


const uint portId(rack->midi.getPortId(false, midiPort.name));
const uint portId(rack->midi.getPortId(false, outPort.name));
CARLA_SAFE_ASSERT_CONTINUE(portId < rack->midi.outs.count()); CARLA_SAFE_ASSERT_CONTINUE(portId < rack->midi.outs.count());


ConnectionToId connectionToId; ConnectionToId connectionToId;
@@ -780,8 +776,8 @@ protected:
return false; return false;
}; };


MidiPort midiPort;
midiPort.rtmidi = rtMidiIn;
MidiInPort midiPort;
midiPort.port = rtMidiIn;


std::strncpy(midiPort.name, portName, STR_MAX); std::strncpy(midiPort.name, portName, STR_MAX);
midiPort.name[STR_MAX] = '\0'; midiPort.name[STR_MAX] = '\0';
@@ -826,8 +822,8 @@ protected:


rtMidiOut->openPort(rtMidiPortIndex, portName); rtMidiOut->openPort(rtMidiPortIndex, portName);


MidiPort midiPort;
midiPort.rtmidi = rtMidiOut;
MidiOutPort midiPort;
midiPort.port = rtMidiOut;


std::strncpy(midiPort.name, portName, STR_MAX); std::strncpy(midiPort.name, portName, STR_MAX);
midiPort.name[STR_MAX] = '\0'; midiPort.name[STR_MAX] = '\0';
@@ -844,17 +840,17 @@ protected:
RackGraph* const rack(pData->graph.rack); RackGraph* const rack(pData->graph.rack);
CARLA_SAFE_ASSERT_RETURN(rack->midi.ins.count() > 0, false); CARLA_SAFE_ASSERT_RETURN(rack->midi.ins.count() > 0, false);


for (LinkedList<MidiPort>::Itenerator it=fMidiIns.begin(); it.valid(); it.next())
for (LinkedList<MidiInPort>::Itenerator it=fMidiIns.begin(); it.valid(); it.next())
{ {
MidiPort& midiPort(it.getValue());
MidiInPort& inPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);


if (std::strcmp(midiPort.name, portName) != 0)
if (std::strcmp(inPort.name, portName) != 0)
continue; continue;


RtMidiIn* const midiInPort((RtMidiIn*)midiPort.rtmidi);

midiInPort->cancelCallback();
delete midiInPort;
inPort.port->cancelCallback();
inPort.port->closePort();
delete inPort.port;


fMidiIns.remove(it); fMidiIns.remove(it);
return true; return true;
@@ -869,18 +865,18 @@ protected:
carla_debug("CarlaEngineRtAudio::disconnectRackMidiOutPort(\"%s\")", portName); carla_debug("CarlaEngineRtAudio::disconnectRackMidiOutPort(\"%s\")", portName);


RackGraph* const rack(pData->graph.rack); RackGraph* const rack(pData->graph.rack);
CARLA_SAFE_ASSERT_RETURN(rack->midi.ins.count() > 0, false);
CARLA_SAFE_ASSERT_RETURN(rack->midi.outs.count() > 0, false);


for (LinkedList<MidiPort>::Itenerator it=fMidiOuts.begin(); it.valid(); it.next())
for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin(); it.valid(); it.next())
{ {
MidiPort& midiPort(it.getValue());
MidiOutPort& outPort(it.getValue());
CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);


if (std::strcmp(midiPort.name, portName) != 0)
if (std::strcmp(outPort.name, portName) != 0)
continue; continue;


RtMidiOut* const midiOutPort((RtMidiOut*)midiPort.rtmidi);

delete midiOutPort;
outPort.port->closePort();
delete outPort.port;


fMidiOuts.remove(it); fMidiOuts.remove(it);
return true; return true;
@@ -902,15 +898,18 @@ private:
// current device name // current device name
CarlaString fDeviceName; CarlaString fDeviceName;


PatchbayPortList fUsedMidiPorts;
struct MidiInPort {
RtMidiIn* port;
char name[STR_MAX+1];
};


struct MidiPort {
RtMidi* rtmidi;
struct MidiOutPort {
RtMidiOut* port;
char name[STR_MAX+1]; char name[STR_MAX+1];
}; };


LinkedList<MidiPort> fMidiIns;
LinkedList<MidiPort> fMidiOuts;
LinkedList<MidiInPort> fMidiIns;
LinkedList<MidiOutPort> fMidiOuts;


struct RtMidiEvent { struct RtMidiEvent {
uint64_t time; // needs to compare to internal time uint64_t time; // needs to compare to internal time


Loading…
Cancel
Save