Fix std::mutex+mingw Build Error
[zynaddsubfx-code.git] / src / Misc / MiddleWare.cpp
blob343eb8509597cd8c409ed508cf78f90a065e77b2
1 /*
2 ZynAddSubFX - a software synthesizer
4 MiddleWare.cpp - Glue Logic And Home Of Non-RT Operations
5 Copyright (C) 2016 Mark McCurry
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 #include "MiddleWare.h"
14 #include <cstring>
15 #include <cstdio>
16 #include <cstdlib>
17 #include <fstream>
18 #include <iostream>
19 #include <dirent.h>
20 #include <sys/stat.h>
21 #include <mutex>
23 #include <rtosc/undo-history.h>
24 #include <rtosc/thread-link.h>
25 #include <rtosc/ports.h>
26 #include <lo/lo.h>
28 #include <unistd.h>
30 #include "../UI/Connection.h"
31 #include "../UI/Fl_Osc_Interface.h"
33 #include <map>
35 #include "Util.h"
36 #include "CallbackRepeater.h"
37 #include "Master.h"
38 #include "Part.h"
39 #include "PresetExtractor.h"
40 #include "../Containers/MultiPseudoStack.h"
41 #include "../Params/PresetsStore.h"
42 #include "../Params/ADnoteParameters.h"
43 #include "../Params/SUBnoteParameters.h"
44 #include "../Params/PADnoteParameters.h"
45 #include "../DSP/FFTwrapper.h"
46 #include "../Synth/OscilGen.h"
47 #include "../Nio/Nio.h"
49 #include <string>
50 #include <future>
51 #include <atomic>
52 #include <list>
54 #define errx(...) {}
55 #define warnx(...) {}
56 #ifndef errx
57 #include <err.h>
58 #endif
60 namespace zyn {
62 using std::string;
63 int Pexitprogram = 0;
65 /******************************************************************************
66 * LIBLO And Reflection Code *
67 * *
68 * All messages that are handled are handled in a serial fashion. *
69 * Thus, changes in the current interface sending messages can be encoded *
70 * into the stream via events which simply echo back the active interface *
71 ******************************************************************************/
72 static void liblo_error_cb(int i, const char *m, const char *loc)
74 fprintf(stderr, "liblo :-( %d-%s@%s\n",i,m,loc);
77 void path_search(const char *m, const char *url)
79 using rtosc::Ports;
80 using rtosc::Port;
82 //assumed upper bound of 32 ports (may need to be resized)
83 char types[256+1];
84 rtosc_arg_t args[256];
85 size_t pos = 0;
86 const Ports *ports = NULL;
87 const char *str = rtosc_argument(m,0).s;
88 const char *needle = rtosc_argument(m,1).s;
90 //zero out data
91 memset(types, 0, sizeof(types));
92 memset(args, 0, sizeof(args));
94 if(!*str) {
95 ports = &Master::ports;
96 } else {
97 const Port *port = Master::ports.apropos(rtosc_argument(m,0).s);
98 if(port)
99 ports = port->ports;
102 if(ports) {
103 //RTness not confirmed here
104 for(const Port &p:*ports) {
105 if(strstr(p.name, needle) != p.name || !p.name)
106 continue;
107 types[pos] = 's';
108 args[pos++].s = p.name;
109 types[pos] = 'b';
110 if(p.metadata && *p.metadata) {
111 args[pos].b.data = (unsigned char*) p.metadata;
112 auto tmp = rtosc::Port::MetaContainer(p.metadata);
113 args[pos++].b.len = tmp.length();
114 } else {
115 args[pos].b.data = (unsigned char*) NULL;
116 args[pos++].b.len = 0;
122 //Reply to requester [wow, these messages are getting huge...]
123 char buffer[1024*20];
124 size_t length = rtosc_amessage(buffer, sizeof(buffer), "/paths", types, args);
125 if(length) {
126 lo_message msg = lo_message_deserialise((void*)buffer, length, NULL);
127 lo_address addr = lo_address_new_from_url(url);
128 if(addr)
129 lo_send_message(addr, buffer, msg);
130 lo_address_free(addr);
131 lo_message_free(msg);
135 static int handler_function(const char *path, const char *types, lo_arg **argv,
136 int argc, lo_message msg, void *user_data)
138 (void) types;
139 (void) argv;
140 (void) argc;
141 MiddleWare *mw = (MiddleWare*)user_data;
142 lo_address addr = lo_message_get_source(msg);
143 if(addr) {
144 const char *tmp = lo_address_get_url(addr);
145 if(tmp != mw->activeUrl()) {
146 mw->transmitMsg("/echo", "ss", "OSC_URL", tmp);
147 mw->activeUrl(tmp);
149 free((void*)tmp);
152 char buffer[2048];
153 memset(buffer, 0, sizeof(buffer));
154 size_t size = 2048;
155 lo_message_serialise(msg, path, buffer, &size);
156 if(!strcmp(buffer, "/path-search") && !strcmp("ss", rtosc_argument_string(buffer))) {
157 path_search(buffer, mw->activeUrl().c_str());
158 } else if(buffer[0]=='/' && strrchr(buffer, '/')[1]) {
159 mw->transmitMsg(rtosc::Ports::collapsePath(buffer));
162 return 0;
165 typedef void(*cb_t)(void*,const char*);
167 //utility method (should be moved to a better location)
168 template <class T, class V>
169 std::vector<T> keys(const std::map<T,V> &m)
171 std::vector<T> vec;
172 for(auto &kv: m)
173 vec.push_back(kv.first);
174 return vec;
178 /*****************************************************************************
179 * Memory Deallocation *
180 *****************************************************************************/
181 void deallocate(const char *str, void *v)
183 //printf("deallocating a '%s' at '%p'\n", str, v);
184 if(!strcmp(str, "Part"))
185 delete (Part*)v;
186 else if(!strcmp(str, "Master"))
187 delete (Master*)v;
188 else if(!strcmp(str, "fft_t"))
189 delete[] (fft_t*)v;
190 else if(!strcmp(str, "KbmInfo"))
191 delete (KbmInfo*)v;
192 else if(!strcmp(str, "SclInfo"))
193 delete (SclInfo*)v;
194 else if(!strcmp(str, "Microtonal"))
195 delete (Microtonal*)v;
196 else
197 fprintf(stderr, "Unknown type '%s', leaking pointer %p!!\n", str, v);
201 /*****************************************************************************
202 * PadSynth Setup *
203 *****************************************************************************/
205 void preparePadSynth(string path, PADnoteParameters *p, rtosc::RtData &d)
207 //printf("preparing padsynth parameters\n");
208 assert(!path.empty());
209 path += "sample";
211 #ifdef WIN32
212 unsigned num = p->sampleGenerator([&path,&d]
213 (unsigned N, PADnoteParameters::Sample &s)
215 //printf("sending info to '%s'\n",
216 // (path+to_s(N)).c_str());
217 d.chain((path+to_s(N)).c_str(), "ifb",
218 s.size, s.basefreq, sizeof(float*), &s.smp);
219 }, []{return false;}, 1);
220 #else
221 std::mutex rtdata_mutex;
222 unsigned num = p->sampleGenerator([&rtdata_mutex, &path,&d]
223 (unsigned N, PADnoteParameters::Sample &s)
225 //printf("sending info to '%s'\n",
226 // (path+to_s(N)).c_str());
227 rtdata_mutex.lock();
228 d.chain((path+to_s(N)).c_str(), "ifb",
229 s.size, s.basefreq, sizeof(float*), &s.smp);
230 rtdata_mutex.unlock();
231 }, []{return false;});
232 #endif
234 //clear out unused samples
235 for(unsigned i = num; i < PAD_MAX_SAMPLES; ++i) {
236 d.chain((path+to_s(i)).c_str(), "ifb",
237 0, 440.0f, sizeof(float*), NULL);
241 /******************************************************************************
242 * Non-RealTime Object Store *
245 * Storage For Objects which need to be interfaced with outside the realtime *
246 * thread (aka they have long lived operations which can be done out-of-band) *
248 * - OscilGen instances as prepare() cannot be done in realtime and PAD *
249 * depends on these instances *
250 * - PADnoteParameter instances as applyparameters() cannot be done in *
251 * realtime *
253 * These instances are collected on every part change and kit change *
254 ******************************************************************************/
255 struct NonRtObjStore
257 std::map<std::string, void*> objmap;
259 void extractMaster(Master *master)
261 for(int i=0; i < NUM_MIDI_PARTS; ++i) {
262 extractPart(master->part[i], i);
266 void extractPart(Part *part, int i)
268 for(int j=0; j < NUM_KIT_ITEMS; ++j) {
269 auto &obj = part->kit[j];
270 extractAD(obj.adpars, i, j);
271 extractPAD(obj.padpars, i, j);
275 void extractAD(ADnoteParameters *adpars, int i, int j)
277 std::string base = "/part"+to_s(i)+"/kit"+to_s(j)+"/";
278 for(int k=0; k<NUM_VOICES; ++k) {
279 std::string nbase = base+"adpars/VoicePar"+to_s(k)+"/";
280 if(adpars) {
281 auto &nobj = adpars->VoicePar[k];
282 objmap[nbase+"OscilSmp/"] = nobj.OscilSmp;
283 objmap[nbase+"FMSmp/"] = nobj.FMSmp;
284 } else {
285 objmap[nbase+"OscilSmp/"] = nullptr;
286 objmap[nbase+"FMSmp/"] = nullptr;
291 void extractPAD(PADnoteParameters *padpars, int i, int j)
293 std::string base = "/part"+to_s(i)+"/kit"+to_s(j)+"/";
294 for(int k=0; k<NUM_VOICES; ++k) {
295 if(padpars) {
296 objmap[base+"padpars/"] = padpars;
297 objmap[base+"padpars/oscilgen/"] = padpars->oscilgen;
298 } else {
299 objmap[base+"padpars/"] = nullptr;
300 objmap[base+"padpars/oscilgen/"] = nullptr;
305 void clear(void)
307 objmap.clear();
310 bool has(std::string loc)
312 return objmap.find(loc) != objmap.end();
315 void *get(std::string loc)
317 return objmap[loc];
320 void handleOscil(const char *msg, rtosc::RtData &d) {
321 string obj_rl(d.message, msg);
322 void *osc = get(obj_rl);
323 assert(osc);
324 strcpy(d.loc, obj_rl.c_str());
325 d.obj = osc;
326 OscilGen::non_realtime_ports.dispatch(msg, d);
328 void handlePad(const char *msg, rtosc::RtData &d) {
329 string obj_rl(d.message, msg);
330 void *pad = get(obj_rl);
331 if(!strcmp(msg, "prepare")) {
332 preparePadSynth(obj_rl, (PADnoteParameters*)pad, d);
333 d.matches++;
334 d.reply((obj_rl+"needPrepare").c_str(), "F");
335 } else {
336 if(!pad)
337 return;
338 strcpy(d.loc, obj_rl.c_str());
339 d.obj = pad;
340 PADnoteParameters::non_realtime_ports.dispatch(msg, d);
341 if(rtosc_narguments(msg)) {
342 if(!strcmp(msg, "oscilgen/prepare"))
343 ; //ignore
344 else {
345 d.reply((obj_rl+"needPrepare").c_str(), "T");
352 /******************************************************************************
353 * Realtime Parameter Store *
355 * Storage for AD/PAD/SUB parameters which are allocated as needed by kits. *
356 * Two classes of events affect this: *
357 * 1. When a message to enable a kit is observed, then the kit is allocated *
358 * and sent prior to the enable message. *
359 * 2. When a part is allocated all part information is rebuilt *
361 * (NOTE pointers aren't really needed here, just booleans on whether it has *
362 * been allocated) *
363 * This may be later utilized for copy/paste support *
364 ******************************************************************************/
365 struct ParamStore
367 ParamStore(void)
369 memset(add, 0, sizeof(add));
370 memset(pad, 0, sizeof(pad));
371 memset(sub, 0, sizeof(sub));
374 void extractPart(Part *part, int i)
376 for(int j=0; j < NUM_KIT_ITEMS; ++j) {
377 auto kit = part->kit[j];
378 add[i][j] = kit.adpars;
379 sub[i][j] = kit.subpars;
380 pad[i][j] = kit.padpars;
384 ADnoteParameters *add[NUM_MIDI_PARTS][NUM_KIT_ITEMS];
385 SUBnoteParameters *sub[NUM_MIDI_PARTS][NUM_KIT_ITEMS];
386 PADnoteParameters *pad[NUM_MIDI_PARTS][NUM_KIT_ITEMS];
389 //XXX perhaps move this to Nio
390 //(there needs to be some standard Nio stub file for this sort of stuff)
391 namespace Nio
393 using std::get;
394 rtosc::Ports ports = {
395 {"sink-list:", 0, 0, [](const char *, rtosc::RtData &d) {
396 auto list = Nio::getSinks();
397 char *ret = rtosc_splat(d.loc, list);
398 d.reply(ret);
399 delete [] ret;
401 {"source-list:", 0, 0, [](const char *, rtosc::RtData &d) {
402 auto list = Nio::getSources();
403 char *ret = rtosc_splat(d.loc, list);
404 d.reply(ret);
405 delete [] ret;
407 {"source::s", 0, 0, [](const char *msg, rtosc::RtData &d) {
408 if(rtosc_narguments(msg) == 0)
409 d.reply(d.loc, "s", Nio::getSource().c_str());
410 else
411 Nio::setSource(rtosc_argument(msg,0).s);}},
412 {"sink::s", 0, 0, [](const char *msg, rtosc::RtData &d) {
413 if(rtosc_narguments(msg) == 0)
414 d.reply(d.loc, "s", Nio::getSink().c_str());
415 else
416 Nio::setSink(rtosc_argument(msg,0).s);}},
421 /* Implementation */
422 class MiddleWareImpl
424 public:
425 MiddleWare *parent;
426 private:
428 public:
429 Config* const config;
430 MiddleWareImpl(MiddleWare *mw, SYNTH_T synth, Config* config,
431 int preferred_port);
432 ~MiddleWareImpl(void);
434 //Check offline vs online mode in plugins
435 void heartBeat(Master *m);
436 int64_t start_time_sec;
437 int64_t start_time_nsec;
438 bool offline;
440 //Apply function while parameters are write locked
441 void doReadOnlyOp(std::function<void()> read_only_fn);
442 void doReadOnlyOpPlugin(std::function<void()> read_only_fn);
443 bool doReadOnlyOpNormal(std::function<void()> read_only_fn, bool canfail=false);
445 void savePart(int npart, const char *filename)
447 //Copy is needed as filename WILL get trashed during the rest of the run
448 std::string fname = filename;
449 //printf("saving part(%d,'%s')\n", npart, filename);
450 doReadOnlyOp([this,fname,npart](){
451 int res = master->part[npart]->saveXML(fname.c_str());
452 (void)res;
453 /*printf("results: '%s' '%d'\n",fname.c_str(), res);*/});
456 void loadPendingBank(int par, Bank &bank)
458 if(((unsigned int)par < bank.banks.size())
459 && (bank.banks[par].dir != bank.bankfiletitle))
460 bank.loadbank(bank.banks[par].dir);
463 void loadPart(int npart, const char *filename, Master *master)
465 actual_load[npart]++;
467 if(actual_load[npart] != pending_load[npart])
468 return;
469 assert(actual_load[npart] <= pending_load[npart]);
471 //load part in async fashion when possible
472 #ifndef WIN32
473 auto alloc = std::async(std::launch::async,
474 [master,filename,this,npart](){
475 Part *p = new Part(*master->memory, synth,
476 master->time,
477 config->cfg.GzipCompression,
478 config->cfg.Interpolation,
479 &master->microtonal, master->fft, &master->watcher,
480 ("/part"+to_s(npart)+"/").c_str());
481 if(p->loadXMLinstrument(filename))
482 fprintf(stderr, "Warning: failed to load part<%s>!\n", filename);
484 auto isLateLoad = [this,npart]{
485 return actual_load[npart] != pending_load[npart];
488 p->applyparameters(isLateLoad);
489 return p;});
491 //Load the part
492 if(idle) {
493 while(alloc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
494 idle(idle_ptr);
498 Part *p = alloc.get();
499 #else
500 Part *p = new Part(*master->memory, synth, master->time,
501 config->cfg.GzipCompression,
502 config->cfg.Interpolation,
503 &master->microtonal, master->fft);
505 if(p->loadXMLinstrument(filename))
506 fprintf(stderr, "Warning: failed to load part<%s>!\n", filename);
508 auto isLateLoad = [this,npart]{
509 return actual_load[npart] != pending_load[npart];
512 p->applyparameters(isLateLoad);
513 #endif
515 obj_store.extractPart(p, npart);
516 kits.extractPart(p, npart);
518 //Give it to the backend and wait for the old part to return for
519 //deallocation
520 parent->transmitMsg("/load-part", "ib", npart, sizeof(Part*), &p);
521 GUI::raiseUi(ui, "/damage", "s", ("/part"+to_s(npart)+"/").c_str());
524 //Load a new cleared Part instance
525 void loadClearPart(int npart)
527 if(npart == -1)
528 return;
530 Part *p = new Part(*master->memory, synth,
531 master->time,
532 config->cfg.GzipCompression,
533 config->cfg.Interpolation,
534 &master->microtonal, master->fft);
535 p->applyparameters();
536 obj_store.extractPart(p, npart);
537 kits.extractPart(p, npart);
539 //Give it to the backend and wait for the old part to return for
540 //deallocation
541 parent->transmitMsg("/load-part", "ib", npart, sizeof(Part *), &p);
542 GUI::raiseUi(ui, "/damage", "s", ("/part" + to_s(npart) + "/").c_str());
545 //Well, you don't get much crazier than changing out all of your RT
546 //structures at once... TODO error handling
547 void loadMaster(const char *filename)
549 Master *m = new Master(synth, config);
550 m->uToB = uToB;
551 m->bToU = bToU;
552 if(filename) {
553 if ( m->loadXML(filename) ) {
554 delete m;
555 return;
557 m->applyparameters();
560 //Update resource locator table
561 updateResources(m);
563 master = m;
565 //Give it to the backend and wait for the old part to return for
566 //deallocation
567 parent->transmitMsg("/load-master", "b", sizeof(Master*), &m);
570 void loadXsz(const char *filename, rtosc::RtData &d)
572 Microtonal *micro = new Microtonal(master->gzip_compression);
573 int err = micro->loadXML(filename);
574 if(err) {
575 d.reply("/alert", "s", "Error: Could not load the xsz file.");
576 delete micro;
577 } else
578 d.chain("/microtonal/paste", "b", sizeof(void*), &micro);
581 void saveXsz(const char *filename, rtosc::RtData &d)
583 int err = 0;
584 doReadOnlyOp([this,filename,&err](){
585 err = master->microtonal.saveXML(filename);});
586 if(err)
587 d.reply("/alert", "s", "Error: Could not save the xsz file.");
590 void loadScl(const char *filename, rtosc::RtData &d)
592 SclInfo *scl = new SclInfo;
593 int err=Microtonal::loadscl(*scl, filename);
594 if(err) {
595 d.reply("/alert", "s", "Error: Could not load the scl file.");
596 delete scl;
597 } else
598 d.chain("/microtonal/paste_scl", "b", sizeof(void*), &scl);
601 void loadKbm(const char *filename, rtosc::RtData &d)
603 KbmInfo *kbm = new KbmInfo;
604 int err=Microtonal::loadkbm(*kbm, filename);
605 if(err) {
606 d.reply("/alert", "s", "Error: Could not load the kbm file.");
607 delete kbm;
608 } else
609 d.chain("/microtonal/paste_kbm", "b", sizeof(void*), &kbm);
612 void updateResources(Master *m)
614 obj_store.clear();
615 obj_store.extractMaster(m);
616 for(int i=0; i<NUM_MIDI_PARTS; ++i)
617 kits.extractPart(m->part[i], i);
620 //If currently broadcasting messages
621 bool broadcast = false;
622 //If message should be forwarded through snoop ports
623 bool forward = false;
624 //if message is in order or out-of-order execution
625 bool in_order = false;
626 //If accepting undo events as user driven
627 bool recording_undo = true;
628 void bToUhandle(const char *rtmsg);
630 void tick(void)
632 if(server)
633 while(lo_server_recv_noblock(server, 0));
635 while(bToU->hasNext()) {
636 const char *rtmsg = bToU->read();
637 bToUhandle(rtmsg);
640 while(auto *m = multi_thread_source.read()) {
641 handleMsg(m->memory);
642 multi_thread_source.free(m);
645 autoSave.tick();
647 heartBeat(master);
649 //XXX This might have problems with a master swap operation
650 if(offline)
651 master->runOSC(0,0,true);
656 void kitEnable(const char *msg);
657 void kitEnable(int part, int kit, int type);
659 // Handle an event with special cases
660 void handleMsg(const char *msg);
662 void write(const char *path, const char *args, ...);
663 void write(const char *path, const char *args, va_list va);
665 void currentUrl(string addr)
667 curr_url = addr;
668 known_remotes.insert(addr);
671 // Send a message to a remote client
672 void sendToRemote(const char *msg, std::string dest);
673 // Send a message to the current remote client
674 void sendToCurrentRemote(const char *msg)
676 sendToRemote(msg, in_order ? curr_url : last_url);
678 // Broadcast a message to all listening remote clients
679 void broadcastToRemote(const char *msg);
683 * Provides a mapping for non-RT objects stored inside the backend
684 * - Oscilgen almost all parameters can be safely set
685 * - Padnote can have anything set on its oscilgen and a very small set
686 * of general parameters
688 NonRtObjStore obj_store;
690 //This code will own the pointer to master, be prepared for odd things if
691 //this assumption is broken
692 Master *master;
694 //The ONLY means that any chunk of UI code should have for interacting with the
695 //backend
696 Fl_Osc_Interface *osc;
697 //Synth Engine Parameters
698 ParamStore kits;
700 //Callback When Waiting on async events
701 void(*idle)(void*);
702 void* idle_ptr;
704 //General UI callback
705 cb_t cb;
706 //UI handle
707 void *ui;
709 std::atomic_int pending_load[NUM_MIDI_PARTS];
710 std::atomic_int actual_load[NUM_MIDI_PARTS];
712 //Undo/Redo
713 rtosc::UndoHistory undo;
715 //MIDI Learn
716 //rtosc::MidiMappernRT midi_mapper;
718 //Link To the Realtime
719 rtosc::ThreadLink *bToU;
720 rtosc::ThreadLink *uToB;
722 //Link to the unknown
723 MultiQueue multi_thread_source;
725 //LIBLO
726 lo_server server;
727 string last_url, curr_url;
728 std::set<string> known_remotes;
730 //Synthesis Rate Parameters
731 const SYNTH_T synth;
733 PresetsStore presetsstore;
735 CallbackRepeater autoSave;
738 /*****************************************************************************
739 * Data Object for Non-RT Class Dispatch *
740 *****************************************************************************/
742 class MwDataObj:public rtosc::RtData
744 public:
745 MwDataObj(MiddleWareImpl *mwi_)
747 loc_size = 1024;
748 loc = new char[loc_size];
749 memset(loc, 0, loc_size);
750 buffer = new char[4*4096];
751 memset(buffer, 0, 4*4096);
752 obj = mwi_;
753 mwi = mwi_;
754 forwarded = false;
757 ~MwDataObj(void)
759 delete[] loc;
760 delete[] buffer;
763 //Replies and broadcasts go to the remote
765 //Chain calls repeat the call into handle()
767 //Forward calls send the message directly to the realtime
768 virtual void reply(const char *path, const char *args, ...) override
770 //printf("reply building '%s'\n", path);
771 va_list va;
772 va_start(va,args);
773 if(!strcmp(path, "/forward")) { //forward the information to the backend
774 args++;
775 path = va_arg(va, const char *);
776 rtosc_vmessage(buffer,4*4096,path,args,va);
777 } else {
778 rtosc_vmessage(buffer,4*4096,path,args,va);
779 reply(buffer);
781 va_end(va);
783 virtual void replyArray(const char *path, const char *args, rtosc_arg_t *argd) override
785 //printf("reply building '%s'\n", path);
786 if(!strcmp(path, "/forward")) { //forward the information to the backend
787 args++;
788 rtosc_amessage(buffer,4*4096,path,args,argd);
789 } else {
790 rtosc_amessage(buffer,4*4096,path,args,argd);
791 reply(buffer);
794 virtual void reply(const char *msg) override{
795 mwi->sendToCurrentRemote(msg);
797 //virtual void broadcast(const char *path, const char *args, ...){(void)path;(void)args;};
798 //virtual void broadcast(const char *msg){(void)msg;};
800 virtual void chain(const char *msg) override
802 assert(msg);
803 // printf("chain call on <%s>\n", msg);
804 mwi->handleMsg(msg);
807 virtual void chain(const char *path, const char *args, ...) override
809 assert(path);
810 va_list va;
811 va_start(va,args);
812 rtosc_vmessage(buffer,4*4096,path,args,va);
813 chain(buffer);
814 va_end(va);
817 virtual void forward(const char *) override
819 forwarded = true;
822 bool forwarded;
823 private:
824 char *buffer;
825 MiddleWareImpl *mwi;
828 static std::vector<std::string> getFiles(const char *folder, bool finddir)
830 DIR *dir = opendir(folder);
832 if(dir == NULL) {
833 return {};
836 struct dirent *fn;
837 std::vector<string> files;
839 while((fn = readdir(dir))) {
840 #ifndef WIN32
841 bool is_dir = fn->d_type & DT_DIR;
842 //it could still be a symbolic link
843 if(!is_dir) {
844 string path = string(folder) + "/" + fn->d_name;
845 struct stat buf;
846 memset((void*)&buf, 0, sizeof(buf));
847 int err = stat(path.c_str(), &buf);
848 if(err)
849 printf("[Zyn:Error] stat cannot handle <%s>:%d\n", path.c_str(), err);
850 if(S_ISDIR(buf.st_mode)) {
851 is_dir = true;
854 #else
855 std::string darn_windows = folder + std::string("/") + std::string(fn->d_name);
856 //printf("attr on <%s> => %x\n", darn_windows.c_str(), GetFileAttributes(darn_windows.c_str()));
857 //printf("desired mask = %x\n", mask);
858 //printf("error = %x\n", INVALID_FILE_ATTRIBUTES);
859 bool is_dir = GetFileAttributes(darn_windows.c_str()) & FILE_ATTRIBUTE_DIRECTORY;
860 #endif
861 if(finddir == is_dir && strcmp(".", fn->d_name))
862 files.push_back(fn->d_name);
865 closedir(dir);
866 std::sort(begin(files), end(files));
867 return files;
872 static int extractInt(const char *msg)
874 const char *mm = msg;
875 while(*mm && !isdigit(*mm)) ++mm;
876 if(isdigit(*mm))
877 return atoi(mm);
878 return -1;
881 static const char *chomp(const char *msg)
883 while(*msg && *msg!='/') ++msg; \
884 msg = *msg ? msg+1 : msg;
885 return msg;
888 using rtosc::RtData;
889 #define rObject Bank
890 #define rBegin [](const char *msg, RtData &d) { (void)msg;(void)d;\
891 rObject &impl = *((rObject*)d.obj);(void)impl;
892 #define rEnd }
893 /*****************************************************************************
894 * Instrument Banks *
896 * Banks and presets in general are not classed as realtime safe *
898 * The supported operations are: *
899 * - Load Names *
900 * - Load Bank *
901 * - Refresh List of Banks *
902 *****************************************************************************/
903 extern const rtosc::Ports bankPorts;
904 const rtosc::Ports bankPorts = {
905 {"rescan:", 0, 0,
906 rBegin;
907 impl.bankpos = 0;
908 impl.rescanforbanks();
909 //Send updated banks
910 int i = 0;
911 for(auto &elm : impl.banks)
912 d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str());
913 d.reply("/bank/bank_select", "i", impl.bankpos);
914 if (i > 0) {
915 impl.loadbank(impl.banks[0].dir);
917 //Reload bank slots
918 for(int i=0; i<BANK_SIZE; ++i) {
919 d.reply("/bankview", "iss",
920 i, impl.ins[i].name.c_str(),
921 impl.ins[i].filename.c_str());
923 } else {
924 //Clear all bank slots
925 for(int i=0; i<BANK_SIZE; ++i) {
926 d.reply("/bankview", "iss", i, "", "");
929 rEnd},
930 {"bank_list:", 0, 0,
931 rBegin;
932 #define MAX_BANKS 256
933 char types[MAX_BANKS*2+1]={0};
934 rtosc_arg_t args[MAX_BANKS*2];
935 int i = 0;
936 for(auto &elm : impl.banks) {
937 types[i] = types [i + 1] = 's';
938 args[i++].s = elm.name.c_str();
939 args[i++].s = elm.dir.c_str();
941 d.replyArray("/bank/bank_list", types, args);
942 #undef MAX_BANKS
943 rEnd},
944 {"types:", 0, 0,
945 rBegin;
946 const char *types[17];
947 types[ 0] = "None";
948 types[ 1] = "Piano";
949 types[ 2] = "Chromatic Percussion";
950 types[ 3] = "Organ";
951 types[ 4] = "Guitar";
952 types[ 5] = "Bass";
953 types[ 6] = "Solo Strings";
954 types[ 7] = "Ensemble";
955 types[ 8] = "Brass";
956 types[ 9] = "Reed";
957 types[10] = "Pipe";
958 types[11] = "Synth Lead";
959 types[12] = "Synth Pad";
960 types[13] = "Synth Effects";
961 types[14] = "Ethnic";
962 types[15] = "Percussive";
963 types[16] = "Sound Effects";
964 char t[17+1]={0};
965 rtosc_arg_t args[17];
966 for(int i=0; i<17; ++i) {
967 t[i] = 's';
968 args[i].s = types[i];
970 d.replyArray("/bank/types", t, args);
971 rEnd},
972 {"tags:", 0, 0,
973 rBegin;
974 const char *types[8];
975 types[ 0] = "fast";
976 types[ 1] = "slow";
977 types[ 2] = "saw";
978 types[ 3] = "bell";
979 types[ 4] = "lead";
980 types[ 5] = "ambient";
981 types[ 6] = "horn";
982 types[ 7] = "alarm";
983 char t[8+1]={0};
984 rtosc_arg_t args[8];
985 for(int i=0; i<8; ++i) {
986 t[i] = 's';
987 args[i].s = types[i];
989 d.replyArray(d.loc, t, args);
990 rEnd},
991 {"slot#1024:", 0, 0,
992 rBegin;
993 const int loc = extractInt(msg);
994 if(loc >= BANK_SIZE)
995 return;
997 d.reply("/bankview", "iss",
998 loc, impl.ins[loc].name.c_str(),
999 impl.ins[loc].filename.c_str());
1000 rEnd},
1001 {"banks:", 0, 0,
1002 rBegin;
1003 int i = 0;
1004 for(auto &elm : impl.banks)
1005 d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str());
1006 rEnd},
1007 {"bank_select::i", 0, 0,
1008 rBegin
1009 if(rtosc_narguments(msg)) {
1010 const int pos = rtosc_argument(msg, 0).i;
1011 d.reply(d.loc, "i", pos);
1012 if(impl.bankpos != pos) {
1013 impl.bankpos = pos;
1014 impl.loadbank(impl.banks[pos].dir);
1016 //Reload bank slots
1017 for(int i=0; i<BANK_SIZE; ++i)
1018 d.reply("/bankview", "iss",
1019 i, impl.ins[i].name.c_str(),
1020 impl.ins[i].filename.c_str());
1022 } else
1023 d.reply("/bank/bank_select", "i", impl.bankpos);
1024 rEnd},
1025 {"rename_slot:is", 0, 0,
1026 rBegin;
1027 const int slot = rtosc_argument(msg, 0).i;
1028 const char *name = rtosc_argument(msg, 1).s;
1029 const int err = impl.setname(slot, name, -1);
1030 if(err) {
1031 d.reply("/alert", "s",
1032 "Failed To Rename Bank Slot, please check file permissions");
1034 rEnd},
1035 {"swap_slots:ii", 0, 0,
1036 rBegin;
1037 const int slota = rtosc_argument(msg, 0).i;
1038 const int slotb = rtosc_argument(msg, 1).i;
1039 const int err = impl.swapslot(slota, slotb);
1040 if(err)
1041 d.reply("/alert", "s",
1042 "Failed To Swap Bank Slots, please check file permissions");
1043 rEnd},
1044 {"clear_slot:i", 0, 0,
1045 rBegin;
1046 const int slot = rtosc_argument(msg, 0).i;
1047 const int err = impl.clearslot(slot);
1048 if(err)
1049 d.reply("/alert", "s",
1050 "Failed To Clear Bank Slot, please check file permissions");
1051 rEnd},
1052 {"msb::i", 0, 0,
1053 rBegin;
1054 if(rtosc_narguments(msg))
1055 impl.setMsb(rtosc_argument(msg, 0).i);
1056 else
1057 d.reply(d.loc, "i", impl.bank_msb);
1058 rEnd},
1059 {"lsb::i", 0, 0,
1060 rBegin;
1061 if(rtosc_narguments(msg))
1062 impl.setLsb(rtosc_argument(msg, 0).i);
1063 else
1064 d.reply(d.loc, "i", impl.bank_lsb);
1065 rEnd},
1066 {"newbank:s", 0, 0,
1067 rBegin;
1068 int err = impl.newbank(rtosc_argument(msg, 0).s);
1069 if(err)
1070 d.reply("/alert", "s", "Error: Could not make a new bank (directory)..");
1071 rEnd},
1072 {"search:s", 0, 0,
1073 rBegin;
1074 auto res = impl.search(rtosc_argument(msg, 0).s);
1075 #define MAX_SEARCH 300
1076 char res_type[MAX_SEARCH+1] = {};
1077 rtosc_arg_t res_dat[MAX_SEARCH] = {};
1078 for(unsigned i=0; i<res.size() && i<MAX_SEARCH; ++i) {
1079 res_type[i] = 's';
1080 res_dat[i].s = res[i].c_str();
1082 d.replyArray("/bank/search_results", res_type, res_dat);
1083 #undef MAX_SEARCH
1084 rEnd},
1085 {"blist:s", 0, 0,
1086 rBegin;
1087 auto res = impl.blist(rtosc_argument(msg, 0).s);
1088 #define MAX_SEARCH 300
1089 char res_type[MAX_SEARCH+1] = {};
1090 rtosc_arg_t res_dat[MAX_SEARCH] = {};
1091 for(unsigned i=0; i<res.size() && i<MAX_SEARCH; ++i) {
1092 res_type[i] = 's';
1093 res_dat[i].s = res[i].c_str();
1095 d.replyArray("/bank/search_results", res_type, res_dat);
1096 #undef MAX_SEARCH
1097 rEnd},
1098 {"search_results:", 0, 0,
1099 rBegin;
1100 d.reply("/bank/search_results", "");
1101 rEnd},
1104 /******************************************************************************
1105 * MiddleWare Snooping Ports *
1107 * These ports handle: *
1108 * - Events going to the realtime thread which cannot be safely handled *
1109 * there *
1110 * - Events generated by the realtime thread which are not destined for a *
1111 * user interface *
1112 ******************************************************************************/
1114 #undef rObject
1115 #define rObject MiddleWareImpl
1117 #ifndef STRINGIFY
1118 #define STRINGIFY2(a) #a
1119 #define STRINGIFY(a) STRINGIFY2(a)
1120 #endif
1123 * BASE/part#/kititem#
1124 * BASE/part#/kit#/adpars/voice#/oscil/\*
1125 * BASE/part#/kit#/adpars/voice#/mod-oscil/\*
1126 * BASE/part#/kit#/padpars/prepare
1127 * BASE/part#/kit#/padpars/oscil/\*
1129 static rtosc::Ports middwareSnoopPorts = {
1130 {"part#" STRINGIFY(NUM_MIDI_PARTS)
1131 "/kit#" STRINGIFY(NUM_KIT_ITEMS) "/adpars/VoicePar#"
1132 STRINGIFY(NUM_VOICES) "/OscilSmp/", 0, &OscilGen::non_realtime_ports,
1133 rBegin;
1134 impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d);
1135 rEnd},
1136 {"part#" STRINGIFY(NUM_MIDI_PARTS)
1137 "/kit#" STRINGIFY(NUM_KIT_ITEMS)
1138 "/adpars/VoicePar#" STRINGIFY(NUM_VOICES) "/FMSmp/", 0, &OscilGen::non_realtime_ports,
1139 rBegin
1140 impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d);
1141 rEnd},
1142 {"part#" STRINGIFY(NUM_MIDI_PARTS)
1143 "/kit#" STRINGIFY(NUM_KIT_ITEMS) "/padpars/", 0, &PADnoteParameters::non_realtime_ports,
1144 rBegin
1145 impl.obj_store.handlePad(chomp(chomp(chomp(msg))), d);
1146 rEnd},
1147 {"bank/", 0, &bankPorts,
1148 rBegin;
1149 d.obj = &impl.master->bank;
1150 bankPorts.dispatch(chomp(msg),d);
1151 rEnd},
1152 {"bank/save_to_slot:ii", 0, 0,
1153 rBegin;
1154 const int part_id = rtosc_argument(msg, 0).i;
1155 const int slot = rtosc_argument(msg, 1).i;
1157 int err = 0;
1158 impl.doReadOnlyOp([&impl,slot,part_id,&err](){
1159 err = impl.master->bank.savetoslot(slot, impl.master->part[part_id]);});
1160 if(err) {
1161 char buffer[1024];
1162 rtosc_message(buffer, 1024, "/alert", "s",
1163 "Failed To Save To Bank Slot, please check file permissions");
1164 GUI::raiseUi(impl.ui, buffer);
1166 rEnd},
1167 {"config/", 0, &Config::ports,
1168 rBegin;
1169 d.obj = impl.config;
1170 Config::ports.dispatch(chomp(msg), d);
1171 rEnd},
1172 {"presets/", 0, &real_preset_ports, [](const char *msg, RtData &d) {
1173 MiddleWareImpl *obj = (MiddleWareImpl*)d.obj;
1174 d.obj = (void*)obj->parent;
1175 real_preset_ports.dispatch(chomp(msg), d);
1176 if(strstr(msg, "paste") && rtosc_argument_string(msg)[0] == 's')
1177 d.reply("/damage", "s", rtosc_argument(msg, 0).s);
1179 {"io/", 0, &Nio::ports, [](const char *msg, RtData &d) {
1180 Nio::ports.dispatch(chomp(msg), d);}},
1181 {"part*/kit*/{Padenabled,Ppadenabled,Psubenabled}:T:F", 0, 0,
1182 rBegin;
1183 impl.kitEnable(msg);
1184 d.forward();
1185 rEnd},
1186 {"save_xlz:s", 0, 0,
1187 rBegin;
1188 impl.doReadOnlyOp([&]() {
1189 const char *file = rtosc_argument(msg, 0).s;
1190 XMLwrapper xml;
1191 Master::saveAutomation(xml, impl.master->automate);
1192 xml.saveXMLfile(file, impl.master->gzip_compression);
1194 rEnd},
1195 {"load_xlz:s", 0, 0,
1196 rBegin;
1197 const char *file = rtosc_argument(msg, 0).s;
1198 XMLwrapper xml;
1199 xml.loadXMLfile(file);
1200 rtosc::AutomationMgr *mgr = new rtosc::AutomationMgr(16,4,8);
1201 mgr->set_ports(Master::ports);
1202 Master::loadAutomation(xml, *mgr);
1203 d.chain("/automate/load-blob", "b", sizeof(void*), &mgr);
1204 rEnd},
1205 {"clear_xlz:", 0, 0,
1206 rBegin;
1207 d.chain("/automate/clear", "");
1208 rEnd},
1209 //scale file stuff
1210 {"load_xsz:s", 0, 0,
1211 rBegin;
1212 const char *file = rtosc_argument(msg, 0).s;
1213 impl.loadXsz(file, d);
1214 rEnd},
1215 {"save_xsz:s", 0, 0,
1216 rBegin;
1217 const char *file = rtosc_argument(msg, 0).s;
1218 impl.saveXsz(file, d);
1219 rEnd},
1220 {"load_scl:s", 0, 0,
1221 rBegin;
1222 const char *file = rtosc_argument(msg, 0).s;
1223 impl.loadScl(file, d);
1224 rEnd},
1225 {"load_kbm:s", 0, 0,
1226 rBegin;
1227 const char *file = rtosc_argument(msg, 0).s;
1228 impl.loadKbm(file, d);
1229 rEnd},
1230 {"save_xmz:s", 0, 0,
1231 rBegin;
1232 const char *file = rtosc_argument(msg, 0).s;
1233 //Copy is needed as filename WILL get trashed during the rest of the run
1234 impl.doReadOnlyOp([&impl,file](){
1235 int res = impl.master->saveXML(file);
1236 (void)res;});
1237 rEnd},
1238 {"save_xiz:is", 0, 0,
1239 rBegin;
1240 const int part_id = rtosc_argument(msg,0).i;
1241 const char *file = rtosc_argument(msg,1).s;
1242 impl.savePart(part_id, file);
1243 rEnd},
1244 {"file_home_dir:", 0, 0,
1245 rBegin;
1246 const char *home = getenv("PWD");
1247 if(!home)
1248 home = getenv("HOME");
1249 if(!home)
1250 home = getenv("USERPROFILE");
1251 if(!home)
1252 home = getenv("HOMEPATH");
1253 if(!home)
1254 home = "/";
1256 string home_ = home;
1257 #ifndef WIN32
1258 if(home_[home_.length()-1] != '/')
1259 home_ += '/';
1260 #endif
1261 d.reply(d.loc, "s", home_.c_str());
1262 rEnd},
1263 {"file_list_files:s", 0, 0,
1264 rBegin;
1265 const char *folder = rtosc_argument(msg, 0).s;
1267 auto files = getFiles(folder, false);
1269 const int N = files.size();
1270 rtosc_arg_t *args = new rtosc_arg_t[N];
1271 char *types = new char[N+1];
1272 types[N] = 0;
1273 for(int i=0; i<N; ++i) {
1274 args[i].s = files[i].c_str();
1275 types[i] = 's';
1278 d.replyArray(d.loc, types, args);
1279 delete [] types;
1280 delete [] args;
1281 rEnd},
1282 {"file_list_dirs:s", 0, 0,
1283 rBegin;
1284 const char *folder = rtosc_argument(msg, 0).s;
1286 auto files = getFiles(folder, true);
1288 const int N = files.size();
1289 rtosc_arg_t *args = new rtosc_arg_t[N];
1290 char *types = new char[N+1];
1291 types[N] = 0;
1292 for(int i=0; i<N; ++i) {
1293 args[i].s = files[i].c_str();
1294 types[i] = 's';
1297 d.replyArray(d.loc, types, args);
1298 delete [] types;
1299 delete [] args;
1300 rEnd},
1301 {"reload_auto_save:i", 0, 0,
1302 rBegin
1303 const int save_id = rtosc_argument(msg,0).i;
1304 const string save_dir = string(getenv("HOME")) + "/.local";
1305 const string save_file = "zynaddsubfx-"+to_s(save_id)+"-autosave.xmz";
1306 const string save_loc = save_dir + "/" + save_file;
1307 impl.loadMaster(save_loc.c_str());
1308 //XXX it would be better to remove the autosave after there is a new
1309 //autosave, but this method should work for non-immediate crashes :-|
1310 remove(save_loc.c_str());
1311 rEnd},
1312 {"delete_auto_save:i", 0, 0,
1313 rBegin
1314 const int save_id = rtosc_argument(msg,0).i;
1315 const string save_dir = string(getenv("HOME")) + "/.local";
1316 const string save_file = "zynaddsubfx-"+to_s(save_id)+"-autosave.xmz";
1317 const string save_loc = save_dir + "/" + save_file;
1318 remove(save_loc.c_str());
1319 rEnd},
1320 {"load_xmz:s", 0, 0,
1321 rBegin;
1322 const char *file = rtosc_argument(msg, 0).s;
1323 impl.loadMaster(file);
1324 d.reply("/damage", "s", "/");
1325 rEnd},
1326 {"reset_master:", 0, 0,
1327 rBegin;
1328 impl.loadMaster(NULL);
1329 d.reply("/damage", "s", "/");
1330 rEnd},
1331 {"load_xiz:is", 0, 0,
1332 rBegin;
1333 const int part_id = rtosc_argument(msg,0).i;
1334 const char *file = rtosc_argument(msg,1).s;
1335 impl.pending_load[part_id]++;
1336 impl.loadPart(part_id, file, impl.master);
1337 rEnd},
1338 {"load-part:is", 0, 0,
1339 rBegin;
1340 const int part_id = rtosc_argument(msg,0).i;
1341 const char *file = rtosc_argument(msg,1).s;
1342 impl.pending_load[part_id]++;
1343 impl.loadPart(part_id, file, impl.master);
1344 rEnd},
1345 {"load-part:iss", 0, 0,
1346 rBegin;
1347 const int part_id = rtosc_argument(msg,0).i;
1348 const char *file = rtosc_argument(msg,1).s;
1349 const char *name = rtosc_argument(msg,2).s;
1350 impl.pending_load[part_id]++;
1351 impl.loadPart(part_id, file, impl.master);
1352 impl.uToB->write(("/part"+to_s(part_id)+"/Pname").c_str(), "s",
1353 name);
1354 rEnd},
1355 {"setprogram:i:c", 0, 0,
1356 rBegin;
1357 Bank &bank = impl.master->bank;
1358 const int slot = rtosc_argument(msg, 0).i + 128*bank.bank_lsb;
1359 if(slot < BANK_SIZE) {
1360 impl.pending_load[0]++;
1361 impl.loadPart(0, impl.master->bank.ins[slot].filename.c_str(), impl.master);
1362 impl.uToB->write("/part0/Pname", "s", impl.master->bank.ins[slot].name.c_str());
1364 rEnd},
1365 {"part#16/clear:", 0, 0,
1366 rBegin;
1367 int id = extractInt(msg);
1368 impl.loadClearPart(id);
1369 d.reply("/damage", "s", ("/part"+to_s(id)).c_str());
1370 rEnd},
1371 {"undo:", 0, 0,
1372 rBegin;
1373 impl.undo.seekHistory(-1);
1374 rEnd},
1375 {"redo:", 0, 0,
1376 rBegin;
1377 impl.undo.seekHistory(+1);
1378 rEnd},
1379 //port to observe the midi mappings
1380 //{"midi-learn-values:", 0, 0,
1381 // rBegin;
1382 // auto &midi = impl.midi_mapper;
1383 // auto key = keys(midi.inv_map);
1384 // //cc-id, path, min, max
1385 //#define MAX_MIDI 32
1386 // rtosc_arg_t args[MAX_MIDI*4];
1387 // char argt[MAX_MIDI*4+1] = {};
1388 // int j=0;
1389 // for(unsigned i=0; i<key.size() && i<MAX_MIDI; ++i) {
1390 // auto val = midi.inv_map[key[i]];
1391 // if(std::get<1>(val) == -1)
1392 // continue;
1393 // argt[4*j+0] = 'i';
1394 // args[4*j+0].i = std::get<1>(val);
1395 // argt[4*j+1] = 's';
1396 // args[4*j+1].s = key[i].c_str();
1397 // argt[4*j+2] = 'i';
1398 // args[4*j+2].i = 0;
1399 // argt[4*j+3] = 'i';
1400 // args[4*j+3].i = 127;
1401 // j++;
1403 // }
1404 // d.replyArray(d.loc, argt, args);
1405 //#undef MAX_MIDI
1406 // rEnd},
1407 //{"learn:s", 0, 0,
1408 // rBegin;
1409 // string addr = rtosc_argument(msg, 0).s;
1410 // auto &midi = impl.midi_mapper;
1411 // auto map = midi.getMidiMappingStrings();
1412 // if(map.find(addr) != map.end())
1413 // midi.map(addr.c_str(), false);
1414 // else
1415 // midi.map(addr.c_str(), true);
1416 // rEnd},
1417 //{"unlearn:s", 0, 0,
1418 // rBegin;
1419 // string addr = rtosc_argument(msg, 0).s;
1420 // auto &midi = impl.midi_mapper;
1421 // auto map = midi.getMidiMappingStrings();
1422 // midi.unMap(addr.c_str(), false);
1423 // midi.unMap(addr.c_str(), true);
1424 // rEnd},
1425 //drop this message into the abyss
1426 {"ui/title:", 0, 0, [](const char *msg, RtData &d) {}},
1427 {"quit:", 0, 0, [](const char *, RtData&) {Pexitprogram = 1;}},
1430 static rtosc::Ports middlewareReplyPorts = {
1431 {"echo:ss", 0, 0,
1432 rBegin;
1433 const char *type = rtosc_argument(msg, 0).s;
1434 const char *url = rtosc_argument(msg, 1).s;
1435 if(!strcmp(type, "OSC_URL"))
1436 impl.currentUrl(url);
1437 rEnd},
1438 {"free:sb", 0, 0,
1439 rBegin;
1440 const char *type = rtosc_argument(msg, 0).s;
1441 void *ptr = *(void**)rtosc_argument(msg, 1).b.data;
1442 deallocate(type, ptr);
1443 rEnd},
1444 {"request-memory:", 0, 0,
1445 rBegin;
1446 //Generate out more memory for the RT memory pool
1447 //5MBi chunk
1448 size_t N = 5*1024*1024;
1449 void *mem = malloc(N);
1450 impl.uToB->write("/add-rt-memory", "bi", sizeof(void*), &mem, N);
1451 rEnd},
1452 {"setprogram:cc:ii", 0, 0,
1453 rBegin;
1454 Bank &bank = impl.master->bank;
1455 const int part = rtosc_argument(msg, 0).i;
1456 const int program = rtosc_argument(msg, 1).i + 128*bank.bank_lsb;
1457 impl.loadPart(part, impl.master->bank.ins[program].filename.c_str(), impl.master);
1458 impl.uToB->write(("/part"+to_s(part)+"/Pname").c_str(), "s", impl.master->bank.ins[program].name.c_str());
1459 rEnd},
1460 {"setbank:c", 0, 0,
1461 rBegin;
1462 impl.loadPendingBank(rtosc_argument(msg,0).i, impl.master->bank);
1463 rEnd},
1464 {"undo_pause:", 0, 0, rBegin; impl.recording_undo = false; rEnd},
1465 {"undo_resume:", 0, 0, rBegin; impl.recording_undo = true; rEnd},
1466 {"undo_change", 0, 0,
1467 rBegin;
1468 if(impl.recording_undo)
1469 impl.undo.recordEvent(msg);
1470 rEnd},
1471 {"broadcast:", 0, 0, rBegin; impl.broadcast = true; rEnd},
1472 {"forward:", 0, 0, rBegin; impl.forward = true; rEnd},
1474 #undef rBegin
1475 #undef rEnd
1477 /******************************************************************************
1478 * MiddleWare Implementation *
1479 ******************************************************************************/
1481 MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_,
1482 Config* config, int preferrred_port)
1483 :parent(mw), config(config), ui(nullptr), synth(std::move(synth_)),
1484 presetsstore(*config), autoSave(-1, [this]() {
1485 auto master = this->master;
1486 this->doReadOnlyOp([master](){
1487 std::string home = getenv("HOME");
1488 std::string save_file = home+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz";
1489 printf("doing an autosave <%s>...\n", save_file.c_str());
1490 int res = master->saveXML(save_file.c_str());
1491 (void)res;});})
1493 bToU = new rtosc::ThreadLink(4096*2*16,1024/16);
1494 uToB = new rtosc::ThreadLink(4096*2*16,1024/16);
1495 //midi_mapper.base_ports = &Master::ports;
1496 //midi_mapper.rt_cb = [this](const char *msg){handleMsg(msg);};
1497 if(preferrred_port != -1)
1498 server = lo_server_new_with_proto(to_s(preferrred_port).c_str(),
1499 LO_UDP, liblo_error_cb);
1500 else
1501 server = lo_server_new_with_proto(NULL, LO_UDP, liblo_error_cb);
1503 if(server) {
1504 lo_server_add_method(server, NULL, NULL, handler_function, mw);
1505 fprintf(stderr, "lo server running on %d\n", lo_server_get_port(server));
1506 } else
1507 fprintf(stderr, "lo server could not be started :-/\n");
1510 //dummy callback for starters
1511 cb = [](void*, const char*){};
1512 idle = 0;
1513 idle_ptr = 0;
1515 master = new Master(synth, config);
1516 master->bToU = bToU;
1517 master->uToB = uToB;
1518 osc = GUI::genOscInterface(mw);
1520 //Grab objects of interest from master
1521 updateResources(master);
1523 //Null out Load IDs
1524 for(int i=0; i < NUM_MIDI_PARTS; ++i) {
1525 pending_load[i] = 0;
1526 actual_load[i] = 0;
1529 //Setup Undo
1530 undo.setCallback([this](const char *msg) {
1531 // printf("undo callback <%s>\n", msg);
1532 char buf[1024];
1533 rtosc_message(buf, 1024, "/undo_pause","");
1534 handleMsg(buf);
1535 handleMsg(msg);
1536 rtosc_message(buf, 1024, "/undo_resume","");
1537 handleMsg(buf);
1540 //Setup starting time
1541 struct timespec time;
1542 clock_gettime(CLOCK_MONOTONIC, &time);
1543 start_time_sec = time.tv_sec;
1544 start_time_nsec = time.tv_nsec;
1546 offline = false;
1549 MiddleWareImpl::~MiddleWareImpl(void)
1552 if(server)
1553 lo_server_free(server);
1555 delete master;
1556 delete osc;
1557 delete bToU;
1558 delete uToB;
1562 /** Threading When Saving
1563 * ----------------------
1565 * Procedure Middleware:
1566 * 1) Middleware sends /freeze_state to backend
1567 * 2) Middleware waits on /state_frozen from backend
1568 * All intervening commands are held for out of order execution
1569 * 3) Aquire memory
1570 * At this time by the memory barrier we are guarenteed that all old
1571 * writes are done and assuming the freezing logic is sound, then it is
1572 * impossible for any other parameter to change at this time
1573 * 3) Middleware performs saving operation
1574 * 4) Middleware sends /thaw_state to backend
1575 * 5) Restore in order execution
1577 * Procedure Backend:
1578 * 1) Observe /freeze_state and disable all mutating events (MIDI CC)
1579 * 2) Run a memory release to ensure that all writes are complete
1580 * 3) Send /state_frozen to Middleware
1581 * time...
1582 * 4) Observe /thaw_state and resume normal processing
1585 void MiddleWareImpl::doReadOnlyOp(std::function<void()> read_only_fn)
1587 assert(uToB);
1588 uToB->write("/freeze_state","");
1590 std::list<const char *> fico;
1591 int tries = 0;
1592 while(tries++ < 10000) {
1593 if(!bToU->hasNext()) {
1594 os_usleep(500);
1595 continue;
1597 const char *msg = bToU->read();
1598 if(!strcmp("/state_frozen", msg))
1599 break;
1600 size_t bytes = rtosc_message_length(msg, bToU->buffer_size());
1601 char *save_buf = new char[bytes];
1602 memcpy(save_buf, msg, bytes);
1603 fico.push_back(save_buf);
1606 assert(tries < 10000);//if this happens, the backend must be dead
1608 std::atomic_thread_fence(std::memory_order_acquire);
1610 //Now it is safe to do any read only operation
1611 read_only_fn();
1613 //Now to resume normal operations
1614 uToB->write("/thaw_state","");
1615 for(auto x:fico) {
1616 uToB->raw_write(x);
1617 delete [] x;
1621 //Offline detection code:
1622 // - Assume that the audio callback should be run at least once every 50ms
1623 // - Atomically provide the number of ms since start to Master
1624 // - Every time middleware ticks provide a heart beat
1625 // - If when the heart beat is provided the backend is more than 200ms behind
1626 // the last heartbeat then it must be offline
1627 // - When marked offline the backend doesn't receive another heartbeat until it
1628 // registers the current beat that it's behind on
1629 void MiddleWareImpl::heartBeat(Master *master)
1631 //Current time
1632 //Last provided beat
1633 //Last acknowledged beat
1634 //Current offline status
1636 struct timespec time;
1637 clock_gettime(CLOCK_MONOTONIC, &time);
1638 uint32_t now = (time.tv_sec-start_time_sec)*100 +
1639 (time.tv_nsec-start_time_nsec)*1e-9*100;
1640 int32_t last_ack = master->last_ack;
1641 int32_t last_beat = master->last_beat;
1643 //everything is considered online for the first second
1644 if(now < 100)
1645 return;
1647 if(offline) {
1648 if(last_beat == last_ack) {
1649 //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO ONLINE
1650 offline = false;
1652 //Send new heart beat
1653 master->last_beat = now;
1655 } else {
1656 //it's unquestionably alive
1657 if(last_beat == last_ack) {
1659 //Send new heart beat
1660 master->last_beat = now;
1661 return;
1664 //it's pretty likely dead
1665 if(last_beat-last_ack > 0 && now-last_beat > 20) {
1666 //The backend has had 200 ms to acquire a new beat
1667 //The backend instead has an older beat
1668 //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO OFFLINE
1669 offline = true;
1670 return;
1673 //who knows if it's alive or not here, give it a few ms to acquire or
1674 //not
1679 void MiddleWareImpl::doReadOnlyOpPlugin(std::function<void()> read_only_fn)
1681 assert(uToB);
1682 int offline = 0;
1683 if(offline) {
1684 std::atomic_thread_fence(std::memory_order_acquire);
1686 //Now it is safe to do any read only operation
1687 read_only_fn();
1688 } else if(!doReadOnlyOpNormal(read_only_fn, true)) {
1689 //check if we just transitioned to offline mode
1691 std::atomic_thread_fence(std::memory_order_acquire);
1693 //Now it is safe to do any read only operation
1694 read_only_fn();
1698 bool MiddleWareImpl::doReadOnlyOpNormal(std::function<void()> read_only_fn, bool canfail)
1700 assert(uToB);
1701 uToB->write("/freeze_state","");
1703 std::list<const char *> fico;
1704 int tries = 0;
1705 while(tries++ < 2000) {
1706 if(!bToU->hasNext()) {
1707 os_usleep(500);
1708 continue;
1710 const char *msg = bToU->read();
1711 if(!strcmp("/state_frozen", msg))
1712 break;
1713 size_t bytes = rtosc_message_length(msg, bToU->buffer_size());
1714 char *save_buf = new char[bytes];
1715 memcpy(save_buf, msg, bytes);
1716 fico.push_back(save_buf);
1719 if(canfail) {
1720 //Now to resume normal operations
1721 uToB->write("/thaw_state","");
1722 for(auto x:fico) {
1723 uToB->raw_write(x);
1724 delete [] x;
1726 return false;
1729 assert(tries < 10000);//if this happens, the backend must be dead
1731 std::atomic_thread_fence(std::memory_order_acquire);
1733 //Now it is safe to do any read only operation
1734 read_only_fn();
1736 //Now to resume normal operations
1737 uToB->write("/thaw_state","");
1738 for(auto x:fico) {
1739 uToB->raw_write(x);
1740 delete [] x;
1742 return true;
1745 void MiddleWareImpl::broadcastToRemote(const char *rtmsg)
1747 //Always send to the local UI
1748 sendToRemote(rtmsg, "GUI");
1750 //Send to remote UI if there's one listening
1751 for(auto rem:known_remotes)
1752 if(rem != "GUI")
1753 sendToRemote(rtmsg, rem);
1755 broadcast = false;
1758 void MiddleWareImpl::sendToRemote(const char *rtmsg, std::string dest)
1760 if(!rtmsg || rtmsg[0] != '/' || !rtosc_message_length(rtmsg, -1)) {
1761 printf("[Warning] Invalid message in sendToRemote <%s>...\n", rtmsg);
1762 return;
1765 //printf("sendToRemote(%s:%s,%s)\n", rtmsg, rtosc_argument_string(rtmsg),
1766 // dest.c_str());
1767 if(dest == "GUI") {
1768 cb(ui, rtmsg);
1769 } else if(!dest.empty()) {
1770 lo_message msg = lo_message_deserialise((void*)rtmsg,
1771 rtosc_message_length(rtmsg, bToU->buffer_size()), NULL);
1772 if(!msg) {
1773 printf("[ERROR] OSC to <%s> Failed To Parse In Liblo\n", rtmsg);
1774 return;
1777 //Send to known url
1778 lo_address addr = lo_address_new_from_url(dest.c_str());
1779 if(addr)
1780 lo_send_message(addr, rtmsg, msg);
1781 lo_address_free(addr);
1782 lo_message_free(msg);
1787 * Handle all events coming from the backend
1789 * This includes forwarded events which need to be retransmitted to the backend
1790 * after the snooping code inspects the message
1792 void MiddleWareImpl::bToUhandle(const char *rtmsg)
1794 //Verify Message isn't a known corruption bug
1795 assert(strcmp(rtmsg, "/part0/kit0/Ppadenableda"));
1796 assert(strcmp(rtmsg, "/ze_state"));
1798 //Dump Incomming Events For Debugging
1799 if(strcmp(rtmsg, "/vu-meter") && false) {
1800 fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 1 + 30, 0 + 40);
1801 fprintf(stdout, "frontend[%c]: '%s'<%s>\n", forward?'f':broadcast?'b':'N',
1802 rtmsg, rtosc_argument_string(rtmsg));
1803 fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
1806 //Activity dot
1807 //printf(".");fflush(stdout);
1809 MwDataObj d(this);
1810 middlewareReplyPorts.dispatch(rtmsg, d, true);
1812 if(!rtmsg) {
1813 fprintf(stderr, "[ERROR] Unexpected Null OSC In Zyn\n");
1814 return;
1817 in_order = true;
1818 //Normal message not captured by the ports
1819 if(d.matches == 0) {
1820 if(forward) {
1821 forward = false;
1822 handleMsg(rtmsg);
1823 } if(broadcast)
1824 broadcastToRemote(rtmsg);
1825 else
1826 sendToCurrentRemote(rtmsg);
1828 in_order = false;
1832 //Allocate kits on a as needed basis
1833 void MiddleWareImpl::kitEnable(const char *msg)
1835 const string argv = rtosc_argument_string(msg);
1836 if(argv != "T")
1837 return;
1838 //Extract fields from:
1839 //BASE/part#/kit#/Pxxxenabled
1840 int type = -1;
1841 if(strstr(msg, "Padenabled"))
1842 type = 0;
1843 else if(strstr(msg, "Ppadenabled"))
1844 type = 1;
1845 else if(strstr(msg, "Psubenabled"))
1846 type = 2;
1847 else
1848 return;
1850 const char *tmp = strstr(msg, "part");
1852 if(tmp == NULL)
1853 return;
1855 const int part = atoi(tmp+4);
1857 tmp = strstr(msg, "kit");
1859 if(tmp == NULL)
1860 return;
1862 const int kit = atoi(tmp+3);
1864 kitEnable(part, kit, type);
1867 void MiddleWareImpl::kitEnable(int part, int kit, int type)
1869 //printf("attempting a kit enable<%d,%d,%d>\n", part, kit, type);
1870 string url = "/part"+to_s(part)+"/kit"+to_s(kit)+"/";
1871 void *ptr = NULL;
1872 if(type == 0 && kits.add[part][kit] == NULL) {
1873 ptr = kits.add[part][kit] = new ADnoteParameters(synth, master->fft,
1874 &master->time);
1875 url += "adpars-data";
1876 obj_store.extractAD(kits.add[part][kit], part, kit);
1877 } else if(type == 1 && kits.pad[part][kit] == NULL) {
1878 ptr = kits.pad[part][kit] = new PADnoteParameters(synth, master->fft,
1879 &master->time);
1880 url += "padpars-data";
1881 obj_store.extractPAD(kits.pad[part][kit], part, kit);
1882 } else if(type == 2 && kits.sub[part][kit] == NULL) {
1883 ptr = kits.sub[part][kit] = new SUBnoteParameters(&master->time);
1884 url += "subpars-data";
1887 //Send the new memory
1888 if(ptr)
1889 uToB->write(url.c_str(), "b", sizeof(void*), &ptr);
1894 * Handle all messages traveling to the realtime side.
1896 void MiddleWareImpl::handleMsg(const char *msg)
1898 //Check for known bugs
1899 assert(msg && *msg && strrchr(msg, '/')[1]);
1900 assert(strstr(msg,"free") == NULL || strstr(rtosc_argument_string(msg), "b") == NULL);
1901 assert(strcmp(msg, "/part0/Psysefxvol"));
1902 assert(strcmp(msg, "/Penabled"));
1903 assert(strcmp(msg, "part0/part0/Ppanning"));
1904 assert(strcmp(msg, "sysefx0sysefx0/preset"));
1905 assert(strcmp(msg, "/sysefx0preset"));
1906 assert(strcmp(msg, "Psysefxvol0/part0"));
1908 if(strcmp("/get-vu", msg) && false) {
1909 fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 6 + 30, 0 + 40);
1910 fprintf(stdout, "middleware: '%s':%s\n", msg, rtosc_argument_string(msg));
1911 fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
1914 const char *last_path = strrchr(msg, '/');
1915 if(!last_path) {
1916 printf("Bad message in handleMsg() <%s>\n", msg);
1917 assert(false);
1918 return;
1921 MwDataObj d(this);
1922 middwareSnoopPorts.dispatch(msg, d, true);
1924 //A message unmodified by snooping
1925 if(d.matches == 0 || d.forwarded) {
1926 //if(strcmp("/get-vu", msg)) {
1927 // printf("Message Continuing on<%s:%s>...\n", msg, rtosc_argument_string(msg));
1929 uToB->raw_write(msg);
1930 } else {
1931 //printf("Message Handled<%s:%s>...\n", msg, rtosc_argument_string(msg));
1935 void MiddleWareImpl::write(const char *path, const char *args, ...)
1937 //We have a free buffer in the threadlink, so use it
1938 va_list va;
1939 va_start(va, args);
1940 write(path, args, va);
1941 va_end(va);
1944 void MiddleWareImpl::write(const char *path, const char *args, va_list va)
1946 //printf("is that a '%s' I see there?\n", path);
1947 char *buffer = uToB->buffer();
1948 unsigned len = uToB->buffer_size();
1949 bool success = rtosc_vmessage(buffer, len, path, args, va);
1950 //printf("working on '%s':'%s'\n",path, args);
1952 if(success)
1953 handleMsg(buffer);
1954 else
1955 warnx("Failed to write message to '%s'", path);
1958 /******************************************************************************
1959 * MidleWare Forwarding Stubs *
1960 ******************************************************************************/
1961 MiddleWare::MiddleWare(SYNTH_T synth, Config* config,
1962 int preferred_port)
1963 :impl(new MiddleWareImpl(this, std::move(synth), config, preferred_port))
1966 MiddleWare::~MiddleWare(void)
1968 delete impl;
1971 void MiddleWare::updateResources(Master *m)
1973 impl->updateResources(m);
1976 Master *MiddleWare::spawnMaster(void)
1978 assert(impl->master);
1979 assert(impl->master->uToB);
1980 return impl->master;
1983 void MiddleWare::enableAutoSave(int interval_sec)
1985 impl->autoSave.dt = interval_sec;
1988 int MiddleWare::checkAutoSave(void)
1990 //save spec zynaddsubfx-PID-autosave.xmz
1991 const std::string home = getenv("HOME");
1992 const std::string save_dir = home+"/.local/";
1994 DIR *dir = opendir(save_dir.c_str());
1996 if(dir == NULL)
1997 return -1;
1999 struct dirent *fn;
2000 int reload_save = -1;
2002 while((fn = readdir(dir))) {
2003 const char *filename = fn->d_name;
2004 const char *prefix = "zynaddsubfx-";
2006 //check for manditory prefix
2007 if(strstr(filename, prefix) != filename)
2008 continue;
2010 int id = atoi(filename+strlen(prefix));
2012 bool in_use = false;
2014 std::string proc_file = "/proc/" + to_s(id) + "/comm";
2015 std::ifstream ifs(proc_file);
2016 if(ifs.good()) {
2017 std::string comm_name;
2018 ifs >> comm_name;
2019 in_use = (comm_name == "zynaddsubfx");
2022 if(!in_use) {
2023 reload_save = id;
2024 break;
2028 closedir(dir);
2030 return reload_save;
2033 void MiddleWare::removeAutoSave(void)
2035 std::string home = getenv("HOME");
2036 std::string save_file = home+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz";
2037 remove(save_file.c_str());
2040 Fl_Osc_Interface *MiddleWare::spawnUiApi(void)
2042 return impl->osc;
2045 void MiddleWare::tick(void)
2047 impl->tick();
2050 void MiddleWare::doReadOnlyOp(std::function<void()> fn)
2052 impl->doReadOnlyOp(fn);
2055 void MiddleWare::setUiCallback(void(*cb)(void*,const char *), void *ui)
2057 impl->cb = cb;
2058 impl->ui = ui;
2061 void MiddleWare::setIdleCallback(void(*cb)(void*), void *ptr)
2063 impl->idle = cb;
2064 impl->idle_ptr = ptr;
2067 void MiddleWare::transmitMsg(const char *msg)
2069 impl->handleMsg(msg);
2072 void MiddleWare::transmitMsg(const char *path, const char *args, ...)
2074 char buffer[1024];
2075 va_list va;
2076 va_start(va,args);
2077 if(rtosc_vmessage(buffer,1024,path,args,va))
2078 transmitMsg(buffer);
2079 else
2080 fprintf(stderr, "Error in transmitMsg(...)\n");
2081 va_end(va);
2084 void MiddleWare::transmitMsg_va(const char *path, const char *args, va_list va)
2086 char buffer[1024];
2087 if(rtosc_vmessage(buffer, 1024, path, args, va))
2088 transmitMsg(buffer);
2089 else
2090 fprintf(stderr, "Error in transmitMsg(va)n");
2093 void MiddleWare::messageAnywhere(const char *path, const char *args, ...)
2095 auto *mem = impl->multi_thread_source.alloc();
2096 if(!mem)
2097 fprintf(stderr, "Middleware::messageAnywhere memory pool out of memory...\n");
2099 va_list va;
2100 va_start(va,args);
2101 if(rtosc_vmessage(mem->memory,mem->size,path,args,va))
2102 impl->multi_thread_source.write(mem);
2103 else {
2104 fprintf(stderr, "Middleware::messageAnywhere message too big...\n");
2105 impl->multi_thread_source.free(mem);
2110 void MiddleWare::pendingSetBank(int bank)
2112 impl->bToU->write("/setbank", "c", bank);
2114 void MiddleWare::pendingSetProgram(int part, int program)
2116 impl->pending_load[part]++;
2117 impl->bToU->write("/setprogram", "cc", part, program);
2120 std::string MiddleWare::activeUrl(void)
2122 return impl->last_url;
2125 void MiddleWare::activeUrl(std::string u)
2127 impl->last_url = u;
2130 const SYNTH_T &MiddleWare::getSynth(void) const
2132 return impl->synth;
2135 const char* MiddleWare::getServerAddress(void) const
2137 if(impl->server)
2138 return lo_server_get_url(impl->server);
2139 else
2140 return "NULL";
2143 const PresetsStore& MiddleWare::getPresetsStore() const
2145 return impl->presetsstore;
2148 PresetsStore& MiddleWare::getPresetsStore()
2150 return impl->presetsstore;