commit 68d578e08fd7e2131b84eb99a465346f524d138e
parent 1b3aaf264142cecacd0a76a661c50ce91fabb3ed
Author: fundamental <mark.d.mccurry@gmail.com>
Date: Fri, 14 Jul 2017 20:49:14 -0400
Add Automation Serialization
Diffstat:
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)