From 8eed5601261dafaf65cf7940095548f166d9fa7f Mon Sep 17 00:00:00 2001 From: wvengen Date: Thu, 11 Jun 2020 20:25:51 +0200 Subject: [PATCH 1/4] Add midi connect controller action --- src/controller/binding.hxx | 11 +++++++- src/controller/controller.hxx | 4 +++ src/controller/genericmidi.cxx | 49 +++++++++++++++++++++++++++++----- src/controller/genericmidi.hxx | 2 ++ src/controller/nonseq.cxx | 4 +-- src/controllerupdater.cxx | 10 +++++++ src/controllerupdater.hxx | 4 +++ src/event.hxx | 27 +++++++++++++++++++ src/eventhandlerdsp.cxx | 20 ++++++++++++++ src/jack.cxx | 17 ++++++++++++ src/jack.hxx | 4 +++ src/observer/midi.cxx | 20 +++++++++----- src/observer/midi.hxx | 6 ++++- 13 files changed, 161 insertions(+), 17 deletions(-) diff --git a/src/controller/binding.hxx b/src/controller/binding.hxx index e4f48108..4fac52a0 100644 --- a/src/controller/binding.hxx +++ b/src/controller/binding.hxx @@ -29,11 +29,16 @@ class Binding { public: Binding() : status(0), data(0), action(0), active(1), - track(-2),scene(-1),send(-1) + track(-2),scene(-1),send(-1),dataList(0),dataListSize(0) { ID = privateID++; } + ~Binding() + { + if (dataList) delete dataList; + } + int ID; unsigned char status; @@ -52,6 +57,10 @@ public: /// maps from Gridlogic::State to MIDI output value from binding std::map clipStateMap; + /// arbitrary-length data (TODO use this instead of data) + unsigned char *dataList; + unsigned int dataListSize; + private: /// static int counter to allow deleting this specific instance static int privateID; diff --git a/src/controller/controller.hxx b/src/controller/controller.hxx index b577a172..99021596 100644 --- a/src/controller/controller.hxx +++ b/src/controller/controller.hxx @@ -95,6 +95,10 @@ public: /// reset controller virtual void reset(); + /// Jack MIDI (dis)connect + virtual void midiConnect( jack_port_t* a, jack_port_t *b ) {} + virtual void midiDisconnect( jack_port_t* a, jack_port_t *b ) {} + private: static int privateID; int ID; diff --git a/src/controller/genericmidi.cxx b/src/controller/genericmidi.cxx index 6c2409f6..589d9dc6 100644 --- a/src/controller/genericmidi.cxx +++ b/src/controller/genericmidi.cxx @@ -138,7 +138,7 @@ void GenericMIDI::recordArm(int t, bool enabled) data[0] = b->status; data[1] = b->data; data[2] = enabled ? 127 : 0; - writeMidi( data ); + writeMidi( data, 3 ); return; } } @@ -154,7 +154,7 @@ void GenericMIDI::metronomeEnable(bool enabled) data[0] = b->status; data[1] = b->data; data[2] = enabled ? 127 : 0; - writeMidi( data ); + writeMidi( data, 3 ); return; } } @@ -171,7 +171,7 @@ void GenericMIDI::trackSend(int t, int send, float v) data[0] = b->status; data[1] = b->data; data[2] = v * 127; - writeMidi( data ); + writeMidi( data, 3 ); return; } } @@ -187,7 +187,7 @@ void GenericMIDI::trackSendActive(int t, int send, bool a) data[0] = b->status; data[1] = b->data; data[2] = a ? 127 : 0; - writeMidi( data ); + writeMidi( data, 3 ); return; } } @@ -352,7 +352,7 @@ void GenericMIDI::setSceneState(int t, int scene, GridLogic::State s) data[2] = it->second; //LUPPP_NOTE("GenericMIDI::sceneState() writing event %i, %i, %i", data[0],data[1],data[2] ); - writeMidi( data ); + writeMidi( data, 3 ); } } } @@ -388,12 +388,28 @@ void GenericMIDI::launchScene( int scene ) data[2] = (i == scene) * 127; //LUPPP_NOTE("this = %i GenericMIDI::launchScene()", this ); - writeMidi( data ); + writeMidi( data, 3 ); } } } +void GenericMIDI::midiConnect(jack_port_t* a, jack_port_t *b) +{ + // skip unless this controller's output port is connected + if (!isMyOutput(a) && !isMyOutput(b)) { + return; + } + + for(unsigned int i = 0; i < actionToMidi.size(); i++) { + Binding* b = actionToMidi.at(i); + + if ( b->action == MIDI_CONNECT && b->dataListSize > 0 ) { + writeMidi( b->dataList, b->dataListSize ); + } + } + +} int GenericMIDI::loadController( std::string file ) { @@ -560,6 +576,27 @@ Binding* GenericMIDI::setupBinding( cJSON* binding ) return 0; } + if ( strcmp( actionJson->valuestring, "midi:connect" ) == 0 ) { + tmp->action = Event::MIDI_CONNECT; + + cJSON* dataList = cJSON_GetObjectItem( binding, "dataList" ); + if ( !dataList ) { + LUPPP_WARN("Binding midi connect: doesn't have dataList field"); + delete tmp; + return 0; + } + + tmp->dataListSize = cJSON_GetArraySize( dataList ); + tmp->dataList = new unsigned char[tmp->dataListSize]; + + for(int i = 0; i < tmp->dataListSize; i++ ) { + cJSON* dataListItem = cJSON_GetArrayItem( dataList, i ); + tmp->dataList[i] = dataListItem->valueint; + } + + return tmp; + } + cJSON* statusJson = cJSON_GetObjectItem( binding, "status" ); cJSON* dataJson = cJSON_GetObjectItem( binding, "data" ); if ( !statusJson || !dataJson ) { diff --git a/src/controller/genericmidi.hxx b/src/controller/genericmidi.hxx index 86564030..c2362a53 100644 --- a/src/controller/genericmidi.hxx +++ b/src/controller/genericmidi.hxx @@ -86,6 +86,8 @@ public: void setupBinding( LupppAction eventType, int midiStatus, int midiData, int track, int scene, int send, int active ); void removeBinding( int bindingID ); + void midiConnect(jack_port_t* a, jack_port_t* b); + private: STATUS stat; diff --git a/src/controller/nonseq.cxx b/src/controller/nonseq.cxx index 33d0de32..054735a5 100644 --- a/src/controller/nonseq.cxx +++ b/src/controller/nonseq.cxx @@ -41,7 +41,7 @@ void NonSeq::launchScene( int scene ) data[2] = scene; //LUPPP_NOTE("NonSeq::launchScene() %i, %i, %i\n", data[0],data[1],data[2] ); - writeMidi( data ); + writeMidi( data, 3 ); } std::string NonSeq::getName() @@ -65,7 +65,7 @@ void NonSeq::setSceneState(int track, int scene, GridLogic::State s) data[2] = track; //LUPPP_NOTE("NonSeq::setSceneState() %i, %i, %i\n", data[0],data[1],data[2] ); - writeMidi( data ); + writeMidi( data, 3 ); } diff --git a/src/controllerupdater.cxx b/src/controllerupdater.cxx index 03298302..0388c97d 100644 --- a/src/controllerupdater.cxx +++ b/src/controllerupdater.cxx @@ -186,3 +186,13 @@ void ControllerUpdater::metronomeEnable(bool b) { for(unsigned int i = 0; i < c.size(); i++) c.at(i)->metronomeEnable(b); } + +void ControllerUpdater::midiConnect(jack_port_t *a, jack_port_t *b) +{ + for(unsigned int i = 0; i < c.size(); i++) c.at(i)->midiConnect(a, b); +} + +void ControllerUpdater::midiDisconnect(jack_port_t *a, jack_port_t *b) +{ + for(unsigned int i = 0; i < c.size(); i++) c.at(i)->midiDisconnect(a, b); +} \ No newline at end of file diff --git a/src/controllerupdater.hxx b/src/controllerupdater.hxx index 7146878e..15a4d746 100644 --- a/src/controllerupdater.hxx +++ b/src/controllerupdater.hxx @@ -21,6 +21,7 @@ #include #include +#include #include "controller/controller.hxx" @@ -87,6 +88,9 @@ public: void metronomeEnable(bool b); + void midiConnect(jack_port_t* a, jack_port_t* b); + void midiDisconnect(jack_port_t* a, jack_port_t* b); + private: std::vector c; }; diff --git a/src/event.hxx b/src/event.hxx index b50ca64c..fec7a883 100644 --- a/src/event.hxx +++ b/src/event.hxx @@ -124,6 +124,8 @@ enum EVENT_TYPE { CONTROLLER_BINDING_MADE, CONTROLLER_BINDING_REMOVE, + MIDI_CONNECT, + QUIT, // for keeping loop index's inside the enum @@ -1170,6 +1172,31 @@ public: } }; +class EventMidiConnect : public EventBase +{ +public: + int type() + { + return int(MIDI_CONNECT); + } + uint32_t size() + { + return sizeof(EventMidiConnect); + } + + jack_port_id_t a; + jack_port_id_t b; + bool connect; + + EventMidiConnect() {} + EventMidiConnect(jack_port_id_t a, jack_port_id_t b, int connect) + { + this->a = a; + this->b = b; + this->connect = connect; + } +}; + #endif // LUPPP_EVENT_H diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index 43dc58d4..ed196155 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -454,6 +454,26 @@ void handleDspEvents() break; } + case Event::MIDI_CONNECT: { + if ( availableRead >= sizeof(EventMidiConnect) ) { + EventMidiConnect ev; + jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventMidiConnect) ); + + // get the jack ports, and notify controllers + + jack_client_t* c = jack->getJackClientPointer(); + jack_port_t* portA = jack_port_by_id( c, ev.a ); + jack_port_t* portB = jack_port_by_id( c, ev.b ); + + if ( ev.connect > 0 ) { + jack->getControllerUpdater()->midiConnect( portA, portB ); + } else { + jack->getControllerUpdater()->midiDisconnect( portA, portB ); + } + } + break; + } + default: { cout << "DSP: Unkown message!! Will clog ringbuffer" << endl; // just do nothing diff --git a/src/jack.cxx b/src/jack.cxx index ff742b8f..b0909bb3 100644 --- a/src/jack.cxx +++ b/src/jack.cxx @@ -295,6 +295,12 @@ Jack::Jack( std::string name ) : LUPPP_ERROR("%s","Error setting timebase callback"); } + if ( jack_set_port_connect_callback(client, + static_connect, + static_cast(this)) ) { + LUPPP_ERROR("%s","Error setting JACK connect callback"); + } + //Controller* m = new AkaiAPC(); // TODO: Add GUI dialog to add controllers, and insert them into the controller map. @@ -758,6 +764,12 @@ int Jack::timebase(jack_transport_state_t state, return 0; } +void Jack::connect(jack_port_id_t a, jack_port_id_t b, int connect) +{ + EventMidiConnect e(a, b, connect); + writeToDspRingbuffer( &e ); +} + int Jack::static_process(jack_nframes_t nframes, void *instance) { return static_cast(instance)->process(nframes); @@ -771,3 +783,8 @@ int Jack::static_timebase(jack_transport_state_t state, { return static_cast(instance)->timebase(state,nframes, pos, newPos ); } + +void Jack::static_connect(jack_port_id_t a, jack_port_id_t b, int connect, void *instance) +{ + static_cast(instance)->connect(a, b, connect); +} diff --git a/src/jack.hxx b/src/jack.hxx index 12844264..40ca6573 100644 --- a/src/jack.hxx +++ b/src/jack.hxx @@ -222,6 +222,8 @@ private: jack_position_t*, int ); + void connect(jack_port_id_t, jack_port_id_t, int); + // static JACK callbacks static int static_process (jack_nframes_t, void *); @@ -231,6 +233,8 @@ private: int, void* ); + static void static_connect (jack_port_id_t, jack_port_id_t, int, void *); + // UI update variables int uiUpdateCounter; int uiUpdateConstant; diff --git a/src/observer/midi.cxx b/src/observer/midi.cxx index e3ed147f..458734f4 100644 --- a/src/observer/midi.cxx +++ b/src/observer/midi.cxx @@ -42,20 +42,16 @@ MidiIO::~MidiIO() } -void MidiIO::writeMidi( unsigned char* data ) +void MidiIO::writeMidi( unsigned char* data, unsigned int length ) { void* portBuffer = jack_port_get_buffer( jackOutputPort, jack->getBuffersize() ); - unsigned char* buffer = jack_midi_event_reserve( portBuffer, 0, 3); + unsigned char* buffer = jack_midi_event_reserve( portBuffer, 0, sizeof(unsigned char)*length ); if( buffer == 0 ) { return; } else { - //memcpy( buffer, data, sizeof(unsigned char)*3 ); - buffer[0] = data[0]; - buffer[1] = data[1]; - buffer[2] = data[2]; + memcpy( buffer, data, sizeof(unsigned char)*length ); } - } int MidiIO::registerMidiPorts(std::string name) @@ -123,3 +119,13 @@ void MidiIO::process(int nframes) } } + +bool MidiIO::isMyOutput(jack_port_t* port) +{ + return port == jackOutputPort; +} + +bool MidiIO::isMyInput(jack_port_t* port) +{ + return port == jackInputPort; +} diff --git a/src/observer/midi.hxx b/src/observer/midi.hxx index 9fb3e657..3a004d46 100644 --- a/src/observer/midi.hxx +++ b/src/observer/midi.hxx @@ -50,7 +50,11 @@ public: virtual void midi(unsigned char* /*data*/) {}; - void writeMidi( unsigned char* /*data*/ ); + void writeMidi(unsigned char* /*data*/, unsigned int length); + +protected: + bool isMyOutput(jack_port_t*); + bool isMyInput(jack_port_t*); private: bool portsRegistered; From c429b766d3e070fa7722c9202b24aad26faf3900 Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 22 Jun 2020 13:10:07 +0200 Subject: [PATCH 2/4] Fix missing jack include --- src/controller/controller.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controller/controller.hxx b/src/controller/controller.hxx index 99021596..d3df6139 100644 --- a/src/controller/controller.hxx +++ b/src/controller/controller.hxx @@ -21,6 +21,7 @@ #define LUPPP_CONTROLLER_H #include +#include #include "../gridlogic.hxx" From 85721e306589f86c3663122b0ca14251a29bd20e Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 22 Jun 2020 12:34:13 +0200 Subject: [PATCH 3/4] Add MIDI beat event --- src/controller/binding.hxx | 1 + src/controller/controller.hxx | 1 + src/controller/genericmidi.cxx | 41 ++++++++++++++++++++++++++++++++++ src/controller/genericmidi.hxx | 2 ++ src/controllerupdater.cxx | 7 +++++- src/controllerupdater.hxx | 2 ++ src/eventhandlerdsp.cxx | 8 +++++++ src/timemanager.cxx | 1 + 8 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/controller/binding.hxx b/src/controller/binding.hxx index 4fac52a0..24692dc5 100644 --- a/src/controller/binding.hxx +++ b/src/controller/binding.hxx @@ -53,6 +53,7 @@ public: int track; int scene; int send; + int beat; /// maps from Gridlogic::State to MIDI output value from binding std::map clipStateMap; diff --git a/src/controller/controller.hxx b/src/controller/controller.hxx index d3df6139..2046f0d6 100644 --- a/src/controller/controller.hxx +++ b/src/controller/controller.hxx @@ -80,6 +80,7 @@ public: /// Time virtual void bpm(int bpm) {} virtual void tapTempo(bool b) {} + virtual void setBarBeat(int ba, int be) {} /// Special virtual void specialScene(int t, int scene) {} diff --git a/src/controller/genericmidi.cxx b/src/controller/genericmidi.cxx index 589d9dc6..959f5b21 100644 --- a/src/controller/genericmidi.cxx +++ b/src/controller/genericmidi.cxx @@ -203,6 +203,18 @@ void GenericMIDI::trackJackSendActivate(int t, bool a) } +void GenericMIDI::setBarBeat(int ba, int be) +{ + for(unsigned int i = 0; i < actionToMidi.size(); i++) { + Binding* b = actionToMidi.at(i); + + // 4 beats per bar hardcoded + if ( b->action == TIME_BAR_BEAT && b->beat == be % 4 ) { + writeMidi( b->dataList, b->dataListSize ); + } + } +} + void GenericMIDI::midi(unsigned char* midi) { int status = midi[0]; @@ -597,6 +609,35 @@ Binding* GenericMIDI::setupBinding( cJSON* binding ) return tmp; } + if ( strcmp( actionJson->valuestring, "metronome:beat" ) == 0 ) { + tmp->action = Event::TIME_BAR_BEAT; + + cJSON* beatJson = cJSON_GetObjectItem( binding, "beat" ); + if ( !beatJson ) { + LUPPP_WARN("Binding metronome beat: doesn't have beat field"); + delete tmp; + return 0; + } + tmp->beat = beatJson->valueint; + + cJSON* dataList = cJSON_GetObjectItem( binding, "dataList" ); + if ( !dataList ) { + LUPPP_WARN("Binding metronome beat: doesn't have dataList field"); + delete tmp; + return 0; + } + + tmp->dataListSize = cJSON_GetArraySize( dataList ); + tmp->dataList = new unsigned char[tmp->dataListSize]; + + for(int i = 0; i < tmp->dataListSize; i++ ) { + cJSON* dataListItem = cJSON_GetArrayItem( dataList, i ); + tmp->dataList[i] = dataListItem->valueint; + } + + return tmp; + } + cJSON* statusJson = cJSON_GetObjectItem( binding, "status" ); cJSON* dataJson = cJSON_GetObjectItem( binding, "data" ); if ( !statusJson || !dataJson ) { diff --git a/src/controller/genericmidi.hxx b/src/controller/genericmidi.hxx index c2362a53..ecf048f2 100644 --- a/src/controller/genericmidi.hxx +++ b/src/controller/genericmidi.hxx @@ -73,6 +73,8 @@ public: bool footswitchNextScene; bool footswitchPrevScene; + virtual void setBarBeat(int ba, int be); + void reset(); diff --git a/src/controllerupdater.cxx b/src/controllerupdater.cxx index 0388c97d..7fd14e3d 100644 --- a/src/controllerupdater.cxx +++ b/src/controllerupdater.cxx @@ -187,6 +187,11 @@ void ControllerUpdater::metronomeEnable(bool b) for(unsigned int i = 0; i < c.size(); i++) c.at(i)->metronomeEnable(b); } +void ControllerUpdater::setBarBeat(int ba, int be) +{ + for(unsigned int i = 0; i < c.size(); i++) c.at(i)->setBarBeat(ba, be); +} + void ControllerUpdater::midiConnect(jack_port_t *a, jack_port_t *b) { for(unsigned int i = 0; i < c.size(); i++) c.at(i)->midiConnect(a, b); @@ -195,4 +200,4 @@ void ControllerUpdater::midiConnect(jack_port_t *a, jack_port_t *b) void ControllerUpdater::midiDisconnect(jack_port_t *a, jack_port_t *b) { for(unsigned int i = 0; i < c.size(); i++) c.at(i)->midiDisconnect(a, b); -} \ No newline at end of file +} diff --git a/src/controllerupdater.hxx b/src/controllerupdater.hxx index 15a4d746..bddda271 100644 --- a/src/controllerupdater.hxx +++ b/src/controllerupdater.hxx @@ -88,6 +88,8 @@ public: void metronomeEnable(bool b); + void setBarBeat(int ba, int be); + void midiConnect(jack_port_t* a, jack_port_t* b); void midiDisconnect(jack_port_t* a, jack_port_t* b); diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index ed196155..0605bcb7 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -290,6 +290,14 @@ void handleDspEvents() } break; } + case Event::TIME_BAR_BEAT: { + if ( availableRead >= sizeof(EventTimeBarBeat) ) { + EventTimeBarBeat ev; + jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventTimeBarBeat) ); + jack->getControllerUpdater()->setBarBeat(ev.bar, ev.beat); + } + break; + } // ======== FX =========== diff --git a/src/timemanager.cxx b/src/timemanager.cxx index 896e7823..7fa4bf6b 100644 --- a/src/timemanager.cxx +++ b/src/timemanager.cxx @@ -268,6 +268,7 @@ void TimeManager::process(Buffers* buffers) // write new beat to UI (bar info currently not used) EventTimeBarBeat e( barCounter, beatCounter ); writeToGuiRingbuffer( &e ); + writeToDspRingbuffer( &e ); From 368f4b9ddc4d9262cddad6a8c3f75850cbb4e4c9 Mon Sep 17 00:00:00 2001 From: wvengen Date: Thu, 25 Nov 2021 19:54:21 +0100 Subject: [PATCH 4/4] Don't use dsp ringbuffer while in dsp thread --- src/eventhandlerdsp.cxx | 8 -------- src/timemanager.cxx | 4 +++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index 0605bcb7..ed196155 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -290,14 +290,6 @@ void handleDspEvents() } break; } - case Event::TIME_BAR_BEAT: { - if ( availableRead >= sizeof(EventTimeBarBeat) ) { - EventTimeBarBeat ev; - jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventTimeBarBeat) ); - jack->getControllerUpdater()->setBarBeat(ev.bar, ev.beat); - } - break; - } // ======== FX =========== diff --git a/src/timemanager.cxx b/src/timemanager.cxx index 7fa4bf6b..7562d85c 100644 --- a/src/timemanager.cxx +++ b/src/timemanager.cxx @@ -27,6 +27,7 @@ #include "buffers.hxx" #include "eventhandler.hxx" +#include "controllerupdater.hxx" #include "observer/time.hxx" @@ -268,7 +269,8 @@ void TimeManager::process(Buffers* buffers) // write new beat to UI (bar info currently not used) EventTimeBarBeat e( barCounter, beatCounter ); writeToGuiRingbuffer( &e ); - writeToDspRingbuffer( &e ); + // and send to cotroller output + jack->getControllerUpdater()->setBarBeat(barCounter, beatCounter);