zynaddsubfx

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

commit a3a9423aed173013cd8cc6c0477e8e6048bc34ad
parent 037152b9a61d5abeeb91f2f9ba987afc5ba11ac9
Author: fundamental <[email protected]>
Date:   Sun,  1 Nov 2015 20:08:14 -0500

Merge branch 'midi-serialization'

Conflicts:
	src/Misc/MiddleWare.cpp

Diffstat:
Msrc/Effects/EffectMgr.cpp | 2+-
Msrc/Misc/Master.cpp | 95+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/Misc/Master.h | 2+-
Msrc/Misc/MiddleWare.cpp | 1022+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/Misc/MiddleWare.h | 6+++++-
Msrc/Misc/PresetExtractor.cpp | 9+++++++++
Msrc/Misc/XMLwrapper.cpp | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Misc/XMLwrapper.h | 27++++++++++++++++++++++++---
Msrc/Params/ADnoteParameters.cpp | 27+++++++++++++++++++++++----
Msrc/Params/PADnoteParameters.cpp | 225++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/Params/PADnoteParameters.h | 4+++-
Msrc/Synth/OscilGen.cpp | 131++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/Synth/OscilGen.h | 4+++-
Msrc/Tests/CMakeLists.txt | 4++++
Asrc/Tests/MessageTest.h | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Tests/MiddlewareTest.h | 18++++++++++--------
Msrc/UI/BankUI.fl | 2+-
Msrc/UI/BankView.cpp | 58++++++++++++++++++++++++++++++++++------------------------
Msrc/UI/Connection.cpp | 2+-
Msrc/UI/MasterUI.fl | 26++++++++++++++++++++++++++
Msrc/UI/OscilGenUI.fl | 4++--
Msrc/globals.h | 2+-
22 files changed, 1202 insertions(+), 732 deletions(-)

