zynaddsubfx

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

commit 68d578e08fd7e2131b84eb99a465346f524d138e
parent 1b3aaf264142cecacd0a76a661c50ce91fabb3ed
Author: fundamental <mark.d.mccurry@gmail.com>
Date:   Fri, 14 Jul 2017 20:49:14 -0400

Add Automation Serialization

Diffstat:
Msrc/Misc/Config.cpp | 2+-
Msrc/Misc/Master.cpp | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Misc/Master.h | 3+++
Msrc/Misc/MiddleWare.cpp | 98++++++++++++++++++++-----------------------------------------------------------
Msrc/Tests/MessageTest.h | 40+++++++++++++++++++++++++++++++++++++---
5 files changed, 179 insertions(+), 80 deletions(-)

diff --git a/src/Misc/Config.cpp b/src/Misc/Config.cpp @@ -150,7 +150,7 @@ static const rtosc::Ports ports = { } }}, - {"favorites:", rProp(parameter), 0, + {"favorites:", /*rProp(parameter)*/ 0, 0, [](const char *msg, rtosc::RtData &d) { Config &c = *(Config*)d.obj; diff --git a/src/Misc/Master.cpp b/src/Misc/Master.cpp @@ -156,7 +156,7 @@ static const Ports mapping_ports = { }; static const Ports auto_param_ports = { - {"used:", rProp(parameter) rProp(read-only) rDoc("If automation is assigned to anything"), 0, + {"used::T:F", rProp(parameter) rProp(read-only) rDoc("If automation is assigned to anything"), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; @@ -172,7 +172,7 @@ static const Ports auto_param_ports = { else d.reply(d.loc, a.slots[slot].automations[param].active ? "T" : "F"); rEnd}, - {"path:", rProp(parameter) rProp(read-only) rDoc("Path of parameter"), 0, + {"path::s", rProp(parameter) rProp(read-only) rDoc("Path of parameter"), 0, rBegin; int slot = d.idx[1]; int param = d.idx[0]; @@ -255,7 +255,7 @@ static const Ports slot_ports = { else d.reply(d.loc, a.slots[slot].active ? "T" : "F"); rEnd}, - {"learning:", rProp(parameter) rMap(default, -1) rDoc("If slot is trying to find a midi learn binding"), 0, + {"learning::i", rProp(parameter) rMap(default, -1) rDoc("If slot is trying to find a midi learn binding"), 0, rBegin; int slot = d.idx[0]; d.reply(d.loc, "i", a.slots[slot].learning); @@ -306,6 +306,40 @@ static const Ports automate_ports = { slot_ports.dispatch(msg, d); d.pop_index(); rEnd}, + {"clear", 0, 0, + rBegin; + for(int i=0; i<a.nslots; ++i) + a.clearSlot(i); + rEnd}, + {"load-blob:b", rProp(internal) rDoc("Load blob from middleware"), 0, + rBegin; + auto &b = **(rtosc::AutomationMgr **)rtosc_argument(msg, 0).b.data; + //XXX this code should likely be in rtosc + for(int i=0; i<a.nslots; ++i) { + auto &slota = a.slots[i]; + auto &slotb = b.slots[i]; + std::swap(slota.learning, slotb.learning); + std::swap(slota.midi_cc, slotb.midi_cc); + std::swap(slota.used, slotb.used); + std::swap(slota.active, slotb.active); + for(int j=0; j<a.per_slot; ++j) { + auto &aa = slota.automations[j]; + auto &ab = slotb.automations[j]; + std::swap(aa.used, ab.used); + std::swap(aa.active, ab.active); + std::swap(aa.param_path, ab.param_path); + std::swap(aa.param_min, ab.param_min); + std::swap(aa.param_max, ab.param_max); + std::swap(aa.param_step, ab.param_step); + std::swap(aa.param_type, ab.param_type); + std::swap(aa.map.offset, ab.map.offset); + std::swap(aa.map.gain, ab.map.gain); + std::swap(aa.map.upoints, ab.map.upoints); + for(int k=0; k<aa.map.npoints; ++k) + std::swap(aa.map.control_points[k], ab.map.control_points[k]); + } + } + rEnd}, }; #undef rBegin @@ -584,6 +618,82 @@ vuData::vuData(void) rmspeakl(0.0f), rmspeakr(0.0f), clipped(0) {} +void Master::saveAutomation(XMLwrapper &xml, const rtosc::AutomationMgr &midi) +{ + xml.beginbranch("automation"); + { + XmlNode metadata("mgr-info"); + metadata["nslots"] = to_s(midi.nslots); + metadata["nautomations"] = to_s(midi.per_slot); + metadata["ncontrol"] = to_s(midi.slots[0].automations[0].map.npoints); + xml.add(metadata); + + for(int i=0; i<midi.nslots; ++i) { + const auto &slot = midi.slots[i]; + if(!slot.used) + return; + xml.beginbranch("slot", i); + XmlNode params("params"); + params["midi-cc"] = to_s(slot.midi_cc); + xml.add(params); + for(int j=0; j<midi.per_slot; ++j) { + const auto &au = slot.automations[j]; + if(!au.used) + return; + xml.beginbranch("automation", j); + XmlNode automation("params"); + automation["path"] = au.param_path; + XmlNode mapping("mapping"); + mapping["gain"] = to_s(au.map.gain); + mapping["offset"] = to_s(au.map.offset); + xml.add(automation); + xml.add(mapping); + xml.endbranch(); + } + + xml.endbranch(); + } + } + xml.endbranch(); +} + +void Master::loadAutomation(XMLwrapper &xml, rtosc::AutomationMgr &midi) +{ + if(xml.enterbranch("automation")) { + for(int i=0; i<midi.nslots; ++i) { + auto &slot = midi.slots[i]; + if(xml.enterbranch("slot", i)) { + for(int j=0; j<midi.per_slot; ++j) { + auto &au = slot.automations[j]; + if(xml.enterbranch("automation", j)) { + float gain = 1.0; + float offset = 0.0; + std::string path = ""; + for(auto node:xml.getBranch()) { + if(node.name == "params") + path = node["path"]; + else if(node.name == "mapping") { + gain = atof(node["gain"].c_str()); + offset = atof(node["offset"].c_str()); + } + } + printf("createBinding(%d, %s, false)\n", i, path.c_str()); + midi.createBinding(i, path.c_str(), false); + midi.setSlotSubGain(i, j, gain); + midi.setSlotSubOffset(i, j, offset); + xml.exitbranch(); + } + } + for(auto node:xml.getBranch()) + if(node.name == "params") + slot.midi_cc = atoi(node["midi-cc"].c_str()); + xml.exitbranch(); + } + } + xml.exitbranch(); + } +} + Master::Master(const SYNTH_T &synth_, Config* config) :HDDRecorder(synth_), time(synth_), ctl(synth_, &time), microtonal(config->cfg.GzipCompression), bank(config), diff --git a/src/Misc/Master.h b/src/Misc/Master.h @@ -59,6 +59,9 @@ class Master /**This adds the parameters to the XML data*/ void add2XML(XMLwrapper& xml); + static void saveAutomation(XMLwrapper &xml, const rtosc::AutomationMgr &midi); + static void loadAutomation(XMLwrapper &xml, rtosc::AutomationMgr &midi); + void defaults(); /**loads all settings from a XML file diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp @@ -224,55 +224,6 @@ void preparePadSynth(string path, PADnoteParameters *p, rtosc::RtData &d) } /****************************************************************************** - * MIDI Serialization * - * * - ******************************************************************************/ -//void saveMidiLearn(XMLwrapper &xml, const rtosc::MidiMappernRT &midi) -//{ -// 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 loadMidiLearn(XMLwrapper &xml, rtosc::MidiMappernRT &midi) -//{ -// using rtosc::Port; -// if(xml.enterbranch("midi-learn")) { -// auto nodes = xml.getBranch(); -// -// //TODO clear mapper -// -// 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("unknown midi bindable <%s>\n", path.c_str()); -// } -// } -// xml.exitbranch(); -// } else -// printf("cannot find 'midi-learn' branch...\n"); -//} - -/****************************************************************************** * Non-RealTime Object Store * * * * * @@ -799,7 +750,7 @@ class MwDataObj:public rtosc::RtData //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, ...) + virtual void reply(const char *path, const char *args, ...) override { //printf("reply building '%s'\n", path); va_list va; @@ -825,7 +776,7 @@ class MwDataObj:public rtosc::RtData reply(buffer); } } - virtual void reply(const char *msg){ + virtual void reply(const char *msg) override{ mwi->sendToCurrentRemote(msg); }; //virtual void broadcast(const char *path, const char *args, ...){(void)path;(void)args;}; @@ -1202,24 +1153,29 @@ static rtosc::Ports middwareSnoopPorts = { 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}, - //{"clear_xlz:", 0, 0, - // rBegin; - // impl.midi_mapper.clear(); - // rEnd}, + {"save_xlz:s", 0, 0, + rBegin; + impl.doReadOnlyOp([&]() { + const char *file = rtosc_argument(msg, 0).s; + XMLwrapper xml; + Master::saveAutomation(xml, impl.master->automate); + 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); + rtosc::AutomationMgr *mgr = new rtosc::AutomationMgr(16,4,8); + mgr->set_ports(Master::ports); + Master::loadAutomation(xml, *mgr); + d.chain("/automate/load-blob", "b", sizeof(void*), &mgr); + rEnd}, + {"clear_xlz:", 0, 0, + rBegin; + d.chain("/automate/clear", ""); + rEnd}, //scale file stuff {"load_xsz:s", 0, 0, rBegin; @@ -1482,10 +1438,6 @@ static rtosc::Ports middlewareReplyPorts = { 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}, }; diff --git a/src/Tests/MessageTest.h b/src/Tests/MessageTest.h @@ -184,14 +184,48 @@ class MessageTest:public CxxTest::TestSuite mw->transmitMsg("/learn", "s", "/Pvolume"); mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 108); + //param is at default until rt-thread is run + TS_ASSERT_EQUALS(ms->Pvolume, 80); + + //Perform a learning operation + run_realtime(); - 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 binding affects control + TS_ASSERT_EQUALS(ms->Pvolume, 108); + + printf("# Trying to save automations\n"); + start_realtime(); mw->transmitMsg("/save_xlz", "s", "test-midi-learn.xlz"); + stop_realtime(); + + //Verify that some file exists + printf("# Verifying file exists\n"); + FILE *f = fopen("test-midi-learn.xlz", "r"); + TS_ASSERT(f); + + if(f) + fclose(f); + + printf("# Clearing automation\n"); + //Clear out state + mw->transmitMsg("/clear_xlz", ""); + //Send dummy message + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 27); + run_realtime(); + + //Verify automation table is clear + TS_ASSERT_EQUALS(ms->Pvolume, 108); + + printf("# Loading automation\n"); mw->transmitMsg("/load_xlz", "s", "test-midi-learn.xlz"); + //Send message + mw->transmitMsg("/virtual_midi_cc", "iii", 0, 23, 28); + run_realtime(); + + //Verify automation table is restored + TS_ASSERT_EQUALS(ms->Pvolume, 28); } void testLfoPaste(void)