|
|
|
@@ -15,19 +15,24 @@ |
|
|
|
* For a full copy of the GNU General Public License see the doc/GPL.txt file. |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "CarlaNative.hpp" |
|
|
|
#include "CarlaNativeExtUI.hpp" |
|
|
|
#include "RtLinkedList.hpp" |
|
|
|
|
|
|
|
#include "midi-base.hpp" |
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
class MidiSequencerPlugin : public NativePluginClass, |
|
|
|
class MidiSequencerPlugin : public NativePluginAndUiClass, |
|
|
|
public AbstractMidiPlayer |
|
|
|
{ |
|
|
|
public: |
|
|
|
MidiSequencerPlugin(const NativeHostDescriptor* const host) |
|
|
|
: NativePluginClass(host), |
|
|
|
: NativePluginAndUiClass(host, CARLA_OS_SEP_STR "midiseq-ui"), |
|
|
|
fWantInEvents(false), |
|
|
|
fMidiOut(this) |
|
|
|
fWasPlayingBefore(false), |
|
|
|
fInEvents(), |
|
|
|
fMidiOut(this), |
|
|
|
leakDetector_MidiSequencerPlugin() |
|
|
|
{ |
|
|
|
// TEST SONG (unsorted to test RtList API) |
|
|
|
|
|
|
|
@@ -122,22 +127,10 @@ public: |
|
|
|
fMidiOut.addNote(3456*m, 0, 62, 90, 325*m); |
|
|
|
} |
|
|
|
|
|
|
|
~MidiSequencerPlugin() override |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
protected: |
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Plugin process calls |
|
|
|
|
|
|
|
void activate() override |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
void deactivate() override |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
void process(float**, float**, const uint32_t frames, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override |
|
|
|
{ |
|
|
|
const NativeTimeInfo* const timePos = getTimeInfo(); |
|
|
|
@@ -155,26 +148,80 @@ protected: |
|
|
|
rawMidiEvent.data[2] = midiEvent->data[2]; |
|
|
|
rawMidiEvent.data[3] = midiEvent->data[3]; |
|
|
|
rawMidiEvent.size = midiEvent->size; |
|
|
|
rawMidiEvent.time = timePos->frame + midiEvent->time; |
|
|
|
rawMidiEvent.time = timePos->playing ? timePos->frame + midiEvent->time : 0; |
|
|
|
|
|
|
|
fInEvents.appendRT(rawMidiEvent); |
|
|
|
} |
|
|
|
|
|
|
|
if (fInEvents.mutex.tryLock()) |
|
|
|
{ |
|
|
|
fInEvents.splice(); |
|
|
|
fInEvents.mutex.unlock(); |
|
|
|
} |
|
|
|
fInEvents.trySplice(); |
|
|
|
} |
|
|
|
|
|
|
|
if (timePos->playing) |
|
|
|
{ |
|
|
|
fMidiOut.play(timePos->frame, frames); |
|
|
|
} |
|
|
|
else if (fWasPlayingBefore) |
|
|
|
{ |
|
|
|
NativeMidiEvent midiEvent; |
|
|
|
|
|
|
|
midiEvent.port = 0; |
|
|
|
midiEvent.time = 0; |
|
|
|
midiEvent.data[0] = MIDI_STATUS_CONTROL_CHANGE; |
|
|
|
midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF; |
|
|
|
midiEvent.data[2] = 0; |
|
|
|
midiEvent.data[3] = 0; |
|
|
|
midiEvent.size = 3; |
|
|
|
|
|
|
|
for (int i=0; i < MAX_MIDI_CHANNELS; ++i) |
|
|
|
{ |
|
|
|
midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE+i); |
|
|
|
NativePluginAndUiClass::writeMidiEvent(&midiEvent); |
|
|
|
} |
|
|
|
|
|
|
|
carla_stdout("WAS PLAYING BEFORE, NOW STOPPED"); |
|
|
|
} |
|
|
|
|
|
|
|
fWasPlayingBefore = timePos->playing; |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Plugin process calls |
|
|
|
// Plugin UI calls |
|
|
|
|
|
|
|
void writeMidiEvent(const uint8_t port, const uint32_t timePosFrame, const RawMidiEvent* const event) override |
|
|
|
void uiShow(const bool show) override |
|
|
|
{ |
|
|
|
NativePluginAndUiClass::uiShow(show); |
|
|
|
|
|
|
|
if (show) |
|
|
|
_sendEventsToUI(); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Plugin state calls |
|
|
|
|
|
|
|
char* getState() const override |
|
|
|
{ |
|
|
|
// TODO: malloc list of events |
|
|
|
|
|
|
|
return nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
void setState(const char* const data) override |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(data != nullptr,); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
fMidiOut.clear(); |
|
|
|
|
|
|
|
// TODO: set events according to data |
|
|
|
|
|
|
|
_sendEventsToUI(); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// AbstractMidiPlayer calls |
|
|
|
|
|
|
|
void writeMidiEvent(const uint8_t port, const uint64_t timePosFrame, const RawMidiEvent* const event) override |
|
|
|
{ |
|
|
|
NativeMidiEvent midiEvent; |
|
|
|
|
|
|
|
@@ -186,48 +233,151 @@ protected: |
|
|
|
midiEvent.data[3] = event->data[3]; |
|
|
|
midiEvent.size = event->size; |
|
|
|
|
|
|
|
NativePluginClass::writeMidiEvent(&midiEvent); |
|
|
|
NativePluginAndUiClass::writeMidiEvent(&midiEvent); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Pipe Server calls |
|
|
|
|
|
|
|
bool msgReceived(const char* const msg) noexcept override |
|
|
|
{ |
|
|
|
if (NativePluginAndUiClass::msgReceived(msg)) |
|
|
|
return true; |
|
|
|
|
|
|
|
if (std::strcmp(msg, "midi-clear-all") == 0) |
|
|
|
{ |
|
|
|
fMidiOut.clear(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
if (std::strcmp(msg, "midievent-add") == 0) |
|
|
|
{ |
|
|
|
uint64_t time; |
|
|
|
uint8_t size; |
|
|
|
|
|
|
|
CARLA_SAFE_ASSERT_RETURN(readNextLineAsULong(time), true); |
|
|
|
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true); |
|
|
|
|
|
|
|
uint8_t data[size], dvalue; |
|
|
|
|
|
|
|
for (uint8_t i=0; i<size; ++i) |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true); |
|
|
|
data[i] = dvalue; |
|
|
|
} |
|
|
|
|
|
|
|
fMidiOut.addRaw(time, data, size); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
if (std::strcmp(msg, "midievent-remove") == 0) |
|
|
|
{ |
|
|
|
uint64_t time; |
|
|
|
uint8_t size; |
|
|
|
|
|
|
|
CARLA_SAFE_ASSERT_RETURN(readNextLineAsULong(time), true); |
|
|
|
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true); |
|
|
|
|
|
|
|
uint8_t data[size], dvalue; |
|
|
|
|
|
|
|
for (uint8_t i=0; i<size; ++i) |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true); |
|
|
|
data[i] = dvalue; |
|
|
|
} |
|
|
|
|
|
|
|
fMidiOut.removeRaw(time, data, size); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
|
|
private: |
|
|
|
bool fWantInEvents; |
|
|
|
bool fWasPlayingBefore; |
|
|
|
|
|
|
|
struct InRtEvents { |
|
|
|
CarlaMutex mutex; |
|
|
|
RtList<RawMidiEvent>::Pool dataPool; |
|
|
|
RtList<RawMidiEvent> data; |
|
|
|
RtList<RawMidiEvent> dataPendingRT; |
|
|
|
RtLinkedList<RawMidiEvent>::Pool dataPool; |
|
|
|
RtLinkedList<RawMidiEvent> data; |
|
|
|
RtLinkedList<RawMidiEvent> dataPendingRT; |
|
|
|
|
|
|
|
InRtEvents() |
|
|
|
: dataPool(MIN_PREALLOCATED_EVENT_COUNT, MAX_PREALLOCATED_EVENT_COUNT), |
|
|
|
InRtEvents() noexcept |
|
|
|
: mutex(), |
|
|
|
dataPool(MIN_PREALLOCATED_EVENT_COUNT, MAX_PREALLOCATED_EVENT_COUNT), |
|
|
|
data(dataPool), |
|
|
|
dataPendingRT(dataPool) {} |
|
|
|
|
|
|
|
~InRtEvents() |
|
|
|
~InRtEvents() noexcept |
|
|
|
{ |
|
|
|
clear(); |
|
|
|
} |
|
|
|
|
|
|
|
void appendRT(const RawMidiEvent& event) |
|
|
|
void appendRT(const RawMidiEvent& event) noexcept |
|
|
|
{ |
|
|
|
dataPendingRT.append(event); |
|
|
|
} |
|
|
|
|
|
|
|
void clear() |
|
|
|
void clear() noexcept |
|
|
|
{ |
|
|
|
mutex.lock(); |
|
|
|
data.clear(); |
|
|
|
dataPendingRT.clear(); |
|
|
|
mutex.unlock(); |
|
|
|
} |
|
|
|
|
|
|
|
void splice() |
|
|
|
void trySplice() noexcept |
|
|
|
{ |
|
|
|
dataPendingRT.spliceAppend(data); |
|
|
|
if (mutex.tryLock()) |
|
|
|
{ |
|
|
|
if (dataPendingRT.count() > 0) |
|
|
|
dataPendingRT.moveTo(data, true); |
|
|
|
mutex.unlock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
CARLA_DECLARE_NON_COPY_STRUCT(InRtEvents); |
|
|
|
|
|
|
|
} fInEvents; |
|
|
|
|
|
|
|
MidiPattern fMidiOut; |
|
|
|
|
|
|
|
void _sendEventsToUI() const noexcept |
|
|
|
{ |
|
|
|
char strBuf[0xff+1]; |
|
|
|
strBuf[0xff] = '\0'; |
|
|
|
|
|
|
|
const CarlaMutexLocker cml1(getPipeLock()); |
|
|
|
const CarlaMutexLocker cml2(fMidiOut.getLock()); |
|
|
|
|
|
|
|
writeMessage("midi-clear-all\n", 16); |
|
|
|
|
|
|
|
for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next()) |
|
|
|
{ |
|
|
|
const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr)); |
|
|
|
CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr); |
|
|
|
|
|
|
|
writeMessage("midievent-add\n", 14); |
|
|
|
|
|
|
|
std::snprintf(strBuf, 0xff, P_INT64 "\n", rawMidiEvent->time); |
|
|
|
writeMessage(strBuf); |
|
|
|
|
|
|
|
std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->size); |
|
|
|
writeMessage(strBuf); |
|
|
|
|
|
|
|
for (uint8_t i=0, size=rawMidiEvent->size; i<size; ++i) |
|
|
|
{ |
|
|
|
std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->data[i]); |
|
|
|
writeMessage(strBuf); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
PluginClassEND(MidiSequencerPlugin) |
|
|
|
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiSequencerPlugin) |
|
|
|
}; |
|
|
|
@@ -236,8 +386,11 @@ private: |
|
|
|
|
|
|
|
static const NativePluginDescriptor midisequencerDesc = { |
|
|
|
/* category */ NATIVE_PLUGIN_CATEGORY_UTILITY, |
|
|
|
/* hints */ NATIVE_PLUGIN_IS_RTSAFE/*|NATIVE_PLUGIN_HAS_GUI*/, |
|
|
|
/* supports */ static_cast<NativePluginSupports>(0x0), |
|
|
|
/* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE |
|
|
|
|NATIVE_PLUGIN_HAS_UI |
|
|
|
|NATIVE_PLUGIN_USES_STATE |
|
|
|
|NATIVE_PLUGIN_USES_TIME), |
|
|
|
/* supports */ NATIVE_PLUGIN_SUPPORTS_EVERYTHING, |
|
|
|
/* audioIns */ 0, |
|
|
|
/* audioOuts */ 0, |
|
|
|
/* midiIns */ 1, |
|
|
|
@@ -246,7 +399,7 @@ static const NativePluginDescriptor midisequencerDesc = { |
|
|
|
/* paramOuts */ 0, |
|
|
|
/* name */ "MIDI Sequencer", |
|
|
|
/* label */ "midisequencer", |
|
|
|
/* maker */ "falkTX", |
|
|
|
/* maker */ "falkTX, tatch", |
|
|
|
/* copyright */ "GNU GPL v2+", |
|
|
|
PluginDescriptorFILL(MidiSequencerPlugin) |
|
|
|
}; |
|
|
|
|