diff --git a/src/Effects/EffectMgr.cpp b/src/Effects/EffectMgr.cpp @@ -45,7 +45,7 @@ static const rtosc::Ports local_ports = { rSelf(EffectMgr), rPaste, rRecurp(filterpars, "Filter Parameter for Dynamic Filter"), - {"parameter#128::i:T:F", rProp(parameter) rProp(alias) rDoc("Parameter Accessor"), + {"parameter#128::i:T:F", rProp(parameter) rProp(alias) rLinear(0,127) rDoc("Parameter Accessor"), NULL, [](const char *msg, rtosc::RtData &d) { diff --git a/src/Misc/Master.cpp b/src/Misc/Master.cpp @@ -166,6 +166,10 @@ static const Ports master_ports = { [](const char *m,RtData &d){ Master *M = (Master*)d.obj; M->noteOff(rtosc_argument(m,0).i,rtosc_argument(m,1).i);}}, + {"virtual_midi_cc:iii", rDoc("MIDI CC Event"), 0, + [](const char *m,RtData &d){ + Master *M = (Master*)d.obj; + M->setController(rtosc_argument(m,0).i,rtosc_argument(m,1).i,rtosc_argument(m,2).i);}}, {"setController:iii", rDoc("MIDI CC Event"), 0, [](const char *m,RtData &d){ @@ -189,19 +193,13 @@ static const Ports master_ports = { [](const char *,RtData &d) { Master *M = (Master*)d.obj; M->frozenState = false;}}, - {"register:iis", rDoc("MIDI Mapping Registration"), 0, - [](const char *m,RtData &d){ - Master *M = (Master*)d.obj; - M->midi.addElm(rtosc_argument(m,0).i, rtosc_argument(m,1).i,rtosc_argument(m,2).s);}}, - {"learn:s", rDoc("Begin Learning for specified address"), 0, - [](const char *m, RtData &d){ - Master *M = (Master*)d.obj; - printf("learning '%s'\n", rtosc_argument(m,0).s); - M->midi.learn(rtosc_argument(m,0).s);}}, - {"unlearn:s", rDoc("Remove Learning for specified address"), 0, - [](const char *m, RtData &d){ + {"midi-learn/", 0, &rtosc::MidiMapperRT::ports, + [](const char *msg, RtData &d) { Master *M = (Master*)d.obj; - M->midi.clear_entry(rtosc_argument(m,0).s);}}, + SNIP; + printf("residue message = <%s>\n", msg); + d.obj = &M->midi; + rtosc::MidiMapperRT::ports.dispatch(msg,d);}}, {"close-ui:", rDoc("Request to close any connection named \"GUI\""), 0, [](const char *, RtData &d) { d.reply("/close-ui", "");}}, @@ -228,7 +226,7 @@ static const Ports master_ports = { {"undo_resume:",rProp(internal) rDoc("resume undo event recording"),0, [](const char *, rtosc::RtData &d) {d.reply("/undo_resume", "");}}, {"config/", rDoc("Top Level Application Configuration Parameters"), &Config::ports, - [](const char *, rtosc::RtData &){}}, + [](const char *, rtosc::RtData &d){d.forward();}}, {"presets/", rDoc("Parameter Presets"), &preset_ports, rBOIL_BEGIN SNIP preset_ports.dispatch(msg, data); @@ -236,12 +234,6 @@ static const Ports master_ports = { }; const Ports &Master::ports = master_ports; -#ifndef PLUGINVERSION -//XXX HACKS -Master *the_master; -rtosc::ThreadLink *the_bToU; -#endif - class DataObj:public rtosc::RtData { public: @@ -252,6 +244,7 @@ class DataObj:public rtosc::RtData loc_size = loc_size_; obj = obj_; bToU = bToU_; + forwarded = false; } virtual void reply(const char *path, const char *args, ...) override @@ -280,9 +273,18 @@ class DataObj:public rtosc::RtData } virtual void broadcast(const char *msg) override { - reply("/broadcast"); + reply("/broadcast", ""); reply(msg); - }; + } + + virtual void forward(const char *reason) override + { + assert(message); + reply("/forward", ""); + printf("forwarding '%s'\n", message); + forwarded = true; + } + bool forwarded; private: rtosc::ThreadLink *bToU; }; @@ -295,20 +297,22 @@ vuData::vuData(void) Master::Master(const SYNTH_T &synth_, Config* config) :HDDRecorder(synth_), ctl(synth_), microtonal(config->cfg.GzipCompression), bank(config), - midi(Master::ports), frozenState(false), pendingMemory(false), + frozenState(false), pendingMemory(false), synth(synth_), time(synth), gzip_compression(config->cfg.GzipCompression) { bToU = NULL; uToB = NULL; + + //Setup MIDI + midi.frontend = [this](const char *msg) {bToU->raw_write(msg);}; + midi.backend = [this](const char *msg) {applyOscEvent(msg);}; + memory = new AllocatorClass(); swaplr = 0; off = 0; smps = 0; bufl = new float[synth.buffersize]; bufr = new float[synth.buffersize]; -#ifndef PLUGINVERSION - the_master = this; -#endif last_xmz[0] = 0; fft = new FFTwrapper(synth.oscilsize); @@ -334,24 +338,6 @@ Master::Master(const SYNTH_T &synth_, Config* config) defaults(); -#ifndef PLUGINVERSION - midi.event_cb = [](const char *m) - { - char loc_buf[1024]; - DataObj d{loc_buf, 1024, the_master, the_bToU}; - memset(loc_buf, 0, sizeof(loc_buf)); - //printf("sending an event to the owner of '%s'\n", m); - Master::ports.dispatch(m+1, d); - }; -#else - midi.event_cb = [](const char *) {}; -#endif - - midi.error_cb = [](const char *a, const char *b) - { - fprintf(stderr, "MIDI- got an error '%s' -- '%s'\n",a,b); - }; - mastercb = 0; mastercb_ptr = 0; } @@ -362,9 +348,19 @@ void Master::applyOscEvent(const char *msg) DataObj d{loc_buf, 1024, this, bToU}; memset(loc_buf, 0, sizeof(loc_buf)); d.matches = 0; - ports.dispatch(msg+1, d); - if(d.matches == 0) - fprintf(stderr, "Unknown path '%s'\n", msg); + + if(strcmp(msg, "/get-vu") && false) { + fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 5 + 30, 0 + 40); + fprintf(stdout, "backend[*]: '%s'<%s>\n", msg, + rtosc_argument_string(msg)); + fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); + } + + ports.dispatch(msg, d, true); + if(d.matches == 0 && !d.forwarded) + fprintf(stderr, "Unknown path '%s:%s'\n", msg, rtosc_argument_string(msg)); + if(d.forwarded) + bToU->raw_write(msg); } void Master::defaults() @@ -450,7 +446,8 @@ void Master::setController(char chan, int type, int par) { if(frozenState) return; - midi.process(chan,type,par); + //TODO add chan back + midi.handleCC(type,par); if((type == C_dataentryhi) || (type == C_dataentrylo) || (type == C_nrpnhi) || (type == C_nrpnlo)) { //Process RPN and NRPN by the Master (ignore the chan) ctl.setparameternumber(type, par); @@ -658,9 +655,7 @@ void Master::AudioOut(float *outl, float *outr) rtosc_argument_string(msg)); fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); } - d.matches = 0; - //fprintf(stdout, "address '%s'\n", uToB->peak()); - ports.dispatch(msg+1, d); + ports.dispatch(msg, d, true); events++; if(!d.matches) {// && !ports.apropos(msg)) { fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); diff --git a/src/Misc/Master.h b/src/Misc/Master.h @@ -166,7 +166,7 @@ class Master //Statistics on output levels vuData vu; - rtosc::MidiTable midi;//<1024,64> + rtosc::MidiMapperRT midi; bool frozenState;//read-only parameters for threadsafe actions Allocator *memory; diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp @@ -40,9 +40,6 @@ #endif using std::string; -#ifndef PLUGINVERSION -extern rtosc::ThreadLink *the_bToU;//XXX -#endif /****************************************************************************** * LIBLO And Reflection Code * @@ -188,131 +185,54 @@ void preparePadSynth(string path, PADnoteParameters *p, rtosc::ThreadLink *uToB) } } -/***************************************************************************** - * Instrument Banks * - * * - * Banks and presets in general are not classed as realtime safe * - * * - * The supported operations are: * - * - Load Names * - * - Load Bank * - * - Refresh List of Banks * - *****************************************************************************/ -void refreshBankView(const Bank &bank, unsigned loc, std::function<void(const char*)> cb) -{ - if(loc >= BANK_SIZE) - return; - - char response[2048]; - if(!rtosc_message(response, 1024, "/bankview", "iss", - loc, bank.ins[loc].name.c_str(), - bank.ins[loc].filename.c_str())) - errx(1, "Failure to handle bank update properly..."); - - - if(cb) - cb(response); -} - -void bankList(Bank &bank, std::function<void(const char*)> cb) -{ - char response[2048]; - int i = 0; - - for(auto &elm : bank.banks) { - if(!rtosc_message(response, 2048, "/bank-list", "iss", - i++, elm.name.c_str(), elm.dir.c_str())) - errx(1, "Failure to handle bank update properly..."); - if(cb) - cb(response); - } -} - -void rescanForBanks(Bank &bank, std::function<void(const char*)> cb) -{ - bank.rescanforbanks(); - bankList(bank, cb); -} - -void loadBank(Bank &bank, int pos, std::function<void(const char*)> cb) +/****************************************************************************** + * MIDI Serialization * + * * + ******************************************************************************/ +void saveMidiLearn(XMLwrapper &xml, const rtosc::MidiMappernRT &midi) { - char response[2048]; - if(!rtosc_message(response, 2048, "/loadbank", "i", pos)) - errx(1, "Failure to handle bank update properly..."); - if(cb) - cb(response); - if(bank.bankpos != pos) { - bank.bankpos = pos; - bank.loadbank(bank.banks[pos].dir); - for(int i=0; i<BANK_SIZE; ++i) - refreshBankView(bank, i, cb); + xml.beginbranch("midi-learn"); + for(auto value:midi.inv_map) { + XmlNode binding("midi-binding"); + auto biject = std::get<3>(value.second); + binding["osc-path"] = value.first; + binding["coarse-CC"] = to_s(std::get<1>(value.second)); + binding["fine-CC"] = to_s(std::get<2>(value.second)); + binding["type"] = "i"; + binding["minimum"] = to_s(biject.min); + binding["maximum"] = to_s(biject.max); + xml.add(binding); } + xml.endbranch(); } -void bankPos(Bank &bank, std::function<void(const char *)> cb) +void loadMidiLearn(XMLwrapper &xml, rtosc::MidiMappernRT &midi) { - char response[2048]; - - if(!rtosc_message(response, 2048, "/loadbank", "i", bank.bankpos)) - errx(1, "Failure to handle bank update properly..."); - if(cb) - cb(response); -} - -/***************************************************************************** - * Data Object for Non-RT Class Dispatch * - *****************************************************************************/ + using rtosc::Port; + if(xml.enterbranch("midi-learn")) { + auto nodes = xml.getBranch(); -class DummyDataObj:public rtosc::RtData -{ - public: - DummyDataObj(char *loc_, size_t loc_size_, void *obj_, MiddleWareImpl *mwi_, - rtosc::ThreadLink *uToB_) - { - memset(loc_, 0, sizeof(loc_size_)); - buffer = new char[4*4096]; - memset(buffer, 0, 4*4096); - loc = loc_; - loc_size = loc_size_; - obj = obj_; - mwi = mwi_; - uToB = uToB_; - } - ~DummyDataObj(void) - { - delete[] buffer; - } + //TODO clear mapper - virtual void reply(const char *path, const char *args, ...) - { - //printf("reply building '%s'\n", path); - va_list va; - va_start(va,args); - if(!strcmp(path, "/forward")) { //forward the information to the backend - args++; - path = va_arg(va, const char *); - //fprintf(stderr, "forwarding information to the backend on '%s'<%s>\n", - // path, args); - rtosc_vmessage(buffer,4*4096,path,args,va); - uToB->raw_write(buffer); + for(auto node:nodes) { + if(node.name != "midi-binding" || + !node.has("osc-path") || + !node.has("coarse-CC")) + continue; + const string path = node["osc-path"]; + const int CC = atoi(node["coarse-CC"].c_str()); + const Port *p = Master::ports.apropos(path.c_str()); + if(p) { + printf("loading midi port...\n"); + midi.addNewMapper(CC, *p, path); } else { - //printf("path = '%s' args = '%s'\n", path, args); - //printf("buffer = '%p'\n", buffer); - rtosc_vmessage(buffer,4*4096,path,args,va); - //printf("buffer = '%s'\n", buffer); - reply(buffer); + printf("unknown midi bindable <%s>\n", path.c_str()); } - va_end(va); } - virtual void reply(const char *msg); - //virtual void broadcast(const char *path, const char *args, ...){(void)path;(void)args;}; - //virtual void broadcast(const char *msg){(void)msg;}; - private: - char *buffer; - MiddleWareImpl *mwi; - rtosc::ThreadLink *uToB; -}; - + xml.exitbranch(); + } else + printf("cannot find 'midi-learn' branch...\n"); +} /****************************************************************************** * Non-RealTime Object Store * @@ -392,6 +312,23 @@ struct NonRtObjStore { return objmap[loc]; } + + void handleOscil(const char *msg, rtosc::RtData &d) { + string obj_rl(d.message, msg); + void *osc = get(obj_rl); + assert(osc); + strcpy(d.loc, obj_rl.c_str()); + d.obj = osc; + OscilGen::non_realtime_ports.dispatch(msg, d); + } + void handlePad(const char *msg, rtosc::RtData &d) { + string obj_rl(d.message, msg); + void *pad = get(obj_rl); + assert(pad); + strcpy(d.loc, obj_rl.c_str()); + d.obj = pad; + PADnoteParameters::non_realtime_ports.dispatch(msg, d); + } }; /****************************************************************************** @@ -463,10 +400,13 @@ namespace Nio }; } + /* Implementation */ class MiddleWareImpl { + public: MiddleWare *parent; + private: //Detect if the name of the process is 'zynaddsubfx' bool isPlugin() const @@ -481,76 +421,15 @@ class MiddleWareImpl return true; } - Config* const config; - public: + Config* const config; MiddleWareImpl(MiddleWare *mw, SYNTH_T synth, Config* config, int preferred_port); ~MiddleWareImpl(void); - void warnMemoryLeaks(void); - //Apply function while parameters are write locked void doReadOnlyOp(std::function<void()> read_only_fn); - - void saveBankSlot(int npart, int nslot, Master *master) - { - int err = 0; - doReadOnlyOp([master,nslot,npart,&err](){ - err = master->bank.savetoslot(nslot, master->part[npart]);}); - if(err) { - char buffer[1024]; - rtosc_message(buffer, 1024, "/alert", "s", - "Failed To Save To Bank Slot, please check file permissions"); - GUI::raiseUi(ui, buffer); - } - } - - void renameBankSlot(int slot, string name, Master *master) - { - int err = master->bank.setname(slot, name, -1); - if(err) { - char buffer[1024]; - rtosc_message(buffer, 1024, "/alert", "s", - "Failed To Rename Bank Slot, please check file permissions"); - GUI::raiseUi(ui, buffer); - } - } - - void swapBankSlot(int slota, int slotb, Master *master) - { - int err = master->bank.swapslot(slota, slotb); - if(err) { - char buffer[1024]; - rtosc_message(buffer, 1024, "/alert", "s", - "Failed To Swap Bank Slots, please check file permissions"); - GUI::raiseUi(ui, buffer); - } - } - - void clearBankSlot(int slot, Master *master) - { - int err = master->bank.clearslot(slot); - if(err) { - char buffer[1024]; - rtosc_message(buffer, 1024, "/alert", "s", - "Failed To Clear Bank Slot, please check file permissions"); - GUI::raiseUi(ui, buffer); - } - } - - void saveMaster(const char *filename) - { - //Copy is needed as filename WILL get trashed during the rest of the run - std::string fname = filename; - //printf("saving master('%s')\n", filename); - doReadOnlyOp([this,fname](){ - int res = master->saveXML(fname.c_str()); - (void)res; - /*printf("results: '%s' '%d'\n",fname.c_str(), res);*/}); - } - void savePart(int npart, const char *filename) { //Copy is needed as filename WILL get trashed during the rest of the run @@ -624,7 +503,7 @@ public: //Give it to the backend and wait for the old part to return for //deallocation - uToB->write("/load-part", "ib", npart, sizeof(Part*), &p); + parent->transmitMsg("/load-part", "ib", npart, sizeof(Part*), &p); GUI::raiseUi(ui, "/damage", "s", ("/part"+to_s(npart)+"/").c_str()); } @@ -644,10 +523,8 @@ public: //Give it to the backend and wait for the old part to return for //deallocation - uToB->write("/load-part", "ib", npart, sizeof(Part*), &p); + parent->transmitMsg("/load-part", "ib", npart, sizeof(Part*), &p); GUI::raiseUi(ui, "/damage", "s", ("/part"+to_s(npart)+"/").c_str()); - //if(osc) - // osc->damage(("/part"+to_s(npart)+"/").c_str()); } //Well, you don't get much crazier than changing out all of your RT @@ -672,7 +549,7 @@ public: //Give it to the backend and wait for the old part to return for //deallocation - uToB->write("/load-master", "b", sizeof(Master*), &m); + parent->transmitMsg("/load-master", "b", sizeof(Master*), &m); } void updateResources(Master *m) @@ -685,9 +562,13 @@ public: //If currently broadcasting messages bool broadcast = false; + //If message should be forwarded through snoop ports + bool forward = false; + //if message is in order or out-of-order execution + bool in_order = false; //If accepting undo events as user driven bool recording_undo = true; - void bToUhandle(const char *rtmsg, bool dummy=false); + void bToUhandle(const char *rtmsg); void tick(void) { @@ -699,86 +580,6 @@ public: } } - bool handlePAD(string path, const char *msg, void *v) - { - if(!v) - return true; - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - DummyDataObj d(buffer, 1024, v, this, uToB); - strcpy(buffer, path.c_str()); - - PADnoteParameters::ports.dispatch(msg, d); - if(!d.matches) { - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); - fprintf(stderr, "Unknown location '%s%s'<%s>\n", - path.c_str(), msg, rtosc_argument_string(msg)); - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - } - - return true; - } - - void handlePresets(const char *msg) - { - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - DummyDataObj d(buffer, 1024, (void*)parent, this, uToB); - strcpy(buffer, "/presets/"); - - //012345678 - ///presets/ - real_preset_ports.dispatch(msg+9, d); - //printf("Something <%s>\n", msg+9); - if(strstr(msg, "paste") && rtosc_argument_string(msg)[0] == 's') { - char buffer[1024]; - rtosc_message(buffer, 1024, "/damage", "s", - rtosc_argument(msg, 0).s); - GUI::raiseUi(ui, buffer); - } - - - if(!d.matches) { - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); - fprintf(stderr, "Unknown location '%s'<%s>\n", - msg, rtosc_argument_string(msg)); - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - } - } - - void handleIo(const char *msg) - { - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - DummyDataObj d(buffer, 1024, (void*)&config, this, uToB); - strcpy(buffer, "/io/"); - - Nio::ports.dispatch(msg+4, d); - if(!d.matches) { - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); - fprintf(stderr, "Unknown location '%s'<%s>\n", - msg, rtosc_argument_string(msg)); - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - } - } - - void handleConfig(const char *msg) - { - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - DummyDataObj d(buffer, 1024, (void*)config, this, uToB); - strcpy(buffer, "/config/"); - - Config::ports.dispatch(msg+8, d); - if(!d.matches) { - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); - fprintf(stderr, "Unknown location '%s'<%s>\n", - msg, rtosc_argument_string(msg)); - fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - } - } - - bool handleOscil(string path, const char *msg, void *v); void kitEnable(const char *msg); void kitEnable(int part, int kit, int type); @@ -786,11 +587,21 @@ public: // Handle an event with special cases void handleMsg(const char *msg); - void write(const char *path, const char *args, ...); void write(const char *path, const char *args, va_list va); + // Send a message to a remote client + void sendToRemote(const char *msg, std::string dest); + // Send a message to the current remote client + void sendToCurrentRemote(const char *msg) + { + sendToRemote(msg, in_order ? curr_url : last_url); + } + // Broadcast a message to all listening remote clients + void broadcastToRemote(const char *msg); + + /* * Provides a mapping for non-RT objects stored inside the backend * - Oscilgen almost all parameters can be safely set @@ -821,8 +632,12 @@ public: std::atomic_int pending_load[NUM_MIDI_PARTS]; std::atomic_int actual_load[NUM_MIDI_PARTS]; + //Undo/Redo rtosc::UndoHistory undo; + //MIDI Learn + rtosc::MidiMappernRT midi_mapper; + //Link To the Realtime rtosc::ThreadLink *bToU; rtosc::ThreadLink *uToB; @@ -833,10 +648,398 @@ public: //Synthesis Rate Parameters const SYNTH_T synth; - + PresetsStore presetsstore; }; +/***************************************************************************** + * Data Object for Non-RT Class Dispatch * + *****************************************************************************/ + +class MwDataObj:public rtosc::RtData +{ + public: + MwDataObj(MiddleWareImpl *mwi_) + { + loc_size = 1024; + loc = new char[loc_size]; + memset(loc, 0, loc_size); + buffer = new char[4*4096]; + memset(buffer, 0, 4*4096); + obj = mwi_; + mwi = mwi_; + forwarded = false; + } + + ~MwDataObj(void) + { + delete[] buffer; + } + + //Replies and broadcasts go to the remote + + //Chain calls repeat the call into handle() + + //Forward calls send the message directly to the realtime + virtual void reply(const char *path, const char *args, ...) + { + //printf("reply building '%s'\n", path); + va_list va; + va_start(va,args); + if(!strcmp(path, "/forward")) { //forward the information to the backend + args++; + path = va_arg(va, const char *); + rtosc_vmessage(buffer,4*4096,path,args,va); + } else { + rtosc_vmessage(buffer,4*4096,path,args,va); + reply(buffer); + } + va_end(va); + } + virtual void reply(const char *msg){ + mwi->sendToCurrentRemote(msg); + }; + //virtual void broadcast(const char *path, const char *args, ...){(void)path;(void)args;}; + //virtual void broadcast(const char *msg){(void)msg;}; + + virtual void chain(const char *msg) override + { + assert(msg); + printf("chain call on <%s>\n", msg); + mwi->handleMsg(msg); + } + + virtual void chain(const char *path, const char *args, ...) override + { + assert(path); + va_list va; + va_start(va,args); + rtosc_vmessage(buffer,4*4096,path,args,va); + chain(buffer); + va_end(va); + } + + virtual void forward(const char *) override + { + forwarded = true; + } + + bool forwarded; + private: + char *buffer; + MiddleWareImpl *mwi; +}; + + + + +static int extractInt(const char *msg) +{ + const char *mm = msg; + while(*mm && !isdigit(*mm)) ++mm; + if(isdigit(*mm)) + return atoi(mm); + return -1; +} + +static const char *chomp(const char *msg) +{ + while(*msg && *msg!='/') ++msg; \ + msg = *msg ? msg+1 : msg; + return msg; +}; + +using rtosc::RtData; +#define rObject Bank +#define rBegin [](const char *msg, RtData &d) { (void)msg;(void)d;\ + rObject &impl = *((rObject*)d.obj);(void)impl; +#define rEnd } +/***************************************************************************** + * Instrument Banks * + * * + * Banks and presets in general are not classed as realtime safe * + * * + * The supported operations are: * + * - Load Names * + * - Load Bank * + * - Refresh List of Banks * + *****************************************************************************/ +rtosc::Ports bankPorts = { + {"rescan:", 0, 0, + rBegin; + impl.rescanforbanks(); + //Send updated banks + int i = 0; + for(auto &elm : impl.banks) + d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str()); + + rEnd}, + {"slot#1024:", 0, 0, + rBegin; + const int loc = extractInt(msg); + if(loc >= BANK_SIZE) + return; + + d.reply("/bankview", "iss", + loc, impl.ins[loc].name.c_str(), + impl.ins[loc].filename.c_str()); + rEnd}, + {"banks:", 0, 0, + rBegin; + int i = 0; + for(auto &elm : impl.banks) + d.reply("/bank/bank_select", "iss", i++, elm.name.c_str(), elm.dir.c_str()); + rEnd}, + {"bank_select::i", 0, 0, + rBegin + if(rtosc_narguments(msg)) { + const int pos = rtosc_argument(msg, 0).i; + d.reply(d.loc, "i", pos); + if(impl.bankpos != pos) { + impl.bankpos = pos; + impl.loadbank(impl.banks[pos].dir); + + //Reload bank slots + for(int i=0; i<BANK_SIZE; ++i) + d.reply("/bankview", "iss", + i, impl.ins[i].name.c_str(), + impl.ins[i].filename.c_str()); + } + } else + d.reply("/bank/bank_select", "i", impl.bankpos); + rEnd}, + {"rename_slot:is", 0, 0, + rBegin; + const int slot = rtosc_argument(msg, 0).i; + const char *name = rtosc_argument(msg, 1).s; + const int err = impl.setname(slot, name, -1); + if(err) { + d.reply("/alert", "s", + "Failed To Rename Bank Slot, please check file permissions"); + } + rEnd}, + {"swap_slots:ii", 0, 0, + rBegin; + const int slota = rtosc_argument(msg, 0).i; + const int slotb = rtosc_argument(msg, 1).i; + const int err = impl.swapslot(slota, slotb); + if(err) + d.reply("/alert", "s", + "Failed To Swap Bank Slots, please check file permissions"); + rEnd}, + {"clear_slot:i", 0, 0, + rBegin; + const int slot = rtosc_argument(msg, 0).i; + const int err = impl.clearslot(slot); + if(err) + d.reply("/alert", "s", + "Failed To Clear Bank Slot, please check file permissions"); + rEnd}, +}; + +/****************************************************************************** + * MiddleWare Snooping Ports * + * * + * These ports handle: * + * - Events going to the realtime thread which cannot be safely handled * + * there * + * - Events generated by the realtime thread which are not destined for a * + * user interface * + ******************************************************************************/ + +#undef rObject +#define rObject MiddleWareImpl +/* + * BASE/part#/kititem# + * BASE/part#/kit#/adpars/voice#/oscil/\* + * BASE/part#/kit#/adpars/voice#/mod-oscil/\* + * BASE/part#/kit#/padpars/prepare + * BASE/part#/kit#/padpars/oscil/\* + */ +static rtosc::Ports middwareSnoopPorts = { + {"part#16/kit#8/adpars/VoicePar#8/OscilSmp/", 0, &OscilGen::non_realtime_ports, + rBegin; + impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); + rEnd}, + {"part#16/kit#8/adpars/VoicePar#8/FMSmp/", 0, &OscilGen::non_realtime_ports, + rBegin + impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); + rEnd}, + {"part#16/kit#8/padpars/", 0, &PADnoteParameters::non_realtime_ports, + rBegin + impl.obj_store.handlePad(chomp(chomp(chomp(msg))), d); + rEnd}, + {"bank/", 0, &bankPorts, + rBegin; + d.obj = &impl.master->bank; + bankPorts.dispatch(chomp(msg),d); + rEnd}, + {"bank/save_to_slot:ii", 0, 0, + rBegin; + const int part_id = rtosc_argument(msg, 0).i; + const int slot = rtosc_argument(msg, 1).i; + + int err = 0; + impl.doReadOnlyOp([&impl,slot,part_id,&err](){ + err = impl.master->bank.savetoslot(slot, impl.master->part[part_id]);}); + if(err) { + char buffer[1024]; + rtosc_message(buffer, 1024, "/alert", "s", + "Failed To Save To Bank Slot, please check file permissions"); + GUI::raiseUi(impl.ui, buffer); + } + rEnd}, + {"config/", 0, &Config::ports, + rBegin; + d.obj = impl.config; + Config::ports.dispatch(chomp(msg), d); + rEnd}, + {"presets/", 0, &real_preset_ports, [](const char *msg, RtData &d) { + MiddleWareImpl *obj = (MiddleWareImpl*)d.obj; + d.obj = (void*)obj->parent; + real_preset_ports.dispatch(chomp(msg), d); + if(strstr(msg, "paste") && rtosc_argument_string(msg)[0] == 's') + d.reply("/damage", "s", rtosc_argument(msg, 0).s); + }}, + {"io/", 0, &Nio::ports, [](const char *msg, RtData &d) { + Nio::ports.dispatch(chomp(msg), d);}}, + {"part*/kit*/{Padenabled,Ppadenabled,Psubenabled}:T:F", 0, 0, + rBegin; + impl.kitEnable(msg); + d.forward(); + rEnd}, + {"save_xlz:s", 0, 0, + rBegin; + const char *file = rtosc_argument(msg, 0).s; + XMLwrapper xml; + saveMidiLearn(xml, impl.midi_mapper); + xml.saveXMLfile(file, impl.master->gzip_compression); + rEnd}, + {"load_xlz:s", 0, 0, + rBegin; + const char *file = rtosc_argument(msg, 0).s; + XMLwrapper xml; + xml.loadXMLfile(file); + loadMidiLearn(xml, impl.midi_mapper); + rEnd}, + {"save_xmz:s", 0, 0, + rBegin; + const char *file = rtosc_argument(msg, 0).s; + //Copy is needed as filename WILL get trashed during the rest of the run + impl.doReadOnlyOp([&impl,file](){ + int res = impl.master->saveXML(file); + (void)res;}); + rEnd}, + {"save_xiz:is", 0, 0, + rBegin; + const int part_id = rtosc_argument(msg,0).i; + const char *file = rtosc_argument(msg,1).s; + impl.savePart(part_id, file); + rEnd}, + {"load_xmz:s", 0, 0, + rBegin; + const char *file = rtosc_argument(msg, 0).s; + impl.loadMaster(file); + rEnd}, + {"reset_master:", 0, 0, + rBegin; + impl.loadMaster(NULL); + rEnd}, + {"load_xiz:is", 0, 0, + rBegin; + const int part_id = rtosc_argument(msg,0).i; + const char *file = rtosc_argument(msg,1).s; + impl.pending_load[part_id]++; + impl.loadPart(part_id, file, impl.master); + rEnd}, + {"load-part:is", 0, 0, + rBegin; + const int part_id = rtosc_argument(msg,0).i; + const char *file = rtosc_argument(msg,1).s; + impl.pending_load[part_id]++; + impl.loadPart(part_id, file, impl.master); + rEnd}, + {"setprogram:i:c", 0, 0, + rBegin; + const int slot = rtosc_argument(msg, 0).i; + impl.pending_load[0]++; + impl.loadPart(0, impl.master->bank.ins[slot].filename.c_str(), impl.master); + rEnd}, + {"part#16/clear:", 0, 0, + rBegin; + impl.loadClearPart(extractInt(msg)); + rEnd}, + {"undo:", 0, 0, + rBegin; + impl.undo.seekHistory(-1); + rEnd}, + {"redo:", 0, 0, + rBegin; + impl.undo.seekHistory(+1); + rEnd}, + {"learn:s", 0, 0, + rBegin; + string addr = rtosc_argument(msg, 0).s; + auto &midi = impl.midi_mapper; + auto map = midi.getMidiMappingStrings(); + if(map.find(addr) != map.end()) + midi.map(addr.c_str(), false); + else + midi.map(addr.c_str(), true); + rEnd}, + //drop this message into the abyss + {"ui/title:", 0, 0, [](const char *msg, RtData &d) {}} +}; + +static rtosc::Ports middlewareReplyPorts = { + {"echo:ss", 0, 0, + rBegin; + const char *type = rtosc_argument(msg, 0).s; + const char *url = rtosc_argument(msg, 1).s; + if(!strcmp(type, "OSC_URL")) + impl.curr_url = url; + rEnd}, + {"free:sb", 0, 0, + rBegin; + const char *type = rtosc_argument(msg, 0).s; + void *ptr = *(void**)rtosc_argument(msg, 1).b.data; + deallocate(type, ptr); + rEnd}, + {"request_memory:", 0, 0, + rBegin; + //Generate out more memory for the RT memory pool + //5MBi chunk + size_t N = 5*1024*1024; + void *mem = malloc(N); + impl.uToB->write("/add-rt-memory", "bi", sizeof(void*), &mem, N); + rEnd}, + {"setprogram:cc:ii", 0, 0, + rBegin; + const int part = rtosc_argument(msg, 0).i; + const int program = rtosc_argument(msg, 1).i; + impl.loadPart(part, impl.master->bank.ins[program].filename.c_str(), impl.master); + rEnd}, + {"undo_pause:", 0, 0, rBegin; impl.recording_undo = false; rEnd}, + {"undo_resume:", 0, 0, rBegin; impl.recording_undo = true; rEnd}, + {"undo_change", 0, 0, + rBegin; + if(impl.recording_undo) + impl.undo.recordEvent(msg); + rEnd}, + {"midi-use-CC:i", 0, 0, + rBegin; + impl.midi_mapper.useFreeID(rtosc_argument(msg, 0).i); + rEnd}, + {"broadcast:", 0, 0, rBegin; impl.broadcast = true; rEnd}, + {"forward:", 0, 0, rBegin; impl.forward = true; rEnd}, +}; +#undef rBegin +#undef rEnd + +/****************************************************************************** + * MiddleWare Implementation * + ******************************************************************************/ + MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, Config* config, int preferrred_port) :parent(mw), config(config), ui(nullptr), synth(std::move(synth_)), @@ -844,6 +1047,8 @@ MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, { bToU = new rtosc::ThreadLink(4096*2,1024); uToB = new rtosc::ThreadLink(4096*2,1024); + midi_mapper.base_ports = &Master::ports; + midi_mapper.rt_cb = [this](const char *msg){handleMsg(msg);}; if(preferrred_port != -1) server = lo_server_new_with_proto(to_s(preferrred_port).c_str(), LO_UDP, liblo_error_cb); @@ -862,9 +1067,6 @@ MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, idle = 0; idle_ptr = 0; -#ifndef PLUGINVERSION - the_bToU = bToU; -#endif master = new Master(synth, config); master->bToU = bToU; master->uToB = uToB; @@ -891,13 +1093,8 @@ MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, }); } -void DummyDataObj::reply(const char *msg) -{ - mwi->bToUhandle(msg, true); -} MiddleWareImpl::~MiddleWareImpl(void) { - warnMemoryLeaks(); if(server) lo_server_free(server); @@ -934,6 +1131,7 @@ MiddleWareImpl::~MiddleWareImpl(void) void MiddleWareImpl::doReadOnlyOp(std::function<void()> read_only_fn) { + assert(uToB); uToB->write("/freeze_state",""); std::list<const char *> fico; @@ -967,126 +1165,74 @@ void MiddleWareImpl::doReadOnlyOp(std::function<void()> read_only_fn) } } -void MiddleWareImpl::bToUhandle(const char *rtmsg, bool dummy) +void MiddleWareImpl::broadcastToRemote(const char *rtmsg) { - assert(strcmp(rtmsg, "/part0/kit0/Ppadenableda")); - assert(strcmp(rtmsg, "/ze_state")); - //Dump Incomming Events For Debugging - if(strcmp(rtmsg, "/vu-meter") && false) { - fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 1 + 30, 0 + 40); - fprintf(stdout, "frontend: '%s'<%s>\n", rtmsg, - rtosc_argument_string(rtmsg)); - fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - } + //Always send to the local UI + sendToRemote(rtmsg, "GUI"); - //Activity dot - //printf(".");fflush(stdout); - - if(!strcmp(rtmsg, "/echo") - && !strcmp(rtosc_argument_string(rtmsg),"ss") - && !strcmp(rtosc_argument(rtmsg,0).s, "OSC_URL")) - curr_url = rtosc_argument(rtmsg,1).s; - else if(!strcmp(rtmsg, "/free") - && !strcmp(rtosc_argument_string(rtmsg),"sb")) { - deallocate(rtosc_argument(rtmsg, 0).s, *((void**)rtosc_argument(rtmsg, 1).b.data)); - } else if(!strcmp(rtmsg, "/request-memory")) { - //Generate out more memory for the RT memory pool - //5MBi chunk - size_t N = 5*1024*1024; - void *mem = malloc(N); - uToB->write("/add-rt-memory", "bi", sizeof(void*), &mem, N); - } else if(!strcmp(rtmsg, "/setprogram") - && !strcmp(rtosc_argument_string(rtmsg),"cc")) { - loadPart(rtosc_argument(rtmsg,0).i, master->bank.ins[rtosc_argument(rtmsg,1).i].filename.c_str(), master); - } else if(!strcmp(rtmsg, "/setbank") - && !strcmp(rtosc_argument_string(rtmsg), "c")) { - loadPendingBank(rtosc_argument(rtmsg,0).i, master->bank); - } else if(!strcmp("/undo_pause", rtmsg)) { - recording_undo = false; - } else if(!strcmp("/undo_resume", rtmsg)) { - recording_undo = true; - } else if(!strcmp("undo_change", rtmsg) && recording_undo) { - undo.recordEvent(rtmsg); - } else if(!strcmp(rtmsg, "/broadcast")) { - broadcast = true; - } else if(broadcast) { - broadcast = false; -#ifdef PLUGINVERSION - if (!curr_url.empty()) // falktx: check added - cb(ui, rtmsg); - - // falktx: changed curr_url to last_url - if(last_url != "GUI") { - lo_message msg = lo_message_deserialise((void*)rtmsg, - rtosc_message_length(rtmsg, bToU->buffer_size()), NULL); - - //Send to known url - if(!last_url.empty()) { - lo_address addr = lo_address_new_from_url(last_url.c_str()); - lo_send_message(addr, rtmsg, msg); - } - } -#else - cb(ui, rtmsg); + //Send to remote UI if there's one listening + if(curr_url != "GUI") + sendToRemote(rtmsg, curr_url); - if(curr_url != "GUI") { - lo_message msg = lo_message_deserialise((void*)rtmsg, - rtosc_message_length(rtmsg, bToU->buffer_size()), NULL); + broadcast = false; +} - //Send to known url - if(!curr_url.empty()) { - lo_address addr = lo_address_new_from_url(curr_url.c_str()); - lo_send_message(addr, rtmsg, msg); - } - } -#endif - } else if((dummy?last_url:curr_url) == "GUI" || !strcmp(rtmsg, "/close-ui")) { +void MiddleWareImpl::sendToRemote(const char *rtmsg, std::string dest) +{ + //printf("sendToRemote(%s:%s,%s)\n", rtmsg, rtosc_argument_string(rtmsg), + // dest.c_str()); + if(dest == "GUI") { cb(ui, rtmsg); - } else{ + } else if(!dest.empty()) { lo_message msg = lo_message_deserialise((void*)rtmsg, rtosc_message_length(rtmsg, bToU->buffer_size()), NULL); //Send to known url - if(!curr_url.empty()) { - lo_address addr = lo_address_new_from_url(dummy?last_url.c_str():curr_url.c_str()); + lo_address addr = lo_address_new_from_url(dest.c_str()); + if(addr) lo_send_message(addr, rtmsg, msg); - } } } -bool MiddleWareImpl::handleOscil(string path, const char *msg, void *v) +/** + * Handle all events coming from the backend + * + * This includes forwarded events which need to be retransmitted to the backend + * after the snooping code inspects the message + */ +void MiddleWareImpl::bToUhandle(const char *rtmsg) { - //printf("handleOscil...\n"); - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - DummyDataObj d(buffer, 1024, v, this, uToB); - strcpy(buffer, path.c_str()); - if(!v) - return true; + //Verify Message isn't a known corruption bug + assert(strcmp(rtmsg, "/part0/kit0/Ppadenableda")); + assert(strcmp(rtmsg, "/ze_state")); - //Paste To Non-Realtime Parameters and then forward - if(strstr(msg, "paste") && !strstr(msg, "padpars")) { + //Dump Incomming Events For Debugging + if(strcmp(rtmsg, "/vu-meter") && false) { + fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 1 + 30, 0 + 40); + fprintf(stdout, "frontend[%c]: '%s'<%s>\n", forward?'f':broadcast?'b':'N', + rtmsg, rtosc_argument_string(rtmsg)); + fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); } - if(!strstr(msg, "padpars")) { - for(auto &p:OscilGen::ports.ports) { - if(strstr(p.name,msg) && strstr(p.metadata, "realtime") && - !strcmp("b", rtosc_argument_string(msg))) { - //printf("sending along packet '%s'...\n", msg); - return false; - } - } - } + //Activity dot + //printf(".");fflush(stdout); - OscilGen::ports.dispatch(msg, d); - if(!d.matches) { - //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); - //fprintf(stderr, "Unknown location '%s%s'<%s>\n", - // path.c_str(), msg, rtosc_argument_string(msg)); - //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); + MwDataObj d(this); + middlewareReplyPorts.dispatch(rtmsg, d, true); + + in_order = true; + //Normal message not captured by the ports + if(d.matches == 0) { + if(forward) { + forward = false; + handleMsg(rtmsg); + } if(broadcast) + broadcastToRemote(rtmsg); + else + sendToCurrentRemote(rtmsg); } + in_order = false; - return true; } //Allocate kits on a as needed basis @@ -1104,8 +1250,7 @@ void MiddleWareImpl::kitEnable(const char *msg) type = 1; else if(strstr(msg, "Psubenabled")) type = 2; - - if(type == -1) + else return; const char *tmp = strstr(msg, "part"); @@ -1148,14 +1293,13 @@ void MiddleWareImpl::kitEnable(int part, int kit, int type) uToB->write(url.c_str(), "b", sizeof(void*), &ptr); } -/* BASE/part#/kititem# - * BASE/part#/kit#/adpars/voice#/oscil/\* - * BASE/part#/kit#/adpars/voice#/mod-oscil/\* - * BASE/part#/kit#/padpars/prepare - * BASE/part#/kit#/padpars/oscil/\* + +/* + * Handle all messages traveling to the realtime side. */ void MiddleWareImpl::handleMsg(const char *msg) { + //Check for known bugs assert(msg && *msg && rindex(msg, '/')[1]); assert(strstr(msg,"free") == NULL || strstr(rtosc_argument_string(msg), "b") == NULL); assert(strcmp(msg, "/part0/Psysefxvol")); @@ -1164,95 +1308,32 @@ void MiddleWareImpl::handleMsg(const char *msg) assert(strcmp(msg, "sysefx0sysefx0/preset")); assert(strcmp(msg, "/sysefx0preset")); assert(strcmp(msg, "Psysefxvol0/part0")); - //fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 6 + 30, 0 + 40); - //fprintf(stdout, "middleware: '%s':%s\n", msg, rtosc_argument_string(msg)); - //fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); + + if(strcmp("/get-vu", msg) && false) { + fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 6 + 30, 0 + 40); + fprintf(stdout, "middleware: '%s':%s\n", msg, rtosc_argument_string(msg)); + fprintf(stdout, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); + } + const char *last_path = rindex(msg, '/'); - if(!last_path) + if(!last_path) { + printf("Bad message in handleMsg() <%s>\n", msg); + assert(false); return; + } + MwDataObj d(this); + middwareSnoopPorts.dispatch(msg, d, true); - //printf("watching '%s' go by\n", msg); - //Get the object resource locator - string obj_rl(msg, last_path+1); - int npart = -1; - char testchr = 0; - - std::function<void(const char*)> bank_cb; - if(last_url == "GUI") - bank_cb = [this](const char *msg){if(osc)osc->tryLink(msg);}; - else - bank_cb = [this](const char *msg){if(osc)osc->tryLink(msg);this->bToUhandle(msg, 1);}; - - if(!strcmp(msg, "/refresh_bank") && !strcmp(rtosc_argument_string(msg), "i")) { - refreshBankView(master->bank, rtosc_argument(msg,0).i, bank_cb); - } else if(!strcmp(msg, "/bank-list") && !strcmp(rtosc_argument_string(msg), "")) { - bankList(master->bank, bank_cb); - } else if(!strcmp(msg, "/rescanforbanks") && !strcmp(rtosc_argument_string(msg), "")) { - rescanForBanks(master->bank, bank_cb); - } else if(!strcmp(msg, "/loadbank") && !strcmp(rtosc_argument_string(msg), "i")) { - loadBank(master->bank, rtosc_argument(msg, 0).i, bank_cb); - } else if(!strcmp(msg, "/loadbank") && !strcmp(rtosc_argument_string(msg), "")) { - bankPos(master->bank, bank_cb); - } else if(obj_store.has(obj_rl)) { - //try some over simplified pattern matching - if(strstr(msg, "oscilgen/") || strstr(msg, "FMSmp/") || strstr(msg, "OscilSmp/")) { - if(!handleOscil(obj_rl, last_path+1, obj_store.get(obj_rl))) - uToB->raw_write(msg); - //else if(strstr(obj_rl.c_str(), "kititem")) - // handleKitItem(obj_rl, objmap[obj_rl],atoi(rindex(msg,'m')+1),rtosc_argument(msg,0).T); - } else if(strstr(msg, "padpars/prepare")) - preparePadSynth(obj_rl,(PADnoteParameters *) obj_store.get(obj_rl), uToB); - else if(strstr(msg, "padpars")) { - if(!handlePAD(obj_rl, last_path+1, obj_store.get(obj_rl))) - uToB->raw_write(msg); - } else //just forward the message - uToB->raw_write(msg); - } else if(strstr(msg, "/save_xmz") && !strcmp(rtosc_argument_string(msg), "s")) { - saveMaster(rtosc_argument(msg,0).s); - } else if(strstr(msg, "/save_xiz") && !strcmp(rtosc_argument_string(msg), "is")) { - savePart(rtosc_argument(msg,0).i,rtosc_argument(msg,1).s); - } else if(strstr(msg, "/load_xmz") && !strcmp(rtosc_argument_string(msg), "s")) { - loadMaster(rtosc_argument(msg,0).s); - } else if(strstr(msg, "/reset_master") && !strcmp(rtosc_argument_string(msg), "")) { - loadMaster(NULL); - } else if(!strcmp(msg, "/load_xiz") && !strcmp(rtosc_argument_string(msg), "is")) { - pending_load[rtosc_argument(msg,0).i]++; - loadPart(rtosc_argument(msg,0).i, rtosc_argument(msg,1).s, master); - } else if(strstr(msg, "load-part") && !strcmp(rtosc_argument_string(msg), "is")) { - pending_load[rtosc_argument(msg,0).i]++; - loadPart(rtosc_argument(msg,0).i, rtosc_argument(msg,1).s, master); - } else if(!strcmp(msg, "/setprogram") - && !strcmp(rtosc_argument_string(msg),"c")) { - pending_load[0]++; - loadPart(0, master->bank.ins[rtosc_argument(msg,0).i].filename.c_str(), master); - } else if(strstr(msg, "save-bank-part") && !strcmp(rtosc_argument_string(msg), "ii")) { - saveBankSlot(rtosc_argument(msg,0).i, rtosc_argument(msg,1).i, master); - } else if(strstr(msg, "bank-rename") && !strcmp(rtosc_argument_string(msg), "is")) { - renameBankSlot(rtosc_argument(msg,0).i, rtosc_argument(msg,1).s, master); - } else if(strstr(msg, "swap-bank-slots") && !strcmp(rtosc_argument_string(msg), "ii")) { - swapBankSlot(rtosc_argument(msg,0).i, rtosc_argument(msg,1).i, master); - } else if(strstr(msg, "clear-bank-slot") && !strcmp(rtosc_argument_string(msg), "i")) { - clearBankSlot(rtosc_argument(msg,0).i, master); - } else if(strstr(msg, "/config/")) { - handleConfig(msg); - } else if(strstr(msg, "/presets/")) { - handlePresets(msg); - } else if(strstr(msg, "/io/")) { - handleIo(msg); - } else if(strstr(msg, "Padenabled") || strstr(msg, "Ppadenabled") || strstr(msg, "Psubenabled")) { - kitEnable(msg); - uToB->raw_write(msg); - } else if(sscanf(msg, "/part%d/clea%c", &npart, &testchr) == 2 && testchr == 'r') { - loadClearPart(npart); - } else if(!strcmp(msg, "/undo")) { - undo.seekHistory(-1); - } else if(!strcmp(msg, "/redo")) { - undo.seekHistory(+1); - } else if(!strcmp(msg, "/ui/title")) { - ;//drop the message into the abyss - } else + //A message unmodified by snooping + if(d.matches == 0 || d.forwarded) { + //if(strcmp("/get-vu", msg)) { + // printf("Message Continuing on<%s:%s>...\n", msg, rtosc_argument_string(msg)); + //} uToB->raw_write(msg); + } else { + //printf("Message Handled<%s:%s>...\n", msg, rtosc_argument_string(msg)); + } } void MiddleWareImpl::write(const char *path, const char *args, ...) @@ -1278,9 +1359,6 @@ void MiddleWareImpl::write(const char *path, const char *args, va_list va) warnx("Failed to write message to '%s'", path); } -void MiddleWareImpl::warnMemoryLeaks(void) -{} - /****************************************************************************** * MidleWare Forwarding Stubs * ******************************************************************************/ @@ -1298,6 +1376,8 @@ void MiddleWare::updateResources(Master *m) } Master *MiddleWare::spawnMaster(void) { + assert(impl->master); + assert(impl->master->uToB); return impl->master; } Fl_Osc_Interface *MiddleWare::spawnUiApi(void) @@ -1343,7 +1423,7 @@ void MiddleWare::transmitMsg(const char *path, const char *args, ...) va_end(va); } -void MiddleWare::transmitMsg(const char *path, const char *args, va_list va) +void MiddleWare::transmitMsg_va(const char *path, const char *args, va_list va) { char buffer[1024]; if(rtosc_vmessage(buffer, 1024, path, args, va)) diff --git a/src/Misc/MiddleWare.h b/src/Misc/MiddleWare.h @@ -32,11 +32,15 @@ class MiddleWare //Handle a rtosc Message uToB void transmitMsg(const char *, const char *args, ...); //Handle a rtosc Message uToB - void transmitMsg(const char *, const char *args, va_list va); + void transmitMsg_va(const char *, const char *args, va_list va); + //Indicate that a bank will be loaded + //NOTE: Can only be called by realtime thread void pendingSetBank(int bank); //Indicate that a program will be loaded on a known part + //NOTE: Can only be called by realtime thread void pendingSetProgram(int part, int program); + //Get/Set the active bToU url std::string activeUrl(void); void activeUrl(std::string u); diff --git a/src/Misc/PresetExtractor.cpp b/src/Misc/PresetExtractor.cpp @@ -32,6 +32,7 @@ const rtosc::Ports real_preset_ports = {"scan-for-presets:", 0, 0, [](const char *, rtosc::RtData &d) { MiddleWare &mw = *(MiddleWare*)d.obj; + assert(d.obj); mw.getPresetsStore().scanforpresets(); auto &pre = mw.getPresetsStore().presets; d.reply(d.loc, "i", pre.size()); @@ -45,6 +46,7 @@ const rtosc::Ports real_preset_ports = {"copy:s:ss:si:ssi", 0, 0, [](const char *msg, rtosc::RtData &d) { MiddleWare &mw = *(MiddleWare*)d.obj; + assert(d.obj); std::string args = rtosc_argument_string(msg); d.reply(d.loc, "s", "clipboard copy..."); printf("\nClipboard Copy...\n"); @@ -65,6 +67,7 @@ const rtosc::Ports real_preset_ports = {"paste:s:ss:si:ssi", 0, 0, [](const char *msg, rtosc::RtData &d) { MiddleWare &mw = *(MiddleWare*)d.obj; + assert(d.obj); std::string args = rtosc_argument_string(msg); d.reply(d.loc, "s", "clipboard paste..."); printf("\nClipboard Paste...\n"); @@ -85,11 +88,13 @@ const rtosc::Ports real_preset_ports = {"clipboard-type:", 0, 0, [](const char *, rtosc::RtData &d) { const MiddleWare &mw = *(MiddleWare*)d.obj; + assert(d.obj); d.reply(d.loc, "s", mw.getPresetsStore().clipboard.type.c_str()); }}, {"delete:s", 0, 0, [](const char *msg, rtosc::RtData &d) { MiddleWare &mw = *(MiddleWare*)d.obj; + assert(d.obj); mw.getPresetsStore().deletepreset(rtosc_argument(msg,0).s); }}, @@ -133,6 +138,7 @@ class Capture:public rtosc::RtData { matches = 0; memset(locbuf, 0, sizeof(locbuf)); + memset(msgbuf, 0, sizeof(msgbuf)); loc = locbuf; loc_size = sizeof(locbuf); obj = obj_; @@ -193,7 +199,9 @@ std::string doCopy(MiddleWare &mw, string url, string name) mw.doReadOnlyOp([&xml, url, name, &mw](){ Master *m = mw.spawnMaster(); //Get the pointer + printf("capture at <%s>\n", (url+"self").c_str()); T *t = (T*)capture<void*>(m, url+"self"); + assert(t); //Extract Via mxml //t->add2XML(&xml); t->copy(mw.getPresetsStore(), name.empty()? NULL:name.c_str()); @@ -303,6 +311,7 @@ void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string ur std::string doClassCopy(std::string type, MiddleWare &mw, string url, string name) { + printf("doClassCopy(%p)\n", mw.spawnMaster()->uToB); if(type == "EnvelopeParams") return doCopy<EnvelopeParams>(mw, url, name); else if(type == "LFOParams") diff --git a/src/Misc/XMLwrapper.cpp b/src/Misc/XMLwrapper.cpp @@ -632,3 +632,55 @@ mxml_node_t *XMLwrapper::addparams(const char *name, unsigned int params, } return element; } + +XmlNode::XmlNode(std::string name_) + :name(name_) +{} + +std::string &XmlNode::operator[](std::string name) +{ + //fetch an existing one + for(auto &a:attrs) + if(a.name == name) + return a.value; + + //create a new one + attrs.push_back({name, ""}); + return attrs[attrs.size()-1].value; +} + +bool XmlNode::has(std::string name_) +{ + //fetch an existing one + for(auto &a:attrs) + if(a.name == name_) + return true; + return false; +} + +void XMLwrapper::add(const XmlNode &node_) +{ + mxml_node_t *element = mxmlNewElement(node, node_.name.c_str()); + for(auto attr:node_.attrs) + mxmlElementSetAttr(element, attr.name.c_str(), + attr.value.c_str()); +} + +std::vector<XmlNode> XMLwrapper::getBranch(void) const +{ + std::vector<XmlNode> res; + mxml_node_t *current = node->child; + while(current) { + if(current->type == MXML_ELEMENT) { + auto elm = current->value.element; + XmlNode n(elm.name); + for(int i=0; i<elm.num_attrs; ++i) { + auto &attr = elm.attrs[i]; + n[attr.name] = attr.value; + } + res.push_back(n); + } + current = mxmlWalkNext(current, node, MXML_NO_DESCEND); + } + return res; +} diff --git a/src/Misc/XMLwrapper.h b/src/Misc/XMLwrapper.h @@ -24,13 +24,30 @@ #include <mxml.h> #include <string> -#ifndef float -#define float float -#endif +#include <vector> #ifndef XML_WRAPPER_H #define XML_WRAPPER_H +class XmlAttr +{ + public: + std::string name; + std::string value; +}; + + +class XmlNode +{ + public: + XmlNode(std::string name_); + std::string name; + std::vector<XmlAttr> attrs; + + std::string &operator[](std::string name); + bool has(std::string); +}; + /**Mxml wrapper*/ class XMLwrapper { @@ -220,6 +237,10 @@ class XMLwrapper */ bool hasPadSynth() const; + void add(const XmlNode &node); + + std::vector<XmlNode> getBranch(void) const; + private: /** diff --git a/src/Params/ADnoteParameters.cpp b/src/Params/ADnoteParameters.cpp @@ -42,8 +42,27 @@ using rtosc::RtData; #define rObject ADnoteVoiceParam static const Ports voicePorts = { - rRecurp(OscilSmp, "Primary Oscillator"), - rRecurp(FMSmp, "Modulating Oscillator"), + //Send Messages To Oscillator Realtime Table + {"OscilSmp/", rDoc("Primary Oscillator"), + &OscilGen::ports, + rBOIL_BEGIN + if(obj->OscilSmp == NULL) return; + data.obj = obj->OscilSmp; + SNIP + OscilGen::realtime_ports.dispatch(msg, data); + if(data.matches == 0) + data.forward(); + rBOIL_END}, + {"FMSmp/", rDoc("Modulating Oscillator"), + &OscilGen::ports, + rBOIL_BEGIN + if(obj->FMSmp == NULL) return; + data.obj = obj->FMSmp; + SNIP + OscilGen::realtime_ports.dispatch(msg, data); + if(data.matches == 0) + data.forward(); + rBOIL_END}, rRecurp(FreqLfo, "Frequency LFO"), rRecurp(AmpLfo, "Amplitude LFO"), rRecurp(FilterLfo, "Filter LFO"), @@ -107,7 +126,7 @@ static const Ports voicePorts = { rToggle(PFMFreqEnvelopeEnabled, "Modulator Frequency Envelope"), rToggle(PFMAmpEnvelopeEnabled, "Modulator Amplitude Envelope"), - + //weird stuff for PCoarseDetune {"detunevalue:", rMap(unit,cents) rDoc("Get detune in cents"), NULL, [](const char *, RtData &d) @@ -148,7 +167,7 @@ static const Ports voicePorts = { obj->PCoarseDetune = k + (obj->PCoarseDetune/1024)*1024; } }}, - + //weird stuff for PCoarseDetune {"FMdetunevalue:", rMap(unit,cents) rDoc("Get modulator detune"), NULL, [](const char *, RtData &d) { diff --git a/src/Params/PADnoteParameters.cpp b/src/Params/PADnoteParameters.cpp @@ -34,113 +34,41 @@ using namespace rtosc; -#define PC(x) rParamZyn(P##x, "undocumented padnote parameter") - -template<int i> -void simpleset(const char *m, rtosc::RtData &d) -{ - unsigned char *addr = ((unsigned char*) d.obj)+i; - if(!rtosc_narguments(m)) - d.reply(d.loc, "c", *addr); - else - *addr = rtosc_argument(m, 0).i; -} - #define rObject PADnoteParameters - -#define P_C(x) rtosc::Port{#x "::c", "::", NULL, \ - simpleset<__builtin_offsetof(class PADnoteParameters, P##x)>} -static const rtosc::Ports PADnotePorts = +static const rtosc::Ports realtime_ports = { - rRecurp(oscilgen, "Oscillator"), rRecurp(FreqLfo, "Frequency LFO"), rRecurp(AmpLfo, "Amplitude LFO"), rRecurp(FilterLfo, "Filter LFO"), - rRecurp(resonance, "Resonance"), rRecurp(FreqEnvelope, "Frequency Envelope"), rRecurp(AmpEnvelope, "Amplitude Envelope"), rRecurp(FilterEnvelope, "Filter Envelope"), rRecurp(GlobalFilter, "Post Filter"), - rParamI(Pmode, rMap(min, 0), rMap(max, 2), "0 - bandwidth, 1 - discrete 2 - continious"), - PC(Volume), - PC(hp.base.type), - PC(hp.base.par1), - PC(hp.freqmult), - PC(hp.modulator.par1), - PC(hp.modulator.freq), - PC(hp.width), - PC(hp.amp.mode), - PC(hp.amp.type), - PC(hp.amp.par1), - PC(hp.amp.par2), - rToggle(Php.autoscale, "Autoscaling Harmonics"), - PC(hp.onehalf), - - PC(bwscale), - - PC(hrpos.type), - PC(hrpos.par1), - PC(hrpos.par2), - PC(hrpos.par3), - - PC(quality.samplesize), - PC(quality.basenote), - PC(quality.oct), - PC(quality.smpoct), - - PC(fixedfreq), - PC(fixedfreqET), - PC(Stereo), - PC(Panning), - PC(AmpVelocityScaleFunction), - PC(PunchStrength), - PC(PunchTime), - PC(PunchStretch), - PC(PunchVelocitySensing), - PC(FilterVelocityScale), - PC(FilterVelocityScaleFunction), + + //Volume + rToggle(PStereo, "Stereo/Mono Mode"), + rParamZyn(PPanning, "Left Right Panning"), + rParamZyn(PVolume, "Synth Volume"), + rParamZyn(PAmpVelocityScaleFunction, "Amplitude Velocity Sensing function"), + + //Punch + rParamZyn(PPunchStrength, "Punch Strength"), + rParamZyn(PPunchTime, "UNKNOWN"), + rParamZyn(PPunchStretch, "How Punch changes with note frequency"), + rParamZyn(PPunchVelocitySensing, "Punch Velocity control"), + + //Filter + rParamZyn(PFilterVelocityScale, "Filter Velocity Magnitude"), + rParamZyn(PFilterVelocityScaleFunction, "Filter Velocity Function Shape"), + + //Freq + rToggle(Pfixedfreq, "Base frequency fixed frequency enable"), + rParamZyn(PfixedfreqET, "Equal temeperate control for fixed frequency operation"), rParamI(PDetune, "Fine Detune"), rParamI(PCoarseDetune, "Coarse Detune"), rParamZyn(PDetuneType, "Magnitude of Detune"), - {"Pbandwidth::i", rProp(parameter) rDoc("Bandwith Of Harmonics"), NULL, - [](const char *msg, rtosc::RtData &d) { - PADnoteParameters *p = ((PADnoteParameters*)d.obj); - if(rtosc_narguments(msg)) { - p->setPbandwidth(rtosc_argument(msg, 0).i); - } else { - d.reply(d.loc, "i", p->Pbandwidth); - }}}, - - {"bandwidthvalue:", rMap(unit, cents) rDoc("Get Bandwidth"), NULL, - [](const char *, rtosc::RtData &d) { - PADnoteParameters *p = ((PADnoteParameters*)d.obj); - d.reply(d.loc, "f", p->setPbandwidth(p->Pbandwidth)); - }}, - - - {"nhr:", rProp(non-realtime) rDoc("Returns the harmonic shifts"), - NULL, [](const char *, rtosc::RtData &d) { - PADnoteParameters *p = ((PADnoteParameters*)d.obj); - const unsigned n = p->synth.oscilsize / 2; - float *tmp = new float[n]; - *tmp = 0; - for(unsigned i=1; i<n; ++i) - tmp[i] = p->getNhr(i); - d.reply(d.loc, "b", n*sizeof(float), tmp); - delete[] tmp;}}, - {"profile:i", rProp(non-realtime) rDoc("UI display of the harmonic profile"), - NULL, [](const char *m, rtosc::RtData &d) { - PADnoteParameters *p = ((PADnoteParameters*)d.obj); - const int n = rtosc_argument(m, 0).i; - if(n<=0) - return; - float *tmp = new float[n]; - float realbw = p->getprofile(tmp, n); - d.reply(d.loc, "b", n*sizeof(float), tmp); - d.reply(d.loc, "i", realbw); - delete[] tmp;}}, {"sample#64:ifb", rProp(internal) rDoc("Nothing to see here"), 0, [](const char *m, rtosc::RtData &d) { @@ -189,9 +117,118 @@ static const rtosc::Ports PADnotePorts = obj->PCoarseDetune = k + (obj->PCoarseDetune/1024)*1024; } }}, + +}; +static const rtosc::Ports non_realtime_ports = +{ + //Harmonic Source Distribution + rRecurp(oscilgen, "Oscillator"), + rRecurp(resonance, "Resonance"), + + //Harmonic Shape + rOption(Pmode, rMap(min, 0), rMap(max, 2), rOptions(bandwidth,discrete,continious), + "Harmonic Distribution Model"), + rOption(Php.base.type, rOptions(Gaussian, Rectanglar, Double Exponential), + "Harmonic profile shape"), + rParamZyn(Php.base.par1, "Harmonic shape distribution parameter"), + rParamZyn(Php.freqmult, "Frequency multiplier on distribution"), + rParamZyn(Php.modulator.par1, "Distribution modulator parameter"), + rParamZyn(Php.modulator.freq, "Frequency of modulator parameter"), + rParamZyn(Php.width, "Width of base harmonic"), + rOption(Php.amp.mode, rOptions(Sum, Mult, Div1, Div2), + "Amplitude harmonic multiplier type"), + + //Harmonic Modulation + rOption(Php.amp.type, rOptions(Off, Gauss, Sine, Flat), + "Type of amplitude multipler"), + rParamZyn(Php.amp.par1, "Amplitude multiplier parameter"), + rParamZyn(Php.amp.par2, "Amplitude multiplier parameter"), + rToggle(Php.autoscale, "Autoscaling Harmonics"), + rOption(Php.onehalf, + rOptions(Full, Upper Half, Lower Half), + "Harmonic cutoff model"), + + //Harmonic Bandwidth + rOption(Pbwscale, + rOptions(Normal, + EqualHz, Quater, + Half, 75%, 150%, + Double, Inv. Half), + "Bandwidth scaling"), + + //Harmonic Position Modulation + rOption(Phrpos.type, + rOptions(Harmonic, ShiftU, ShiftL, PowerU, PowerL, Sine, + Power, Shift), + "Harmonic Overtone shifting mode"), + rParamZyn(Phrpos.par1, "Harmonic position parameter"), + rParamZyn(Phrpos.par2, "Harmonic position parameter"), + rParamZyn(Phrpos.par3, "Harmonic position parameter"), + + //Quality + rOption(Pquality.samplesize, + rOptions(16k (Tiny), 32k, 64k (Small), 128k, + 256k (Normal), 512k, 1M (Big)), + "Size of each wavetable element"), + rOption(Pquality.basenote, + rOptions( C-2, G-2, C-3, G-3, C-4, + G-4, C-5, G-5, G-6,), + "Base note for wavetable"), + rOption(Pquality.smpoct, + rOptions(0.5, 1, 2, 3, 4, 6, 12), + "Samples per octave"), + rParamI(Pquality.oct, rLinear(0,7), + "Number of octaves to sample (above the first sample"), + + {"Pbandwidth::i", rProp(parameter) rLinear(0,1000) rDoc("Bandwith Of Harmonics"), NULL, + [](const char *msg, rtosc::RtData &d) { + PADnoteParameters *p = ((PADnoteParameters*)d.obj); + if(rtosc_narguments(msg)) { + p->setPbandwidth(rtosc_argument(msg, 0).i); + } else { + d.reply(d.loc, "i", p->Pbandwidth); + }}}, + + {"bandwidthvalue:", rMap(unit, cents) rDoc("Get Bandwidth"), NULL, + [](const char *, rtosc::RtData &d) { + PADnoteParameters *p = ((PADnoteParameters*)d.obj); + d.reply(d.loc, "f", p->setPbandwidth(p->Pbandwidth)); + }}, + + + {"nhr:", rProp(non-realtime) rDoc("Returns the harmonic shifts"), + NULL, [](const char *, rtosc::RtData &d) { + PADnoteParameters *p = ((PADnoteParameters*)d.obj); + const unsigned n = p->synth.oscilsize / 2; + float *tmp = new float[n]; + *tmp = 0; + for(unsigned i=1; i<n; ++i) + tmp[i] = p->getNhr(i); + d.reply(d.loc, "b", n*sizeof(float), tmp); + delete[] tmp;}}, + {"profile:i", rProp(non-realtime) rDoc("UI display of the harmonic profile"), + NULL, [](const char *m, rtosc::RtData &d) { + PADnoteParameters *p = ((PADnoteParameters*)d.obj); + const int n = rtosc_argument(m, 0).i; + if(n<=0) + return; + float *tmp = new float[n]; + float realbw = p->getprofile(tmp, n); + d.reply(d.loc, "b", n*sizeof(float), tmp); + d.reply(d.loc, "i", realbw); + delete[] tmp;}}, +}; + +const rtosc::Ports &PADnoteParameters::non_realtime_ports = ::non_realtime_ports; +const rtosc::Ports &PADnoteParameters::realtime_ports = ::realtime_ports; + + +const rtosc::MergePorts PADnoteParameters::ports = +{ + &non_realtime_ports, + &realtime_ports }; -const rtosc::Ports &PADnoteParameters::ports = PADnotePorts; PADnoteParameters::PADnoteParameters(const SYNTH_T &synth_, FFTwrapper *fft_) :Presets(), synth(synth_) diff --git a/src/Params/PADnoteParameters.h b/src/Params/PADnoteParameters.h @@ -165,7 +165,9 @@ class PADnoteParameters:public Presets void sampleGenerator(PADnoteParameters::callback cb, std::function<bool()> do_abort); - static const rtosc::Ports &ports; + static const rtosc::MergePorts ports; + static const rtosc::Ports &non_realtime_ports; + static const rtosc::Ports &realtime_ports; private: void generatespectrum_bandwidthMode(float *spectrum, diff --git a/src/Synth/OscilGen.cpp b/src/Synth/OscilGen.cpp @@ -38,10 +38,8 @@ pthread_t main_thread; -#define PC(x) rParamZyn(P##x, "undocumented oscilgen parameter") - #define rObject OscilGen -const rtosc::Ports OscilGen::ports = { +const rtosc::Ports OscilGen::non_realtime_ports = { rSelf(OscilGen), rPaste, //TODO ensure min/max @@ -51,21 +49,10 @@ const rtosc::Ports OscilGen::ports = { dB scale (-100)), "Type of magnitude for harmonics"), rOption(Pcurrentbasefunc, - rOptions(sine, triangle, - pulse, - saw, - power, - gauss, - diode, - abssine, - pulsesine, - stretchsine, - chirp, - absstretchsine, - chebyshev, - sqr, - spike, - circle), rOpt(127,use-as-base waveform), + rOptions(sine, triangle, pulse, saw, power, gauss, + diode, abssine, pulsesine, stretchsine, + chirp, absstretchsine, chebyshev, sqr, + spike, circle), rOpt(127,use-as-base waveform), "Base Waveform for harmonics"), rParamZyn(Pbasefuncpar, "Morph between possible base function shapes " @@ -82,58 +69,32 @@ const rtosc::Ports OscilGen::ports = { rParamZyn(Pwaveshaping, "Degree Of Waveshaping"), rOption(Pwaveshapingfunction, rOptions(Undistorted, - Arctangent, - Asymmetric, - Pow, - Sine, - Quantisize, - Zigzag, - Limiter, - Upper Limiter, - Lower Limiter, - Inverse Limiter, - Clip, - Asym2, - Pow2, - sigmoid), "Shape of distortion to be applied"), + Arctangent, Asymmetric, Pow, Sine, Quantisize, + Zigzag, Limiter, Upper Limiter, Lower Limiter, + Inverse Limiter, Clip, Asym2, Pow2, sigmoid), + "Shape of distortion to be applied"), rOption(Pfiltertype, rOptions(No Filter, lp, hp1, hp1b, bp1, bs1, lp2, hp2, bp2, bs2, cos, sin, low_shelf, s), "Harmonic Filter"), - PC(filterpar1), - PC(filterpar2), + rParamZyn(Pfilterpar1, "Filter parameter"), + rParamZyn(Pfilterpar2, "Filter parameter"), rToggle(Pfilterbeforews, "Filter before waveshaping spectra;" "When enabled oscilfilter(freqs); then waveshape(freqs);, " "otherwise waveshape(freqs); then oscilfilter(freqs);"), - PC(satype), + rOption(Psatype, rOptions(None, Pow, ThrsD, ThrsU), + "Spectral Adjustment Type"), rParamZyn(Psapar, "Spectral Adjustment Parameter"), rParamI(Pharmonicshift, "Amount of shift on harmonics"), rToggle(Pharmonicshiftfirst, "If harmonics are shifted before waveshaping/filtering"), rOption(Pmodulation, rOptions(None, Rev, Sine, Power), "Frequency Modulation To Combined Spectra"), - rParamZyn(Pmodulationpar1, - "modulation parameter"), - rParamZyn(Pmodulationpar2, - "modulation parameter"), - rParamZyn(Pmodulationpar3, - "modulation parameter"), - //FIXME realtime parameters lurking below - PC(rand), - rParamZyn(Pamprandpower, - "Variance of harmonic randomness"), - rOption(Pamprandtype, rOptions(None, Pow, Sin), - "Harmonic random distribution to select from"), - rOption(Padaptiveharmonics, - rOptions(OFF, ON, Square, 2xSub, 2xAdd, 3xSub, 3xAdd, 4xSub, 4xAdd), - "Adaptive Harmonics Mode"), - rParamI(Padaptiveharmonicsbasefreq, rLinear(0,255), - "Base frequency of adaptive harmonic (30..3000Hz)"), - rParamI(Padaptiveharmonicspower,rLinear(0,200), - "Adaptive Harmonic Strength"), - rParamZyn(Padaptiveharmonicspar, - "Adaptive Harmonics Postprocessing Power"), + rParamZyn(Pmodulationpar1, "modulation parameter"), + rParamZyn(Pmodulationpar2, "modulation parameter"), + rParamZyn(Pmodulationpar3, "modulation parameter"), + //TODO update to rArray and test - {"phase#128::c", rProp(parameter) rDoc("Sets harmonic phase"), + {"phase#128::c:i", rProp(parameter) rLinear(0,127) rDoc("Sets harmonic phase"), NULL, [](const char *m, rtosc::RtData &d) { const char *mm = m; while(*mm && !isdigit(*mm)) ++mm; @@ -144,7 +105,7 @@ const rtosc::Ports OscilGen::ports = { phase = rtosc_argument(m,0).i; }}, //TODO update to rArray and test - {"magnitude#128::c", rProp(parameter) rDoc("Sets harmonic magnitude"), + {"magnitude#128::c:i", rProp(parameter) rLinear(0,127) rDoc("Sets harmonic magnitude"), NULL, [](const char *m, rtosc::RtData &d) { //printf("I'm at '%s'\n", d.loc); const char *mm = m; @@ -152,8 +113,20 @@ const rtosc::Ports OscilGen::ports = { unsigned char &mag = ((OscilGen*)d.obj)->Phmag[atoi(mm)]; if(!rtosc_narguments(m)) d.reply(d.loc, "c", mag); - else + else { mag = rtosc_argument(m,0).i; + printf("setting magnitude\n\n"); + //XXX hack hack + char *repath = strdup(d.loc); + char *edit = rindex(repath, '/')+1; + strcpy(edit, "prepare"); + OscilGen &o = *((OscilGen*)d.obj); + fft_t *data = new fft_t[o.synth.oscilsize / 2]; + o.prepare(data); + fprintf(stderr, "sending '%p' of fft data\n", data); + d.chain(repath, "b", sizeof(fft_t*), &data); + o.pendingfreqs = data; + } }}, {"base-spectrum:", rProp(non-realtime) rDoc("Returns spectrum of base waveshape"), NULL, [](const char *, rtosc::RtData &d) { @@ -203,8 +176,8 @@ const rtosc::Ports OscilGen::ports = { OscilGen &o = *(OscilGen*)d.obj; fft_t *data = new fft_t[o.synth.oscilsize / 2]; o.prepare(data); - //fprintf(stderr, "sending '%p' of fft data\n", data); - d.reply("/forward", "sb", d.loc, sizeof(fft_t*), &data); + fprintf(stderr, "sending '%p' of fft data\n", data); + d.chain(d.loc, "b", sizeof(fft_t*), &data); o.pendingfreqs = data; }}, {"convert2sine:", rProp(non-realtime) rDoc("Translates waveform into FS"), @@ -214,20 +187,42 @@ const rtosc::Ports OscilGen::ports = { {"use-as-base:", rProp(non-realtime) rDoc("Translates current waveform into base"), NULL, [](const char *, rtosc::RtData &d) { ((OscilGen*)d.obj)->useasbase(); - }}, - {"prepare:b", rProp(internal) rProp(non-realtime) rProp(pointer) rDoc("Sets prepared fft data"), + }}}; + +#define rForwardCb [](const char *msg, rtosc::RtData &d) {\ + printf("fowarding...\n"); d.forward();} +const rtosc::Ports OscilGen::realtime_ports{ + rSelf(OscilGen), + rParamZyn(Prand, "Oscilator Phase Randomness: smaller than 0 is \"" + "group\", larger than 0 is for each harmonic"), + rParamZyn(Pamprandpower, + "Variance of harmonic randomness"), + rOption(Pamprandtype, rOptions(None, Pow, Sin), + "Harmonic random distribution to select from"), + rOption(Padaptiveharmonics, + rOptions(OFF, ON, Square, 2xSub, 2xAdd, 3xSub, 3xAdd, 4xSub, 4xAdd), + "Adaptive Harmonics Mode"), + rParamI(Padaptiveharmonicsbasefreq, rLinear(0,255), + "Base frequency of adaptive harmonic (30..3000Hz)"), + rParamI(Padaptiveharmonicspower,rLinear(0,200), + "Adaptive Harmonic Strength"), + rParamZyn(Padaptiveharmonicspar, + "Adaptive Harmonics Postprocessing Power"), + {"prepare:b", rProp(internal) rProp(realtime) rProp(pointer) rDoc("Sets prepared fft data"), NULL, [](const char *m, rtosc::RtData &d) { - //fprintf(stderr, "prepare:b got a message from '%s'\n", m); + fprintf(stderr, "prepare:b got a message from '%s'\n", m); OscilGen &o = *(OscilGen*)d.obj; assert(rtosc_argument(m,0).b.len == sizeof(void*)); d.reply("/free", "sb", "fft_t", sizeof(void*), &o.oscilFFTfreqs); - //fprintf(stderr, "\n\n"); - //fprintf(stderr, "The ID of this of this thread is: %ld\n", (long)pthread_self()); - //fprintf(stderr, "o.oscilFFTfreqs = %p\n", o.oscilFFTfreqs); - assert(main_thread != pthread_self()); assert(o.oscilFFTfreqs !=*(fft_t**)rtosc_argument(m,0).b.data); o.oscilFFTfreqs = *(fft_t**)rtosc_argument(m,0).b.data; }}, + +}; + +const rtosc::MergePorts OscilGen::ports{ + &OscilGen::realtime_ports, + &OscilGen::non_realtime_ports }; diff --git a/src/Synth/OscilGen.h b/src/Synth/OscilGen.h @@ -114,7 +114,9 @@ class OscilGen:public Presets bool ADvsPAD; //if it is used by ADsynth or by PADsynth - static const rtosc::Ports ports; + static const rtosc::MergePorts ports; + static const rtosc::Ports non_realtime_ports; + static const rtosc::Ports realtime_ports; /* Oscillator Frequencies - * this is different than the hamonics set-up by the user, diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt @@ -14,6 +14,7 @@ CXXTEST_ADD_TEST(RandTest RandTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RandTest.h) CXXTEST_ADD_TEST(PADnoteTest PadNoteTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PadNoteTest.h) CXXTEST_ADD_TEST(PluginTest PluginTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PluginTest.h) CXXTEST_ADD_TEST(MiddlewareTest MiddlewareTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MiddlewareTest.h) +CXXTEST_ADD_TEST(MessageTest MessageTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MessageTest.h) CXXTEST_ADD_TEST(UnisonTest UnisonTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UnisonTest.h) #CXXTEST_ADD_TEST(RtAllocTest RtAllocTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RtAllocTest.h) CXXTEST_ADD_TEST(AllocatorTest AllocatorTest.cpp @@ -41,6 +42,9 @@ target_link_libraries(PluginTest zynaddsubfx_core zynaddsubfx_nio target_link_libraries(MiddlewareTest zynaddsubfx_core zynaddsubfx_nio zynaddsubfx_gui_bridge ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}) +target_link_libraries(MessageTest zynaddsubfx_core zynaddsubfx_nio + zynaddsubfx_gui_bridge + ${GUI_LIBRARIES} ${NIO_LIBRARIES} ${AUDIO_LIBRARIES}) target_link_libraries(UnisonTest ${test_lib}) #target_link_libraries(RtAllocTest ${test_lib}) target_link_libraries(AllocatorTest ${test_lib}) diff --git a/src/Tests/MessageTest.h b/src/Tests/MessageTest.h @@ -0,0 +1,212 @@ +/* + ZynAddSubFX - a software synthesizer + + PluginTest.h - CxxTest for embedding zyn + Copyright (C) 2013-2013 Mark McCurry + Authors: Mark McCurry + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License (version 2 or later) for more details. + + You should have received a copy of the GNU General Public License (version 2) + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +#include <cxxtest/TestSuite.h> +#include <cmath> +#include <cstdlib> +#include <iostream> +#include <fstream> +#include <string> +#include <thread> +#include <rtosc/thread-link.h> +#include <unistd.h> +#include "../Misc/MiddleWare.h" +#include "../Misc/Master.h" +#include "../Misc/Part.h" +#include "../Misc/PresetExtractor.h" +#include "../Misc/PresetExtractor.cpp" +#include "../Misc/Util.h" +#include "../globals.h" +#include "../UI/NSM.H" +class NSM_Client *nsm = 0; +MiddleWare *middleware = 0; + +using namespace std; + +char *instance_name=(char*)""; + +#define NUM_MIDDLEWARE 3 + +class MessageTest:public CxxTest::TestSuite +{ + public: + Config config; + void setUp() { + synth = new SYNTH_T; + mw = new MiddleWare(std::move(*synth), &config); + ms = mw->spawnMaster(); + realtime = NULL; + } + + void tearDown() { + delete mw; + delete synth; + } + +#if 0 + void _testKitEnable(void) + { + const char *msg = NULL; + mw->transmitMsg("/part0/kit0/Psubenabled", "T"); + TS_ASSERT(ms->uToB->hasNext()); + msg = ms->uToB->read(); + TS_ASSERT_EQUALS(string("/part0/kit0/subpars-data"), msg); + TS_ASSERT(ms->uToB->hasNext()); + msg = ms->uToB->read(); + TS_ASSERT_EQUALS(string("/part0/kit0/Psubenabled"), msg); + } + + void _testBankCapture(void) + { + mw->transmitMsg("/bank/slots", ""); + TS_ASSERT(!ms->uToB->hasNext()); + mw->transmitMsg("/bank/fake", ""); + TS_ASSERT(ms->uToB->hasNext()); + const char *msg = ms->uToB->read(); + TS_ASSERT_EQUALS(string("/bank/fake"), msg); + } + + void _testOscCopyPaste(void) + { + //Enable pad synth + mw->transmitMsg("/part0/kit0/Ppadenabled", "T"); + + TS_ASSERT(ms->uToB->hasNext()); + ms->applyOscEvent(ms->uToB->read()); + TS_ASSERT(ms->uToB->hasNext()); + ms->applyOscEvent(ms->uToB->read()); + TS_ASSERT(!ms->uToB->hasNext()); + + ms->part[0]->kit[0].adpars->VoicePar[0].FMSmp->Pbasefuncpar = 32; + + int do_exit = 0; + std::thread t([&do_exit,this](){ + int tries = 0; + while(tries < 10000 && do_exit == 0) { + if(!ms->uToB->hasNext()) { + usleep(500); + continue; + } + const char *msg = ms->uToB->read(); + printf("RT: handling <%s>\n", msg); + ms->applyOscEvent(msg); + }}); + + //Copy From ADsynth modulator + printf("====Copy From ADsynth modulator\n"); + mw->transmitMsg("/presets/copy", "s", "/part0/kit0/adpars/VoicePar0/FMSmp/"); + + TS_ASSERT(ms->part[0]->kit[0].padpars->oscilgen->Pbasefuncpar != 32); + //Paste to PADsynth + printf("====Paste to PADsynth\n"); + mw->transmitMsg("/presets/paste", "s", "/part0/kit0/padpars/oscilgen/"); + do_exit = 1; + t.join(); + TS_ASSERT_EQUALS(ms->part[0]->kit[0].padpars->oscilgen->Pbasefuncpar, 32); + } +#endif + + void start_realtime(void) + { + do_exit = false; + realtime = new std::thread([this](){ + int tries = 0; + while(tries < 10000) { + if(!ms->uToB->hasNext()) { + if(do_exit) + break; + + usleep(500); + continue; + } + const char *msg = ms->uToB->read(); + printf("RT: handling <%s>\n", msg); + ms->applyOscEvent(msg); + }}); + } + + void stop_realtime(void) + { + do_exit = true; + realtime->join(); + delete realtime; + realtime = NULL; + } + + void run_realtime(void) + { + start_realtime(); + stop_realtime(); + } + + void testMidiLearn(void) + { + mw->transmitMsg("/learn", "s", "/Pvolume"); + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 108); + TS_ASSERT_EQUALS(ms->Pvolume, 80); + + //Perform a learning operation + + run_realtime(); //1. runs learning and identifies a CC to bind + mw->tick(); //2. produces new binding table + run_realtime(); //3. applies new binding table + + + //Verify that the learning actually worked + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 13); + run_realtime(); + TS_ASSERT_EQUALS(ms->Pvolume, 13); + + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 2); + run_realtime(); + TS_ASSERT_EQUALS(ms->Pvolume, 2); + + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 0); + run_realtime(); + TS_ASSERT_EQUALS(ms->Pvolume, 0); + + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 127); + run_realtime(); + TS_ASSERT_EQUALS(ms->Pvolume, 127); + } + + void testMidiLearnSave(void) + { + mw->transmitMsg("/learn", "s", "/Pvolume"); + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 108); + + //Perform a learning operation + + run_realtime(); //1. runs learning and identifies a CC to bind + mw->tick(); //2. produces new binding table + run_realtime(); //3. applies new binding table + + mw->transmitMsg("/save_xlz", "s", "test-midi-learn.xlz"); + mw->transmitMsg("/load_xlz", "s", "test-midi-learn.xlz"); + } + + private: + SYNTH_T *synth; + MiddleWare *mw; + Master *ms; + std::thread *realtime; + bool do_exit; +}; diff --git a/src/Tests/MiddlewareTest.h b/src/Tests/MiddlewareTest.h @@ -39,6 +39,8 @@ using namespace std; char *instance_name=(char*)""; +#define NUM_MIDDLEWARE 3 + class PluginTest:public CxxTest::TestSuite { public: @@ -58,19 +60,19 @@ class PluginTest:public CxxTest::TestSuite delete synth; synth = NULL; - for(int i = 0; i < 16; ++i) { + for(int i = 0; i < NUM_MIDDLEWARE; ++i) { synth = new SYNTH_T; synth->buffersize = 256; synth->samplerate = 48000; //synth->alias(); middleware[i] = new MiddleWare(std::move(*synth), &config); master[i] = middleware[i]->spawnMaster(); - printf("Octave size = %d\n", master[i]->microtonal.getoctavesize()); + //printf("Octave size = %d\n", master[i]->microtonal.getoctavesize()); } } void tearDown() { - for(int i = 0; i < 16; ++i) + for(int i = 0; i < NUM_MIDDLEWARE; ++i) delete middleware[i]; delete[] outL; @@ -82,7 +84,7 @@ class PluginTest:public CxxTest::TestSuite void testInit() { for(int x=0; x<100; ++x) { - for(int i=0; i<16; ++i) { + for(int i=0; i<NUM_MIDDLEWARE; ++i) { middleware[i]->tick(); master[i]->GetAudioOutSamples(rand()%1025, synth->samplerate, outL, outR); @@ -102,7 +104,7 @@ class PluginTest:public CxxTest::TestSuite TS_ASSERT_LESS_THAN(0.1f, sum); } - + string loadfile(string fname) const { std::ifstream t(fname.c_str()); @@ -113,7 +115,7 @@ class PluginTest:public CxxTest::TestSuite void testLoad(void) { - for(int i=0; i<16; ++i) { + for(int i=0; i<NUM_MIDDLEWARE; ++i) { middleware[i]->transmitMsg("/load-part", "is", 0, (string(SOURCE_DIR) + "/../../instruments/banks/Organ/0037-Church Organ 1.xiz").c_str()); middleware[i]->tick(); master[i]->GetAudioOutSamples(synth->buffersize, synth->samplerate, outL, outR); @@ -133,6 +135,6 @@ class PluginTest:public CxxTest::TestSuite private: SYNTH_T *synth; float *outR, *outL; - MiddleWare *middleware[16]; - Master *master[16]; + MiddleWare *middleware[NUM_MIDDLEWARE]; + Master *master[NUM_MIDDLEWARE]; }; diff --git a/src/UI/BankUI.fl b/src/UI/BankUI.fl @@ -89,7 +89,7 @@ refreshmainwindow();} callback {refreshmainwindow();} xywh {5 8 220 20} down_box BORDER_BOX labelfont 1 align 0 textfont 1 textsize 11 code0 {bankview->init(osc, modeselect, npart);} - code1 {o->init("loadbank");} + code1 {o->init("bank/bank_select");} class BankList } {} Fl_Button {} { diff --git a/src/UI/BankView.cpp b/src/UI/BankView.cpp @@ -15,13 +15,14 @@ BankList::BankList(int x,int y, int w, int h, const char *label) void BankList::init(std::string path) { ext = path; - oscRegister("bank-list"); + oscRegister("bank/bank_select"); oscRegister(path.c_str()); + oscWrite("bank/banks", ""); } void BankList::OSC_raw(const char *msg) { - if(!strcmp(msg, "/bank-list") && !strcmp(rtosc_argument_string(msg),"iss")) { + if(!strcmp(msg, "/bank/bank_select") && !strcmp(rtosc_argument_string(msg),"iss")) { const int pos = rtosc_argument(msg, 0).i; const char *path = rtosc_argument(msg, 1).s; @@ -31,9 +32,8 @@ void BankList::OSC_raw(const char *msg) this->clear(); this->add(path); - osc->write("/loadbank"); } - if(!strcmp(msg, "/loadbank")&& !strcmp(rtosc_argument_string(msg),"i")) { + if(!strcmp(msg, "/bank/bank_select")&& !strcmp(rtosc_argument_string(msg),"i")) { value(rtosc_argument(msg, 0).i); } } @@ -246,9 +246,12 @@ void BankView::init(Fl_Osc_Interface *osc_, BankViewControls *bvc_, int *npart_) for(int i=0; i<160; ++i) slots[i]->init(i, this); + //Create Slot Listeners + for(int i=0; i<160; ++i) + osc->createLink("/bank/slot"+to_s(i), this); //Request Values for(int i=0; i<160; ++i) - osc->write("/refresh_bank", "i", i); + osc->write("/bank/slot"+to_s(i), ""); } /* @@ -271,8 +274,8 @@ void BankView::react(int event, int nslot) //Rename slot if (event==2 && !isempty && mode!=4) { if(const char *name=fl_input("Slot (instrument) name:", slot.name())) { - osc->write("/bank-rename", "is", nslot, name); - osc->write("/refresh_bank", "i", nslot); + osc->write("/bank/rename_slot", "is", nslot, name); + osc->write("/bank/slot"+to_s(nslot), ""); } } @@ -289,8 +292,8 @@ void BankView::react(int event, int nslot) if(event==1 && mode==2){ if(isempty || fl_choice("Overwrite the slot no. %d ?","No","Yes",NULL,nslot+1)) { - osc->write("/save-bank-part", "ii", *npart, nslot); - osc->write("/refresh_bank", "i", nslot); + osc->write("/bank/save_to_slot", "ii", *npart, nslot); + osc->write("/bank/slot"+to_s(nslot), ""); } bvc->mode(1); } @@ -300,8 +303,8 @@ void BankView::react(int event, int nslot) if(event==1 && mode==3) { if (!isempty && fl_choice("Clear the slot no. %d ?","No","Yes",NULL, nslot+1)) { - osc->write("/clear-bank-slot", "i", nslot); - osc->write("/refresh_bank", "i", nslot); + osc->write("/bank/clear-slot", "i", nslot); + osc->write("/bank/slot"+to_s(nslot), ""); } bvc->mode(1); } @@ -309,9 +312,9 @@ void BankView::react(int event, int nslot) //Swap if(mode==4) { if(event==1 && nselected>=0){ - osc->write("/swap-bank-slots", "ii", nselected, nslot); - osc->write("/refresh_bank", "i", nslot); - osc->write("/refresh_bank", "i", nselected); + osc->write("/bank/swap_slots", "ii", nselected, nslot); + osc->write("/bank/slot"+to_s(nslot), ""); + osc->write("/bank/slot"+to_s(nselected), ""); nselected=-1; } else if(nselected<0 || event==2) { nselected=nslot; @@ -321,15 +324,22 @@ void BankView::react(int event, int nslot) void BankView::OSC_raw(const char *msg) { - if(strcmp(rtosc_argument_string(msg), "iss")) - return; - - int nslot = rtosc_argument(msg,0).i; - const char *name = rtosc_argument(msg,1).s; - const char *fname = rtosc_argument(msg,2).s; - - if(0 <= nslot && nslot < 160) - slots[nslot]->update(name, fname); + if(!strcmp(rtosc_argument_string(msg), "iss")) { + int nslot = rtosc_argument(msg,0).i; + const char *name = rtosc_argument(msg,1).s; + const char *fname = rtosc_argument(msg,2).s; + + if(0 <= nslot && nslot < 160) + slots[nslot]->update(name, fname); + } if(!strcmp(rtosc_argument_string(msg), "ss")) { + while(*msg && !isdigit(*msg)) msg++; + int nslot = atoi(msg); + const char *name = rtosc_argument(msg,0).s; + const char *fname = rtosc_argument(msg,1).s; + + if(0 <= nslot && nslot < 160) + slots[nslot]->update(name, fname); + } } void BankView::cbwig(Fl_Widget *w) @@ -345,6 +355,6 @@ void BankView::refresh(void) return; for(int i=0; i<160; ++i) - osc->write("/refresh_bank", "i", i); + osc->write("/bank/slot"+to_s(i), ""); } diff --git a/src/UI/Connection.cpp b/src/UI/Connection.cpp @@ -263,7 +263,7 @@ class UI_Interface:public Fl_Osc_Interface ////fprintf(stderr, "."); //fprintf(stderr, "write(%s:%s)\n", s.c_str(), args); //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); - impl->transmitMsg(s.c_str(), args, va); + impl->transmitMsg_va(s.c_str(), args, va); va_end(va); } diff --git a/src/UI/MasterUI.fl b/src/UI/MasterUI.fl @@ -313,6 +313,32 @@ class MasterUI {open xywh {15 15 100 20} divider } MenuItem {} { + label {&Load Midi Learn...} + callback {char *filename; +filename=fl_file_chooser("Open:","({*.xlz})",NULL,0); +if (filename==NULL) return; + +osc->write("/load_xlz", "s", filename);} + xywh {40 40 100 20} + } + MenuItem {} { + label {Save Midi Learn...} + callback {char *filename; +int result; +filename=fl_file_chooser("Save:","({*.xlz})",NULL,0); +if (filename==NULL) return; +filename=fl_filename_setext(filename,".xlz"); + +result=fileexists(filename); +if (result) { + result=0; + if (!fl_choice("The file exists. \\nOverwrite it?","No","Yes",NULL)) return; +}; + +osc->write("/save_xlz", "s", filename);} + xywh {30 30 100 20} divider + } + MenuItem {} { label {&Load Scale Settings...} callback {char *filename; filename=fl_file_chooser("Open:","({*.xsz})",NULL,0); diff --git a/src/UI/OscilGenUI.fl b/src/UI/OscilGenUI.fl @@ -121,7 +121,7 @@ class Oscilharmonic {: {public Fl_Group} display->redraw();} xywh {0 15 15 115} type {Vert Knob} box NO_BOX selection_color 222 maximum 127 step 1 value 64 - code0 {o->phase=false;//o->value(127-oscil->Phmag[n]);} + code0 {o->phase=false;o->ext = "magnitude"+to_s(n);//o->value(127-oscil->Phmag[n]);} code1 {//if (oscil->Phmag[n]==64) o->selection_color(0);} class OGSlider } @@ -134,7 +134,7 @@ o->osc->requestValue(o->loc+"waveform"); display->redraw();} xywh {0 135 15 75} type {Vert Knob} box NO_BOX selection_color 222 maximum 127 step 1 value 64 - code0 {o->phase=true;//o->value(oscil->Phphase[n]);} + code0 {o->phase=true;o->ext = "phase"+to_s(n);//o->value(oscil->Phphase[n]);} class OGSlider } Fl_Box {} { diff --git a/src/globals.h b/src/globals.h @@ -34,7 +34,7 @@ #endif //Forward Declarations -namespace rtosc{struct Ports; class ThreadLink;}; +namespace rtosc{struct Ports; struct ClonePorts; struct MergePorts; class ThreadLink;}; class EffectMgr; class ADnoteParameters; struct ADnoteGlobalParam;