zynaddsubfx

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

commit f36ff7247556ddeb733edaee0397505f2b077940
parent bc5e0eebd7077a69ae3a6eefe1baf8f7c318f663
Author: Johannes Lorenz <[email protected]>
Date:   Thu,  6 Jul 2017 23:39:45 +0200

Merge branch 'master' into default_values

Diffstat:
Msrc/Misc/Master.cpp | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/Misc/Master.h | 4++--
Msrc/Misc/Microtonal.cpp | 2+-
Msrc/Misc/MiddleWare.cpp | 228++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/Misc/Schema.cpp | 9+++++++++
Msrc/Misc/Util.h | 2++
Msrc/Misc/XMLwrapper.cpp | 2+-
Msrc/Params/FilterParams.cpp | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/Params/FilterParams.h | 19++++++++++++-------
Msrc/Plugin/ZynAddSubFX/DistrhoPluginInfo.h | 16++++++++++++++++
Msrc/Plugin/ZynAddSubFX/ZynAddSubFX.cpp | 20+++++++++++++++++---
Msrc/Tests/MessageTest.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Tests/PluginTest.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Tests/guitar-adnote.xmz | 32++++++++++++++++----------------
Msrc/UI/MasterUI.fl | 2+-
Msrc/main.cpp | 2+-
16 files changed, 666 insertions(+), 190 deletions(-)

