zynaddsubfx

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

MiddleWare.cpp (89181B)


      1 /*
      2   ZynAddSubFX - a software synthesizer
      3 
      4   MiddleWare.cpp - Glue Logic And Home Of Non-RT Operations
      5   Copyright (C) 2016 Mark McCurry
      6 
      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.
     11 */
     12 #include "MiddleWare.h"
     13 
     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>
     22 
     23 #include <rtosc/undo-history.h>
     24 #include <rtosc/thread-link.h>
     25 #include <rtosc/ports.h>
     26 #include <rtosc/port-sugar.h>
     27 #include <lo/lo.h>
     28 
     29 #include <unistd.h>
     30 
     31 #include "../UI/Connection.h"
     32 #include "../UI/Fl_Osc_Interface.h"
     33 
     34 #include <map>
     35 #include <queue>
     36 
     37 #include "Util.h"
     38 #include "CallbackRepeater.h"
     39 #include "Master.h"
     40 #include "MsgParsing.h"
     41 #include "Part.h"
     42 #include "PresetExtractor.h"
     43 #include "../Containers/MultiPseudoStack.h"
     44 #include "../Params/PresetsStore.h"
     45 #include "../Params/EnvelopeParams.h"
     46 #include "../Params/LFOParams.h"
     47 #include "../Params/FilterParams.h"
     48 #include "../Effects/EffectMgr.h"
     49 #include "../Synth/Resonance.h"
     50 #include "../Params/ADnoteParameters.h"
     51 #include "../Params/SUBnoteParameters.h"
     52 #include "../Params/PADnoteParameters.h"
     53 #include "../DSP/FFTwrapper.h"
     54 #include "../Synth/OscilGen.h"
     55 #include "../Nio/Nio.h"
     56 
     57 #include <string>
     58 #include <future>
     59 #include <atomic>
     60 #include <list>
     61 
     62 #define errx(...) {}
     63 #define warnx(...) {}
     64 #ifndef errx
     65 #include <err.h>
     66 #endif
     67 
     68 namespace zyn {
     69 
     70 using std::string;
     71 int Pexitprogram = 0;
     72 
     73 #ifdef __APPLE__
     74 #include <mach/clock.h>
     75 #include <mach/mach.h>
     76 #endif
     77 
     78 /* work around missing clock_gettime on OSX */
     79 static void monotonic_clock_gettime(struct timespec *ts) {
     80 #ifdef __APPLE__
     81     clock_serv_t cclock;
     82     mach_timespec_t mts;
     83     host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
     84     clock_get_time(cclock, &mts);
     85     mach_port_deallocate(mach_task_self(), cclock);
     86     ts->tv_sec = mts.tv_sec;
     87     ts->tv_nsec = mts.tv_nsec;
     88 #else
     89     clock_gettime(CLOCK_MONOTONIC, ts);
     90 #endif
     91 }
     92 
     93 /******************************************************************************
     94  *                        LIBLO And Reflection Code                           *
     95  *                                                                            *
     96  * All messages that are handled are handled in a serial fashion.             *
     97  * Thus, changes in the current interface sending messages can be encoded     *
     98  * into the stream via events which simply echo back the active interface     *
     99  ******************************************************************************/
    100 static void liblo_error_cb(int i, const char *m, const char *loc)
    101 {
    102     fprintf(stderr, "liblo :-( %d-%s@%s\n",i,m,loc);
    103 }
    104 
    105 // we need to access this before the definitions
    106 // bad style?
    107 static const rtosc::Ports& getNonRtParamPorts();
    108 
    109 static int handler_function(const char *path, const char *types, lo_arg **argv,
    110         int argc, lo_message msg, void *user_data)
    111 {
    112     (void) types;
    113     (void) argv;
    114     (void) argc;
    115     MiddleWare *mw = (MiddleWare*)user_data;
    116     lo_address addr = lo_message_get_source(msg);
    117     if(addr) {
    118         const char *tmp = lo_address_get_url(addr);
    119         if(tmp != mw->activeUrl()) {
    120             mw->transmitMsg("/echo", "ss", "OSC_URL", tmp);
    121             mw->activeUrl(tmp);
    122         }
    123         free((void*)tmp);
    124     }
    125 
    126     char buffer[2048];
    127     memset(buffer, 0, sizeof(buffer));
    128     size_t size = 2048;
    129     lo_message_serialise(msg, path, buffer, &size);
    130 
    131     if(buffer[0]=='/' && strrchr(buffer, '/')[1])
    132     {
    133         mw->transmitMsg(rtosc::Ports::collapsePath(buffer));
    134     }
    135 
    136     return 0;
    137 }
    138 
    139 typedef void(*cb_t)(void*,const char*);
    140 
    141 //utility method (should be moved to a better location)
    142 template <class T, class V>
    143 std::vector<T> keys(const std::map<T,V> &m)
    144 {
    145     std::vector<T> vec;
    146     for(auto &kv: m)
    147         vec.push_back(kv.first);
    148     return vec;
    149 }
    150 
    151 
    152 /*****************************************************************************
    153  *                    Memory Deallocation                                    *
    154  *****************************************************************************/
    155 void deallocate(const char *str, void *v)
    156 {
    157     //printf("deallocating a '%s' at '%p'\n", str, v);
    158     if(!strcmp(str, "Part"))
    159         delete (Part*)v;
    160     else if(!strcmp(str, "Master"))
    161         delete (Master*)v;
    162     else if(!strcmp(str, "fft_t"))
    163         delete[] (fft_t*)v;
    164     else if(!strcmp(str, "KbmInfo"))
    165         delete (KbmInfo*)v;
    166     else if(!strcmp(str, "SclInfo"))
    167         delete (SclInfo*)v;
    168     else if(!strcmp(str, "Microtonal"))
    169         delete (Microtonal*)v;
    170     else if(!strcmp(str, "ADnoteParameters"))
    171         delete (ADnoteParameters*)v;
    172     else if(!strcmp(str, "SUBnoteParameters"))
    173         delete (SUBnoteParameters*)v;
    174     else if(!strcmp(str, "PADnoteParameters"))
    175         delete (PADnoteParameters*)v;
    176     else if(!strcmp(str, "EffectMgr"))
    177         delete (EffectMgr*)v;
    178     else if(!strcmp(str, "EnvelopeParams"))
    179         delete (EnvelopeParams*)v;
    180     else if(!strcmp(str, "FilterParams"))
    181         delete (FilterParams*)v;
    182     else if(!strcmp(str, "LFOParams"))
    183         delete (LFOParams*)v;
    184     else if(!strcmp(str, "OscilGen"))
    185         delete (OscilGen*)v;
    186     else if(!strcmp(str, "Resonance"))
    187         delete (Resonance*)v;
    188     else if(!strcmp(str, "rtosc::AutomationMgr"))
    189         delete (rtosc::AutomationMgr*)v;
    190     else if(!strcmp(str, "PADsample"))
    191         delete[] (float*)v;
    192     else
    193         fprintf(stderr, "Unknown type '%s', leaking pointer %p!!\n", str, v);
    194 }
    195 
    196 
    197 /*****************************************************************************
    198  *                    PadSynth Setup                                         *
    199  *****************************************************************************/
    200 
    201 // This lets MiddleWare compute non-realtime PAD synth data and send it to the backend
    202 void preparePadSynth(string path, PADnoteParameters *p, rtosc::RtData &d)
    203 {
    204     //printf("preparing padsynth parameters\n");
    205     assert(!path.empty());
    206     path += "sample";
    207 
    208 #ifdef WIN32
    209     unsigned num = p->sampleGenerator([&path,&d]
    210                        (unsigned N, PADnoteParameters::Sample &&s)
    211                        {
    212                            //printf("sending info to '%s'\n",
    213                            //       (path+to_s(N)).c_str());
    214                            d.chain((path+to_s(N)).c_str(), "ifb",
    215                                    s.size, s.basefreq, sizeof(float*), &s.smp);
    216                        }, []{return false;}, 1);
    217 #else
    218     std::mutex rtdata_mutex;
    219     unsigned num = p->sampleGenerator([&rtdata_mutex, &path,&d]
    220                        (unsigned N, PADnoteParameters::Sample&& s)
    221                        {
    222                            //printf("sending info to '%s'\n",
    223                            //       (path+to_s(N)).c_str());
    224                            rtdata_mutex.lock();
    225                            // send non-realtime computed data to PADnoteParameters
    226                            d.chain((path+to_s(N)).c_str(), "ifb",
    227                                    s.size, s.basefreq, sizeof(float*), &s.smp);
    228                            rtdata_mutex.unlock();
    229                        }, []{return false;});
    230 #endif
    231 
    232     //clear out unused samples
    233     for(unsigned i = num; i < PAD_MAX_SAMPLES; ++i) {
    234         d.chain((path+to_s(i)).c_str(), "ifb",
    235                 0, 440.0f, sizeof(float*), NULL);
    236     }
    237 }
    238 
    239 /******************************************************************************
    240  *                      MIDI Serialization                                    *
    241  *                                                                            *
    242  ******************************************************************************/
    243 void saveMidiLearn(XMLwrapper &xml, const rtosc::MidiMappernRT &midi)
    244 {
    245     xml.beginbranch("midi-learn");
    246     for(auto value:midi.inv_map) {
    247         XmlNode binding("midi-binding");
    248         auto biject = std::get<3>(value.second);
    249         binding["osc-path"]  = value.first;
    250         binding["coarse-CC"] = to_s(std::get<1>(value.second));
    251         binding["fine-CC"]   = to_s(std::get<2>(value.second));
    252         binding["type"]      = "i";
    253         binding["minimum"]   = to_s(biject.min);
    254         binding["maximum"]   = to_s(biject.max);
    255         xml.add(binding);
    256     }
    257     xml.endbranch();
    258 }
    259 
    260 void loadMidiLearn(XMLwrapper &xml, rtosc::MidiMappernRT &midi)
    261 {
    262     using rtosc::Port;
    263     if(xml.enterbranch("midi-learn")) {
    264         auto nodes = xml.getBranch();
    265 
    266         //TODO clear mapper
    267 
    268         for(auto node:nodes) {
    269             if(node.name != "midi-binding" ||
    270                     !node.has("osc-path") ||
    271                     !node.has("coarse-CC"))
    272                 continue;
    273             const string path = node["osc-path"];
    274             const int    CC   = atoi(node["coarse-CC"].c_str());
    275             const Port  *p    = Master::ports.apropos(path.c_str());
    276             if(p) {
    277                 printf("loading midi port...\n");
    278                 midi.addNewMapper(CC, *p, path);
    279             } else {
    280                 printf("unknown midi bindable <%s>\n", path.c_str());
    281             }
    282         }
    283         xml.exitbranch();
    284     } else
    285         printf("cannot find 'midi-learn' branch...\n");
    286 }
    287 
    288 void connectMidiLearn(int par, int chan, bool isNrpn, string path, rtosc::MidiMappernRT &midi)
    289 {
    290     const rtosc::Port *p = Master::ports.apropos(path.c_str());
    291     if(p) {
    292         if(isNrpn)
    293             printf("mapping midi NRPN: %d, CH: %d to Port: %s\n", par, chan, path.c_str());
    294         else
    295             printf("mapping midi CC: %d, CH: %d to Port: %s\n", par, chan, path.c_str());
    296 
    297         if(chan<1) chan=1;
    298         int ID = (isNrpn<<18) + (((chan-1)&0x0f)<<14) + par;
    299         //~ printf("ID = %d\n", ID);
    300 
    301         midi.addNewMapper(ID, *p, path);
    302     } else {
    303         printf("unknown port to midi bind <%s>\n", path.c_str());
    304     }
    305 }
    306 /******************************************************************************
    307  *                      Non-RealTime Object Store                             *
    308  *                                                                            *
    309  *                                                                            *
    310  * Storage For Objects which need to be interfaced with outside the realtime  *
    311  * thread (aka they have long lived operations which can be done out-of-band) *
    312  *                                                                            *
    313  * - OscilGen instances as prepare() cannot be done in realtime and PAD       *
    314  *   depends on these instances                                               *
    315  * - PADnoteParameter instances as applyparameters() cannot be done in        *
    316  *   realtime                                                                 *
    317  *                                                                            *
    318  * These instances are collected on every part change and kit change          *
    319  ******************************************************************************/
    320 struct NonRtObjStore
    321 {
    322     std::map<std::string, void*> objmap;
    323 
    324     void extractMaster(Master *master)
    325     {
    326         for(int i=0; i < NUM_MIDI_PARTS; ++i) {
    327             extractPart(master->part[i], i);
    328         }
    329     }
    330 
    331     void extractPart(Part *part, int i)
    332     {
    333         for(int j=0; j < NUM_KIT_ITEMS; ++j) {
    334             auto &obj = part->kit[j];
    335             extractAD(obj.adpars, i, j);
    336             extractPAD(obj.padpars, i, j);
    337         }
    338     }
    339 
    340     void extractAD(ADnoteParameters *adpars, int i, int j)
    341     {
    342         std::string base = "/part"+to_s(i)+"/kit"+to_s(j)+"/";
    343         for(int k=0; k<NUM_VOICES; ++k) {
    344             std::string nbase = base+"adpars/VoicePar"+to_s(k)+"/";
    345             if(adpars) {
    346                 auto &nobj = adpars->VoicePar[k];
    347                 objmap[nbase+"OscilSmp/"] = nobj.OscilGn;
    348                 objmap[nbase+"FMSmp/"]    = nobj.FmGn;
    349             } else {
    350                 objmap[nbase+"OscilSmp/"] = nullptr;
    351                 objmap[nbase+"FMSmp/"]    = nullptr;
    352             }
    353         }
    354     }
    355 
    356     void extractPAD(PADnoteParameters *padpars, int i, int j)
    357     {
    358         std::string base = "/part"+to_s(i)+"/kit"+to_s(j)+"/";
    359         for(int k=0; k<NUM_VOICES; ++k) {
    360             if(padpars) {
    361                 objmap[base+"padpars/"]       = padpars;
    362                 objmap[base+"padpars/oscilgen/"] = padpars->oscilgen;
    363             } else {
    364                 objmap[base+"padpars/"]       = nullptr;
    365                 objmap[base+"padpars/oscilgen/"] = nullptr;
    366             }
    367         }
    368     }
    369 
    370     void clear(void)
    371     {
    372         objmap.clear();
    373     }
    374 
    375     bool has(std::string loc)
    376     {
    377         return objmap.find(loc) != objmap.end();
    378     }
    379 
    380     void *get(std::string loc)
    381     {
    382         return objmap[loc];
    383     }
    384 
    385     void handleOscil(const char *msg, rtosc::RtData &d) {
    386         string obj_rl(d.message, msg);
    387         assert(d.message);
    388         assert(msg);
    389         assert(msg >= d.message);
    390         assert(msg - d.message < 256);
    391         void *osc = get(obj_rl);
    392         if(osc)
    393         {
    394             strcpy(d.loc, obj_rl.c_str());
    395             d.obj = osc;
    396             OscilGen::non_realtime_ports.dispatch(msg, d);
    397         }
    398         else {
    399             // print warning, except in rtosc::walk_ports
    400             if(!strstr(d.message, "/pointer"))
    401             {
    402                 fprintf(stderr, "Warning: trying to access oscil object \"%s\","
    403                                 "which does not exist\n", obj_rl.c_str());
    404             }
    405             d.obj = nullptr; // tell walk_ports that there's nothing to recurse here...
    406         }
    407     }
    408     void handlePad(const char *msg, rtosc::RtData &d) {
    409         string obj_rl(d.message, msg);
    410         void *pad = get(obj_rl);
    411         if(!strcmp(msg, "prepare")) {
    412             preparePadSynth(obj_rl, (PADnoteParameters*)pad, d);
    413             d.matches++;
    414             d.reply((obj_rl+"needPrepare").c_str(), "F");
    415         } else {
    416             if(pad)
    417             {
    418                 strcpy(d.loc, obj_rl.c_str());
    419                 d.obj = pad;
    420                 PADnoteParameters::non_realtime_ports.dispatch(msg, d);
    421                 if(d.matches && rtosc_narguments(msg)) {
    422                     if(!strcmp(msg, "oscilgen/prepare"))
    423                         ; //ignore
    424                     else {
    425                         d.reply((obj_rl+"needPrepare").c_str(), "T");
    426                     }
    427                 }
    428             }
    429             else {
    430                 // print warning, except in rtosc::walk_ports
    431                 if(!strstr(d.message, "/pointer"))
    432                 {
    433                     fprintf(stderr, "Warning: trying to access pad synth object "
    434                                     "\"%s\", which does not exist\n",
    435                             obj_rl.c_str());
    436                 }
    437                 d.obj = nullptr; // tell walk_ports that there's nothing to recurse here...
    438             }
    439         }
    440     }
    441 };
    442 
    443 /******************************************************************************
    444  *                      Realtime Parameter Store                              *
    445  *                                                                            *
    446  * Storage for AD/PAD/SUB parameters which are allocated as needed by kits.   *
    447  * Two classes of events affect this:                                         *
    448  * 1. When a message to enable a kit is observed, then the kit is allocated   *
    449  *    and sent prior to the enable message.                                   *
    450  * 2. When a part is allocated all part information is rebuilt                *
    451  *                                                                            *
    452  * (NOTE pointers aren't really needed here, just booleans on whether it has  *
    453  * been  allocated)                                                           *
    454  * This may be later utilized for copy/paste support                          *
    455  ******************************************************************************/
    456 struct ParamStore
    457 {
    458     ParamStore(void)
    459     {
    460         memset(add, 0, sizeof(add));
    461         memset(pad, 0, sizeof(pad));
    462         memset(sub, 0, sizeof(sub));
    463     }
    464 
    465     void extractPart(Part *part, int i)
    466     {
    467         for(int j=0; j < NUM_KIT_ITEMS; ++j) {
    468             auto kit = part->kit[j];
    469             add[i][j] = kit.adpars;
    470             sub[i][j] = kit.subpars;
    471             pad[i][j] = kit.padpars;
    472         }
    473     }
    474 
    475     ADnoteParameters  *add[NUM_MIDI_PARTS][NUM_KIT_ITEMS];
    476     SUBnoteParameters *sub[NUM_MIDI_PARTS][NUM_KIT_ITEMS];
    477     PADnoteParameters *pad[NUM_MIDI_PARTS][NUM_KIT_ITEMS];
    478 };
    479 
    480 //XXX perhaps move this to Nio
    481 //(there needs to be some standard Nio stub file for this sort of stuff)
    482 namespace Nio
    483 {
    484     using std::get;
    485     rtosc::Ports ports = {
    486         {"sink-list:", 0, 0, [](const char *, rtosc::RtData &d) {
    487                 auto list = Nio::getSinks();
    488                 char *ret = rtosc_splat(d.loc, list);
    489                 d.reply(ret);
    490                 delete [] ret;
    491             }},
    492         {"source-list:", 0, 0, [](const char *, rtosc::RtData &d) {
    493                 auto list = Nio::getSources();
    494                 char *ret = rtosc_splat(d.loc, list);
    495                 d.reply(ret);
    496                 delete [] ret;
    497             }},
    498         {"source::s", 0, 0, [](const char *msg, rtosc::RtData &d) {
    499                 if(rtosc_narguments(msg) == 0)
    500                     d.reply(d.loc, "s", Nio::getSource().c_str());
    501                 else
    502                     Nio::setSource(rtosc_argument(msg,0).s);}},
    503         {"sink::s", 0, 0, [](const char *msg, rtosc::RtData &d) {
    504                 if(rtosc_narguments(msg) == 0)
    505                     d.reply(d.loc, "s", Nio::getSink().c_str());
    506                 else
    507                     Nio::setSink(rtosc_argument(msg,0).s);}},
    508         {"audio-compressor::T:F", 0, 0, [](const char *msg, rtosc::RtData &d) {
    509                 if(rtosc_narguments(msg) == 0)
    510                     d.reply(d.loc, Nio::getAudioCompressor() ? "T" : "F");
    511                 else
    512                     Nio::setAudioCompressor(rtosc_argument(msg,0).T);}},
    513     };
    514 }
    515 
    516 
    517 /* Implementation */
    518 
    519 class mw_dispatcher_t : public master_dispatcher_t
    520 {
    521     MiddleWare* mw;
    522     bool do_dispatch(const char *msg) override
    523     {
    524         mw->transmitMsg(msg);
    525         return true; // we cannot yet say if the port matched
    526                      // we will query the Master after everything will be done
    527     }
    528     void vUpdateMaster(Master* m) { mw->switchMaster(m); }
    529 
    530 public:
    531     mw_dispatcher_t(MiddleWare* mw) : mw(mw) {}
    532 };
    533 
    534 class MiddleWareImpl
    535 {
    536     // messages chained with MwDataObj::chain
    537     // must yet be handled after a previous handleMsg
    538     std::queue<std::vector<char>> msgsToHandle;
    539 public:
    540     MiddleWare *parent;
    541     Config* const config;
    542     MiddleWareImpl(MiddleWare *mw, SYNTH_T synth, Config* config,
    543                    int preferred_port);
    544     ~MiddleWareImpl(void);
    545     void discardAllbToUButHandleFree();
    546     void recreateMinimalMaster();
    547 
    548     //Check offline vs online mode in plugins
    549     void heartBeat(Master *m);
    550     int64_t start_time_sec;
    551     int64_t start_time_nsec;
    552     bool offline;
    553 
    554     //Apply function while parameters are write locked
    555     void doReadOnlyOp(std::function<void()> read_only_fn);
    556     void doReadOnlyOpPlugin(std::function<void()> read_only_fn);
    557     bool doReadOnlyOpNormal(std::function<void()> read_only_fn, bool canfail=false);
    558 
    559     void savePart(int npart, const char *filename)
    560     {
    561         // Due to a possible bug in ThreadLink, filename may get trashed when
    562         // the read-only operation writes to the buffer again. Copy to string:
    563         std::string fname = filename;
    564         //printf("saving part(%d,'%s')\n", npart, filename);
    565         doReadOnlyOp([this,fname,npart](){
    566                 int res = master->part[npart]->saveXML(fname.c_str());
    567                 (void)res;
    568                 /*printf("results: '%s' '%d'\n",fname.c_str(), res);*/});
    569     }
    570 
    571     void loadPendingBank(int par, Bank &bank)
    572     {
    573         if(((unsigned int)par < bank.banks.size())
    574            && (bank.banks[par].dir != bank.bankfiletitle))
    575             bank.loadbank(bank.banks[par].dir);
    576     }
    577 
    578     void loadPart(int npart, const char *filename, Master *master, rtosc::RtData &d)
    579     {
    580         actual_load[npart]++;
    581 
    582         if(actual_load[npart] != pending_load[npart])
    583             return;
    584         assert(actual_load[npart] <= pending_load[npart]);
    585         assert(filename);
    586 
    587         //load part in async fashion when possible
    588 #ifndef WIN32
    589         auto alloc = std::async(std::launch::async,
    590                 [master,filename,this,npart](){
    591                 Part *p = new Part(*master->memory, synth,
    592                                    master->time,
    593                                    master->sync,
    594                                    config->cfg.GzipCompression,
    595                                    config->cfg.Interpolation,
    596                                    &master->microtonal, master->fft, &master->watcher,
    597                                    ("/part"+to_s(npart)+"/").c_str());
    598                 p->partno  = npart % NUM_MIDI_CHANNELS;
    599                 p->Prcvchn = npart % NUM_MIDI_CHANNELS;
    600                 if(p->loadXMLinstrument(filename))
    601                     fprintf(stderr, "Warning: failed to load part<%s>!\n", filename);
    602 
    603                 auto isLateLoad = [this,npart]{
    604                 return actual_load[npart] != pending_load[npart];
    605                 };
    606 
    607                 p->applyparameters(isLateLoad);
    608                 return p;});
    609 
    610         //Load the part
    611         if(idle) {
    612             while(alloc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
    613                 idle(idle_ptr);
    614             }
    615         }
    616 
    617         Part *p = alloc.get();
    618 #else
    619         Part *p = new Part(*master->memory, synth, master->time,
    620                 config->cfg.GzipCompression,
    621                 config->cfg.Interpolation,
    622                 &master->microtonal, master->fft);
    623         p->partno  = npart % NUM_MIDI_CHANNELS;
    624         p->Prcvchn = npart % NUM_MIDI_CHANNELS;
    625 
    626         if(p->loadXMLinstrument(filename))
    627             fprintf(stderr, "Warning: failed to load part<%s>!\n", filename);
    628 
    629         auto isLateLoad = [this,npart]{
    630             return actual_load[npart] != pending_load[npart];
    631         };
    632 
    633         p->applyparameters(isLateLoad);
    634 #endif
    635 
    636         obj_store.extractPart(p, npart);
    637         kits.extractPart(p, npart);
    638 
    639         //Give it to the backend and wait for the old part to return for
    640         //deallocation
    641         parent->transmitMsg("/load-part", "ib", npart, sizeof(Part*), &p);
    642         d.broadcast("/damage", "s", ("/part"+to_s(npart)+"/").c_str());
    643     }
    644 
    645     //Load a new cleared Part instance
    646     void loadClearPart(int npart)
    647     {
    648         if(npart == -1)
    649             return;
    650 
    651         Part *p = new Part(*master->memory, synth,
    652                 master->time,
    653                 master->sync,
    654                 config->cfg.GzipCompression,
    655                 config->cfg.Interpolation,
    656                 &master->microtonal, master->fft);
    657         p->partno  = npart % NUM_MIDI_CHANNELS;
    658         p->Prcvchn = npart % NUM_MIDI_CHANNELS;
    659         p->applyparameters();
    660         obj_store.extractPart(p, npart);
    661         kits.extractPart(p, npart);
    662 
    663         //Give it to the backend and wait for the old part to return for
    664         //deallocation
    665         parent->transmitMsg("/load-part", "ib", npart, sizeof(Part *), &p);
    666         for(void* uihandle : ui)
    667             GUI::raiseUi(uihandle, "/damage", "s", ("/part" + to_s(npart) + "/").c_str());
    668     }
    669 
    670     //Well, you don't get much crazier than changing out all of your RT
    671     //structures at once...
    672     int loadMaster(const char *filename, bool osc_format = false)
    673     {
    674         Master *m = new Master(synth, config);
    675         m->uToB = uToB;
    676         m->bToU = bToU;
    677 
    678         if(filename) {
    679             if(osc_format)
    680             {
    681                 mw_dispatcher_t dispatcher(parent);
    682                 if( m->loadOSC(filename, &dispatcher) < 0 ) {
    683                     delete m;
    684                     return -1;
    685                 }
    686             }
    687             else
    688             {
    689                 if ( m->loadXML(filename) ) {
    690                     delete m;
    691                     return -1;
    692                 }
    693             }
    694             m->applyparameters();
    695         }
    696 
    697         //Update resource locator table
    698         updateResources(m);
    699 
    700         previous_master = master;
    701         master = m;
    702 
    703         //Give it to the backend and wait for the old part to return for
    704         //deallocation
    705         parent->transmitMsg("/load-master", "b", sizeof(Master*), &m);
    706         return 0;
    707     }
    708 
    709     // Save all possible parameters
    710     // In user language, this is called "saving a master", but we
    711     // are saving parameters owned by Master and by MiddleWare
    712     // Return 0 if OK, <0 if not
    713     int saveParams(const char *filename, std::string& savefile,
    714 		   bool osc_format = false)
    715     {
    716         int res;
    717         if(osc_format)
    718         {
    719             mw_dispatcher_t dispatcher(parent);
    720 
    721             // allocate an "empty" master
    722             // after the savefile will have been saved, it will be loaded into this
    723             // dummy master, and then the two masters will be compared
    724             zyn::Config config;
    725             config.cfg.SaveFullXml = master->SaveFullXml;
    726 
    727             zyn::SYNTH_T* synth2 = new zyn::SYNTH_T;
    728             synth2->buffersize = master->synth.buffersize;
    729             synth2->samplerate = master->synth.samplerate;
    730             synth2->alias();
    731 
    732             {
    733                 zyn::Master master2(*synth2, &config);
    734                 master->copyMasterCbTo(&master2);
    735                 master2.frozenState = true;
    736 
    737                 std::set<std::string> alreadyWritten;
    738                 rtosc_version m_version =
    739                 {
    740                     (unsigned char) version.get_major(),
    741                     (unsigned char) version.get_minor(),
    742                     (unsigned char) version.get_revision()
    743                 };
    744                 savefile = rtosc::save_to_file(getNonRtParamPorts(), this, "ZynAddSubFX", m_version, alreadyWritten, {});
    745                 savefile += '\n';
    746 
    747                 doReadOnlyOp([this,filename,&dispatcher,&master2,&savefile,&res,&alreadyWritten]()
    748                 {
    749                     savefile = master->saveOSC(savefile, alreadyWritten);
    750 #if 1
    751                     // load the savefile string into another master to compare the results
    752                     // between the original and the savefile-loaded master
    753                     // this requires a temporary master switch
    754                     Master* old_master = master;
    755                     dispatcher.updateMaster(&master2);
    756                     while(old_master->isMasterSwitchUpcoming()) { os_usleep(50000); }
    757 
    758                     res = master2.loadOSCFromStr(savefile.c_str(), &dispatcher);
    759                     // TODO: compare MiddleWare, too?
    760 
    761                     // The above call is done by this thread (i.e. the MiddleWare thread), but
    762                     // it sends messages to master2 in order to load the values
    763                     // We need to wait until savefile has been loaded into master2
    764                     int i;
    765                     for(i = 0; i < 20 && master2.uToB->hasNext(); ++i)
    766                         os_usleep(50000);
    767                     if(i >= 20) // >= 1 second?
    768                     {
    769                         // Master failed to fetch its messages
    770                         res = -1;
    771                     }
    772                     printf("Saved in less than %d ms.\n", 50*i);
    773 
    774                     dispatcher.updateMaster(old_master);
    775                     while(master2.isMasterSwitchUpcoming()) { os_usleep(50000); }
    776 #endif
    777                     if(res < 0)
    778                     {
    779                         std::cerr << "invalid savefile (or a backend error)!" << std::endl;
    780                         std::cerr << "complete savefile:" << std::endl;
    781                         std::cerr << savefile << std::endl;
    782                         std::cerr << "first entry that could not be parsed:" << std::endl;
    783 
    784                         for(int i = -res + 1; savefile[i]; ++i)
    785                         if(savefile[i] == '\n')
    786                         {
    787                             savefile.resize(i);
    788                             break;
    789                         }
    790                         std::cerr << (savefile.c_str() - res) << std::endl;
    791 
    792                         res = -1;
    793                     }
    794                     else
    795                     {
    796                         char* xml = master->getXMLData(),
    797                             * xml2 = master2.getXMLData();
    798                         // TODO: below here can be moved out of read only op
    799 
    800                         res = strcmp(xml, xml2) ? -1 : 0;
    801 
    802                         if(res == 0)
    803                         {
    804                             if(filename && *filename)
    805                             {
    806                                 std::ofstream ofs(filename);
    807                                 ofs << savefile;
    808                             }
    809                         }
    810                         else
    811                         {
    812                             std::cout << savefile << std::endl;
    813                             std::cerr << "Cannot write OSC savefile!! (see tmp1.txt and tmp2.txt)"
    814                                       << std::endl;
    815                             {
    816                                 std::ofstream tmp1("tmp1.txt"), tmp2("tmp2.txt");
    817                                 tmp1 << xml;
    818                                 tmp2 << xml2;
    819                             }
    820                             int sys_ret = system("diff tmp1.txt tmp2.txt");
    821                             if(sys_ret)
    822                                 puts("FAILED to compare tmp1.txt and tmp2.txt. Please compare manually.");
    823                             res = -1;
    824                         }
    825 
    826                         free(xml);
    827                         free(xml2);
    828                     }
    829                 });
    830             }
    831             delete synth2;
    832         }
    833         else // xml format
    834         {
    835             doReadOnlyOp([this,filename,&res](){
    836                              res = master->saveXML(filename);});
    837         }
    838         return res;
    839     }
    840 
    841     void loadXsz(const char *filename, rtosc::RtData &d)
    842     {
    843         Microtonal *micro = new Microtonal(master->gzip_compression);
    844         int err = micro->loadXML(filename);
    845         if(err) {
    846             d.reply("/alert", "s", "Error: Could not load the xsz file.");
    847             delete micro;
    848         } else
    849             d.chain("/microtonal/paste", "b", sizeof(void*), &micro);
    850     }
    851 
    852     void saveXsz(const char *filename, rtosc::RtData &d)
    853     {
    854         int err = 0;
    855         doReadOnlyOp([this,filename,&err](){
    856                 err = master->microtonal.saveXML(filename);});
    857         if(err)
    858             d.reply("/alert", "s", "Error: Could not save the xsz file.");
    859     }
    860 
    861     void loadScl(const char *filename, rtosc::RtData &d)
    862     {
    863         SclInfo *scl = new SclInfo;
    864         int err=Microtonal::loadscl(*scl, filename);
    865         if(err) {
    866             d.reply("/alert", "s", "Error: Could not load the scl file.");
    867             delete scl;
    868         } else
    869             d.chain("/microtonal/paste_scl", "b", sizeof(void*), &scl);
    870     }
    871 
    872     void loadKbm(const char *filename, rtosc::RtData &d)
    873     {
    874         KbmInfo *kbm = new KbmInfo;
    875         int err=Microtonal::loadkbm(*kbm, filename);
    876         if(err) {
    877             d.reply("/alert", "s", "Error: Could not load the kbm file.");
    878             delete kbm;
    879         } else
    880             d.chain("/microtonal/paste_kbm", "b", sizeof(void*), &kbm);
    881     }
    882 
    883     void updateResources(Master *m)
    884     {
    885         obj_store.clear();
    886         obj_store.extractMaster(m);
    887         for(int i=0; i<NUM_MIDI_PARTS; ++i)
    888             kits.extractPart(m->part[i], i);
    889     }
    890 
    891     //If currently broadcasting messages
    892     bool broadcast = false;
    893     //If message should be forwarded through snoop ports
    894     bool forward   = false;
    895     //if message is in order or out-of-order execution
    896     bool in_order  = false;
    897     //If accepting undo events as user driven
    898     bool recording_undo = true;
    899     void bToUhandle(const char *rtmsg);
    900 
    901     void tick(void)
    902     {
    903         if(server)
    904             while(lo_server_recv_noblock(server, 0));
    905 
    906         while(bToU->hasNext()) {
    907             const char *rtmsg = bToU->read();
    908             bToUhandle(rtmsg);
    909         }
    910 
    911         while(auto *m = multi_thread_source.read()) {
    912             handleMsg(m->memory);
    913             multi_thread_source.free(m);
    914         }
    915 
    916         autoSave.tick();
    917 
    918         heartBeat(master);
    919 
    920         if(offline)
    921         {
    922             //pass previous master in case it will have to be freed
    923             //similar to previous_master->runOSC(0,0,true)
    924             //but note that previous_master could have been freed already
    925             master->runOSC(0,0,true, previous_master);
    926         }
    927     }
    928 
    929 
    930     void kitEnable(const char *msg);
    931     void kitEnable(int part, int kit, int type);
    932 
    933     // Handle an event with special cases
    934     void handleMsg(const char *msg, bool msg_comes_from_realtime = false);
    935 
    936     // Add a message for handleMsg to a queue
    937     void queueMsg(const char* msg)
    938     {
    939         msgsToHandle.emplace(msg, msg+rtosc_message_length(msg, -1));
    940     }
    941 
    942     void write(const char *path, const char *args, ...);
    943     void write(const char *path, const char *args, va_list va);
    944 
    945     void currentUrl(string addr)
    946     {
    947         curr_url = addr;
    948         known_remotes.insert(addr);
    949     }
    950 
    951     // Send a message to a remote client
    952     void sendToRemote(const char *msg, std::string dest);
    953     // Send a message to the current remote client
    954     void sendToCurrentRemote(const char *msg)
    955     {
    956         sendToRemote(msg, in_order ? curr_url : last_url);
    957     }
    958     // Broadcast a message to all listening remote clients
    959     void broadcastToRemote(const char *msg);
    960 
    961 
    962     /*
    963      * Provides a mapping for non-RT objects stored inside the backend
    964      * - Oscilgen almost all parameters can be safely set
    965      * - Padnote can have anything set on its oscilgen and a very small set
    966      *   of general parameters
    967      */
    968     NonRtObjStore obj_store;
    969 
    970     //This code will own the pointer to master, be prepared for odd things if
    971     //this assumption is broken
    972     Master *master;
    973 
    974     //The master before the last load operation, if any
    975     //Only valid until freed
    976     Master *previous_master = nullptr;
    977 
    978     //Synth Engine Parameters
    979     ParamStore kits;
    980 
    981     //Callback When Waiting on async events
    982     void(*idle)(void*);
    983     void* idle_ptr;
    984 
    985     //General UI callbacks
    986     cb_t cb[2];
    987     //UI handles
    988     void *ui[2];
    989 
    990     //The ONLY means that any chunk of UI code should have for interacting with the
    991     //backend
    992     //Note: Only the first UI is defined to have such an interface
    993     Fl_Osc_Interface *osc;
    994 
    995     std::atomic_int pending_load[NUM_MIDI_PARTS];
    996     std::atomic_int actual_load[NUM_MIDI_PARTS];
    997 
    998     //Undo/Redo
    999     rtosc::UndoHistory undo;
   1000 
   1001     //MIDI Learn
   1002     rtosc::MidiMappernRT midi_mapper;
   1003 
   1004     //Link To the Realtime
   1005     rtosc::ThreadLink *bToU;
   1006     rtosc::ThreadLink *uToB;
   1007 
   1008     //Link to the unknown
   1009     MultiQueue multi_thread_source;
   1010 
   1011     //LIBLO
   1012     lo_server server;
   1013     string last_url, curr_url;
   1014     std::set<string> known_remotes;
   1015 
   1016     //Synthesis Rate Parameters
   1017     SYNTH_T synth;
   1018 
   1019     PresetsStore presetsstore;
   1020 
   1021     CallbackRepeater autoSave;
   1022 };
   1023 
   1024 /*****************************************************************************
   1025  *                      Data Object for Non-RT Class Dispatch                *
   1026  *****************************************************************************/
   1027 
   1028 class MwDataObj:public rtosc::RtData
   1029 {
   1030     public:
   1031         MwDataObj(MiddleWareImpl *mwi_)
   1032         {
   1033             loc_size = 1024;
   1034             loc = new char[loc_size];
   1035             memset(loc, 0, loc_size);
   1036             buffer = new char[4*4096];
   1037             memset(buffer, 0, 4*4096);
   1038             obj       = mwi_;
   1039             mwi       = mwi_;
   1040             forwarded = false;
   1041         }
   1042 
   1043         ~MwDataObj(void)
   1044         {
   1045             delete[] loc;
   1046             delete[] buffer;
   1047         }
   1048 
   1049         //Replies and broadcasts go to the remote
   1050 
   1051         //Chain calls repeat the call into handle()
   1052 
   1053         //Forward calls send the message directly to the realtime
   1054         virtual void reply(const char *path, const char *args, ...) override
   1055         {
   1056             //printf("reply building '%s'\n", path);
   1057             va_list va;
   1058             va_start(va,args);
   1059             if(!strcmp(path, "/forward")) { //forward the information to the backend
   1060                 args++;
   1061                 path = va_arg(va, const char *);
   1062                 rtosc_vmessage(buffer,4*4096,path,args,va);
   1063             } else {
   1064                 rtosc_vmessage(buffer,4*4096,path,args,va);
   1065                 reply(buffer);
   1066             }
   1067             va_end(va);
   1068         }
   1069         virtual void replyArray(const char *path, const char *args, rtosc_arg_t *argd) override
   1070         {
   1071             //printf("reply building '%s'\n", path);
   1072             if(!strcmp(path, "/forward")) { //forward the information to the backend
   1073                 args++;
   1074                 rtosc_amessage(buffer,4*4096,path,args,argd);
   1075             } else {
   1076                 rtosc_amessage(buffer,4*4096,path,args,argd);
   1077                 reply(buffer);
   1078             }
   1079         }
   1080         //! In the case of MiddleWare, "reply" always means sending back to
   1081         //! the front-end. If a message from the back-end gets "replied", this
   1082         //! only means that its counterpart has been sent from the front-end via
   1083         //! MiddleWare to the backend, so the reply has to go back to the
   1084         //! front-end. The back-end itself usually doesn't ask things, so it
   1085         //! will not get replies.
   1086         virtual void reply(const char *msg) override{
   1087             mwi->sendToCurrentRemote(msg);
   1088         }
   1089 
   1090         virtual void broadcast(const char *msg) override {
   1091             mwi->broadcastToRemote(msg);
   1092         }
   1093 
   1094         virtual void chain(const char *msg) override
   1095         {
   1096             assert(msg);
   1097             // printf("chain call on <%s>\n", msg);
   1098             mwi->queueMsg(msg);
   1099         }
   1100 
   1101         virtual void chain(const char *path, const char *args, ...) override
   1102         {
   1103             assert(path);
   1104             va_list va;
   1105             va_start(va,args);
   1106             rtosc_vmessage(buffer,4*4096,path,args,va);
   1107             chain(buffer);
   1108             va_end(va);
   1109         }
   1110 
   1111         virtual void forward(const char *) override
   1112         {
   1113             forwarded = true;
   1114         }
   1115 
   1116         bool forwarded;
   1117     private:
   1118         char *buffer;
   1119         MiddleWareImpl   *mwi;
   1120 };
   1121 
   1122 static std::vector<std::string> getFiles(const char *folder, bool finddir)
   1123 {
   1124     DIR *dir = opendir(folder);
   1125 
   1126     if(dir == NULL) {
   1127         return {};
   1128     }
   1129 
   1130     struct dirent *fn;
   1131     std::vector<string> files;
   1132     bool has_updir = false;
   1133 
   1134     while((fn = readdir(dir))) {
   1135 #ifndef WIN32
   1136         bool is_dir = fn->d_type == DT_DIR;
   1137         //it could still be a symbolic link
   1138         if(!is_dir) {
   1139             string path = string(folder) + "/" + fn->d_name;
   1140             struct stat buf;
   1141             memset((void*)&buf, 0, sizeof(buf));
   1142             int err = stat(path.c_str(), &buf);
   1143             if(err)
   1144                 printf("[Zyn:Error] stat cannot handle <%s>:%d\n", path.c_str(), err);
   1145             if(S_ISDIR(buf.st_mode)) {
   1146                 is_dir = true;
   1147             }
   1148         }
   1149 #else
   1150         std::string darn_windows = folder + std::string("/") + std::string(fn->d_name);
   1151         //printf("attr on <%s> => %x\n", darn_windows.c_str(), GetFileAttributes(darn_windows.c_str()));
   1152         //printf("desired mask =  %x\n", mask);
   1153         //printf("error = %x\n", INVALID_FILE_ATTRIBUTES);
   1154         bool is_dir = GetFileAttributes(darn_windows.c_str()) & FILE_ATTRIBUTE_DIRECTORY;
   1155 #endif
   1156         if(finddir == is_dir && strcmp(".", fn->d_name))
   1157             files.push_back(fn->d_name);
   1158 
   1159         if(!strcmp("..", fn->d_name))
   1160             has_updir = true;
   1161     }
   1162 
   1163     if(finddir == true && has_updir == false)
   1164         files.push_back("..");
   1165 
   1166     closedir(dir);
   1167     std::sort(begin(files), end(files));
   1168     return files;
   1169 }
   1170 
   1171 
   1172 
   1173 static int extractInt(const char *msg)
   1174 {
   1175     const char *mm = msg;
   1176     while(*mm && !isdigit(*mm)) ++mm;
   1177     if(isdigit(*mm))
   1178         return atoi(mm);
   1179     return -1;
   1180 }
   1181 
   1182 static const char *chomp(const char *msg)
   1183 {
   1184     while(*msg && *msg!='/') ++msg;
   1185     msg = *msg ? msg+1 : msg;
   1186     return msg;
   1187 };
   1188 
   1189 using rtosc::RtData;
   1190 #define rObject Bank
   1191 #define rBegin [](const char *msg, RtData &d) { (void)msg;(void)d;\
   1192     rObject &impl = *((rObject*)d.obj);(void)impl;
   1193 #define rEnd }
   1194 /*****************************************************************************
   1195  *                       Instrument Banks                                    *
   1196  *                                                                           *
   1197  * Banks and presets in general are not classed as realtime safe             *
   1198  *                                                                           *
   1199  * The supported operations are:                                             *
   1200  * - Load Names                                                              *
   1201  * - Load Bank                                                               *
   1202  * - Refresh List of Banks                                                   *
   1203  *****************************************************************************/
   1204 extern const rtosc::Ports bankPorts;
   1205 const rtosc::Ports bankPorts = {
   1206     {"rescan:", 0, 0,
   1207         rBegin;
   1208         impl.bankpos = 0;
   1209         impl.rescanforbanks();
   1210         //Send updated banks
   1211         int i = 0;
   1212         for(auto &elm : impl.banks)
   1213             d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str());
   1214         d.reply("/bank/bank_select", "i", impl.bankpos);
   1215         if (i > 0) {
   1216             impl.loadbank(impl.banks[0].dir);
   1217 
   1218             //Reload bank slots
   1219             for(int i=0; i<BANK_SIZE; ++i) {
   1220                 d.reply("/bankview", "iss",
   1221                     i, impl.ins[i].name.c_str(),
   1222                     impl.ins[i].filename.c_str());
   1223             }
   1224         } else {
   1225             //Clear all bank slots
   1226             for(int i=0; i<BANK_SIZE; ++i) {
   1227                 d.reply("/bankview", "iss", i, "", "");
   1228             }
   1229         }
   1230         d.broadcast("/damage", "s", "/bank/");
   1231         rEnd},
   1232     {"bank_list:", 0, 0,
   1233         rBegin;
   1234 #define MAX_BANKS 256
   1235         char        types[MAX_BANKS*2+1]={0};
   1236         rtosc_arg_t args[MAX_BANKS*2];
   1237         int i = 0;
   1238         for(auto &elm : impl.banks) {
   1239             types[i] = types [i + 1] = 's';
   1240             args[i++].s = elm.name.c_str();
   1241             args[i++].s = elm.dir.c_str();
   1242         }
   1243         d.replyArray("/bank/bank_list", types, args);
   1244 #undef MAX_BANKS
   1245         rEnd},
   1246     {"types:", 0, 0,
   1247         rBegin;
   1248         const char *types[17];
   1249         types[ 0] = "None";
   1250         types[ 1] = "Piano";
   1251         types[ 2] = "Chromatic Percussion";
   1252         types[ 3] = "Organ";
   1253         types[ 4] = "Guitar";
   1254         types[ 5] = "Bass";
   1255         types[ 6] = "Solo Strings";
   1256         types[ 7] = "Ensemble";
   1257         types[ 8] = "Brass";
   1258         types[ 9] = "Reed";
   1259         types[10] = "Pipe";
   1260         types[11] = "Synth Lead";
   1261         types[12] = "Synth Pad";
   1262         types[13] = "Synth Effects";
   1263         types[14] = "Ethnic";
   1264         types[15] = "Percussive";
   1265         types[16] = "Sound Effects";
   1266         char        t[17+1]={0};
   1267         rtosc_arg_t args[17];
   1268         for(int i=0; i<17; ++i) {
   1269             t[i]      = 's';
   1270             args[i].s = types[i];
   1271         }
   1272         d.replyArray("/bank/types", t, args);
   1273         rEnd},
   1274     {"tags:", 0, 0,
   1275         rBegin;
   1276         const char *types[8];
   1277         types[ 0] = "fast";
   1278         types[ 1] = "slow";
   1279         types[ 2] = "saw";
   1280         types[ 3] = "bell";
   1281         types[ 4] = "lead";
   1282         types[ 5] = "ambient";
   1283         types[ 6] = "horn";
   1284         types[ 7] = "alarm";
   1285         char        t[8+1]={0};
   1286         rtosc_arg_t args[8];
   1287         for(int i=0; i<8; ++i) {
   1288             t[i]      = 's';
   1289             args[i].s = types[i];
   1290         }
   1291         d.replyArray(d.loc, t, args);
   1292         rEnd},
   1293     {"slot#1024:", 0, 0,
   1294         rBegin;
   1295         const int loc = extractInt(msg);
   1296         if(loc >= BANK_SIZE)
   1297             return;
   1298 
   1299         d.reply("/bankview", "iss",
   1300                 loc, impl.ins[loc].name.c_str(),
   1301                 impl.ins[loc].filename.c_str());
   1302         rEnd},
   1303     {"banks:", 0, 0,
   1304         rBegin;
   1305         int i = 0;
   1306         for(auto &elm : impl.banks)
   1307             d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str());
   1308         rEnd},
   1309     {"bank_select::i", 0, 0,
   1310         rBegin
   1311            if(rtosc_narguments(msg)) {
   1312                const int pos = rtosc_argument(msg, 0).i;
   1313                d.reply(d.loc, "i", pos);
   1314                if(impl.bankpos != pos) {
   1315                    impl.bankpos = pos;
   1316                    impl.loadbank(impl.banks[pos].dir);
   1317 
   1318                    //Reload bank slots
   1319                    for(int i=0; i<BANK_SIZE; ++i)
   1320                        d.reply("/bankview", "iss",
   1321                                    i, impl.ins[i].name.c_str(),
   1322                                    impl.ins[i].filename.c_str());
   1323                }
   1324             } else
   1325                 d.reply("/bank/bank_select", "i", impl.bankpos);
   1326         rEnd},
   1327     {"rename_slot:is", 0, 0,
   1328         rBegin;
   1329         const int   slot = rtosc_argument(msg, 0).i;
   1330         const char *name = rtosc_argument(msg, 1).s;
   1331         const int err = impl.setname(slot, name, -1);
   1332         if(err) {
   1333             d.reply("/alert", "s",
   1334                     "Failed To Rename Bank Slot, please check file permissions");
   1335         }
   1336         rEnd},
   1337     {"swap_slots:ii", 0, 0,
   1338         rBegin;
   1339         const int slota = rtosc_argument(msg, 0).i;
   1340         const int slotb = rtosc_argument(msg, 1).i;
   1341         const int err = impl.swapslot(slota, slotb);
   1342         if(err)
   1343             d.reply("/alert", "s",
   1344                     "Failed To Swap Bank Slots, please check file permissions");
   1345         rEnd},
   1346     {"clear_slot:i", 0, 0,
   1347         rBegin;
   1348         const int slot = rtosc_argument(msg, 0).i;
   1349         const int err  = impl.clearslot(slot);
   1350         if(err)
   1351             d.reply("/alert", "s",
   1352                     "Failed To Clear Bank Slot, please check file permissions");
   1353         rEnd},
   1354     {"msb::i", 0, 0,
   1355         rBegin;
   1356         if(rtosc_narguments(msg))
   1357             impl.setMsb(rtosc_argument(msg, 0).i);
   1358         else
   1359             d.reply(d.loc, "i", impl.bank_msb);
   1360         rEnd},
   1361     {"lsb::i", 0, 0,
   1362         rBegin;
   1363         if(rtosc_narguments(msg))
   1364             impl.setLsb(rtosc_argument(msg, 0).i);
   1365         else
   1366             d.reply(d.loc, "i", impl.bank_lsb);
   1367         rEnd},
   1368     {"newbank:s", 0, 0,
   1369         rBegin;
   1370         int err = impl.newbank(rtosc_argument(msg, 0).s);
   1371         if(err)
   1372             d.reply("/alert", "s", "Error: Could not make a new bank (directory)..");
   1373         rEnd},
   1374     {"search:s", 0, 0,
   1375         rBegin;
   1376         auto res = impl.search(rtosc_argument(msg, 0).s);
   1377 #define MAX_SEARCH 300
   1378         char res_type[MAX_SEARCH+1] = {};
   1379         rtosc_arg_t res_dat[MAX_SEARCH] = {};
   1380         res_type[0] = 'N'; // Will be overwritten if there is any actual data
   1381         for(unsigned i=0; i<res.size() && i<MAX_SEARCH; ++i) {
   1382             res_type[i]  = 's';
   1383             res_dat[i].s = res[i].c_str();
   1384         }
   1385         d.replyArray("/bank/search_results", res_type, res_dat);
   1386 #undef MAX_SEARCH
   1387         rEnd},
   1388     {"blist:s", 0, 0,
   1389         rBegin;
   1390         auto res = impl.blist(rtosc_argument(msg, 0).s);
   1391 #define MAX_SEARCH 300
   1392         char res_type[MAX_SEARCH+1] = {};
   1393         rtosc_arg_t res_dat[MAX_SEARCH] = {};
   1394         res_type[0] = 'N'; // Will be overwritten if there is any actual data
   1395         for(unsigned i=0; i<res.size() && i<MAX_SEARCH; ++i) {
   1396             res_type[i]  = 's';
   1397             res_dat[i].s = res[i].c_str();
   1398         }
   1399         d.replyArray("/bank/search_results", res_type, res_dat);
   1400 #undef MAX_SEARCH
   1401         rEnd},
   1402     {"search_results:", 0, 0,
   1403         rBegin;
   1404         d.reply("/bank/search_results", "N");
   1405         rEnd},
   1406 };
   1407 
   1408 /******************************************************************************
   1409  *                       MiddleWare Snooping Ports                            *
   1410  *                                                                            *
   1411  * These ports handle:                                                        *
   1412  * - Events going to the realtime thread which cannot be safely handled       *
   1413  *   there                                                                    *
   1414  * - Events generated by the realtime thread which are not destined for a     *
   1415  *   user interface                                                           *
   1416  ******************************************************************************/
   1417 
   1418 #undef  rObject
   1419 #define rObject MiddleWareImpl
   1420 
   1421 #ifndef STRINGIFY
   1422 #define STRINGIFY2(a) #a
   1423 #define STRINGIFY(a) STRINGIFY2(a)
   1424 #endif
   1425 
   1426 /*
   1427  * common snoop port callbacks
   1428  */
   1429 template<bool osc_format>
   1430 void load_cb(const char *msg, RtData &d)
   1431 {
   1432     MiddleWareImpl &impl = *((MiddleWareImpl*)d.obj);
   1433     const char *file = rtosc_argument(msg, 0).s;
   1434     uint64_t request_time = 0;
   1435     if(rtosc_narguments(msg) > 1)
   1436         request_time = rtosc_argument(msg, 1).t;
   1437 
   1438     if(!impl.loadMaster(file, osc_format)) { // return-value 0 <=> OK
   1439         d.broadcast("/damage", "s", "/");
   1440         d.broadcast(d.loc, "stT", file, request_time);
   1441     }
   1442     else
   1443         d.broadcast(d.loc, "stF", file, request_time);
   1444 }
   1445 
   1446 template<bool osc_format>
   1447 void save_cb(const char *msg, RtData &d)
   1448 {
   1449     MiddleWareImpl &impl = *((MiddleWareImpl*)d.obj);
   1450     // Due to a possible bug in ThreadLink, filename may get trashed when
   1451     // the read-only operation writes to the buffer again. Copy to string:
   1452     const string file = rtosc_argument(msg, 0).s;
   1453     uint64_t request_time = 0;
   1454     bool saveToString = false;
   1455     if(rtosc_narguments(msg) > 1)
   1456         request_time = rtosc_argument(msg, 1).t;
   1457     if(rtosc_narguments(msg) > 2)
   1458         saveToString = rtosc_argument(msg, 2).T;
   1459 
   1460     std::string savefile;
   1461     int res = impl.saveParams(file.c_str(), savefile, osc_format);
   1462     d.broadcast(d.loc, (res == 0) ? "stT" : "stF",
   1463                 file.c_str(), request_time);
   1464 
   1465     if(saveToString)
   1466     {
   1467         std::size_t max_each = 768;
   1468         std::size_t msgcount = 0;
   1469         std::size_t msgmax = (savefile.length()-1) / max_each;
   1470         for(std::size_t pos = 0; pos < savefile.length(); pos += max_each)
   1471         {
   1472             std::size_t len = std::min(max_each, savefile.length() - pos);
   1473             d.reply(d.loc, "stiis",
   1474                     file.c_str(), request_time, msgcount++, msgmax,
   1475                     savefile.substr(pos, len).c_str());
   1476         }
   1477     }
   1478 }
   1479 
   1480 #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
   1481 #pragma GCC push_options
   1482 #pragma GCC optimize("O0")
   1483 #endif
   1484 
   1485 void gcc_10_1_0_is_dumb(const std::vector<std::string> &files,
   1486         const int N,
   1487         char *types,
   1488         rtosc_arg_t *args)
   1489 {
   1490         types[N] = 0;
   1491         for(int i=0; i<N; ++i) {
   1492             args[i].s = files[i].c_str();
   1493             types[i]  = 's';
   1494         }
   1495 }
   1496 
   1497 #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
   1498 #pragma GCC pop_options
   1499 #endif
   1500 
   1501 /*
   1502  * BASE/part#/kititem#
   1503  * BASE/part#/kit#/adpars/voice#/oscil/\*
   1504  * BASE/part#/kit#/adpars/voice#/mod-oscil/\*
   1505  * BASE/part#/kit#/padpars/prepare
   1506  * BASE/part#/kit#/padpars/oscil/\*
   1507  */
   1508 static rtosc::Ports nonRtParamPorts = {
   1509     {"part#" STRINGIFY(NUM_MIDI_PARTS)
   1510         "/kit#" STRINGIFY(NUM_KIT_ITEMS) "/adpars/VoicePar#"
   1511             STRINGIFY(NUM_VOICES) "/OscilSmp/", 0, &OscilGen::non_realtime_ports,
   1512         rBegin;
   1513         impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d);
   1514         rEnd},
   1515     {"part#" STRINGIFY(NUM_MIDI_PARTS)
   1516         "/kit#" STRINGIFY(NUM_KIT_ITEMS)
   1517             "/adpars/VoicePar#" STRINGIFY(NUM_VOICES) "/FMSmp/", 0, &OscilGen::non_realtime_ports,
   1518         rBegin
   1519         impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d);
   1520         rEnd},
   1521     {"part#" STRINGIFY(NUM_MIDI_PARTS)
   1522         "/kit#" STRINGIFY(NUM_KIT_ITEMS) "/padpars/", 0, &PADnoteParameters::non_realtime_ports,
   1523         rBegin
   1524         impl.obj_store.handlePad(chomp(chomp(chomp(msg))), d);
   1525         rEnd},
   1526 };
   1527 
   1528 static rtosc::Ports middwareSnoopPortsWithoutNonRtParams = {
   1529     {"bank/", 0, &bankPorts,
   1530         rBegin;
   1531         d.obj = &impl.master->bank;
   1532         bankPorts.dispatch(chomp(msg),d);
   1533         rEnd},
   1534     {"bank/save_to_slot:ii", 0, 0,
   1535         rBegin;
   1536         const int part_id = rtosc_argument(msg, 0).i;
   1537         const int slot    = rtosc_argument(msg, 1).i;
   1538 
   1539         int err = 0;
   1540         impl.doReadOnlyOp([&impl,slot,part_id,&err](){
   1541                 err = impl.master->bank.savetoslot(slot, impl.master->part[part_id]);});
   1542         if(err) {
   1543             d.reply("/alert", "s",
   1544                     "Failed To Save To Bank Slot, please check file permissions");
   1545         }
   1546         else d.broadcast("/damage", "s", "/bank/search_results/");
   1547         rEnd},
   1548     {"config/", 0, &Config::ports,
   1549         rBegin;
   1550         d.obj = impl.config;
   1551         Config::ports.dispatch(chomp(msg), d);
   1552         rEnd},
   1553     {"presets/", 0,  &real_preset_ports,          [](const char *msg, RtData &d) {
   1554         MiddleWareImpl *obj = (MiddleWareImpl*)d.obj;
   1555         d.obj = (void*)obj->parent;
   1556         real_preset_ports.dispatch(chomp(msg), d);
   1557         if(strstr(msg, "paste") && rtosc_argument_string(msg)[0] == 's')
   1558             d.broadcast("/damage", "s", rtosc_argument(msg, 0).s);
   1559         }},
   1560     {"io/", 0, &Nio::ports,               [](const char *msg, RtData &d) {
   1561         Nio::ports.dispatch(chomp(msg), d);}},
   1562     {"part*/kit*/{Padenabled,Ppadenabled,Psubenabled}:T:F", 0, 0,
   1563         rBegin;
   1564         impl.kitEnable(msg);
   1565         d.forward();
   1566         rEnd},
   1567     {"save_xcz:s", 0, 0,
   1568         rBegin;
   1569         const char *file = rtosc_argument(msg, 0).s;
   1570         XMLwrapper xml;
   1571         saveMidiLearn(xml, impl.midi_mapper);
   1572         xml.saveXMLfile(file, impl.master->gzip_compression);
   1573         rEnd},
   1574     {"load_xcz:s", 0, 0,
   1575         rBegin;
   1576         const char *file = rtosc_argument(msg, 0).s;
   1577         XMLwrapper xml;
   1578         xml.loadXMLfile(file);
   1579         loadMidiLearn(xml, impl.midi_mapper);
   1580         rEnd},
   1581     {"clear_xcz:", 0, 0,
   1582         rBegin;
   1583         impl.midi_mapper.clear();
   1584         rEnd},
   1585     {"midi-map-cc:is", rDoc("bind a midi CC on CH to an OSC path"), 0,
   1586         rBegin;
   1587         const int par = rtosc_argument(msg, 0).i;
   1588         const string path = rtosc_argument(msg, 1).s;
   1589         connectMidiLearn(par, 1, false, path, impl.midi_mapper);
   1590         rEnd},
   1591     {"midi-map-cc:iis", rDoc("bind a midi CC on CH to an OSC path"), 0,
   1592         rBegin;
   1593         const int par = rtosc_argument(msg, 0).i;
   1594         const int ch = rtosc_argument(msg, 1).i;
   1595         const string path = rtosc_argument(msg, 2).s;
   1596         connectMidiLearn(par, ch, false, path, impl.midi_mapper);
   1597         rEnd},
   1598     {"midi-map-nrpn:iis", rDoc("bind nrpn on channel to an OSC path"), 0,
   1599         rBegin;
   1600         const int par = rtosc_argument(msg, 0).i;
   1601         const int ch = rtosc_argument(msg, 1).i;
   1602         const string path = rtosc_argument(msg, 2).s;
   1603         connectMidiLearn(par, ch, true, path, impl.midi_mapper);
   1604         rEnd},
   1605     {"save_xlz:s", 0, 0,
   1606         rBegin;
   1607         impl.doReadOnlyOp([&]() {
   1608                 const char *file = rtosc_argument(msg, 0).s;
   1609                 XMLwrapper xml;
   1610                 Master::saveAutomation(xml, impl.master->automate);
   1611                 xml.saveXMLfile(file, impl.master->gzip_compression);
   1612                 });
   1613         rEnd},
   1614     {"load_xlz:s", 0, 0,
   1615         rBegin;
   1616         const char *file = rtosc_argument(msg, 0).s;
   1617         XMLwrapper xml;
   1618         xml.loadXMLfile(file);
   1619         rtosc::AutomationMgr *mgr = new rtosc::AutomationMgr(16,4,8);
   1620         mgr->set_ports(Master::ports);
   1621         Master::loadAutomation(xml, *mgr);
   1622         d.chain("/automate/load-blob", "b", sizeof(void*), &mgr);
   1623         rEnd},
   1624     {"clear_xlz:", 0, 0,
   1625         rBegin;
   1626         d.chain("/automate/clear", "");
   1627         rEnd},
   1628     //scale file stuff
   1629     {"load_xsz:s", 0, 0,
   1630         rBegin;
   1631         const char *file = rtosc_argument(msg, 0).s;
   1632         impl.loadXsz(file, d);
   1633         rEnd},
   1634     {"save_xsz:s", 0, 0,
   1635         rBegin;
   1636         const char *file = rtosc_argument(msg, 0).s;
   1637         impl.saveXsz(file, d);
   1638         rEnd},
   1639     {"load_scl:s", rDoc("Load a scale from a file"), 0,
   1640         rBegin;
   1641         const char *file = rtosc_argument(msg, 0).s;
   1642         impl.loadScl(file, d);
   1643         rEnd},
   1644     {"load_kbm:s", 0, 0,
   1645         rBegin;
   1646         const char *file = rtosc_argument(msg, 0).s;
   1647         impl.loadKbm(file, d);
   1648         rEnd},
   1649     {"save_xmz:s:st:stT:stF", 0, 0, save_cb<false>},
   1650     {"save_osc:s:st:stT:stF", 0, 0, save_cb<true>},
   1651     {"save_xiz:is", 0, 0,
   1652         rBegin;
   1653         const int   part_id = rtosc_argument(msg,0).i;
   1654         const char *file    = rtosc_argument(msg,1).s;
   1655         impl.savePart(part_id, file);
   1656         rEnd},
   1657     {"file_home_dir:", 0, 0,
   1658         rBegin;
   1659         const char *home = getenv("PWD");
   1660         if(!home)
   1661             home = getenv("HOME");
   1662         if(!home)
   1663             home = getenv("USERPROFILE");
   1664         if(!home)
   1665             home = getenv("HOMEPATH");
   1666         if(!home)
   1667             home = "/";
   1668 
   1669         string home_ = home;
   1670 #ifndef WIN32
   1671         if(home_[home_.length()-1] != '/')
   1672             home_ += '/';
   1673 #endif
   1674         d.reply(d.loc, "s", home_.c_str());
   1675         rEnd},
   1676     {"file_list_files:s", 0, 0,
   1677         rBegin;
   1678         const char *folder = rtosc_argument(msg, 0).s;
   1679 
   1680         auto files = getFiles(folder, false);
   1681 
   1682         const int N = files.size();
   1683         rtosc_arg_t *args  = new rtosc_arg_t[N];
   1684         char        *types = new char[N+1];
   1685         gcc_10_1_0_is_dumb(files, N, types, args);
   1686 
   1687         d.replyArray(d.loc, types, args);
   1688         delete [] types;
   1689         delete [] args;
   1690         rEnd},
   1691     {"file_list_dirs:s", 0, 0,
   1692         rBegin;
   1693         const char *folder = rtosc_argument(msg, 0).s;
   1694 
   1695         auto files = getFiles(folder, true);
   1696 
   1697         const int N = files.size();
   1698         rtosc_arg_t *args  = new rtosc_arg_t[N];
   1699         char        *types = new char[N+1];
   1700         gcc_10_1_0_is_dumb(files, N, types, args);
   1701 
   1702         d.replyArray(d.loc, types, args);
   1703         delete [] types;
   1704         delete [] args;
   1705         rEnd},
   1706     {"reload_auto_save:i", 0, 0,
   1707         rBegin
   1708         const int save_id      = rtosc_argument(msg,0).i;
   1709         const string save_dir  = string(getenv("HOME")) + "/.local";
   1710         const string save_file = "zynaddsubfx-"+to_s(save_id)+"-autosave.xmz";
   1711         const string save_loc  = save_dir + "/" + save_file;
   1712         impl.loadMaster(save_loc.c_str());
   1713         //XXX it would be better to remove the autosave after there is a new
   1714         //autosave, but this method should work for non-immediate crashes :-|
   1715         remove(save_loc.c_str());
   1716         rEnd},
   1717     {"delete_auto_save:i", 0, 0,
   1718         rBegin
   1719         const int save_id      = rtosc_argument(msg,0).i;
   1720         const string save_dir  = string(getenv("HOME")) + "/.local";
   1721         const string save_file = "zynaddsubfx-"+to_s(save_id)+"-autosave.xmz";
   1722         const string save_loc  = save_dir + "/" + save_file;
   1723         remove(save_loc.c_str());
   1724         rEnd},
   1725     {"load_xmz:s:st", 0, 0, load_cb<false>},
   1726     {"load_osc:s:st", 0, 0, load_cb<true>},
   1727     {"reset_master:", 0, 0,
   1728         rBegin;
   1729         impl.loadMaster(NULL);
   1730         d.broadcast("/damage", "s", "/");
   1731         rEnd},
   1732     {"load_xiz:is:ist", 0, 0,
   1733         rBegin;
   1734         const int part_id = rtosc_argument(msg,0).i;
   1735         const char *file  = rtosc_argument(msg,1).s;
   1736         uint64_t request_time = 0;
   1737         if(rtosc_narguments(msg) > 2)
   1738             request_time = rtosc_argument(msg, 2).t;
   1739         impl.pending_load[part_id]++;
   1740         impl.loadPart(part_id, file, impl.master, d);
   1741         d.broadcast(d.loc, "stT", file, request_time);
   1742         rEnd},
   1743     {"load-part:is", 0, 0,
   1744         rBegin;
   1745         const int part_id = rtosc_argument(msg,0).i;
   1746         const char *file  = rtosc_argument(msg,1).s;
   1747         impl.pending_load[part_id]++;
   1748         impl.loadPart(part_id, file, impl.master, d);
   1749         rEnd},
   1750     {"load-part:iss", 0, 0,
   1751         rBegin;
   1752         const int part_id = rtosc_argument(msg,0).i;
   1753         const char *file  = rtosc_argument(msg,1).s;
   1754         const char *name  = rtosc_argument(msg,2).s;
   1755         impl.pending_load[part_id]++;
   1756         impl.loadPart(part_id, file, impl.master, d);
   1757         impl.uToB->write(("/part"+to_s(part_id)+"/Pname").c_str(), "s",
   1758                 name);
   1759         rEnd},
   1760     {"setprogram:i:c", 0, 0,
   1761         rBegin;
   1762         Bank &bank     = impl.master->bank;
   1763         const int slot = rtosc_argument(msg, 0).i + 128*bank.bank_lsb;
   1764         if(slot < BANK_SIZE) {
   1765             impl.pending_load[0]++;
   1766             impl.loadPart(0, impl.master->bank.ins[slot].filename.c_str(), impl.master, d);
   1767             impl.uToB->write("/part0/Pname", "s", impl.master->bank.ins[slot].name.c_str());
   1768         }
   1769         rEnd},
   1770     {"part#16/clear:", 0, 0,
   1771         rBegin;
   1772         int id = extractInt(msg);
   1773         impl.loadClearPart(id);
   1774         d.broadcast("/damage", "s", ("/part"+to_s(id)).c_str());
   1775         rEnd},
   1776     {"undo:", 0, 0,
   1777         rBegin;
   1778         impl.undo.seekHistory(-1);
   1779         rEnd},
   1780     {"redo:", 0, 0,
   1781         rBegin;
   1782         impl.undo.seekHistory(+1);
   1783         rEnd},
   1784     //port to observe the midi mappings
   1785     {"mlearn-values:", 0, 0,
   1786         rBegin;
   1787         auto &midi  = impl.midi_mapper;
   1788         auto  key   = keys(midi.inv_map);
   1789         //cc-id, path, min, max
   1790 #define MAX_MIDI 32
   1791         rtosc_arg_t args[MAX_MIDI*4];
   1792         char        argt[MAX_MIDI*4+1] = {};
   1793         int j=0;
   1794         for(unsigned i=0; i<key.size() && i<MAX_MIDI; ++i) {
   1795             auto par = midi.inv_map[key[i]];
   1796             if(std::get<1>(par) == -1)
   1797                 continue;
   1798             auto bounds = midi.getBounds(key[i].c_str());
   1799             argt[4*j+0]   = 'i';
   1800             args[4*j+0].i = std::get<1>(par);
   1801             argt[4*j+1]   = 's';
   1802             args[4*j+1].s = key[i].c_str();
   1803             argt[4*j+2]   = 'f';
   1804             args[4*j+2].f = std::get<0>(bounds);
   1805             argt[4*j+3]   = 'f';
   1806             args[4*j+3].f = std::get<1>(bounds);
   1807             j++;
   1808 
   1809         }
   1810         d.replyArray(d.loc, argt, args);
   1811 #undef  MAX_MIDI
   1812         rEnd},
   1813     {"mlearn:s", 0, 0,
   1814         rBegin;
   1815         string addr = rtosc_argument(msg, 0).s;
   1816         auto &midi  = impl.midi_mapper;
   1817         auto map    = midi.getMidiMappingStrings();
   1818         if(map.find(addr) != map.end())
   1819             midi.map(addr.c_str(), false);
   1820         else
   1821             midi.map(addr.c_str(), true);
   1822         rEnd},
   1823     {"munlearn:s", 0, 0,
   1824         rBegin;
   1825         string addr = rtosc_argument(msg, 0).s;
   1826         auto &midi  = impl.midi_mapper;
   1827         auto map    = midi.getMidiMappingStrings();
   1828         midi.unMap(addr.c_str(), false);
   1829         midi.unMap(addr.c_str(), true);
   1830         rEnd},
   1831     //drop this message into the abyss
   1832     {"ui/title:", 0, 0, [](const char *, RtData &) {}},
   1833     {"quit:", rDoc("Stops the Zynaddsubfx process"),
   1834      0, [](const char *, RtData&) {Pexitprogram = 1;}},
   1835     // may only be called when Master is not being run
   1836     {"change-synth:iiit", 0, 0,
   1837         rBegin
   1838         // save all data, overwrite all params defining SYNTH,
   1839         // restart the master and load all data back into it
   1840 
   1841         char* data = nullptr;
   1842         impl.master->getalldata(&data);
   1843         delete impl.master;
   1844 
   1845         impl.synth.samplerate = (unsigned)rtosc_argument(msg, 0).i;
   1846         impl.synth.buffersize = rtosc_argument(msg, 1).i;
   1847         impl.synth.oscilsize = rtosc_argument(msg, 2).i;
   1848         impl.synth.alias();
   1849 
   1850         impl.recreateMinimalMaster();
   1851         impl.master->defaults();
   1852         impl.master->putalldata(data);
   1853         impl.master->applyparameters();
   1854         impl.master->initialize_rt();
   1855         impl.updateResources(impl.master);
   1856 
   1857         d.broadcast("/change-synth", "t", rtosc_argument(msg, 3).t);
   1858         rEnd
   1859     }
   1860 };
   1861 
   1862 static rtosc::MergePorts middwareSnoopPorts =
   1863 {
   1864     &nonRtParamPorts,
   1865     &middwareSnoopPortsWithoutNonRtParams
   1866 };
   1867 
   1868 const rtosc::MergePorts allPorts =
   1869 {
   1870     // order is important: params should be queried on Master first
   1871     // (because MiddleWare often just redirects, hiding the metadata)
   1872     &Master::ports,
   1873     &middwareSnoopPorts
   1874 };
   1875 const rtosc::Ports& getNonRtParamPorts() { return nonRtParamPorts; }
   1876 const rtosc::MergePorts& MiddleWare::getAllPorts() { return allPorts; }
   1877 
   1878 static rtosc::Ports middlewareReplyPorts = {
   1879     {"echo:ss", 0, 0,
   1880         rBegin;
   1881         const char *type = rtosc_argument(msg, 0).s;
   1882         const char *url  = rtosc_argument(msg, 1).s;
   1883         if(!strcmp(type, "OSC_URL"))
   1884             impl.currentUrl(url);
   1885         rEnd},
   1886     {"free:sb", 0, 0,
   1887         rBegin;
   1888         const char *type = rtosc_argument(msg, 0).s;
   1889         void       *ptr  = *(void**)rtosc_argument(msg, 1).b.data;
   1890         deallocate(type, ptr);
   1891         rEnd},
   1892     {"request-memory:", 0, 0,
   1893         rBegin;
   1894         //Generate out more memory for the RT memory pool
   1895         //8MBi chunk
   1896         size_t N  = 8*1024*1024;
   1897         void *mem = malloc(N);
   1898         impl.uToB->write("/add-rt-memory", "bi", sizeof(void*), &mem, N);
   1899         rEnd},
   1900     {"setprogram:cc:ii", 0, 0,
   1901         rBegin;
   1902         Bank &bank        = impl.master->bank;
   1903         const int part    = rtosc_argument(msg, 0).i;
   1904         const int program = rtosc_argument(msg, 1).i + 128*bank.bank_lsb;
   1905         if (program >= BANK_SIZE) {
   1906             fprintf(stderr, "bank:program number %d:%d is out of range.",
   1907                     program >> 7, program & 0x7f);
   1908             return;
   1909         }
   1910         const char *fn  = impl.master->bank.ins[program].filename.c_str();
   1911         impl.loadPart(part, fn, impl.master, d);
   1912         impl.uToB->write(("/part"+to_s(part)+"/Pname").c_str(), "s",
   1913                          fn ? impl.master->bank.ins[program].name.c_str() : "");
   1914         rEnd},
   1915     {"setbank:c", 0, 0,
   1916         rBegin;
   1917         impl.loadPendingBank(rtosc_argument(msg,0).i, impl.master->bank);
   1918         rEnd},
   1919     {"undo_pause:", 0, 0, rBegin; impl.recording_undo = false; rEnd},
   1920     {"undo_resume:", 0, 0, rBegin; impl.recording_undo = true; rEnd},
   1921     {"undo_change", 0, 0,
   1922         rBegin;
   1923         if(impl.recording_undo)
   1924             impl.undo.recordEvent(msg);
   1925         rEnd},
   1926     {"midi-use-CC:i", 0, 0,
   1927         rBegin;
   1928         impl.midi_mapper.useFreeID(rtosc_argument(msg, 0).i);
   1929         rEnd},
   1930     {"broadcast:", 0, 0, rBegin; impl.broadcast = true; rEnd},
   1931     {"forward:", 0, 0, rBegin; impl.forward = true; rEnd},
   1932 };
   1933 #undef rBegin
   1934 #undef rEnd
   1935 
   1936 /******************************************************************************
   1937  *                         MiddleWare Implementation                          *
   1938  ******************************************************************************/
   1939 
   1940 MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_,
   1941     Config* config, int preferrred_port)
   1942     :parent(mw), config(config), ui{nullptr,nullptr}, synth(std::move(synth_)),
   1943     presetsstore(*config), autoSave(-1, [this]() {
   1944             auto master = this->master;
   1945             this->doReadOnlyOp([master](){
   1946                 std::string home = getenv("HOME");
   1947                 std::string save_file = home+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz";
   1948                 printf("doing an autosave <%s>...\n", save_file.c_str());
   1949                 int res = master->saveXML(save_file.c_str());
   1950                 (void)res;});})
   1951 {
   1952     bToU = new rtosc::ThreadLink(4096*2*16,1024/16);
   1953     uToB = new rtosc::ThreadLink(4096*2*16,1024/16);
   1954     midi_mapper.base_ports = &Master::ports;
   1955     midi_mapper.rt_cb      = [this](const char *msg){handleMsg(msg);};
   1956     if(preferrred_port != -1)
   1957         server = lo_server_new_with_proto(to_s(preferrred_port).c_str(),
   1958                                           LO_UDP, liblo_error_cb);
   1959     else
   1960         server = lo_server_new_with_proto(NULL, LO_UDP, liblo_error_cb);
   1961 
   1962     if(server) {
   1963         lo_server_add_method(server, NULL, NULL, handler_function, mw);
   1964         fprintf(stderr, "lo server running on %d\n", lo_server_get_port(server));
   1965     } else
   1966         fprintf(stderr, "lo server could not be started :-/\n");
   1967 
   1968 
   1969     //dummy callback for starters
   1970     for(cb_t& uicb : cb)
   1971         uicb = [](void*, const char*){};
   1972     idle = 0;
   1973     idle_ptr = 0;
   1974 
   1975     recreateMinimalMaster();
   1976     osc    = GUI::genOscInterface(mw);
   1977 
   1978     //Grab objects of interest from master
   1979     updateResources(master);
   1980 
   1981     //Null out Load IDs
   1982     for(int i=0; i < NUM_MIDI_PARTS; ++i) {
   1983         pending_load[i] = 0;
   1984         actual_load[i] = 0;
   1985     }
   1986 
   1987     //Setup Undo
   1988     undo.setCallback([this](const char *msg) {
   1989            // printf("undo callback <%s>\n", msg);
   1990             char buf[1024];
   1991             rtosc_message(buf, 1024, "/undo_pause","");
   1992             handleMsg(buf);
   1993             handleMsg(msg);
   1994             rtosc_message(buf, 1024, "/undo_resume","");
   1995             handleMsg(buf);
   1996             });
   1997 
   1998     //Setup starting time
   1999     struct timespec time;
   2000     monotonic_clock_gettime(&time);
   2001     start_time_sec  = time.tv_sec;
   2002     start_time_nsec = time.tv_nsec;
   2003 
   2004     offline = false;
   2005 }
   2006 
   2007 void MiddleWareImpl::discardAllbToUButHandleFree()
   2008 {
   2009     while(bToU->hasNext()) {
   2010         const char *rtmsg = bToU->read();
   2011         if(!strcmp(rtmsg, "/free"))
   2012                 bToUhandle(rtmsg);
   2013     }
   2014 }
   2015 
   2016 MiddleWareImpl::~MiddleWareImpl(void)
   2017 {
   2018     discardAllbToUButHandleFree();
   2019 
   2020     if(server)
   2021         lo_server_free(server);
   2022 
   2023     delete master;
   2024     delete osc;
   2025     delete bToU;
   2026     delete uToB;
   2027 
   2028 }
   2029 
   2030 void zyn::MiddleWareImpl::recreateMinimalMaster()
   2031 {
   2032     master = new Master(synth, config);
   2033     master->bToU = bToU;
   2034     master->uToB = uToB;
   2035 }
   2036 
   2037 /** Threading When Saving
   2038  *  ----------------------
   2039  *
   2040  * Procedure Middleware:
   2041  *   1) Middleware sends /freeze_state to backend
   2042  *   2) Middleware waits on /state_frozen from backend
   2043  *      All intervening commands are held for out of order execution
   2044  *   3) Acquire memory
   2045  *      At this time by the memory barrier we are guaranteed that all old
   2046  *      writes are done and assuming the freezing logic is sound, then it is
   2047  *      impossible for any other parameter to change at this time
   2048  *   3) Middleware performs saving operation
   2049  *   4) Middleware sends /thaw_state to backend
   2050  *   5) Restore in order execution
   2051  *
   2052  * Procedure Backend:
   2053  *   1) Observe /freeze_state and disable all mutating events (MIDI CC)
   2054  *   2) Run a memory release to ensure that all writes are complete
   2055  *   3) Send /state_frozen to Middleware
   2056  *   time...
   2057  *   4) Observe /thaw_state and resume normal processing
   2058  */
   2059 
   2060 void MiddleWareImpl::doReadOnlyOp(std::function<void()> read_only_fn)
   2061 {
   2062     assert(uToB);
   2063     uToB->write("/freeze_state","");
   2064 
   2065     int tries = 0;
   2066     while(tries++ < 10000) {
   2067         if(!bToU->hasNextLookahead()) {
   2068             os_usleep(500);
   2069             continue;
   2070         }
   2071         const char *msg = bToU->read_lookahead();
   2072         if(!strcmp("/state_frozen", msg))
   2073             break;
   2074     }
   2075 
   2076     assert(tries < 10000);//if this happens, the backend must be dead
   2077 
   2078     std::atomic_thread_fence(std::memory_order_acquire);
   2079 
   2080     //Now it is safe to do any read only operation
   2081     read_only_fn();
   2082 
   2083     //Now to resume normal operations
   2084     uToB->write("/thaw_state","");
   2085 }
   2086 
   2087 //Offline detection code:
   2088 // - Assume that the audio callback should be run at least once every 50ms
   2089 // - Atomically provide the number of ms since start to Master
   2090 // - Every time middleware ticks provide a heart beat
   2091 // - If when the heart beat is provided the backend is more than 200ms behind
   2092 //   the last heartbeat then it must be offline
   2093 // - When marked offline the backend doesn't receive another heartbeat until it
   2094 //   registers the current beat that it's behind on
   2095 void MiddleWareImpl::heartBeat(Master *master)
   2096 {
   2097     //Current time
   2098     //Last provided beat
   2099     //Last acknowledged beat
   2100     //Current offline status
   2101 
   2102     struct timespec time;
   2103     monotonic_clock_gettime(&time);
   2104     uint32_t now = (time.tv_sec-start_time_sec)*100 +
   2105                    (time.tv_nsec-start_time_nsec)*1e-9*100;
   2106     int32_t last_ack   = master->last_ack;
   2107     int32_t last_beat  = master->last_beat;
   2108 
   2109     //everything is considered online for the first second
   2110     if(now < 100)
   2111         return;
   2112 
   2113     if(offline) {
   2114         if(last_beat == last_ack) {
   2115             //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO ONLINE
   2116             offline = false;
   2117 
   2118             //Send new heart beat
   2119             master->last_beat = now;
   2120         }
   2121     } else {
   2122         //it's unquestionably alive
   2123         if(last_beat == last_ack) {
   2124 
   2125             //Send new heart beat
   2126             master->last_beat = now;
   2127             return;
   2128         }
   2129 
   2130         //it's pretty likely dead
   2131         if(last_beat-last_ack > 0 && now-last_beat > 20) {
   2132             //The backend has had 200 ms to acquire a new beat
   2133             //The backend instead has an older beat
   2134             //XXX INSERT MESSAGE HERE ABOUT TRANSITION TO OFFLINE
   2135             offline = true;
   2136             return;
   2137         }
   2138 
   2139         //who knows if it's alive or not here, give it a few ms to acquire or
   2140         //not
   2141     }
   2142 
   2143 }
   2144 
   2145 void MiddleWareImpl::doReadOnlyOpPlugin(std::function<void()> read_only_fn)
   2146 {
   2147     assert(uToB);
   2148     int offline = 0;
   2149     if(offline) {
   2150         std::atomic_thread_fence(std::memory_order_acquire);
   2151 
   2152         //Now it is safe to do any read only operation
   2153         read_only_fn();
   2154     } else if(!doReadOnlyOpNormal(read_only_fn, true)) {
   2155         //check if we just transitioned to offline mode
   2156 
   2157         std::atomic_thread_fence(std::memory_order_acquire);
   2158 
   2159         //Now it is safe to do any read only operation
   2160         read_only_fn();
   2161     }
   2162 }
   2163 
   2164 bool MiddleWareImpl::doReadOnlyOpNormal(std::function<void()> read_only_fn, bool canfail)
   2165 {
   2166     assert(uToB);
   2167     uToB->write("/freeze_state","");
   2168 
   2169     int tries = 0;
   2170     while(tries++ < 2000) {
   2171         if(!bToU->hasNextLookahead()) {
   2172             os_usleep(500);
   2173             continue;
   2174         }
   2175         const char *msg = bToU->read_lookahead();
   2176         if(!strcmp("/state_frozen", msg))
   2177             break;
   2178     }
   2179 
   2180     if(canfail) {
   2181         //Now to resume normal operations
   2182         uToB->write("/thaw_state","");
   2183         return false;
   2184     }
   2185 
   2186     assert(tries < 10000);//if this happens, the backend must be dead
   2187 
   2188     std::atomic_thread_fence(std::memory_order_acquire);
   2189 
   2190     //Now it is safe to do any read only operation
   2191     read_only_fn();
   2192 
   2193     //Now to resume normal operations
   2194     uToB->write("/thaw_state","");
   2195     return true;
   2196 }
   2197 
   2198 void MiddleWareImpl::broadcastToRemote(const char *rtmsg)
   2199 {
   2200     //Always send to the local UIs
   2201     sendToRemote(rtmsg, "GUI");
   2202     sendToRemote(rtmsg, "GUI2");
   2203 
   2204     //Send to remote UI if there's one listening
   2205     for(auto rem:known_remotes)
   2206         if(rem != "GUI" && rem != "GUI2")
   2207             sendToRemote(rtmsg, rem);
   2208 
   2209     broadcast = false;
   2210 }
   2211 
   2212 void MiddleWareImpl::sendToRemote(const char *rtmsg, std::string dest)
   2213 {
   2214     if(!rtmsg || rtmsg[0] != '/' || !rtosc_message_length(rtmsg, -1)) {
   2215         printf("[Warning] Invalid message in sendToRemote <%s, %s>...\n",
   2216                rtmsg, dest.c_str());
   2217         return;
   2218     }
   2219 
   2220     //printf("sendToRemote(%s:%s,%s)\n", rtmsg, rtosc_argument_string(rtmsg),
   2221     //        dest.c_str());
   2222     if(dest == "GUI") {
   2223         cb[0](ui[0], rtmsg);
   2224     } else if(dest == "GUI2") {
   2225         cb[1](ui[1], rtmsg);
   2226     } else if(!dest.empty()) {
   2227         lo_message msg  = lo_message_deserialise((void*)rtmsg,
   2228                 rtosc_message_length(rtmsg, bToU->buffer_size()), NULL);
   2229         if(!msg) {
   2230             printf("[ERROR] OSC to <%s> Failed To Parse In Liblo\n", rtmsg);
   2231             return;
   2232         }
   2233 
   2234         //Send to known url
   2235         lo_address addr = lo_address_new_from_url(dest.c_str());
   2236         if(addr && server)
   2237             lo_send_message_from(addr, server, rtmsg, msg);
   2238         lo_address_free(addr);
   2239         lo_message_free(msg);
   2240     }
   2241 }
   2242 
   2243 /**
   2244  * Handle all events coming from the backend
   2245  *
   2246  * This includes forwarded events which need to be retransmitted to the backend
   2247  * after the snooping code inspects the message
   2248  */
   2249 void MiddleWareImpl::bToUhandle(const char *rtmsg)
   2250 {
   2251     //Verify Message isn't a known corruption bug
   2252     assert(strcmp(rtmsg, "/part0/kit0/Ppadenableda"));
   2253     assert(strcmp(rtmsg, "/ze_state"));
   2254 
   2255     //Dump Incoming Events For Debugging
   2256     if(strcmp(rtmsg, "/vu-meter") && false) {
   2257         fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 1 + 30, 0 + 40);
   2258         fprintf(stdout, "frontend[%c]: '%s'<%s>\n", forward?'f':broadcast?'b':'N',
   2259                 rtmsg, rtosc_argument_string(rtmsg));
   2260         fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
   2261     }
   2262 
   2263     //Activity dot
   2264     //printf(".");fflush(stdout);
   2265 
   2266     MwDataObj d(this);
   2267     middlewareReplyPorts.dispatch(rtmsg, d, true);
   2268 
   2269     if(!rtmsg) {
   2270         fprintf(stderr, "[ERROR] Unexpected Null OSC In Zyn\n");
   2271         return;
   2272     }
   2273 
   2274     in_order = true;
   2275     //Normal message not captured by the ports
   2276     if(d.matches == 0) {
   2277         if(forward) {
   2278             forward = false;
   2279             handleMsg(rtmsg, true);
   2280         } else if(broadcast)
   2281             broadcastToRemote(rtmsg);
   2282         else
   2283             sendToCurrentRemote(rtmsg);
   2284     }
   2285     in_order = false;
   2286 
   2287 }
   2288 
   2289 //Allocate kits on a as needed basis
   2290 void MiddleWareImpl::kitEnable(const char *msg)
   2291 {
   2292     const string argv = rtosc_argument_string(msg);
   2293     if(argv != "T")
   2294         return;
   2295     //Extract fields from:
   2296     //BASE/part#/kit#/Pxxxenabled
   2297     int type = -1;
   2298     if(strstr(msg, "Padenabled"))
   2299         type = 0;
   2300     else if(strstr(msg, "Ppadenabled"))
   2301         type = 1;
   2302     else if(strstr(msg, "Psubenabled"))
   2303         type = 2;
   2304     else
   2305         return;
   2306 
   2307     int part, kit;
   2308     bool res = idsFromMsg(msg, &part, &kit, nullptr);
   2309     assert(res);
   2310     kitEnable(part, kit, type);
   2311 }
   2312 
   2313 void MiddleWareImpl::kitEnable(int part, int kit, int type)
   2314 {
   2315     //printf("attempting a kit enable<%d,%d,%d>\n", part, kit, type);
   2316     string url = "/part"+to_s(part)+"/kit"+to_s(kit)+"/";
   2317     void *ptr = NULL;
   2318     if(type == 0 && kits.add[part][kit] == NULL) {
   2319         ptr = kits.add[part][kit] = new ADnoteParameters(synth, master->fft,
   2320                                                          &master->time);
   2321         url += "adpars-data";
   2322         obj_store.extractAD(kits.add[part][kit], part, kit);
   2323     } else if(type == 1 && kits.pad[part][kit] == NULL) {
   2324         ptr = kits.pad[part][kit] = new PADnoteParameters(synth, master->fft,
   2325                                                           &master->time);
   2326         url += "padpars-data";
   2327         obj_store.extractPAD(kits.pad[part][kit], part, kit);
   2328     } else if(type == 2 && kits.sub[part][kit] == NULL) {
   2329         ptr = kits.sub[part][kit] = new SUBnoteParameters(&master->time);
   2330         url += "subpars-data";
   2331     }
   2332 
   2333     //Send the new memory
   2334     if(ptr)
   2335         uToB->write(url.c_str(), "b", sizeof(void*), &ptr);
   2336 }
   2337 
   2338 
   2339 /*
   2340  * Handle all messages traveling to the realtime side.
   2341  */
   2342 void MiddleWareImpl::handleMsg(const char *msg, bool msg_comes_from_realtime)
   2343 {
   2344     // handle path-search
   2345     if(!msg_comes_from_realtime)
   2346     {
   2347         if(!strcmp(msg, "/path-search") &&
   2348             (!strcmp("ss",  rtosc_argument_string(msg)) ||
   2349              !strcmp("ssT", rtosc_argument_string(msg)) ) )
   2350         {
   2351             constexpr bool debug_path_search = false;
   2352             if(debug_path_search) {
   2353                 fprintf(stderr, "MW: path-search: %s, %s\n",
   2354                         rtosc_argument(msg, 0).s, rtosc_argument(msg, 1).s);
   2355             }
   2356             bool reply_with_query = rtosc_narguments(msg) == 3;
   2357 
   2358             char reply_buffer[1024*20];
   2359             std::size_t length =
   2360                 rtosc::path_search(MiddleWare::getAllPorts(), msg, 128,
   2361                                    reply_buffer, sizeof(reply_buffer),
   2362                                    rtosc::path_search_opts::sorted_and_unique_prefix,
   2363                                    reply_with_query);
   2364             if(length) {
   2365                 sendToRemote(reply_buffer, parent->activeUrl());
   2366             }
   2367             else {
   2368                 if(debug_path_search)
   2369                     fprintf(stderr, "  -> no reply!\n");
   2370             }
   2371             return;
   2372         }
   2373     }
   2374 
   2375     //Check for known bugs
   2376     assert(msg && *msg && strrchr(msg, '/')[1]);
   2377     assert(strstr(msg,"free") == NULL || strstr(rtosc_argument_string(msg), "b") == NULL);
   2378     assert(strcmp(msg, "/part0/Psysefxvol"));
   2379     assert(strcmp(msg, "/Penabled"));
   2380     assert(strcmp(msg, "part0/part0/Ppanning"));
   2381     assert(strcmp(msg, "sysefx0sysefx0/preset"));
   2382     assert(strcmp(msg, "/sysefx0preset"));
   2383     assert(strcmp(msg, "Psysefxvol0/part0"));
   2384 
   2385     if(strcmp("/get-vu", msg) && false) {
   2386         fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 6 + 30, 0 + 40);
   2387         fprintf(stdout, "middleware: '%s':%s\n", msg, rtosc_argument_string(msg));
   2388         fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
   2389     }
   2390 
   2391     const char *last_path = strrchr(msg, '/');
   2392     if(!last_path) {
   2393         printf("Bad message in handleMsg() <%s>\n", msg);
   2394         assert(false);
   2395         return;
   2396     }
   2397 
   2398     MwDataObj d(this);
   2399     middwareSnoopPorts.dispatch(msg, d, true);
   2400 
   2401     //A message unmodified by snooping
   2402     if(d.matches == 0 || d.forwarded) {
   2403         if(msg_comes_from_realtime) {
   2404             // don't reply the same msg to realtime - avoid cycles
   2405             //printf("Message from RT will not be replied to RT: <%s:%s>...\n",
   2406             //       msg, rtosc_argument_string(msg));
   2407         } else {
   2408             //if(strcmp("/get-vu", msg)) {
   2409             //    printf("Message Continuing on<%s:%s>...\n", msg, rtosc_argument_string(msg));
   2410             //}
   2411             uToB->raw_write(msg);
   2412         }
   2413     } else {
   2414         //printf("Message Handled<%s:%s>...\n", msg, rtosc_argument_string(msg));
   2415     }
   2416 
   2417     // now handle all chained messages
   2418     while(!msgsToHandle.empty())
   2419     {
   2420         std::vector<char> front = msgsToHandle.front();
   2421         msgsToHandle.pop();
   2422         handleMsg(front.data());
   2423     }
   2424 }
   2425 
   2426 void MiddleWareImpl::write(const char *path, const char *args, ...)
   2427 {
   2428     //We have a free buffer in the threadlink, so use it
   2429     va_list va;
   2430     va_start(va, args);
   2431     write(path, args, va);
   2432     va_end(va);
   2433 }
   2434 
   2435 void MiddleWareImpl::write(const char *path, const char *args, va_list va)
   2436 {
   2437     //printf("is that a '%s' I see there?\n", path);
   2438     char *buffer = uToB->buffer();
   2439     unsigned len = uToB->buffer_size();
   2440     bool success = rtosc_vmessage(buffer, len, path, args, va);
   2441     //printf("working on '%s':'%s'\n",path, args);
   2442 
   2443     if(success)
   2444         handleMsg(buffer);
   2445     else
   2446         warnx("Failed to write message to '%s'", path);
   2447 }
   2448 
   2449 /******************************************************************************
   2450  *                         MidleWare Forwarding Stubs                         *
   2451  ******************************************************************************/
   2452 MiddleWare::MiddleWare(SYNTH_T synth, Config* config,
   2453                        int preferred_port)
   2454 :impl(new MiddleWareImpl(this, std::move(synth), config, preferred_port))
   2455 {}
   2456 
   2457 MiddleWare::~MiddleWare(void)
   2458 {
   2459     delete impl;
   2460 }
   2461 
   2462 void MiddleWare::updateResources(Master *m)
   2463 {
   2464     impl->updateResources(m);
   2465 }
   2466 
   2467 Master *MiddleWare::spawnMaster(void)
   2468 {
   2469     assert(impl->master);
   2470     assert(impl->master->uToB);
   2471     return impl->master;
   2472 }
   2473 
   2474 void MiddleWare::enableAutoSave(int interval_sec)
   2475 {
   2476     impl->autoSave.dt = interval_sec;
   2477 }
   2478 
   2479 int MiddleWare::checkAutoSave(void) const
   2480 {
   2481     //save spec zynaddsubfx-PID-autosave.xmz
   2482     const std::string home     = getenv("HOME");
   2483     const std::string save_dir = home+"/.local/";
   2484 
   2485     DIR *dir = opendir(save_dir.c_str());
   2486 
   2487     if(dir == NULL)
   2488         return -1;
   2489 
   2490     struct dirent *fn;
   2491     int    reload_save = -1;
   2492 
   2493     while((fn = readdir(dir))) {
   2494         const char *filename = fn->d_name;
   2495         const char *prefix = "zynaddsubfx-";
   2496 
   2497         //check for manditory prefix
   2498         if(strstr(filename, prefix) != filename)
   2499             continue;
   2500 
   2501         int id = atoi(filename+strlen(prefix));
   2502 
   2503         bool in_use = false;
   2504 
   2505         std::string proc_file = "/proc/" + to_s(id) + "/comm";
   2506         std::ifstream ifs(proc_file);
   2507         if(ifs.good()) {
   2508             std::string comm_name;
   2509             ifs >> comm_name;
   2510             in_use = (comm_name == "zynaddsubfx");
   2511         }
   2512 
   2513         if(!in_use) {
   2514             reload_save = id;
   2515             break;
   2516         }
   2517     }
   2518 
   2519     closedir(dir);
   2520 
   2521     return reload_save;
   2522 }
   2523 
   2524 void MiddleWare::removeAutoSave(void)
   2525 {
   2526     std::string home = getenv("HOME");
   2527     std::string save_file = home+"/.local/zynaddsubfx-"+to_s(getpid())+"-autosave.xmz";
   2528     remove(save_file.c_str());
   2529 }
   2530 
   2531 Fl_Osc_Interface *MiddleWare::spawnUiApi(void)
   2532 {
   2533     return impl->osc;
   2534 }
   2535 
   2536 void MiddleWare::tick(void)
   2537 {
   2538     impl->tick();
   2539 }
   2540 
   2541 void MiddleWare::doReadOnlyOp(std::function<void()> fn)
   2542 {
   2543     impl->doReadOnlyOp(fn);
   2544 }
   2545 
   2546 void MiddleWare::setUiCallback(std::size_t gui_id, void(*cb)(void*,const char *), void *ui)
   2547 {
   2548     assert(gui_id < sizeof(impl->cb)/sizeof(impl->cb[0]));
   2549     impl->cb[gui_id] = cb;
   2550     impl->ui[gui_id] = ui;
   2551 }
   2552 
   2553 void MiddleWare::setIdleCallback(void(*cb)(void*), void *ptr)
   2554 {
   2555     impl->idle     = cb;
   2556     impl->idle_ptr = ptr;
   2557 }
   2558 
   2559 void MiddleWare::transmitMsg(const char *msg)
   2560 {
   2561     impl->handleMsg(msg);
   2562 }
   2563 
   2564 void MiddleWare::transmitMsg(const char *path, const char *args, ...)
   2565 {
   2566     char buffer[1024];
   2567     va_list va;
   2568     va_start(va,args);
   2569     if(rtosc_vmessage(buffer,1024,path,args,va))
   2570         transmitMsg(buffer);
   2571     else
   2572         fprintf(stderr, "Error in transmitMsg(...)\n");
   2573     va_end(va);
   2574 }
   2575 
   2576 void MiddleWare::transmitMsg_va(const char *path, const char *args, va_list va)
   2577 {
   2578     char buffer[1024];
   2579     if(rtosc_vmessage(buffer, 1024, path, args, va))
   2580         transmitMsg(buffer);
   2581     else
   2582         fprintf(stderr, "Error in transmitMsg(va)n");
   2583 }
   2584 
   2585 void MiddleWare::transmitMsgGui(std::size_t gui_id, const char *msg)
   2586 {
   2587     if(gui_id == 0 && activeUrl() != "GUI") {
   2588         transmitMsg("/echo", "ss", "OSC_URL", "GUI");
   2589         activeUrl("GUI");
   2590     } else if(gui_id == 1 && activeUrl() != "GUI2") {
   2591         transmitMsg("/echo", "ss", "OSC_URL", "GUI2");
   2592         activeUrl("GUI2");
   2593     }
   2594     transmitMsg(msg);
   2595 }
   2596 
   2597 void MiddleWare::transmitMsgGui(std::size_t gui_id, const char *path, const char *args, ...)
   2598 {
   2599     char buffer[1024];
   2600     va_list va;
   2601     va_start(va,args);
   2602     if(rtosc_vmessage(buffer,1024,path,args,va))
   2603         transmitMsgGui(gui_id, buffer);
   2604     else
   2605         fprintf(stderr, "Error in transmitMsgGui(...)\n");
   2606     va_end(va);
   2607 }
   2608 
   2609 void MiddleWare::transmitMsgGui_va(std::size_t gui_id, const char *path, const char *args, va_list va)
   2610 {
   2611     char buffer[1024];
   2612     if(rtosc_vmessage(buffer, 1024, path, args, va))
   2613         transmitMsgGui(gui_id, buffer);
   2614     else
   2615         fprintf(stderr, "Error in transmitMsgGui(va)n");
   2616 }
   2617 
   2618 void MiddleWare::messageAnywhere(const char *path, const char *args, ...)
   2619 {
   2620     auto *mem = impl->multi_thread_source.alloc();
   2621     if(!mem)
   2622         fprintf(stderr, "Middleware::messageAnywhere memory pool out of memory...\n");
   2623 
   2624     va_list va;
   2625     va_start(va,args);
   2626     if(rtosc_vmessage(mem->memory,mem->size,path,args,va))
   2627         impl->multi_thread_source.write(mem);
   2628     else {
   2629         fprintf(stderr, "Middleware::messageAnywhere message too big...\n");
   2630         impl->multi_thread_source.free(mem);
   2631     }
   2632     va_end(va);
   2633 }
   2634 
   2635 
   2636 void MiddleWare::pendingSetBank(int bank)
   2637 {
   2638     impl->bToU->write("/setbank", "c", bank);
   2639 }
   2640 void MiddleWare::pendingSetProgram(int part, int program)
   2641 {
   2642     impl->pending_load[part]++;
   2643     impl->bToU->write("/setprogram", "cc", part, program);
   2644 }
   2645 
   2646 std::string zyn::MiddleWare::getProgramName(int program) const
   2647 {
   2648     return impl->master->bank.ins[program].name;
   2649 }
   2650 
   2651 std::string MiddleWare::activeUrl(void) const
   2652 {
   2653     return impl->last_url;
   2654 }
   2655 
   2656 void MiddleWare::activeUrl(std::string u)
   2657 {
   2658     impl->last_url = u;
   2659 }
   2660 
   2661 const SYNTH_T &MiddleWare::getSynth(void) const
   2662 {
   2663     return impl->synth;
   2664 }
   2665 
   2666 char* MiddleWare::getServerAddress(void) const
   2667 {
   2668     if(impl->server)
   2669         return lo_server_get_url(impl->server);
   2670     else
   2671         return nullptr;
   2672 }
   2673 
   2674 char* MiddleWare::getServerPort(void) const
   2675 {
   2676     char* addr = getServerAddress();
   2677     char* result;
   2678     if(addr)
   2679     {
   2680         result = lo_url_get_port(addr);
   2681         free(addr);
   2682     }
   2683     else
   2684         result = nullptr;
   2685     return result;
   2686 }
   2687 
   2688 const PresetsStore& MiddleWare::getPresetsStore() const
   2689 {
   2690     return impl->presetsstore;
   2691 }
   2692 
   2693 PresetsStore& MiddleWare::getPresetsStore()
   2694 {
   2695     return impl->presetsstore;
   2696 }
   2697 
   2698 void MiddleWare::switchMaster(Master* new_master)
   2699 {
   2700     // this function is kept similar to loadMaster
   2701     assert(impl->master->frozenState);
   2702 
   2703     new_master->uToB = impl->uToB;
   2704     new_master->bToU = impl->bToU;
   2705     impl->updateResources(new_master);
   2706     impl->master = new_master;
   2707 
   2708     if(impl->master->hasMasterCb())
   2709     {
   2710         impl->master->setMasterSwitchUpcoming();
   2711         // inform the realtime thread about the switch
   2712         // this will be done by calling the mastercb
   2713         transmitMsg("/switch-master", "b", sizeof(Master*), &new_master);
   2714     }
   2715 }
   2716 
   2717 void MiddleWare::discardAllbToUButHandleFree()
   2718 {
   2719     impl->discardAllbToUButHandleFree();
   2720 }
   2721 
   2722 }