zynaddsubfx

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

commit 37b6486c4c066c475f3265d4facd09a80839555a
parent 5d040f1a2c0542d78b32f6d0df9a2386187478eb
Author: fundamental <[email protected]>
Date:   Wed, 28 Jun 2017 14:39:04 -0400

Merge branch 'floating-point-control'

Adds floating point parameters and enhanced representations
to filter parameters

Diffstat:
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/Tests/MessageTest.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Tests/PluginTest.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Tests/guitar-adnote.xmz | 32++++++++++++++++----------------
8 files changed, 292 insertions(+), 57 deletions(-)

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'} @@ -114,6 +116,9 @@ void dump_param_cb(const rtosc::Port *p, const char *full_name, void *v) 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;; @@ -189,6 +194,10 @@ void dump_param_cb(const rtosc::Port *p, const char *full_name, void *v) 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 @@ -184,4 +184,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"), @@ -222,6 +226,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 @@ -257,16 +320,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) @@ -306,10 +374,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; @@ -337,21 +405,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; } /* @@ -427,11 +495,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)) { @@ -481,14 +549,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")) { @@ -526,11 +608,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); @@ -565,7 +647,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/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" />