diff --git a/src/Misc/Master.cpp b/src/Misc/Master.cpp @@ -114,6 +114,201 @@ static const Ports sysefsendto = }} }; +#define rBegin [](const char *msg, RtData &d) { rtosc::AutomationMgr &a = *(AutomationMgr*)d.obj +#define rEnd } + +static int extract_num(const char *&msg) +{ + while(*msg && !isdigit(*msg)) msg++; + int num = atoi(msg); + while(isdigit(*msg)) msg++; + return num; +} + +static int get_next_int(const char *msg) +{ + return extract_num(msg); +} + +static const Ports mapping_ports = { + {"offset::f", rProp(parameter) rShort("off") rLinear(-50, 50) rMap(unit, percent), 0, + rBegin; + int slot = d.idx[1]; + int param = d.idx[0]; + if(!strcmp("f",rtosc_argument_string(msg))) { + a.setSlotSubOffset(slot, param, rtosc_argument(msg, 0).f); + a.updateMapping(slot, param); + d.broadcast(d.loc, "f", a.getSlotSubOffset(slot, param)); + } else + d.reply(d.loc, "f", a.getSlotSubOffset(slot, param)); + rEnd}, + {"gain::f", rProp(parameter) rShort("gain") rLinear(-200, 200) rMap(unit, percent), 0, + rBegin; + int slot = d.idx[1]; + int param = d.idx[0]; + if(!strcmp("f",rtosc_argument_string(msg))) { + a.setSlotSubGain(slot, param, rtosc_argument(msg, 0).f); + a.updateMapping(slot, param); + d.broadcast(d.loc, "f", a.getSlotSubGain(slot, param)); + } else + d.reply(d.loc, "f", a.getSlotSubGain(slot, param)); + rEnd}, +}; + +static const Ports auto_param_ports = { + {"used:", rProp(parameter) rProp(read-only) rDoc("If automation is assigned to anything"), 0, + rBegin; + int slot = d.idx[1]; + int param = d.idx[0]; + + d.reply(d.loc, a.slots[slot].automations[param].used ? "T" : "F"); + rEnd}, + {"active::T:F", rProp(parameter) rDoc("If automation is being actively used"), 0, + rBegin; + int slot = d.idx[1]; + int param = d.idx[0]; + if(rtosc_narguments(msg)) + a.slots[slot].automations[param].active = rtosc_argument(msg, 0).T; + 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, + rBegin; + int slot = d.idx[1]; + int param = d.idx[0]; + d.reply(d.loc, "s", a.slots[slot].automations[param].param_path); + rEnd}, + {"clear:", 0, 0, + rBegin; + int slot = d.idx[1]; + int param = d.idx[0]; + a.clearSlotSub(slot, param); + rEnd}, + {"mapping/", 0, &mapping_ports, + rBegin; + SNIP; + mapping_ports.dispatch(msg, d); + rEnd}, + + //{"mapping", rDoc("Parameter mapping control"), 0, + // rBegin; + // int slot = d.idx[1]; + // int param = d.idx[0]; + // if(!strcmp("b", rtosc_argument_string(msg))) { + // int len = rtosc_argument(msg, 0).b.len / sizeof(float); + // float *data = (float*)rtosc_argument(msg, 0).b.data; + // } else { + // d.reply(d.loc, "b", + // a.slots[slot].automations[param].map.npoints*sizeof(float), + // a.slots[slot].automations[param].map.control_points); + // } + // rEnd}, +}; + +static const Ports slot_ports = { + //{"learn-binding:s", rDoc("Create binding for automation path with midi-learn"), 0, + // rBegin; + // (void) m; + // //m->automate.createBinding(rtosc_argument(msg, 0).i, + // // rtosc_argument(msg, 1).s, + // // rtosc_argument(msg, 2).T); + // rEnd}, + //{"create-binding:s", rDoc("Create binding for automation path"), 0, + // rBegin; + // m->automate.createBinding(rtosc_argument(msg, 0).i, + // rtosc_argument(msg, 1).s, + // rtosc_argument(msg, 2).T); + // rEnd}, + {"value::f", rProp(parameter) rMap(default, 0.5) rLinear(0, 1) rDoc("Access current value in slot 'i' (0..1)"), 0, + rBegin; + int num = d.idx[0]; + if(!strcmp("f",rtosc_argument_string(msg))) { + a.setSlot(num, rtosc_argument(msg, 0).f); + d.broadcast(d.loc, "f", a.getSlot(num)); + } else + d.reply(d.loc, "f", a.getSlot(num)); + rEnd}, + + {"name::s", rProp(parameter) rDoc("Access name of automation slot"), 0, + rBegin; + int num = d.idx[0]; + if(!strcmp("s",rtosc_argument_string(msg))) { + a.setName(num, rtosc_argument(msg, 0).s); + d.broadcast(d.loc, "s", a.getName(num)); + } else + d.reply(d.loc, "s", a.getName(num)); + rEnd}, + {"midi-cc::i", rProp(parameter) rMap(default, -1) rDoc("Access assigned midi CC slot") , 0, + rBegin; + int slot = d.idx[0]; + if(rtosc_narguments(msg)) + a.slots[slot].midi_cc = rtosc_argument(msg, 0).i; + else + d.reply(d.loc, "i", a.slots[slot].midi_cc); + + rEnd}, + {"active::T:F", rProp(parameter) rMap(default, F) rDoc("If Slot is enabled"), 0, + rBegin; + int slot = d.idx[0]; + if(rtosc_narguments(msg)) + a.slots[slot].active = rtosc_argument(msg, 0).T; + 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, + rBegin; + int slot = d.idx[0]; + d.reply(d.loc, "i", a.slots[slot].learning); + rEnd}, + {"clear:", 0, 0, + rBegin; + int slot = d.idx[0]; + a.clearSlot(slot); + rEnd}, + {"param#4/", rDoc("Info on individual param mappings"), &auto_param_ports, + rBegin; + (void)a; + d.push_index(get_next_int(msg)); + SNIP; + auto_param_ports.dispatch(msg, d); + d.pop_index(); + rEnd}, +}; + +static const Ports automate_ports = { + {"active-slot::i", rProp(parameter) rMap(min, -1) rMap(max, 16) rDoc("Active Slot for macro learning"), 0, + rBegin; + if(!strcmp("i",rtosc_argument_string(msg))) { + a.active_slot = rtosc_argument(msg, 0).i; + d.broadcast(d.loc, "i", a.active_slot); + } else + d.reply(d.loc, "i", a.active_slot); + rEnd}, + {"learn-binding-new-slot:s", rDoc("Learn a parameter assigned to a new slot"), 0, + rBegin; + int free_slot = a.free_slot(); + if(free_slot >= 0) { + a.createBinding(free_slot, rtosc_argument(msg, 0).s, true); + a.active_slot = free_slot; + } + rEnd}, + {"learn-binding-same-slot:s", rDoc("Learn a parameter appending to the active-slot"), 0, + rBegin; + if(a.active_slot >= 0) + a.createBinding(a.active_slot, rtosc_argument(msg, 0).s, true); + rEnd}, + {"slot#16/", rDoc("Parameters of individual automation slots"), &slot_ports, + rBegin; + (void)a; + d.push_index(get_next_int(msg)); + SNIP; + slot_ports.dispatch(msg, d); + d.pop_index(); + rEnd}, +}; + +#undef rBegin +#undef rEnd #define rBegin [](const char *msg, RtData &d) { Master *m = (Master*)d.obj #define rEnd } @@ -124,6 +319,7 @@ static const Ports watchPorts = { rEnd}, }; + extern const Ports bankPorts; static const Ports master_ports = { rString(last_xmz, XMZ_PATH_MAX, "File name for last name loaded if any."), @@ -250,13 +446,12 @@ static const Ports master_ports = { [](const char *,RtData &d) { Master *M = (Master*)d.obj; M->frozenState = false;}}, - {"midi-learn/", 0, &rtosc::MidiMapperRT::ports, + {"automate/", rDoc("MIDI Learn/Plugin Automation support"), &automate_ports, [](const char *msg, RtData &d) { - Master *M = (Master*)d.obj; SNIP; - printf("residue message = <%s>\n", msg); - d.obj = &M->midi; - rtosc::MidiMapperRT::ports.dispatch(msg,d);}}, + d.obj = (void*)&((Master*)d.obj)->automate; + automate_ports.dispatch(msg, d); + }}, {"close-ui:", rDoc("Request to close any connection named \"GUI\""), 0, [](const char *, RtData &d) { d.reply("/close-ui", "");}}, @@ -307,6 +502,14 @@ static const Ports master_ports = { rBOIL_END}, {"bank/", rDoc("Controls for instrument banks"), &bankPorts, [](const char*,RtData&) {}}, + {"learn:s", rProp(depricated) rDoc("MIDI Learn"), 0, + rBegin; + int free_slot = m->automate.free_slot(); + if(free_slot >= 0) { + m->automate.createBinding(free_slot, rtosc_argument(msg, 0).s, true); + m->automate.active_slot = free_slot; + } + rEnd}, }; #undef rBegin @@ -383,15 +586,18 @@ vuData::vuData(void) Master::Master(const SYNTH_T &synth_, Config* config) :HDDRecorder(synth_), time(synth_), ctl(synth_, &time), microtonal(config->cfg.GzipCompression), bank(config), + automate(16,4,8), frozenState(false), pendingMemory(false), synth(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);}; + //Setup MIDI Learn + automate.set_ports(master_ports); + automate.set_instance(this); + //midi.frontend = [this](const char *msg) {bToU->raw_write(msg);}; + automate.backend = [this](const char *msg) {applyOscEvent(msg);}; memory = new AllocatorClass(); swaplr = 0; @@ -541,8 +747,7 @@ void Master::setController(char chan, int type, int par) { if(frozenState) return; - //TODO add chan back - midi.handleCC(type,par); + automate.handleMidi(chan, 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); @@ -746,13 +951,19 @@ bool Master::runOSC(float *outl, float *outr, bool offline) } if(!d.matches) {// && !ports.apropos(msg)) { fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40); - fprintf(stderr, "Unknown address<BACKEND:%s> '%s:%s'\n", + fprintf(stderr, "Unknown address<BACKEND:%s> '%s:%s'\n", offline ? "offline" : "online", uToB->peak(), rtosc_argument_string(uToB->peak())); fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40); } } + + if(automate.damaged) { + d.broadcast("/damage", "s", "/automate/"); + automate.damaged = 0; + } + if(events>1 && false) fprintf(stderr, "backend: %d events per cycle\n",events); diff --git a/src/Misc/Master.h b/src/Misc/Master.h @@ -16,7 +16,7 @@ #define MASTER_H #include "../globals.h" #include "Microtonal.h" -#include <rtosc/miditable.h> +#include <rtosc/automations.h> #include <rtosc/ports.h> #include "Time.h" @@ -179,7 +179,7 @@ class Master WatchManager watcher; //Midi Learn - rtosc::MidiMapperRT midi; + rtosc::AutomationMgr automate; bool frozenState;//read-only parameters for threadsafe actions Allocator *memory; diff --git a/src/Misc/Microtonal.cpp b/src/Misc/Microtonal.cpp @@ -863,7 +863,7 @@ void Microtonal::apply(void) { char buf[100*MAX_OCTAVE_SIZE] = {0}; char tmpbuf[100] = {0}; - for (int i=0;i<getoctavesize();i++){ + for (int i=0;i<octavesize;i++){ if (i!=0) strncat(buf, "\n", sizeof(buf)-1); tuningtoline(i,tmpbuf,100); diff --git a/src/Misc/MiddleWare.cpp b/src/Misc/MiddleWare.cpp @@ -227,50 +227,50 @@ 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"); -} +//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 * @@ -747,7 +747,7 @@ public: rtosc::UndoHistory undo; //MIDI Learn - rtosc::MidiMappernRT midi_mapper; + //rtosc::MidiMappernRT midi_mapper; //Link To the Realtime rtosc::ThreadLink *bToU; @@ -1202,24 +1202,24 @@ 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; + // 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}, //scale file stuff {"load_xsz:s", 0, 0, rBegin; @@ -1391,51 +1391,51 @@ static rtosc::Ports middwareSnoopPorts = { impl.undo.seekHistory(+1); rEnd}, //port to observe the midi mappings - {"midi-learn-values:", 0, 0, - rBegin; - auto &midi = impl.midi_mapper; - auto key = keys(midi.inv_map); - //cc-id, path, min, max -#define MAX_MIDI 32 - rtosc_arg_t args[MAX_MIDI*4]; - char argt[MAX_MIDI*4+1] = {0}; - int j=0; - for(unsigned i=0; i<key.size() && i<MAX_MIDI; ++i) { - auto val = midi.inv_map[key[i]]; - if(std::get<1>(val) == -1) - continue; - argt[4*j+0] = 'i'; - args[4*j+0].i = std::get<1>(val); - argt[4*j+1] = 's'; - args[4*j+1].s = key[i].c_str(); - argt[4*j+2] = 'i'; - args[4*j+2].i = 0; - argt[4*j+3] = 'i'; - args[4*j+3].i = 127; - j++; - - } - d.replyArray(d.loc, argt, args); -#undef MAX_MIDI - 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}, - {"unlearn:s", 0, 0, - rBegin; - string addr = rtosc_argument(msg, 0).s; - auto &midi = impl.midi_mapper; - auto map = midi.getMidiMappingStrings(); - midi.unMap(addr.c_str(), false); - midi.unMap(addr.c_str(), true); - rEnd}, + //{"midi-learn-values:", 0, 0, + // rBegin; + // auto &midi = impl.midi_mapper; + // auto key = keys(midi.inv_map); + // //cc-id, path, min, max +//#define MAX_MIDI 32 + // rtosc_arg_t args[MAX_MIDI*4]; + // char argt[MAX_MIDI*4+1] = {0}; + // int j=0; + // for(unsigned i=0; i<key.size() && i<MAX_MIDI; ++i) { + // auto val = midi.inv_map[key[i]]; + // if(std::get<1>(val) == -1) + // continue; + // argt[4*j+0] = 'i'; + // args[4*j+0].i = std::get<1>(val); + // argt[4*j+1] = 's'; + // args[4*j+1].s = key[i].c_str(); + // argt[4*j+2] = 'i'; + // args[4*j+2].i = 0; + // argt[4*j+3] = 'i'; + // args[4*j+3].i = 127; + // j++; + + // } + // d.replyArray(d.loc, argt, args); +//#undef MAX_MIDI + // 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}, + //{"unlearn:s", 0, 0, + // rBegin; + // string addr = rtosc_argument(msg, 0).s; + // auto &midi = impl.midi_mapper; + // auto map = midi.getMidiMappingStrings(); + // midi.unMap(addr.c_str(), false); + // midi.unMap(addr.c_str(), true); + // rEnd}, //drop this message into the abyss {"ui/title:", 0, 0, [](const char *msg, RtData &d) {}}, {"quit:", 0, 0, [](const char *, RtData&) {Pexitprogram = 1;}}, @@ -1482,10 +1482,10 @@ 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}, + //{"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}, }; @@ -1510,8 +1510,8 @@ MiddleWareImpl::MiddleWareImpl(MiddleWare *mw, SYNTH_T synth_, { bToU = new rtosc::ThreadLink(4096*2*16,1024/16); uToB = new rtosc::ThreadLink(4096*2*16,1024/16); - midi_mapper.base_ports = &Master::ports; - midi_mapper.rt_cb = [this](const char *msg){handleMsg(msg);}; + //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); diff --git a/src/Misc/Schema.cpp b/src/Misc/Schema.cpp @@ -21,6 +21,8 @@ namespace zyn { * - 'shortname' : string [OPTIONAL] * - 'tooltip' : string [OPTIONAL] * - 'type' : type + * - 'units' : unit-type + * - 'scale' : scale-type * - 'domain' : range [OPTIONAL] * - 'options' : [option...] [OPTIONAL] * type : {'int', 'float', 'boolean'} @@ -115,6 +117,9 @@ void dump_param_cb(const rtosc::Port *p, const char *full_name, const char*, auto mparameter = meta.find("parameter"); auto mdoc = meta.find("documentation"); auto msname = meta.find("shortname"); + auto units = meta.find("unit"); + auto scale = meta.find("scale"); + opts options; string doc; string name = p->name;; @@ -190,6 +195,10 @@ void dump_param_cb(const rtosc::Port *p, const char *full_name, const char*, o << " \"shortname\": \"" << msname.value << "\",\n"; o << " \"name\" : \"" << name << "\",\n"; o << " \"tooltip\" : \"" << doc << "\",\n"; + if(units != meta.end()) + o << " \"units\" : \"" << units.value << "\",\n"; + if(scale != meta.end()) + o << " \"scale\" : \"" << scale.value << "\",\n"; o << " \"type\" : \"" << type << "\""; if(min && max) o << ",\n \"range\" : [" << min << "," << max << "]"; diff --git a/src/Misc/Util.h b/src/Misc/Util.h @@ -179,4 +179,6 @@ rPresetType, \ } +#define rUnit(x) rMap(unit, x) + #endif diff --git a/src/Misc/XMLwrapper.cpp b/src/Misc/XMLwrapper.cpp @@ -253,7 +253,7 @@ void XMLwrapper::addparreal(const string &name, float val) union { float in; uint32_t out; } convert; char buf[11]; convert.in = val; - sprintf(buf, "0x%8X", convert.out); + sprintf(buf, "0x%0.8X", convert.out); addparams("par_real", 3, "name", name.c_str(), "value", stringFrom<float>(val).c_str(), "exact_value", buf); } diff --git a/src/Params/FilterParams.cpp b/src/Params/FilterParams.cpp @@ -3,7 +3,9 @@ FilterParams.cpp - Parameters for filter Copyright (C) 2002-2005 Nasca Octavian Paul + Copyright (C) 2017 Mark McCurry Author: Nasca Octavian Paul + Mark McCurry This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -66,14 +68,16 @@ const rtosc::Ports FilterParams::ports = { rOption(Ptype, rShort("type"), rOptions(LP1, HP1, LP2, HP2, BP, notch, peak, l.shelf, h.shelf), "Filter Type"), - rParamZyn(Pfreq, rShort("cutoff"), "Center Freq"), - rParamZyn(Pq, rShort("q"), - "Quality Factor (resonance/bandwidth)"), rParamI(Pstages, rShort("stages"), rLinear(0,5), "Filter Stages"), - rParamZyn(Pfreqtrack, rShort("f.track"), + rParamF(baseq, rShort("q"), rUnit(none), rLog(0.1, 1000), + "Quality Factor (resonance/bandwidth)"), + rParamF(basefreq, rShort("cutoff"), rUnit(Hz), rLog(31.25, 32000), + "Base cutoff frequency"), + rParamF(freqtracking, rShort("f.track"), rUnit(%), rLinear(-100, 100), "Frequency Tracking amount"), - rParamZyn(Pgain, rShort("gain"), "Output Gain"), + rParamF(gain, rShort("gain"), rUnit(dB), rLinear(-30, 30), + "Output Gain"), rParamI(Pnumformants, rShort("formants"), rLinear(1,12), "Number of formants to be used"), rParamZyn(Pformantslowness, rShort("slew"), @@ -223,6 +227,65 @@ const rtosc::Ports FilterParams::ports = { } d.replyArray(d.loc, type, args); }}, + + //Old 0..127 parameter mappings + {"Pfreq::i", rLinear(0, 127) rShort("cutoff") rProp(deprecated) rDoc("Center Freq"), 0, + [](const char *msg, RtData &d) { + FilterParams *obj = (FilterParams*)d.obj; + if(rtosc_narguments(msg)) { + int Pfreq = rtosc_argument(msg, 0).i; + obj->basefreq = (Pfreq / 64.0f - 1.0f) * 5.0f; + obj->basefreq = powf(2.0f, obj->basefreq + 9.96578428f); + rChangeCb; + d.broadcast(d.loc, "i", Pfreq); + } else { + float tmp = obj->basefreq; + tmp = log2f(tmp) - 9.96578428f; + tmp = (tmp / 5.0 + 1.0) * 64.0f; + int Pfreq = roundf(tmp); + d.reply(d.loc, "i", Pfreq); + } + }}, + {"Pfreqtrack::i", rLinear(0, 127) rShort("f.track") rProp(deprecated) rDoc("Frequency Tracking amount"), 0, + [](const char *msg, RtData &d) { + FilterParams *obj = (FilterParams*)d.obj; + if(rtosc_narguments(msg)) { + int Pfreqtracking = rtosc_argument(msg, 0).i; + obj->freqtracking = 100 * (Pfreqtracking - 64.0f) / (64.0f); + rChangeCb; + d.broadcast(d.loc, "i", Pfreqtracking); + } else { + int Pfreqtracking = obj->freqtracking/100.0*64.0 + 64.0; + d.reply(d.loc, "i", Pfreqtracking); + } + }}, + {"Pgain::i", rLinear(0, 127) rShort("gain") rProp(deprecated) rDoc("Output Gain"), 0, + [](const char *msg, RtData &d) { + FilterParams *obj = (FilterParams*)d.obj; + if(rtosc_narguments(msg)) { + int Pgain = rtosc_argument(msg, 0).i; + obj->gain = (Pgain / 64.0f - 1.0f) * 30.0f; //-30..30dB + rChangeCb; + d.broadcast(d.loc, "i", Pgain); + } else { + int Pgain = roundf((obj->gain/30.0f + 1.0f) * 64.0f); + d.reply(d.loc, "i", Pgain); + } + }}, + {"Pq::i", rLinear(0,127) rShort("q") rProp(deprecated) + rDoc("Quality Factor (resonance/bandwidth)"), 0, + [](const char *msg, RtData &d) { + FilterParams *obj = (FilterParams*)d.obj; + if(rtosc_narguments(msg)) { + int Pq = rtosc_argument(msg, 0).i; + obj->baseq = expf(powf((float) Pq / 127.0f, 2) * logf(1000.0f)) - 0.9f; + rChangeCb; + d.broadcast(d.loc, "i", Pq); + } else { + int Pq = roundf(127.0f * sqrtf(logf(0.9f + obj->baseq)/logf(1000.0f))); + d.reply(d.loc, "i", Pq); + } + }}, }; #undef rChangeCb #define rChangeCb @@ -258,16 +321,21 @@ void FilterParams::defaults() Pfreq = Dfreq; Pq = Dq; - Pstages = 0; - Pfreqtrack = 64; - Pgain = 64; - Pcategory = 0; + Pstages = 0; + basefreq = (Pfreq / 64.0f - 1.0f) * 5.0f; + basefreq = powf(2.0f, basefreq + 9.96578428f); + baseq = expf(powf((float) Pq / 127.0f, 2) * logf(1000.0f)) - 0.9f; + + gain = 0.0f; + freqtracking = 0.0f; + + Pcategory = 0; Pnumformants = 3; Pformantslowness = 64; for(int j = 0; j < FF_MAX_VOWELS; ++j) defaults(j); - ; + Psequencesize = 3; for(int i = 0; i < FF_MAX_SEQUENCE; ++i) @@ -307,10 +375,10 @@ void FilterParams::getfromFilterParams(FilterParams *pars) Pfreq = pars->Pfreq; Pq = pars->Pq; - Pstages = pars->Pstages; - Pfreqtrack = pars->Pfreqtrack; - Pgain = pars->Pgain; - Pcategory = pars->Pcategory; + Pstages = pars->Pstages; + freqtracking = pars->freqtracking; + gain = pars->gain; + Pcategory = pars->Pcategory; Pnumformants = pars->Pnumformants; Pformantslowness = pars->Pformantslowness; @@ -338,21 +406,21 @@ void FilterParams::getfromFilterParams(FilterParams *pars) */ float FilterParams::getfreq() const { - return (Pfreq / 64.0f - 1.0f) * 5.0f; + return log2(basefreq) - log2f(1000.0f); } float FilterParams::getq() const { - return expf(powf((float) Pq / 127.0f, 2) * logf(1000.0f)) - 0.9f; + return baseq; } float FilterParams::getfreqtracking(float notefreq) const { - return logf(notefreq / 440.0f) * (Pfreqtrack - 64.0f) / (64.0f * LOG_2); + return log2f(notefreq / 440.0f) * (freqtracking / 100.0); } float FilterParams::getgain() const { - return (Pgain / 64.0f - 1.0f) * 30.0f; //-30..30dB + return gain; } /* @@ -428,11 +496,11 @@ void FilterParams::add2XML(XMLwrapper& xml) //filter parameters xml.addpar("category", Pcategory); xml.addpar("type", Ptype); - xml.addpar("freq", Pfreq); - xml.addpar("q", Pq); + xml.addparreal("basefreq", basefreq); + xml.addparreal("baseq", baseq); xml.addpar("stages", Pstages); - xml.addpar("freq_track", Pfreqtrack); - xml.addpar("gain", Pgain); + xml.addparreal("freq_tracking", freqtracking); + xml.addparreal("gain", gain); //formant filter parameters if((Pcategory == 1) || (!xml.minimal)) { @@ -482,14 +550,28 @@ void FilterParams::getfromXMLsection(XMLwrapper& xml, int n) void FilterParams::getfromXML(XMLwrapper& xml) { + const bool upgrade_3_0_2 = (xml.fileversion() < version_type(3,0,2)) && (xml.getparreal("basefreq", -1) < 0); + //filter parameters - Pcategory = xml.getpar127("category", Pcategory); - Ptype = xml.getpar127("type", Ptype); - Pfreq = xml.getpar127("freq", Pfreq); - Pq = xml.getpar127("q", Pq); - Pstages = xml.getpar127("stages", Pstages); - Pfreqtrack = xml.getpar127("freq_track", Pfreqtrack); - Pgain = xml.getpar127("gain", Pgain); + Pcategory = xml.getpar127("category", Pcategory); + Ptype = xml.getpar127("type", Ptype); + Pstages = xml.getpar127("stages", Pstages); + if(upgrade_3_0_2) { + int Pfreq = xml.getpar127("freq", 0); + basefreq = (Pfreq / 64.0f - 1.0f) * 5.0f; + basefreq = powf(2.0f, basefreq + 9.96578428f); + int Pq = xml.getpar127("q", 0); + baseq = expf(powf((float) Pq / 127.0f, 2) * logf(1000.0f)) - 0.9f; + int Pgain = xml.getpar127("gain", 0); + gain = (Pgain / 64.0f - 1.0f) * 30.0f; //-30..30dB + int Pfreqtracking = xml.getpar127("freq_track", 0); + freqtracking = 100 * (Pfreqtracking - 64.0f) / (64.0f); + } else { + basefreq = xml.getparreal("basefreq", 1000); + baseq = xml.getparreal("baseq", 10); + gain = xml.getparreal("gain", 0); + freqtracking = xml.getparreal("freq_track", 0); + } //formant filter parameters if(xml.enterbranch("FORMANT_FILTER")) { @@ -527,11 +609,11 @@ void FilterParams::paste(FilterParams &x) { COPY(Pcategory); COPY(Ptype); - COPY(Pfreq); + COPY(basefreq); COPY(Pq); COPY(Pstages); - COPY(Pfreqtrack); - COPY(Pgain); + COPY(freqtracking); + COPY(gain); COPY(Pnumformants); COPY(Pformantslowness); @@ -566,7 +648,6 @@ void FilterParams::paste(FilterParams &x) void FilterParams::pasteArray(FilterParams &x, int nvowel) { - printf("FilterParameters::pasting-an-array<%d>\n", nvowel); for(int nformant = 0; nformant < FF_MAX_FORMANTS; ++nformant) { auto &self = Pvowels[nvowel].formants[nformant]; auto &update = x.Pvowels[nvowel].formants[nformant]; diff --git a/src/Params/FilterParams.h b/src/Params/FilterParams.h @@ -46,13 +46,18 @@ class FilterParams:public PresetsArray float getfreqtracking(float notefreq) const ; float getgain() const ; - unsigned char Pcategory; //Filter category (Analog/Formant/StVar) - unsigned char Ptype; // Filter type (for analog lpf,hpf,bpf..) - unsigned char Pfreq; // Frequency (64-central frequency) - unsigned char Pq; // Q parameters (resonance or bandwidth) - unsigned char Pstages; //filter stages+1 - unsigned char Pfreqtrack; //how the filter frequency is changing according the note frequency - unsigned char Pgain; //filter's output gain + unsigned Pcategory:2; //< Filter category (Analog/Formant/StVar) + unsigned Ptype:8; //< Filter type (for analog lpf,hpf,bpf..) + unsigned Pstages:8; //< filter stages+1 + float basefreq; //< Base cutoff frequency (Hz) + float baseq; //< Q parameters (resonance or bandwidth) + float freqtracking; //< Tracking of center frequency with note frequency (percentage) + float gain; //< filter's output gain (dB) + + int Pq; //dummy + int Pfreq; //dummy + int Pfreqtrack; //dummy + int Pgain; //dummy //Formant filter parameters unsigned char Pnumformants; //how many formants are used diff --git a/src/Plugin/ZynAddSubFX/DistrhoPluginInfo.h b/src/Plugin/ZynAddSubFX/DistrhoPluginInfo.h @@ -41,6 +41,22 @@ #define DISTRHO_PLUGIN_WANT_FULL_STATE 1 enum Parameters { + kParamSlot1, + kParamSlot2, + kParamSlot3, + kParamSlot4, + kParamSlot5, + kParamSlot6, + kParamSlot7, + kParamSlot8, + kParamSlot9, + kParamSlot10, + kParamSlot11, + kParamSlot12, + kParamSlot13, + kParamSlot14, + kParamSlot15, + kParamSlot16, kParamOscPort, kParamCount }; diff --git a/src/Plugin/ZynAddSubFX/ZynAddSubFX.cpp b/src/Plugin/ZynAddSubFX/ZynAddSubFX.cpp @@ -224,6 +224,15 @@ protected: parameter.ranges.def = 0.0f; break; } + if(kParamSlot1 <= index && index <= kParamSlot16) { + parameter.hints = kParameterIsAutomable; + parameter.name = ("Slot " + zyn::to_s(index-kParamSlot1 + 1)).c_str(); + parameter.symbol = ("slot" + zyn::to_s(index-kParamSlot1 + 1)).c_str(); + parameter.unit = ""; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 1.0f; + parameter.ranges.def = 0.5f; + } } /** @@ -236,9 +245,11 @@ protected: { case kParamOscPort: return oscPort; - default: - return 0.0f; } + if(kParamSlot1 <= index && index <= kParamSlot16) { + return master->automate.getSlot(index - kParamSlot1); + } + return 0.0f; } /** @@ -247,9 +258,12 @@ protected: When a parameter is marked as automable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ - void setParameterValue(uint32_t /*index*/, float /*value*/) noexcept override + void setParameterValue(uint32_t index, float value) noexcept override { // only an output port for now + if(kParamSlot1 <= index && index <= kParamSlot16) { + master->automate.setSlot(index - kParamSlot1, value); + } } /* -------------------------------------------------------------------------------------------------------- diff --git a/src/Tests/MessageTest.h b/src/Tests/MessageTest.h @@ -243,6 +243,53 @@ class MessageTest:public CxxTest::TestSuite TS_ASSERT_EQUALS(field2, 35); } + void testFilterDepricated(void) + { + vector<string> v = {"Pfreq", "Pfreqtrack", "Pgain", "Pq"}; + for(int i=0; i<v.size(); ++i) { + string path = "/part0/kit0/adpars/GlobalPar/GlobalFilter/"+v[i]; + for(int j=0; j<128; ++j) { + mw->transmitMsg(path.c_str(), "i", j); //Set + mw->transmitMsg(path.c_str(), ""); //Get + } + + } + while(ms->uToB->hasNext()) { + const char *msg = ms->uToB->read(); + //printf("RT: handling <%s>\n", msg); + ms->applyOscEvent(msg); + } + + int id = 0; + int state = 0; + int value = 0; + // 0 - broadcast + // 1 - true value (set) + // 2 - expected value (get) + while(ms->bToU->hasNext()) { + const char *msg = ms->bToU->read(); + if(state == 0) { + TS_ASSERT_EQUALS(rtosc_narguments(msg), 0); + state = 1; + } else if(state == 1) { + TS_ASSERT_EQUALS(rtosc_narguments(msg), 1); + value = rtosc_argument(msg, 0).i; + state = 2; + } else if(state == 2) { + int val = rtosc_argument(msg, 0).i; + if(value != val) { + printf("%s - %d should equal %d\n", msg, value, val); + TS_ASSERT(0); + } + state = 0; + } + + //printf("Message #%d %s:%s\n", id++, msg, rtosc_argument_string(msg)); + //if(rtosc_narguments(msg)) + // printf(" %d\n", rtosc_argument(msg, 0).i); + } + } + private: SYNTH_T *synth; diff --git a/src/Tests/PluginTest.h b/src/Tests/PluginTest.h @@ -33,6 +33,95 @@ NSM_Client *nsm = 0; char *instance_name=(char*)""; MiddleWare *middleware; +void fill_vec_with_lines(std::vector<string> &v, string s) +{ + std::istringstream stream(s); + std::string line; + while(std::getline(stream, line)) + v.push_back(line); +} +void print_string_differences(string orig, string next) +{ + std::vector<string> a, b; + fill_vec_with_lines(a, orig); + fill_vec_with_lines(b, next); + int N = a.size(); + int M = b.size(); + printf("%d vs %d\n", N, M); + + //Original String by New String + //Each step is either an insertion, deletion, or match + //Match is 0 cost and moves +1 State (if symbols are the same) + //Replace is 3 cost and moves +1 State (if symbols are different) + //Insertion is 2 cost and moves +2 State (+2 if symbols are different) + //Deletion is 1 cost and moves +0 State (+2 if symbols are different) + char *transition = new char[N*M]; + int *cost = new int[N*M]; + + const int match = 1; + const int insert = 2; + const int del = 3; + for(int i=0; i<N; ++i) { + for(int j=0; j<M; ++j) { + transition[i*M + j] = 0; + cost[i*M + j] = 0xffff; + } + } + + //Just assume the -1 line is the same + cost[0*M + 0] = (a[0] == b[0])*3; + cost[0*M + 1] = (a[1] == b[0])*2 + 2; + for(int i=1; i<N; ++i) { + for(int j=0; j<M; ++j) { + int cost_match = 0xffffff; + int cost_ins = 0xffffff; + int cost_del = 0xffffff; + cost_del = cost[(i-1)*M + j] + 2 + (a[i] != b[j])*2; + if(j > 1) + cost_ins = cost[(i-1)*M + (j-2)] + 1 + 2*(a[i] != b[j]); + if(j > 0) + cost_match = cost[(i-1)*M + (j-1)] + 2*(a[i] != b[j]); + + if(cost_match >= 0xffff && cost_ins >= 0xffff && cost_del >= 0xffff) { + ; + } else if(cost_match < cost_ins && cost_match < cost_del) { + cost[i*M+j] = cost_match; + transition[i*M+j] = match; + } else if(cost_ins < cost_del) { + cost[i*M+j] = cost_ins; + transition[i*M+j] = insert; + } else { + cost[i*M+j] = cost_del; + transition[i*M+j] = del; + } + } + } + + int total_cost = cost[(N-1)*M + (M-1)]; + if(total_cost < 500) { + printf("total cost = %d\n", cost[(N-1)*M + (M-1)]); + + //int b_pos = b.size()-1; + int a_pos = a.size()-1; + for(int i=(M-1); i >= 0; --i) { + if(a[a_pos] != b[i]) { + printf("- %s\n", a[a_pos].c_str()); + printf("+ %s\n", b[i].c_str()); + } + if(transition[i*M+a_pos] == match) { + //printf("R"); + a_pos -= 1; + } else if(transition[i*M+a_pos] == del) { + //printf("D"); + } else if(transition[i*M+a_pos] == insert) { + //printf("I"); + a_pos -= 2; + } + } + //printf("%d vs %d\n", N, M); + } +} + class PluginTest:public CxxTest::TestSuite { public: @@ -104,6 +193,8 @@ class PluginTest:public CxxTest::TestSuite TS_ASSERT_EQUALS((int)(fdata.length()+1), res); TS_ASSERT(fdata == result); + if(fdata != result) + print_string_differences(fdata, result); } diff --git a/src/Tests/guitar-adnote.xmz b/src/Tests/guitar-adnote.xmz @@ -137,11 +137,11 @@ version-revision="1" ZynAddSubFX-author="Nasca Octavian Paul"> <FILTER> <par name="category" value="0" /> <par name="type" value="2" /> -<par name="freq" value="70" /> -<par name="q" value="40" /> +<par_real name="basefreq" value="1383.91" exact_value="0x44ACFD1C" /> +<par_real name="baseq" value="1.08427" exact_value="0x3F8AC956" /> <par name="stages" value="0" /> -<par name="freq_track" value="64" /> -<par name="gain" value="64" /> +<par_real name="freq_tracking" value="0" exact_value="0x00000000" /> +<par_real name="gain" value="0" exact_value="0x00000000" /> </FILTER> <FILTER_ENVELOPE> <par_bool name="free_mode" value="no" /> @@ -309,11 +309,11 @@ version-revision="1" ZynAddSubFX-author="Nasca Octavian Paul"> <FILTER> <par name="category" value="0" /> <par name="type" value="2" /> -<par name="freq" value="65" /> -<par name="q" value="68" /> +<par_real name="basefreq" value="1055.65" exact_value="0x4483F4A4" /> +<par_real name="baseq" value="6.34546" exact_value="0x40CB0DF9" /> <par name="stages" value="0" /> -<par name="freq_track" value="64" /> -<par name="gain" value="64" /> +<par_real name="freq_tracking" value="0" exact_value="0x00000000" /> +<par_real name="gain" value="0" exact_value="0x00000000" /> </FILTER> <par_bool name="filter_envelope_enabled" value="yes" /> <FILTER_ENVELOPE> @@ -1060,11 +1060,11 @@ version-revision="1" ZynAddSubFX-author="Nasca Octavian Paul"> <FILTER> <par name="category" value="0" /> <par name="type" value="4" /> -<par name="freq" value="80" /> -<par name="q" value="40" /> +<par_real name="basefreq" value="2378.41" exact_value="0x4514A69F" /> +<par_real name="baseq" value="1.08427" exact_value="0x3F8AC956" /> <par name="stages" value="0" /> -<par name="freq_track" value="64" /> -<par name="gain" value="64" /> +<par_real name="freq_tracking" value="0" exact_value="0x00000000" /> +<par_real name="gain" value="0" exact_value="0x00000000" /> </FILTER> <par name="filter_velocity_sensing" value="64" /> <par name="filter_velocity_sensing_amplitude" value="64" /> @@ -3411,11 +3411,11 @@ version-revision="1" ZynAddSubFX-author="Nasca Octavian Paul"> <FILTER> <par name="category" value="0" /> <par name="type" value="2" /> -<par name="freq" value="111" /> -<par name="q" value="95" /> +<par_real name="basefreq" value="12745.1" exact_value="0x4647248B" /> +<par_real name="baseq" value="46.8148" exact_value="0x423B4262" /> <par name="stages" value="0" /> -<par name="freq_track" value="64" /> -<par name="gain" value="64" /> +<par_real name="freq_tracking" value="0" exact_value="0x00000000" /> +<par_real name="gain" value="0" exact_value="0x00000000" /> </FILTER> <FILTER_ENVELOPE> <par_bool name="free_mode" value="no" /> diff --git a/src/UI/MasterUI.fl b/src/UI/MasterUI.fl @@ -2,7 +2,7 @@ version 1.0302 header_name {.h} code_name {.cc} -decl {//Copyright (c) 2002-2009 Nasca Octavian Paul - (c) 2009-2016 Mark McCurry} {private local +decl {//Copyright (c) 2002-2009 Nasca Octavian Paul - (c) 2009-2017 Mark McCurry} {private local } decl {//License: GNU GPL version 2 or later} {private local diff --git a/src/main.cpp b/src/main.cpp @@ -220,7 +220,7 @@ int main(int argc, char *argv[]) << "\nZynAddSubFX - Copyright (c) 2002-2013 Nasca Octavian Paul and others" << endl; cerr - << " Copyright (c) 2009-2016 Mark McCurry [active maintainer]" + << " Copyright (c) 2009-2017 Mark McCurry [active maintainer]" << endl; cerr << "Compiled: " << __DATE__ << " " << __TIME__ << endl; cerr << "This program is free software (GNU GPL v2 or later) and \n";