zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit 07b9a3adf4612bc368f803aa90a987a6e18c6ece
parent f8afad6833b019be46b384034fc2f91733403055
Author: Johannes Lorenz <j.git@lorenz-ho.me>
Date:   Sun, 26 Jan 2025 01:05:39 +0100

Allow 2 in-process UIs

This allow MiddleWare to exchange OSC messages with a further, second
in-process UI. This will be needed for a further commit where
both PortChecker's clients will be in-place UIs.

Since this is only needed for PortChecker, the second UI is reduced to
OSC messaging and on purpose has no `Fl_Osc_Interface`.

Diffstat:
Msrc/Misc/MiddleWare.cpp | 55+++++++++++++++++++++++++++++++++----------------------
Msrc/Misc/MiddleWare.h | 8++++----
Msrc/Plugin/ZynAddSubFX/ZynAddSubFX.cpp | 2+-
Msrc/Tests/PortChecker.cpp | 32++++++++++++++++++--------------
Msrc/Tests/SaveOSC.cpp | 82++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/UI/Connection.cpp | 6+++---
Msrc/UI/NSM.C | 6+++---
Msrc/main.cpp | 4++--
8 files changed, 105 insertions(+), 90 deletions(-)

diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp @@ -661,7 +661,8 @@ public: //Give it to the backend and wait for the old part to return for //deallocation parent->transmitMsg("/load-part", "ib", npart, sizeof(Part *), &p); - GUI::raiseUi(ui, "/damage", "s", ("/part" + to_s(npart) + "/").c_str()); + for(void* uihandle : ui) + GUI::raiseUi(uihandle, "/damage", "s", ("/part" + to_s(npart) + "/").c_str()); } //Well, you don't get much crazier than changing out all of your RT @@ -972,9 +973,6 @@ public: //Only valid until freed Master *previous_master = nullptr; - //The ONLY means that any chunk of UI code should have for interacting with the - //backend - Fl_Osc_Interface *osc; //Synth Engine Parameters ParamStore kits; @@ -982,10 +980,15 @@ public: void(*idle)(void*); void* idle_ptr; - //General UI callback - cb_t cb; - //UI handle - void *ui; + //General UI callbacks + cb_t cb[2]; + //UI handles + void *ui[2]; + + //The ONLY means that any chunk of UI code should have for interacting with the + //backend + //Note: Only the first UI is defined to have such an interface + Fl_Osc_Interface *osc; std::atomic_int pending_load[NUM_MIDI_PARTS]; std::atomic_int actual_load[NUM_MIDI_PARTS]; @@ -1934,7 +1937,7 @@ static rtosc::Ports middlewareReplyPorts = { MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, Config* config, int preferrred_port) - :parent(mw), config(config), ui(nullptr), synth(std::move(synth_)), + :parent(mw), config(config), ui{nullptr,nullptr}, synth(std::move(synth_)), presetsstore(*config), autoSave(-1, [this]() { auto master = this->master; this->doReadOnlyOp([master](){ @@ -1962,7 +1965,8 @@ MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, //dummy callback for starters - cb = [](void*, const char*){}; + for(cb_t& uicb : cb) + uicb = [](void*, const char*){}; idle = 0; idle_ptr = 0; @@ -2191,12 +2195,13 @@ bool MiddleWareImpl::doReadOnlyOpNormal(std::function<void()> read_only_fn, bool void MiddleWareImpl::broadcastToRemote(const char *rtmsg) { - //Always send to the local UI + //Always send to the local UIs sendToRemote(rtmsg, "GUI"); + sendToRemote(rtmsg, "GUI2"); //Send to remote UI if there's one listening for(auto rem:known_remotes) - if(rem != "GUI") + if(rem != "GUI" && rem != "GUI2") sendToRemote(rtmsg, rem); broadcast = false; @@ -2213,7 +2218,9 @@ void MiddleWareImpl::sendToRemote(const char *rtmsg, std::string dest) //printf("sendToRemote(%s:%s,%s)\n", rtmsg, rtosc_argument_string(rtmsg), // dest.c_str()); if(dest == "GUI") { - cb(ui, rtmsg); + cb[0](ui[0], rtmsg); + } else if(dest == "GUI2") { + cb[1](ui[1], rtmsg); } else if(!dest.empty()) { lo_message msg = lo_message_deserialise((void*)rtmsg, rtosc_message_length(rtmsg, bToU->buffer_size()), NULL); @@ -2534,10 +2541,11 @@ void MiddleWare::doReadOnlyOp(std::function<void()> fn) impl->doReadOnlyOp(fn); } -void MiddleWare::setUiCallback(void(*cb)(void*,const char *), void *ui) +void MiddleWare::setUiCallback(int gui_id, void(*cb)(void*,const char *), void *ui) { - impl->cb = cb; - impl->ui = ui; + assert(gui_id < sizeof(impl->cb)/sizeof(impl->cb[0])); + impl->cb[gui_id] = cb; + impl->ui[gui_id] = ui; } void MiddleWare::setIdleCallback(void(*cb)(void*), void *ptr) @@ -2572,32 +2580,35 @@ void MiddleWare::transmitMsg_va(const char *path, const char *args, va_list va) fprintf(stderr, "Error in transmitMsg(va)n"); } -void MiddleWare::transmitMsgGui(const char *msg) +void MiddleWare::transmitMsgGui(int gui_id, const char *msg) { - if(activeUrl() != "GUI") { + if(gui_id == 0 && activeUrl() != "GUI") { transmitMsg("/echo", "ss", "OSC_URL", "GUI"); activeUrl("GUI"); + } else if(gui_id == 1 && activeUrl() != "GUI2") { + transmitMsg("/echo", "ss", "OSC_URL", "GUI2"); + activeUrl("GUI2"); } transmitMsg(msg); } -void MiddleWare::transmitMsgGui(const char *path, const char *args, ...) +void MiddleWare::transmitMsgGui(int gui_id, const char *path, const char *args, ...) { char buffer[1024]; va_list va; va_start(va,args); if(rtosc_vmessage(buffer,1024,path,args,va)) - transmitMsgGui(buffer); + transmitMsgGui(gui_id, buffer); else fprintf(stderr, "Error in transmitMsgGui(...)\n"); va_end(va); } -void MiddleWare::transmitMsgGui_va(const char *path, const char *args, va_list va) +void MiddleWare::transmitMsgGui_va(int gui_id, const char *path, const char *args, va_list va) { char buffer[1024]; if(rtosc_vmessage(buffer, 1024, path, args, va)) - transmitMsgGui(buffer); + transmitMsgGui(gui_id, buffer); else fprintf(stderr, "Error in transmitMsgGui(va)n"); } diff --git a/src/Misc/MiddleWare.h b/src/Misc/MiddleWare.h @@ -51,7 +51,7 @@ class MiddleWare //return UI interface Fl_Osc_Interface *spawnUiApi(void); //Set callback to push UI events to - void setUiCallback(void(*cb)(void*,const char *),void *ui); + void setUiCallback(int gui_id, void(*cb)(void*,const char *),void *ui); //Set callback to run while busy void setIdleCallback(void(*cb)(void*),void *ptr); //Handle events @@ -66,11 +66,11 @@ class MiddleWare void transmitMsg_va(const char *, const char *args, va_list va); //Handle a rtosc Message uToB, if sender is GUI - void transmitMsgGui(const char * msg); + void transmitMsgGui(int gui_id, const char * msg); //Handle a rtosc Message uToB, if sender is GUI - void transmitMsgGui(const char *, const char *args, ...); + void transmitMsgGui(int gui_id, const char *, const char *args, ...); //Handle a rtosc Message uToB, if sender is GUI - void transmitMsgGui_va(const char *, const char *args, va_list va); + void transmitMsgGui_va(int gui_id, const char *, const char *args, va_list va); //Send a message to middleware from an arbitrary thread void messageAnywhere(const char *msg, const char *args, ...); diff --git a/src/Plugin/ZynAddSubFX/ZynAddSubFX.cpp b/src/Plugin/ZynAddSubFX/ZynAddSubFX.cpp @@ -528,7 +528,7 @@ private: void _initMaster() { middleware = new zyn::MiddleWare(std::move(synth), &config); - middleware->setUiCallback(__uiCallback, this); + middleware->setUiCallback(0, __uiCallback, this); middleware->setIdleCallback(__idleCallback, this); _masterChangedCallback(middleware->spawnMaster()); diff --git a/src/Tests/PortChecker.cpp b/src/Tests/PortChecker.cpp @@ -24,13 +24,10 @@ class PortChecker void _masterChangedCallback(zyn::Master* m) { master = m; - master->setMasterChangedCallback(__masterChangedCallback, this); - } - - // TODO: eliminate static callbacks - static void __masterChangedCallback(void* ptr, zyn::Master* m) - { - ((PortChecker*)ptr)->_masterChangedCallback(m); + master->setMasterChangedCallback( + [](void* p, zyn::Master* m) { + ((PortChecker*)p)->_masterChangedCallback(m); }, + this); } void setUp() { @@ -44,7 +41,14 @@ class PortChecker synth->alias(); mw = new zyn::MiddleWare(std::move(*synth), &config); - mw->setUiCallback(_uiCallback, this); + mw->setUiCallback(0, + [](void* p, const char* msg) { + ((PortChecker*)p)->uiCallback0(msg); }, + this); + mw->setUiCallback(1, + [](void* p, const char* msg) { + ((PortChecker*)p)->uiCallback1(msg); }, + this); _masterChangedCallback(mw->spawnMaster()); realtime = nullptr; } @@ -57,7 +61,12 @@ class PortChecker delete synth; } - void uiCallback(const char* msg) + void uiCallback0(const char* msg) + { + (void)msg; + } + + void uiCallback1(const char* msg) { (void)msg; } @@ -76,11 +85,6 @@ class PortChecker PortChecker() { setUp(); } ~PortChecker() { tearDown(); } - static void _uiCallback(void* ptr, const char* msg) - { - ((PortChecker*)ptr)->uiCallback(msg); - } - int run() { assert(mw); diff --git a/src/Tests/SaveOSC.cpp b/src/Tests/SaveOSC.cpp @@ -72,7 +72,7 @@ class SaveOSCTest synth->alias(); mw = new zyn::MiddleWare(std::move(*synth), &config); - mw->setUiCallback(_uiCallback, this); + mw->setUiCallback(0, _uiCallback, this); _masterChangedCallback(mw->spawnMaster()); realtime = nullptr; } @@ -106,9 +106,9 @@ class SaveOSCTest if(prependArg == -1) { - mw->transmitMsgGui(osc_path, "stT", arg1, start_time.val.t); + mw->transmitMsgGui(0, osc_path, "stT", arg1, start_time.val.t); } else { - mw->transmitMsgGui(osc_path, "istT", prependArg, arg1, start_time.val.t); + mw->transmitMsgGui(0, osc_path, "istT", prependArg, arg1, start_time.val.t); } int attempt; @@ -236,7 +236,7 @@ class SaveOSCTest if (strstr(filename.c_str(), ".xiz") == filename.c_str() + filename.length() - 4) { - mw->transmitMsgGui("/reset_master", ""); + mw->transmitMsgGui(0, "/reset_master", ""); fprintf(stderr, "Loading XIZ file %s...\n", filename.c_str()); load_ok = timeOutOperation("/load_xiz", filename.c_str(), 1000, 0); } @@ -281,39 +281,39 @@ class SaveOSCTest int test_presets() { // enable almost everything, in order to test all ports - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFreqEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFreqLfoEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PAAEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PAmpEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PAmpLfoEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFilterLfoEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFMEnabled", "i", 1); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFMFreqEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/adpars/VoicePar0/PFMAmpEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/Psubenabled", "T"); - mw->transmitMsgGui("/part0/kit0/subpars/PBandWidthEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/subpars/PFreqEnvelopeEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/subpars/PGlobalFilterEnabled", "T"); - mw->transmitMsgGui("/part0/kit0/Ppadenabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFilterEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFreqEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFreqLfoEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PAAEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PAmpEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PAmpLfoEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFilterEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFilterEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFilterLfoEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFMEnabled", "i", 1); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFMFreqEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/adpars/VoicePar0/PFMAmpEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/Psubenabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/subpars/PBandWidthEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/subpars/PFreqEnvelopeEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/subpars/PGlobalFilterEnabled", "T"); + mw->transmitMsgGui(0, "/part0/kit0/Ppadenabled", "T"); // use all effects as ins fx - mw->transmitMsgGui("/insefx0/efftype", "S", "Reverb"); - mw->transmitMsgGui("/insefx1/efftype", "S", "Phaser"); - mw->transmitMsgGui("/insefx2/efftype", "S", "Echo"); - mw->transmitMsgGui("/insefx3/efftype", "S", "Distortion"); - mw->transmitMsgGui("/insefx4/efftype", "S", "Sympathetic"); - mw->transmitMsgGui("/insefx5/efftype", "S", "DynFilter"); - mw->transmitMsgGui("/insefx6/efftype", "S", "Alienwah"); - mw->transmitMsgGui("/insefx7/efftype", "S", "EQ"); - mw->transmitMsgGui("/part0/partefx0/efftype", "S", "Chorus"); + mw->transmitMsgGui(0, "/insefx0/efftype", "S", "Reverb"); + mw->transmitMsgGui(0, "/insefx1/efftype", "S", "Phaser"); + mw->transmitMsgGui(0, "/insefx2/efftype", "S", "Echo"); + mw->transmitMsgGui(0, "/insefx3/efftype", "S", "Distortion"); + mw->transmitMsgGui(0, "/insefx4/efftype", "S", "Sympathetic"); + mw->transmitMsgGui(0, "/insefx5/efftype", "S", "DynFilter"); + mw->transmitMsgGui(0, "/insefx6/efftype", "S", "Alienwah"); + mw->transmitMsgGui(0, "/insefx7/efftype", "S", "EQ"); + mw->transmitMsgGui(0, "/part0/partefx0/efftype", "S", "Chorus"); // use all effects as sys fx (except Chorus, it does not differ) - mw->transmitMsgGui("/sysefx0/efftype", "S", "Reverb"); - mw->transmitMsgGui("/sysefx1/efftype", "S", "Phaser"); - mw->transmitMsgGui("/sysefx2/efftype", "S", "Echo"); - mw->transmitMsgGui("/sysefx3/efftype", "S", "Distortion"); + mw->transmitMsgGui(0, "/sysefx0/efftype", "S", "Reverb"); + mw->transmitMsgGui(0, "/sysefx1/efftype", "S", "Phaser"); + mw->transmitMsgGui(0, "/sysefx2/efftype", "S", "Echo"); + mw->transmitMsgGui(0, "/sysefx3/efftype", "S", "Distortion"); int res = EXIT_SUCCESS; @@ -329,21 +329,21 @@ class SaveOSCTest int npresets_part[] = {10}; for(; insefxstr[7] < '8'; ++insefxstr[7]) if(preset < npresets_ins[insefxstr[7]-'0']) - mw->transmitMsgGui(insefxstr, "i", preset); + mw->transmitMsgGui(0, insefxstr, "i", preset); for(; partefxstr[14] < '1'; ++partefxstr[14]) if(preset < npresets_part[partefxstr[14]-'0']) - mw->transmitMsgGui(partefxstr, "i", preset); + mw->transmitMsgGui(0, partefxstr, "i", preset); if(preset == 13) // for presets 13-17, test the up to 5 sysefx { - mw->transmitMsgGui("/sysefx0/efftype", "S", "Sympathetic"); - mw->transmitMsgGui("/sysefx1/efftype", "S", "DynFilter"); - mw->transmitMsgGui("/sysefx2/efftype", "S", "Alienwah"); - mw->transmitMsgGui("/sysefx3/efftype", "S", "EQ"); + mw->transmitMsgGui(0, "/sysefx0/efftype", "S", "Sympathetic"); + mw->transmitMsgGui(0, "/sysefx1/efftype", "S", "DynFilter"); + mw->transmitMsgGui(0, "/sysefx2/efftype", "S", "Alienwah"); + mw->transmitMsgGui(0, "/sysefx3/efftype", "S", "EQ"); } int type_offset = (preset>=13)?4:0; for(; sysefxstr[7] < '4'; ++sysefxstr[7]) if(preset%13 < npresets_ins[sysefxstr[7]-'0'+type_offset]) - mw->transmitMsgGui(sysefxstr, "i", preset%13); + mw->transmitMsgGui(0, sysefxstr, "i", preset%13); char filename[] = "file0"; filename[4] += preset; diff --git a/src/UI/Connection.cpp b/src/UI/Connection.cpp @@ -295,7 +295,7 @@ class UI_Interface:public Fl_Osc_Interface char *tmp = strdup(s.c_str()); s = rtosc::Ports::collapsePath(tmp); free(tmp); - impl->transmitMsgGui(s.c_str(),""); + impl->transmitMsgGui(0, s.c_str(),""); } void write(string s, const char *args, ...) override @@ -309,7 +309,7 @@ class UI_Interface:public Fl_Osc_Interface ////fprintf(stderr, "."); //fprintf(stderr, "write(%s:%s)\n", s.c_str(), args); //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - impl->transmitMsgGui_va(s.c_str(), args, va); + impl->transmitMsgGui_va(0, s.c_str(), args, va); va_end(va); } @@ -319,7 +319,7 @@ class UI_Interface:public Fl_Osc_Interface ////fprintf(stderr, "."); //fprintf(stderr, "rawWrite(%s:%s)\n", msg, rtosc_argument_string(msg)); //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - impl->transmitMsgGui(rtosc::Ports::collapsePath((char*)msg)); + impl->transmitMsgGui(0, rtosc::Ports::collapsePath((char*)msg)); } void writeValue(string s, string ss) override diff --git a/src/UI/NSM.C b/src/UI/NSM.C @@ -62,7 +62,7 @@ NSM_Client::command_save(char **out_msg) if(!project_filename) return ERR_NO_SESSION_OPEN; - middleware->transmitMsgGui("/save_xmz", "s", project_filename); + middleware->transmitMsgGui(0, "/save_xmz", "s", project_filename); return r; } @@ -97,9 +97,9 @@ NSM_Client::command_open(const char *name, int r = ERR_OK; if(0 == stat(new_filename, &st)) - middleware->transmitMsgGui("/load_xmz", "s", new_filename); + middleware->transmitMsgGui(0, "/load_xmz", "s", new_filename); else - middleware->transmitMsgGui("/reset_master", ""); + middleware->transmitMsgGui(0, "/reset_master", ""); if(project_filename) free(project_filename); diff --git a/src/main.cpp b/src/main.cpp @@ -630,7 +630,7 @@ int main(int argc, char *argv[]) printf("[INFO] startup OSC\n"); typedef std::vector<const char *> wait_t; wait_t msg_waitlist; - middleware->setUiCallback([](void*v,const char*msg) { + middleware->setUiCallback(0, [](void*v,const char*msg) { wait_t &wait = *(wait_t*)v; size_t len = rtosc_message_length(msg, -1); char *copy = new char[len]; @@ -641,7 +641,7 @@ int main(int argc, char *argv[]) printf("[INFO] UI calbacks\n"); if(!noui) gui = GUI::createUi(middleware->spawnUiApi(), &Pexitprogram); - middleware->setUiCallback(GUI::raiseUi, gui); + middleware->setUiCallback(0, GUI::raiseUi, gui); middleware->setIdleCallback([](void*){GUI::tickUi(gui);}, NULL); //Replay Startup Responses