zynaddsubfx

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

commit a65b407cb85e94c4017d98642b93db951baca808
parent a6b23e8a7b9ac3f6a95b522833400d4365c25fa2
Author: Johannes Lorenz <j.git@lorenz-ho.me>
Date:   Sat,  4 Apr 2020 23:38:35 +0200

Merge branch 'master' of https://git.code.sf.net/p/zynaddsubfx/code

Diffstat:
Msrc/Containers/NotePool.cpp | 2+-
Msrc/Containers/NotePool.h | 2+-
Msrc/Misc/Master.cpp | 27++++++++++++++++++---------
Msrc/Misc/Master.h | 1+
Msrc/Misc/Microtonal.cpp | 234++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/Misc/Microtonal.h | 20+++++++++++++++-----
Msrc/Misc/Part.cpp | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/Misc/Part.h | 30+++++++++++++++++++++---------
Msrc/Nio/InMgr.cpp | 11+++++++++++
Msrc/Nio/InMgr.h | 5+++--
Msrc/Nio/JackEngine.cpp | 49+++++++++++++++++++++++++++++++++----------------
Msrc/Nio/MidiIn.cpp | 37++++++++++++++++++++++++++++++-------
Msrc/Params/Controller.cpp | 61++++++++++++++++++++++++-------------------------------------
Msrc/Params/Controller.h | 13++++++-------
Msrc/Params/EnvelopeParams.cpp | 2+-
Msrc/Params/FilterParams.cpp | 2+-
Msrc/Synth/ADnote.cpp | 150++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/Synth/ADnote.h | 10+++++-----
Msrc/Synth/ModFilter.cpp | 3+--
Msrc/Synth/ModFilter.h | 9++++-----
Msrc/Synth/PADnote.cpp | 65++++++++++++++++++++++++++++++++++-------------------------------
Msrc/Synth/PADnote.h | 8++++----
Msrc/Synth/SUBnote.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/Synth/SUBnote.h | 14+++++++-------
Msrc/Synth/SynthNote.cpp | 51++++++++++++++++++++++++++++++++++++++-------------
Msrc/Synth/SynthNote.h | 20+++++++++++++-------
Msrc/Tests/AdNoteTest.h | 7+++----
Msrc/Tests/ControllerTest.h | 14+++++++-------
Msrc/Tests/MemoryStressTest.h | 3+--
Msrc/Tests/PadNoteTest.h | 7+++----
Msrc/Tests/SubNoteTest.h | 7+++----
Msrc/Tests/TriggerTest.h | 7+++----
Msrc/Tests/UnisonTest.h | 8+++-----
Msrc/UI/MicrotonalUI.fl | 2+-
Msrc/globals.h | 33+++++++++++++++++++++++++++++++++
35 files changed, 650 insertions(+), 462 deletions(-)

diff --git a/src/Containers/NotePool.cpp b/src/Containers/NotePool.cpp @@ -205,7 +205,7 @@ void NotePool::insertLegatoNote(note_t note, uint8_t sendto, SynthDescriptor des }; //There should only be one pair of notes which are still playing -void NotePool::applyLegato(note_t note, LegatoParams &par) +void NotePool::applyLegato(note_t note, const LegatoParams &par) { for(auto &desc:activeDesc()) { desc.note = note; diff --git a/src/Containers/NotePool.h b/src/Containers/NotePool.h @@ -122,7 +122,7 @@ class NotePool void insertLegatoNote(note_t note, uint8_t sendto, SynthDescriptor desc); void upgradeToLegato(void); - void applyLegato(note_t note, LegatoParams &par); + void applyLegato(note_t note, const LegatoParams &par); void makeUnsustainable(note_t note); diff --git a/src/Misc/Master.cpp b/src/Misc/Master.cpp @@ -973,15 +973,10 @@ void Master::noteOff(char chan, note_t note) */ void Master::polyphonicAftertouch(char chan, note_t note, char velocity) { - if(velocity) { - for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) - if(chan == part[npart]->Prcvchn) - if(part[npart]->Penabled) - part[npart]->PolyphonicAftertouch(note, velocity, keyshift); - - } - else - this->noteOff(chan, note); + for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) + if(chan == part[npart]->Prcvchn) + if(part[npart]->Penabled) + part[npart]->PolyphonicAftertouch(note, velocity); } /* @@ -1023,6 +1018,20 @@ void Master::setController(char chan, int type, int par) } } +/* + * Per note controllers + */ +void Master::setController(char chan, int type, note_t note, float value) +{ + if(frozenState) + return; + + /* Send the controller to all part assigned to the channel */ + for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) + if((chan == part[npart]->Prcvchn) && (part[npart]->Penabled != 0)) + part[npart]->SetController(type, note, value, keyshift); +} + void Master::vuUpdate(const float *outr, const float *outl) { //Peak computation (for vumeters) diff --git a/src/Misc/Master.h b/src/Misc/Master.h @@ -115,6 +115,7 @@ class Master void noteOff(char chan, note_t note); void polyphonicAftertouch(char chan, note_t note, char velocity); void setController(char chan, int type, int par); + void setController(char chan, int type, note_t note, float value); //void NRPN... diff --git a/src/Misc/Microtonal.cpp b/src/Misc/Microtonal.cpp @@ -221,7 +221,7 @@ void Microtonal::defaults() Pmapping[i] = i; for(int i = 0; i < MAX_OCTAVE_SIZE; ++i) { - octave[i].tuning = powf(2, (i % octavesize + 1) / 12.0f); + octave[i].tuning_log2 = (i % octavesize + 1) / 12.0f; octave[i].type = 1; octave[i].x1 = (i % octavesize + 1) * 100; octave[i].x2 = 0; @@ -255,110 +255,132 @@ unsigned char Microtonal::getoctavesize() const } /* - * Get the frequency according the note number + * Update the logarithmic power of two frequency according the note number */ -float Microtonal::getnotefreq(float note_log2_freq, int keyshift) const +bool Microtonal::updatenotefreq_log2(float &note_log2_freq, int keyshift) const { note_t note = roundf(12.0f * note_log2_freq); + float freq_log2 = note_log2_freq; // in this function will appears many times things like this: // var=(a+b*100)%b // I had written this way because if I use var=a%b gives unwanted results when a<0 // This is the same with divisions. - if((Pinvertupdown != 0) && ((Pmappingenabled == 0) || (Penabled == 0))) + if((Pinvertupdown != 0) && ((Pmappingenabled == 0) || (Penabled == 0))) { note = (int) Pinvertupdowncenter * 2 - note; + freq_log2 = Pinvertupdowncenter * (2.0f / 12.0f) - freq_log2; + } - //compute global fine detune - float globalfinedetunerap = - powf(2.0f, (Pglobalfinedetune - 64.0f) / 1200.0f); //-64.0f .. 63.0f cents + /* compute global fine detune, -64.0f .. 63.0f cents */ + const float globalfinedetunerap_log2 = (Pglobalfinedetune - 64.0f) / 1200.0f; - if(Penabled == 0) //12tET - return powf(2.0f, note_log2_freq + - ((keyshift - PAnote) / 12.0f)) * PAfreq * globalfinedetunerap; + if(Penabled == 0) { /* 12tET */ + freq_log2 += (keyshift - PAnote) / 12.0f; + } + else { /* Microtonal */ + const int scaleshift = + ((int)Pscaleshift - 64 + (int) octavesize * 100) % octavesize; + + /* compute the keyshift */ + float rap_keyshift_log2; + if(keyshift != 0) { + const int kskey = (keyshift + (int)octavesize * 100) % octavesize; + const int ksoct = (keyshift + (int)octavesize * 100) / octavesize - 100; + + rap_keyshift_log2 = + ((kskey == 0) ? 0.0f : octave[kskey - 1].tuning_log2) + + (octave[octavesize - 1].tuning_log2 * ksoct); + } + else { + rap_keyshift_log2 = 0.0f; + } - int scaleshift = - ((int)Pscaleshift - 64 + (int) octavesize * 100) % octavesize; + /* if the mapping is enabled */ + if(Pmappingenabled) { + if((note < Pfirstkey) || (note > Plastkey)) + goto failure; + + /* + * Compute how many mapped keys are from middle note to reference note + * and find out the proportion between the freq. of middle note and "A" note + */ + int tmp = PAnote - Pmiddlenote; + const bool minus = (tmp < 0); + if(minus) + tmp = -tmp; + + int deltanote = 0; + for(int i = 0; i < tmp; ++i) + if(Pmapping[i % Pmapsize] >= 0) + deltanote++; + + float rap_anote_middlenote_log2; + if(deltanote == 0) { + rap_anote_middlenote_log2 = 0.0f; + } + else { + rap_anote_middlenote_log2 = + octave[(deltanote - 1) % octavesize].tuning_log2 + + octave[octavesize - 1].tuning_log2 * ((deltanote - 1) / octavesize); + } + if(minus) + rap_anote_middlenote_log2 = -rap_anote_middlenote_log2; + + /* Convert from note (midi) to degree (note from the tuning) */ + int degoct = + (note - (int)Pmiddlenote + (int) Pmapsize + * 200) / (int)Pmapsize - 200; + int degkey = (note - Pmiddlenote + (int)Pmapsize * 100) % Pmapsize; + degkey = Pmapping[degkey]; + + /* check if key is not mapped */ + if(degkey < 0) + goto failure; + + /* + * Invert the keyboard upside-down if it is asked for + * TODO: do the right way by using Pinvertupdowncenter + */ + if(Pinvertupdown != 0) { + degkey = octavesize - degkey - 1; + degoct = -degoct; + } - //compute the keyshift - float rap_keyshift = 1.0f; - if(keyshift != 0) { - int kskey = (keyshift + (int)octavesize * 100) % octavesize; - int ksoct = (keyshift + (int)octavesize * 100) / octavesize - 100; - rap_keyshift = (kskey == 0) ? (1.0f) : (octave[kskey - 1].tuning); - rap_keyshift *= powf(octave[octavesize - 1].tuning, ksoct); - } + degkey = degkey + scaleshift; + degoct += degkey / octavesize; + degkey %= octavesize; - //if the mapping is enabled - if(Pmappingenabled) { - if((note < Pfirstkey) || (note > Plastkey)) - return -1.0f; - //Compute how many mapped keys are from middle note to reference note - //and find out the proportion between the freq. of middle note and "A" note - int tmp = PAnote - Pmiddlenote, minus = 0; - if(tmp < 0) { - tmp = -tmp; - minus = 1; + /* compute the logrithmic frequency of the note */ + freq_log2 = + ((degkey == 0) ? 0.0f : octave[degkey - 1].tuning_log2) + + (octave[octavesize - 1].tuning_log2 * degoct) - + rap_anote_middlenote_log2; } - int deltanote = 0; - for(int i = 0; i < tmp; ++i) - if(Pmapping[i % Pmapsize] >= 0) - deltanote++; - float rap_anote_middlenote = - (deltanote == - 0) ? (1.0f) : (octave[(deltanote - 1) % octavesize].tuning); - if(deltanote) - rap_anote_middlenote *= - powf(octave[octavesize - 1].tuning, - (deltanote - 1) / octavesize); - if(minus) - rap_anote_middlenote = 1.0f / rap_anote_middlenote; - - //Convert from note (midi) to degree (note from the tunning) - int degoct = - (note - (int)Pmiddlenote + (int) Pmapsize - * 200) / (int)Pmapsize - 200; - int degkey = (note - Pmiddlenote + (int)Pmapsize * 100) % Pmapsize; - degkey = Pmapping[degkey]; - if(degkey < 0) - return -1.0f; //this key is not mapped - - //invert the keyboard upside-down if it is asked for - //TODO: do the right way by using Pinvertupdowncenter - if(Pinvertupdown != 0) { - degkey = octavesize - degkey - 1; - degoct = -degoct; + else { /* if the mapping is disabled */ + const int nt = note - PAnote + scaleshift; + const int ntkey = (nt + (int)octavesize * 100) % octavesize; + const int ntoct = (nt - ntkey) / octavesize; + + freq_log2 = + octave[(ntkey + octavesize - 1) % octavesize].tuning_log2 + + octave[octavesize - 1].tuning_log2 * (ntkey ? ntoct : (ntoct - 1)); } - //compute the frequency of the note - degkey = degkey + scaleshift; - degoct += degkey / octavesize; - degkey %= octavesize; - - float freq = (degkey == 0) ? (1.0f) : octave[degkey - 1].tuning; - freq *= powf(octave[octavesize - 1].tuning, degoct); - freq *= PAfreq / rap_anote_middlenote; - freq *= globalfinedetunerap; if(scaleshift) - freq /= octave[scaleshift - 1].tuning; - return freq * rap_keyshift; - } - else { //if the mapping is disabled - int nt = note - PAnote + scaleshift; - int ntkey = (nt + (int)octavesize * 100) % octavesize; - int ntoct = (nt - ntkey) / octavesize; - - float oct = octave[octavesize - 1].tuning; - float freq = - octave[(ntkey + octavesize - 1) % octavesize].tuning * powf(oct, - ntoct) - * PAfreq; - if(!ntkey) - freq /= oct; - if(scaleshift) - freq /= octave[scaleshift - 1].tuning; - freq *= globalfinedetunerap; - return freq * rap_keyshift; + freq_log2 -= octave[scaleshift - 1].tuning_log2; + freq_log2 += rap_keyshift_log2; } + + /* common part */ + freq_log2 += log2f(PAfreq); + freq_log2 += globalfinedetunerap_log2; + + /* update value */ + note_log2_freq = freq_log2; + return true; + +failure: + return false; } bool Microtonal::operator==(const Microtonal &micro) const @@ -395,7 +417,7 @@ bool Microtonal::operator!=(const Microtonal &micro) const MCREQ(Pmapping[i]); for(int i = 0; i < octavesize; ++i) { - FMCREQ(octave[i].tuning); + FMCREQ(octave[i].tuning_log2); MCREQ(octave[i].type); MCREQ(octave[i].x1); MCREQ(octave[i].x2); @@ -418,8 +440,11 @@ bool Microtonal::operator!=(const Microtonal &micro) const */ int Microtonal::linetotunings(OctaveTuning &octave, const char *line) { - int x1 = -1, x2 = -1, type = -1; - float x = -1.0f, tmp, tuning = 1.0f; + int x1 = -1, x2 = -1; + int type; + float tmp; + float x = -1.0f; + float tuning_log2 = 0.0f; if(strstr(line, "/") == NULL) { if(strstr(line, ".") == NULL) { // M case (M=M/1) sscanf(line, "%d", &x1); @@ -443,7 +468,7 @@ int Microtonal::linetotunings(OctaveTuning &octave, const char *line) } if(x1 <= 0) - x1 = 1; //not allow zero frequency sounds (consider 0 as 1) + x1 = 1; //do not allow zero frequency sounds (consider 0 as 1) //convert to float if the number are too big if((type == 2) @@ -455,16 +480,18 @@ int Microtonal::linetotunings(OctaveTuning &octave, const char *line) case 1: x1 = (int) floor(x); tmp = fmod(x, 1.0f); - x2 = (int) (floor(tmp * 1e6)); - tuning = powf(2.0f, x / 1200.0f); + x2 = (int) floor(tmp * 1e6); + tuning_log2 = x / 1200.0f; break; case 2: x = ((float)x1) / x2; - tuning = x; + tuning_log2 = log2f(x); break; + default: + return 1; } - octave.tuning = tuning; + octave.tuning_log2 = tuning_log2; octave.type = type; octave.x1 = x1; octave.x2 = x2; @@ -504,7 +531,7 @@ int Microtonal::texttotunings(const char *text) return -2; //the input is empty octavesize = nl; for(int i = 0; i < octavesize; ++i) { - octave[i].tuning = tmpoctave[i].tuning; + octave[i].tuning_log2 = tmpoctave[i].tuning_log2; octave[i].type = tmpoctave[i].type; octave[i].x1 = tmpoctave[i].x1; octave[i].x2 = tmpoctave[i].x2; @@ -620,7 +647,7 @@ int Microtonal::loadscl(SclInfo &scl, const char *filename) scl.octavesize = nnotes; for(int i = 0; i < scl.octavesize; ++i) { - scl.octave[i].tuning = tmpoctave[i].tuning; + scl.octave[i].tuning_log2 = tmpoctave[i].tuning_log2; scl.octave[i].type = tmpoctave[i].type; scl.octave[i].x1 = tmpoctave[i].x1; scl.octave[i].x2 = tmpoctave[i].x2; @@ -730,8 +757,8 @@ void Microtonal::add2XML(XMLwrapper& xml) const for(int i = 0; i < octavesize; ++i) { xml.beginbranch("DEGREE", i); if(octave[i].type == 1) - xml.addparreal("cents", octave[i].tuning); - ; + xml.addparreal("cents", powf(2.0f, octave[i].tuning_log2)); + if(octave[i].type == 2) { xml.addpar("numerator", octave[i].x1); xml.addpar("denominator", octave[i].x2); @@ -780,7 +807,8 @@ void Microtonal::getfromXML(XMLwrapper& xml) if(xml.enterbranch("DEGREE", i) == 0) continue; octave[i].x2 = 0; - octave[i].tuning = xml.getparreal("cents", octave[i].tuning); + octave[i].tuning_log2 = log2f(xml.getparreal("cents", + powf(2.0f, octave[i].tuning_log2))) / log2(2.0f); octave[i].x1 = xml.getpar127("numerator", octave[i].x1); octave[i].x2 = xml.getpar127("denominator", octave[i].x2); @@ -788,10 +816,10 @@ void Microtonal::getfromXML(XMLwrapper& xml) octave[i].type = 2; else { octave[i].type = 1; - //populate fields for display - float x = logf(octave[i].tuning) / LOG_2 * 1200.0f; - octave[i].x1 = (int) floor(x); - octave[i].x2 = (int) (floor((x-octave[i].x1) * 1.0e6)); + /* populate fields for display */ + const float x = octave[i].tuning_log2 * 1200.0f; + octave[i].x1 = (int) floorf(x); + octave[i].x2 = (int) (floorf((x-octave[i].x1) * 1.0e6)); } diff --git a/src/Misc/Microtonal.h b/src/Misc/Microtonal.h @@ -41,9 +41,12 @@ struct KbmInfo struct OctaveTuning { unsigned char type; //1 for cents or 2 for division - // the real tuning (eg. +1.05946f for one halftone) - // or 2.0f for one octave - float tuning; + /* + * The real tuning in logarithmic power of two. + * For example 1/12 for one halftone and + * 1 for one octave. + */ + float tuning_log2; //the real tunning is x1/x2 unsigned int x1, x2; @@ -66,10 +69,17 @@ class Microtonal /**Destructor*/ ~Microtonal(); void defaults(); + /**Updates the logarithmic power of two frequency for a given note + */ + bool updatenotefreq_log2(float &note_log2_freq, int keyshift) const; /**Calculates the frequency for a given note */ - float getnotefreq(float note_log2_freq, int keyshift) const; - + float getnotefreq(float note_log2_freq, int keyshift) const { + if (updatenotefreq_log2(note_log2_freq, keyshift)) + return powf(2.0f, note_log2_freq); + else + return -1.0f; + }; //Parameters /**if the keys are inversed (the pitch is lower to keys from the right direction)*/ diff --git a/src/Misc/Part.cpp b/src/Misc/Part.cpp @@ -287,7 +287,7 @@ Part::Part(Allocator &alloc, const SYNTH_T &synth_, const AbsTime &time_, } killallnotes = false; - oldfreq = -1.0f; + oldfreq_log2 = -1.0f; cleanup(); @@ -469,9 +469,8 @@ static int kit_usage(const Part::Kit *kits, int note, int mode) /* * Note On Messages */ -bool Part::NoteOn(note_t note, +bool Part::NoteOnInternal(note_t note, unsigned char velocity, - int masterkeyshift, float note_log2_freq) { //Verify Basic Mode and sanity @@ -490,7 +489,6 @@ bool Part::NoteOn(note_t note, if(isMonoMode() || isLegatoMode()) { monomemPush(note); monomem[note].velocity = velocity; - monomem[note].mkeyshift = masterkeyshift; monomem[note].note_log2_freq = note_log2_freq; } else if(!monomemEmpty()) @@ -504,31 +502,26 @@ bool Part::NoteOn(note_t note, //Compute Note Parameters const float vel = getVelocity(velocity, Pvelsns, Pveloffs); - const int partkeyshift = (int)Pkeyshift - 64; - const int keyshift = masterkeyshift + partkeyshift; - const float notebasefreq = getBaseFreq(note_log2_freq, keyshift); - - if(notebasefreq < 0.0f) - return false; //Portamento lastnote = note; - if(oldfreq < 1.0f) - oldfreq = notebasefreq;//this is only the first note is played + + /* check if first note is played */ + if(oldfreq_log2 < 0.0f) + oldfreq_log2 = note_log2_freq; // For Mono/Legato: Force Portamento Off on first // notes. That means it is required that the previous note is // still held down or sustained for the Portamento to activate // (that's like Legato). - bool portamento = false; - if(Ppolymode || isRunningNote) - portamento = ctl.initportamento(oldfreq, notebasefreq, doingLegato); + const bool portamento = (Ppolymode || isRunningNote) && + ctl.initportamento(oldfreq_log2, note_log2_freq, doingLegato); - oldfreq = notebasefreq; + oldfreq_log2 = note_log2_freq; //Adjust Existing Notes if(doingLegato) { - LegatoParams pars = {notebasefreq, vel, portamento, note_log2_freq, true, prng()}; + LegatoParams pars = {vel, portamento, note_log2_freq, true, prng()}; notePool.applyLegato(note, pars); return true; } @@ -543,7 +536,7 @@ bool Part::NoteOn(note_t note, if(Pkitmode != 0 && !item.validNote(note)) continue; - SynthParams pars{memory, ctl, synth, time, notebasefreq, vel, + SynthParams pars{memory, ctl, synth, time, vel, portamento, note_log2_freq, false, prng()}; const int sendto = Pkitmode ? item.sendto() : 0; @@ -604,14 +597,18 @@ void Part::NoteOff(note_t note) //release the key } void Part::PolyphonicAftertouch(note_t note, - unsigned char velocity, - int masterkeyshift) + unsigned char velocity) { - (void) masterkeyshift; - if(!Pnoteon || !inRange(note, Pminkey, Pmaxkey) || Pdrummode) return; + /* + * Don't allow the velocity to reach zero. + * Keep it alive until note off. + */ + if(velocity == 0) + velocity = 1; + // MonoMem stuff: if(!Ppolymode) // if Poly is off monomem[note].velocity = velocity; // Store this note's velocity. @@ -715,6 +712,47 @@ void Part::SetController(unsigned int type, int par) break; } } + +/* + * Per note controllers. + */ +void Part::SetController(unsigned int type, note_t note, float value, + int masterkeyshift) +{ + if(!Pnoteon || !inRange(note, Pminkey, Pmaxkey) || Pdrummode) + return; + + switch (type) { + case C_aftertouch: + PolyphonicAftertouch(note, floorf(value)); + break; + case C_pitch: { + if (getNoteLog2Freq(masterkeyshift, value) == false) + break; + + /* Make sure MonoMem's frequency information is kept up to date */ + if(!Ppolymode) + monomem[note].note_log2_freq = value; + + for(auto &d:notePool.activeDesc()) { + if(d.note == note && d.playing()) + for(auto &s:notePool.activeNotes(d)) + s.note->setPitch(value); + } + break; + } + case C_filtercutoff: + for(auto &d:notePool.activeDesc()) { + if(d.note == note && d.playing()) + for(auto &s:notePool.activeNotes(d)) + s.note->setFilterCutoff(value); + } + break; + default: + break; + } +} + /* * Release the sustained keys */ @@ -750,18 +788,19 @@ void Part::MonoMemRenote() { note_t mmrtempnote = monomemBack(); // Last list element. monomemPop(mmrtempnote); // We remove it, will be added again in NoteOn(...). - NoteOn(mmrtempnote, + NoteOnInternal(mmrtempnote, monomem[mmrtempnote].velocity, - monomem[mmrtempnote].mkeyshift, monomem[mmrtempnote].note_log2_freq); } -float Part::getBaseFreq(float note_log2_freq, int keyshift) const +bool Part::getNoteLog2Freq(int masterkeyshift, float &note_log2_freq) { - if(Pdrummode) - return 440.0f * powf(2.0f, note_log2_freq - (69.0f / 12.0f)); - else - return microtonal->getnotefreq(note_log2_freq, keyshift); + if(Pdrummode) { + note_log2_freq += log2f(440.0f) - 69.0f / 12.0f; + return true; + } + return microtonal->updatenotefreq_log2(note_log2_freq, + (int)Pkeyshift - 64 + masterkeyshift); } float Part::getVelocity(uint8_t velocity, uint8_t velocity_sense, diff --git a/src/Misc/Part.h b/src/Misc/Part.h @@ -42,20 +42,34 @@ class Part // Midi commands implemented + //returns true when successful + bool getNoteLog2Freq(int masterkeyshift, float &note_log2_freq); + //returns true when note is successfully applied bool NoteOn(note_t note, uint8_t vel, int shift) REALTIME { - return (NoteOn(note, vel, shift, note / 12.0f)); + float log2_freq = note / 12.0f; + return (getNoteLog2Freq(shift, log2_freq) && + NoteOnInternal(note, vel, log2_freq)); + }; + + //returns true when note is successfully applied + bool NoteOn(note_t note, uint8_t vel, int shift, + float log2_freq) REALTIME { + return (getNoteLog2Freq(shift, log2_freq) && + NoteOnInternal(note, vel, log2_freq)); }; - bool NoteOn(note_t note, + + //returns true when note is successfully applied + bool NoteOnInternal(note_t note, unsigned char velocity, - int masterkeyshift, float note_log2_freq) REALTIME; void NoteOff(note_t note) REALTIME; void PolyphonicAftertouch(note_t note, - unsigned char velocity, - int masterkeyshift) REALTIME; + unsigned char velocity) REALTIME; void AllNotesOff() REALTIME; //panic void SetController(unsigned int type, int par) REALTIME; + void SetController(unsigned int type, note_t, float value, + int masterkeyshift) REALTIME; void ReleaseSustainedKeys() REALTIME; //this is called when the sustain pedal is released void ReleaseAllKeys() REALTIME; //this is called on AllNotesOff controller @@ -164,7 +178,6 @@ class Part private: void MonoMemRenote(); // MonoMem stuff. - float getBaseFreq(float note_log2_freq, int keyshift) const; float getVelocity(uint8_t velocity, uint8_t velocity_sense, uint8_t velocity_offset) const; void verifyKeyMode(void); @@ -191,15 +204,14 @@ class Part short monomemnotes[256]; // A list to remember held notes. struct { unsigned char velocity; - int mkeyshift; // I'm not sure masterkeyshift should be remembered. float note_log2_freq; } monomem[256]; /* 256 is to cover all possible note values. monomem[] is used in conjunction with the list to - store the velocity and masterkeyshift values of a given note (the list only store note values). + store the velocity and logarithmic frequency values of a given note. For example 'monomem[note].velocity' would be the velocity value of the note 'note'.*/ - float oldfreq; //this is used for portamento + float oldfreq_log2; //this is used for portamento Microtonal *microtonal; FFTwrapper *fft; WatchManager *wm; diff --git a/src/Nio/InMgr.cpp b/src/Nio/InMgr.cpp @@ -45,6 +45,13 @@ ostream &operator<<(ostream &out, const MidiEvent &ev) << " value(" << ev.value << ")"; break; + case M_FLOAT_CTRL: + out << "MidiNote: controller(" << ev.num << ")\n" + << " channel(" << ev.channel << ")\n" + << " note(" << ev.value << ")\n" + << " log2_value(" << ev.log2_freq << ")"; + break; + case M_PGMCHANGE: out << "PgmChange: program(" << ev.num << ")\n" << " channel(" << ev.channel << ")"; @@ -120,6 +127,10 @@ void InMgr::flush(unsigned frameStart, unsigned frameStop) master->setController(ev.channel, ev.num, ev.value); break; + case M_FLOAT_CTRL: + master->setController(ev.channel, ev.num, ev.value, ev.log2_freq); + break; + case M_PGMCHANGE: for(int i=0; i < NUM_MIDI_PARTS; ++i) { //set the program of the parts assigned to the midi channel diff --git a/src/Nio/InMgr.h b/src/Nio/InMgr.h @@ -23,7 +23,8 @@ enum midi_type { M_CONTROLLER = 2, // for controller M_PGMCHANGE = 3, // for program change M_PRESSURE = 4, // for polyphonic aftertouch - M_FLOAT_NOTE = 5 // for floating point note + M_FLOAT_NOTE = 5, // for floating point note + M_FLOAT_CTRL = 6 // for floating point controller }; struct MidiEvent { @@ -33,7 +34,7 @@ struct MidiEvent { int num; //note, controller or program number int value; //velocity or controller value int time; //time offset of event (used only in jack->jack case at the moment) - float log2_freq; //type=5 for logarithmic representation of note + float log2_freq; //type=5,6 for logarithmic representation of note/parameter }; //super simple class to manage the inputs diff --git a/src/Nio/JackEngine.cpp b/src/Nio/JackEngine.cpp @@ -382,61 +382,78 @@ void JackEngine::handleMidi(unsigned long frames) void *midi_buf = jack_port_get_buffer(midi.inport, frames); jack_midi_event_t jack_midi_event; jack_nframes_t event_index = 0; - unsigned char *midi_data; + unsigned char buf[3]; unsigned char type; while(jack_midi_event_get(&jack_midi_event, midi_buf, event_index++) == 0) { - MidiEvent ev; - midi_data = jack_midi_event.buffer; - type = midi_data[0] & 0xF0; - ev.channel = midi_data[0] & 0x0F; + MidiEvent ev = {}; + + memset(buf, 0, sizeof(buf)); + memcpy(buf, jack_midi_event.buffer, + std::min(sizeof(buf), jack_midi_event.size)); + + /* make sure the values are within range */ + buf[1] &= 0x7F; + buf[2] &= 0x7F; + type = buf[0] & 0xF0; + ev.channel = buf[0] & 0x0F; ev.time = midi.jack_sync ? jack_midi_event.time : 0; switch(type) { case 0x80: /* note-off */ ev.type = M_NOTE; - ev.num = midi_data[1]; + ev.num = buf[1]; ev.value = 0; InMgr::getInstance().putEvent(ev); break; case 0x90: /* note-on */ ev.type = M_NOTE; - ev.num = midi_data[1]; - ev.value = midi_data[2]; + ev.num = buf[1]; + ev.value = buf[2]; InMgr::getInstance().putEvent(ev); break; case 0xA0: /* pressure, aftertouch */ ev.type = M_PRESSURE; - ev.num = midi_data[1]; - ev.value = midi_data[2]; + ev.num = buf[1]; + ev.value = buf[2]; InMgr::getInstance().putEvent(ev); break; case 0xB0: /* controller */ ev.type = M_CONTROLLER; - ev.num = midi_data[1]; - ev.value = midi_data[2]; + ev.num = buf[1]; + ev.value = buf[2]; InMgr::getInstance().putEvent(ev); break; case 0xC0: /* program change */ ev.type = M_PGMCHANGE; - ev.num = midi_data[1]; - ev.value = 0; + ev.num = buf[1]; InMgr::getInstance().putEvent(ev); break; case 0xE0: /* pitch bend */ ev.type = M_CONTROLLER; ev.num = C_pitchwheel; - ev.value = ((midi_data[2] << 7) | midi_data[1]) - 8192; + ev.value = ((buf[2] << 7) | buf[1]) - 8192; InMgr::getInstance().putEvent(ev); break; - /* XXX TODO: handle MSB/LSB controllers and RPNs and NRPNs */ + default: + for (size_t x = 0; x < jack_midi_event.size; x += 3) { + size_t y = jack_midi_event.size - x; + if (y >= 3) { + memcpy(buf, (uint8_t *)jack_midi_event.buffer + x, 3); + } else { + memset(buf, 0, sizeof(buf)); + memcpy(buf, (uint8_t *)jack_midi_event.buffer + x, y); + } + midiProcess(buf[0], buf[1], buf[2]); + } + break; } } } diff --git a/src/Nio/MidiIn.cpp b/src/Nio/MidiIn.cpp @@ -65,20 +65,43 @@ void MidiIn::midiProcess(unsigned char head, if (sysex_offset >= 10 && sysex_data[1] == 0x0A && sysex_data[2] == 0x55) { - ev.type = M_FLOAT_NOTE; ev.channel = sysex_data[3] & 0x0F; - ev.num = sysex_data[4]; - ev.value = sysex_data[5]; - ev.log2_freq = (sysex_data[6] + + ev.log2_freq = sysex_data[6] + (sysex_data[7] / (128.0f)) + (sysex_data[8] / (128.0f * 128.0f)) + - (sysex_data[9] / (128.0f * 128.0f * 128.0f)) - ) / 12.0f; + (sysex_data[9] / (128.0f * 128.0f * 128.0f)); + + switch (sysex_data[3] >> 4) { + case 0: /* Note ON */ + ev.type = M_FLOAT_NOTE; + ev.num = sysex_data[4]; + ev.value = sysex_data[5]; + ev.log2_freq /= 12.0f; + break; + case 1: /* Pressure, Aftertouch */ + ev.type = M_FLOAT_CTRL; + ev.num = C_aftertouch; + ev.value = sysex_data[4]; + break; + case 2: /* Controller */ + ev.type = M_FLOAT_CTRL; + ev.num = sysex_data[5]; + ev.value = sysex_data[4]; + break; + case 3: /* Absolute pitch */ + ev.type = M_FLOAT_CTRL; + ev.num = C_pitch; + ev.value = sysex_data[4]; + ev.log2_freq /= 12.0f; + break; + default: + return; + } InMgr::getInstance().putEvent(ev); } return; /* message complete */ } else { - return; /* wait for more data */ + return; /* wait for more data */ } } switch(head & 0xf0) { diff --git a/src/Params/Controller.cpp b/src/Params/Controller.cpp @@ -131,11 +131,10 @@ void Controller::defaults() portamento.updowntimestretch = 64; portamento.pitchthresh = 3; portamento.pitchthreshtype = 1; - portamento.noteusing = -1; resonancecenter.depth = 64; resonancebandwidth.depth = 64; - initportamento(440.0f, 440.0f, false); // Now has a third argument + initportamento(log2f(440.0f), log2f(440.0f), false); setportamento(0); } @@ -270,8 +269,8 @@ void Controller::setportamento(int value) portamento.portamento = ((value < 64) ? 0 : 1); } -int Controller::initportamento(float oldfreq, - float newfreq, +int Controller::initportamento(float oldfreq_log2, + float newfreq_log2, bool legatoflag) { portamento.x = 0.0f; @@ -280,59 +279,47 @@ int Controller::initportamento(float oldfreq, if(portamento.portamento == 0) return 0; } - else // No legato, do the original if...return - if((portamento.used != 0) || (portamento.portamento == 0)) - return 0; - ; + else { // No legato, do the original if...return + if((portamento.used != 0) || (portamento.portamento == 0)) + return 0; + } float portamentotime = powf(100.0f, portamento.time / 127.0f) / 50.0f; //portamento time in seconds + const float deltafreq_log2 = oldfreq_log2 - newfreq_log2; + const float absdeltaf_log2 = fabsf(deltafreq_log2); if(portamento.proportional) { - //If there is a min(float,float) and a max(float,float) then they - //could be used here - //Linear functors could also make this nicer - if(oldfreq > newfreq) //2 is the center of propRate - portamentotime *= - powf(oldfreq / newfreq - / (portamento.propRate / 127.0f * 3 + .05), - (portamento.propDepth / 127.0f * 1.6f + .2)); - else //1 is the center of propDepth - portamentotime *= - powf(newfreq / oldfreq - / (portamento.propRate / 127.0f * 3 + .05), - (portamento.propDepth / 127.0f * 1.6f + .2)); + const float absdeltaf = powf(2.0f, absdeltaf_log2); + + portamentotime *= powf(absdeltaf + / (portamento.propRate / 127.0f * 3 + .05), + (portamento.propDepth / 127.0f * 1.6f + .2)); } - if((portamento.updowntimestretch >= 64) && (newfreq < oldfreq)) { + if((portamento.updowntimestretch >= 64) && (newfreq_log2 < oldfreq_log2)) { if(portamento.updowntimestretch == 127) return 0; portamentotime *= powf(0.1f, (portamento.updowntimestretch - 64) / 63.0f); } - if((portamento.updowntimestretch < 64) && (newfreq > oldfreq)) { + if((portamento.updowntimestretch < 64) && (newfreq_log2 > oldfreq_log2)) { if(portamento.updowntimestretch == 0) return 0; portamentotime *= powf(0.1f, (64.0f - portamento.updowntimestretch) / 64.0f); } - //printf("%f->%f : Time %f\n",oldfreq,newfreq,portamentotime); - portamento.dx = synth.buffersize_f / (portamentotime * synth.samplerate_f); - portamento.origfreqrap = oldfreq / newfreq; - - float tmprap = ((portamento.origfreqrap > 1.0f) ? - (portamento.origfreqrap) : - (1.0f / portamento.origfreqrap)); + portamento.origfreqdelta_log2 = deltafreq_log2; - float thresholdrap = powf(2.0f, portamento.pitchthresh / 12.0f); - if((portamento.pitchthreshtype == 0) && (tmprap - 0.00001f > thresholdrap)) + const float threshold_log2 = portamento.pitchthresh / 12.0f; + if((portamento.pitchthreshtype == 0) && (absdeltaf_log2 - 0.00001f > threshold_log2)) return 0; - if((portamento.pitchthreshtype == 1) && (tmprap + 0.00001f < thresholdrap)) + if((portamento.pitchthreshtype == 1) && (absdeltaf_log2 + 0.00001f < threshold_log2)) return 0; - portamento.used = 1; - portamento.freqrap = portamento.origfreqrap; + portamento.used = 1; + portamento.freqdelta_log2 = deltafreq_log2; return 1; } @@ -346,8 +333,8 @@ void Controller::updateportamento() portamento.x = 1.0f; portamento.used = 0; } - portamento.freqrap = - (1.0f - portamento.x) * portamento.origfreqrap + portamento.x; + portamento.freqdelta_log2 = + (1.0f - portamento.x) * portamento.origfreqdelta_log2; } diff --git a/src/Params/Controller.h b/src/Params/Controller.h @@ -169,12 +169,11 @@ class Controller unsigned char updowntimestretch; /**this value is used to compute the actual portamento * - * This is a multiplyer to change the frequency of the newer - * frequency to fit the profile of the portamento. + * This is the logarithmic power of two frequency + * adjustment of the newer frequency to fit the profile of + * the portamento. * This will be linear with respect to x.*/ - float freqrap; - /**this is used by the Part for knowing which note uses the portamento*/ - int noteusing; + float freqdelta_log2; /**if a the portamento is used by a note * \todo see if this can be a bool*/ int used; @@ -185,8 +184,8 @@ class Controller float x; /**dx is the increment to x when updateportamento is called*/ float dx; - /** this is used for computing oldfreq value from x*/ - float origfreqrap; + /** this is used for computing freqdelta_log2 value from x*/ + float origfreqdelta_log2; } portamento; struct { //Resonance Center Frequency diff --git a/src/Params/EnvelopeParams.cpp b/src/Params/EnvelopeParams.cpp @@ -329,7 +329,7 @@ void EnvelopeParams::init(zyn::consumer_location_t _loc) case ad_voice_filter: ADSRinit_filter(90, 0.025, 40, 0.025, 0.009, 40); break; case ad_voice_fm_freq: ASRinit(20, 3.62, 40, 1.876); break; case ad_voice_fm_amp: ADSRinit(80, 90, 127, 100); break; - case sub_freq: ASRinit(0.01, 0.025, 64, 0.5); break; + case sub_freq: ASRinit(30, 0.025, 64, 0.5); break; case sub_bandwidth: ASRinit_bw(100, 0.97, 64, 0.5); break; default: throw std::logic_error("Invalid envelope consumer location"); }; diff --git a/src/Params/FilterParams.cpp b/src/Params/FilterParams.cpp @@ -469,7 +469,7 @@ float FilterParams::getq() const } float FilterParams::getfreqtracking(float notefreq) const { - return log2f(notefreq / 440.0f) * (freqtracking / 100.0); + return log2f(notefreq / 440.0f) * (freqtracking / 100.0f); } float FilterParams::getgain() const diff --git a/src/Synth/ADnote.cpp b/src/Synth/ADnote.cpp @@ -31,7 +31,7 @@ #define LENGTHOF(x) ((int)(sizeof(x)/sizeof(x[0]))) namespace zyn { -ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars, +ADnote::ADnote(ADnoteParameters *pars_, const SynthParams &spars, WatchManager *wm, const char *prefix) :SynthNote(spars), watch_be4_add(wm, prefix, "noteout/be4_mix"), watch_after_add(wm,prefix,"noteout/after_mix"), watch_punch(wm, prefix, "noteout/punch"), watch_legato(wm, prefix, "noteout/legato"), pars(*pars_) @@ -46,7 +46,6 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars, portamento = spars.portamento; note_log2_freq = spars.note_log2_freq; NoteEnabled = ON; - basefreq = spars.frequency; velocity = spars.velocity; initial_seed = spars.seed; current_prng_state = spars.seed; @@ -75,7 +74,7 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars, pars.GlobalPar.PPunchVelocitySensing)); float time = powf(10, 3.0f * pars.GlobalPar.PPunchTime / 127.0f) / 10000.0f; //0.1f .. 100 ms - float stretch = powf(440.0f / spars.frequency, + float stretch = powf(440.0f / powf(2.0f, spars.note_log2_freq), pars.GlobalPar.PPunchStretch / 64.0f); NoteGlobalPar.Punch.dt = 1.0f / (time * synth.samplerate_f * stretch); } @@ -425,6 +424,8 @@ void ADnote::setupVoiceMod(int nvoice, bool first_run) { auto &param = pars.VoicePar[nvoice]; auto &voice = NoteVoicePar[nvoice]; + float FMVolume; + if (param.Type != 0) voice.FMEnabled = NONE; else @@ -496,27 +497,28 @@ void ADnote::setupVoiceMod(int nvoice, bool first_run) case PW_MOD: fmvoldamp = powf(440.0f / getvoicebasefreq(nvoice), param.PFMVolumeDamp / 64.0f); - voice.FMVolume = (expf(fmvolume_ * FM_AMP_MULTIPLIER) - 1.0f) + FMVolume = (expf(fmvolume_ * FM_AMP_MULTIPLIER) - 1.0f) * fmvoldamp * 4.0f; break; case FREQ_MOD: - voice.FMVolume = (expf(fmvolume_ * FM_AMP_MULTIPLIER) - 1.0f) + FMVolume = (expf(fmvolume_ * FM_AMP_MULTIPLIER) - 1.0f) * fmvoldamp * 4.0f; break; default: if(fmvoldamp > 1.0f) fmvoldamp = 1.0f; - voice.FMVolume = fmvolume_ * fmvoldamp; + FMVolume = fmvolume_ * fmvoldamp; + break; } //Voice's modulator velocity sensing - NoteVoicePar[nvoice].FMVolume *= + voice.FMVolume = FMVolume * VelF(velocity, pars.VoicePar[nvoice].PFMVelocityScaleFunction); } SynthNote *ADnote::cloneLegato(void) { - SynthParams sp{memory, ctl, synth, time, legato.param.freq, velocity, + SynthParams sp{memory, ctl, synth, time, velocity, (bool)portamento, legato.param.note_log2_freq, true, initial_seed }; return memory.alloc<ADnote>(&pars, sp); @@ -526,7 +528,7 @@ SynthNote *ADnote::cloneLegato(void) // initparameters() stuck together with some lines removed so that it // only alter the already playing note (to perform legato). It is // possible I left stuff that is not required for this. -void ADnote::legatonote(LegatoParams lpars) +void ADnote::legatonote(const LegatoParams &lpars) { //ADnoteParameters &pars = *partparams; // Manage legato stuff @@ -535,22 +537,22 @@ void ADnote::legatonote(LegatoParams lpars) portamento = lpars.portamento; note_log2_freq = lpars.note_log2_freq; - basefreq = lpars.frequency; initial_seed = lpars.seed; current_prng_state = lpars.seed; - if(velocity > 1.0f) + if(lpars.velocity > 1.0f) velocity = 1.0f; - velocity = lpars.velocity; + else + velocity = lpars.velocity; + + const float basefreq = powf(2.0f, note_log2_freq); NoteGlobalPar.Detune = getdetune(pars.GlobalPar.PDetuneType, pars.GlobalPar.PCoarseDetune, pars.GlobalPar.PDetune); bandwidthDetuneMultiplier = pars.getBandwidthDetuneMultiplier(); - if(pars.GlobalPar.PPanning == 0) { - NoteGlobalPar.Panning = getRandomFloat(); - } else + if(pars.GlobalPar.PPanning) NoteGlobalPar.Panning = pars.GlobalPar.PPanning / 128.0f; NoteGlobalPar.Filter->updateSense(velocity, @@ -559,112 +561,94 @@ void ADnote::legatonote(LegatoParams lpars) for(int nvoice = 0; nvoice < NUM_VOICES; ++nvoice) { - if(NoteVoicePar[nvoice].Enabled == OFF) + auto &voice = NoteVoicePar[nvoice]; + float FMVolume; + + if(voice.Enabled == OFF) continue; //(gf) Stay the same as first note in legato. - NoteVoicePar[nvoice].fixedfreq = pars.VoicePar[nvoice].Pfixedfreq; - NoteVoicePar[nvoice].fixedfreqET = pars.VoicePar[nvoice].PfixedfreqET; + voice.fixedfreq = pars.VoicePar[nvoice].Pfixedfreq; + voice.fixedfreqET = pars.VoicePar[nvoice].PfixedfreqET; //use the Globalpars.detunetype if the detunetype is 0 if(pars.VoicePar[nvoice].PDetuneType != 0) { - NoteVoicePar[nvoice].Detune = getdetune( + voice.Detune = getdetune( pars.VoicePar[nvoice].PDetuneType, pars.VoicePar[nvoice].PCoarseDetune, 8192); //coarse detune - NoteVoicePar[nvoice].FineDetune = getdetune( + voice.FineDetune = getdetune( pars.VoicePar[nvoice].PDetuneType, 0, pars.VoicePar[nvoice].PDetune); //fine detune } else { - NoteVoicePar[nvoice].Detune = getdetune( + voice.Detune = getdetune( pars.GlobalPar.PDetuneType, pars.VoicePar[nvoice].PCoarseDetune, 8192); //coarse detune - NoteVoicePar[nvoice].FineDetune = getdetune( + voice.FineDetune = getdetune( pars.GlobalPar.PDetuneType, 0, pars.VoicePar[nvoice].PDetune); //fine detune } if(pars.VoicePar[nvoice].PFMDetuneType != 0) - NoteVoicePar[nvoice].FMDetune = getdetune( + voice.FMDetune = getdetune( pars.VoicePar[nvoice].PFMDetuneType, pars.VoicePar[nvoice].PFMCoarseDetune, pars.VoicePar[nvoice].PFMDetune); else - NoteVoicePar[nvoice].FMDetune = getdetune( + voice.FMDetune = getdetune( pars.GlobalPar.PDetuneType, pars.VoicePar[nvoice].PFMCoarseDetune, pars.VoicePar[nvoice].PFMDetune); - //Get the voice's oscil or external's voice oscil - int vc = nvoice; - if(pars.VoicePar[nvoice].Pextoscil != -1) - vc = pars.VoicePar[nvoice].Pextoscil; - if(!pars.GlobalPar.Hrandgrouping) - pars.VoicePar[vc].OscilSmp->newrandseed(getRandomUint()); - - pars.VoicePar[vc].OscilSmp->get(NoteVoicePar[nvoice].OscilSmp, - getvoicebasefreq(nvoice), - pars.VoicePar[nvoice].Presonance); //(gf)Modif of the above line. - - //I store the first elements to the last position for speedups - for(int i = 0; i < OSCIL_SMP_EXTRA_SAMPLES; ++i) - NoteVoicePar[nvoice].OscilSmp[synth.oscilsize - + i] = - NoteVoicePar[nvoice].OscilSmp[i]; - - auto &voiceFilter = NoteVoicePar[nvoice].Filter; + auto &voiceFilter = voice.Filter; if(voiceFilter) { const auto &vce = pars.VoicePar[nvoice]; voiceFilter->updateSense(velocity, vce.PFilterVelocityScale, vce.PFilterVelocityScaleFunction); } - NoteVoicePar[nvoice].filterbypass = + voice.filterbypass = pars.VoicePar[nvoice].Pfilterbypass; - NoteVoicePar[nvoice].FMVoice = pars.VoicePar[nvoice].PFMVoice; + voice.FMVoice = pars.VoicePar[nvoice].PFMVoice; //Compute the Voice's modulator volume (incl. damping) float fmvoldamp = powf(440.0f / getvoicebasefreq(nvoice), pars.VoicePar[nvoice].PFMVolumeDamp / 64.0f - 1.0f); - switch(NoteVoicePar[nvoice].FMEnabled) { + switch(voice.FMEnabled) { case PHASE_MOD: case PW_MOD: fmvoldamp = powf(440.0f / getvoicebasefreq( nvoice), pars.VoicePar[nvoice].PFMVolumeDamp / 64.0f); - NoteVoicePar[nvoice].FMVolume = + FMVolume = (expf(pars.VoicePar[nvoice].FMvolume / 100.0f * FM_AMP_MULTIPLIER) - 1.0f) * fmvoldamp * 4.0f; break; case FREQ_MOD: - NoteVoicePar[nvoice].FMVolume = + FMVolume = (expf(pars.VoicePar[nvoice].FMvolume / 100.0f * FM_AMP_MULTIPLIER) - 1.0f) * fmvoldamp * 4.0f; break; default: if(fmvoldamp > 1.0f) fmvoldamp = 1.0f; - NoteVoicePar[nvoice].FMVolume = + FMVolume = pars.VoicePar[nvoice].FMvolume / 100.0f * fmvoldamp; + break; } //Voice's modulator velocity sensing - NoteVoicePar[nvoice].FMVolume *= + voice.FMVolume = FMVolume * VelF(velocity, pars.VoicePar[nvoice].PFMVelocityScaleFunction); - - NoteVoicePar[nvoice].DelayTicks = - (int)((expf(pars.VoicePar[nvoice].PDelay / 127.0f - * logf(50.0f)) - - 1.0f) / synth.buffersize_f / 10.0f * synth.samplerate_f); } /// initparameters(); @@ -833,7 +817,7 @@ void ADnote::initparameters(WatchManager *wm, const char *prefix) { int tmp[NUM_VOICES]; ScratchString pre = prefix; - //ADnoteParameters &pars = *partparams; + const float basefreq = powf(2.0f, note_log2_freq); // Global Parameters NoteGlobalPar.initparameters(pars.GlobalPar, synth, @@ -1063,27 +1047,29 @@ void ADnote::setfreqFM(int nvoice, float in_freq) /* * Get Voice base frequency */ -float ADnote::getvoicebasefreq(int nvoice) const +float ADnote::getvoicebasefreq(int nvoice, float adjust_log2) const { - float detune = NoteVoicePar[nvoice].Detune / 100.0f + const float detune = NoteVoicePar[nvoice].Detune / 100.0f + NoteVoicePar[nvoice].FineDetune / 100.0f * ctl.bandwidth.relbw * bandwidthDetuneMultiplier + NoteGlobalPar.Detune / 100.0f; - if(NoteVoicePar[nvoice].fixedfreq == 0) - return this->basefreq * powf(2, detune / 12.0f); + if(NoteVoicePar[nvoice].fixedfreq == 0) { + return powf(2.0f, note_log2_freq + detune / 12.0f + adjust_log2); + } else { //the fixed freq is enabled - float fixedfreq = 440.0f; - int fixedfreqET = NoteVoicePar[nvoice].fixedfreqET; + const int fixedfreqET = NoteVoicePar[nvoice].fixedfreqET; + float fixedfreq_log2 = log2f(440.0f); + if(fixedfreqET != 0) { //if the frequency varies according the keyboard note - float tmp = (note_log2_freq - (69.0f / 12.0f)) - * (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); + float tmp_log2 = (note_log2_freq - fixedfreq_log2) * + (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); if(fixedfreqET <= 64) - fixedfreq *= powf(2.0f, tmp); + fixedfreq_log2 += tmp_log2; else - fixedfreq *= powf(3.0f, tmp); + fixedfreq_log2 += tmp_log2 * log2f(3.0f); } - return fixedfreq * powf(2.0f, detune / 12.0f); + return powf(2.0f, fixedfreq_log2 + detune / 12.0f + adjust_log2); } } @@ -1092,8 +1078,7 @@ float ADnote::getvoicebasefreq(int nvoice) const */ float ADnote::getFMvoicebasefreq(int nvoice) const { - float detune = NoteVoicePar[nvoice].FMDetune / 100.0f; - return getvoicebasefreq(nvoice) * powf(2, detune / 12.0f); + return getvoicebasefreq(nvoice, NoteVoicePar[nvoice].FMDetune / 1200.0f); } /* @@ -1101,9 +1086,11 @@ float ADnote::getFMvoicebasefreq(int nvoice) const */ void ADnote::computecurrentparameters() { + const float relfreq = getFilterCutoffRelFreq(); int nvoice; float voicefreq, voicepitch, FMfreq, FMrelativepitch, globalpitch; + globalpitch = 0.01f * (NoteGlobalPar.FreqEnvelope->envout() + NoteGlobalPar.FreqLfo->lfoout() * ctl.modwheel.relmod); @@ -1112,14 +1099,12 @@ void ADnote::computecurrentparameters() * NoteGlobalPar.AmpEnvelope->envout_dB() * NoteGlobalPar.AmpLfo->amplfoout(); - NoteGlobalPar.Filter->update(ctl.filtercutoff.relfreq, - ctl.filterq.relq); - + NoteGlobalPar.Filter->update(relfreq, ctl.filterq.relq); //compute the portamento, if it is used by this note - float portamentofreqrap = 1.0f; + float portamentofreqdelta_log2 = 0.0f; if(portamento != 0) { //this voice use portamento - portamentofreqrap = ctl.portamento.freqrap; + portamentofreqdelta_log2 = ctl.portamento.freqdelta_log2; if(ctl.portamento.used == 0) //the portamento has finished portamento = 0; //this note is no longer "portamented" } @@ -1151,8 +1136,7 @@ void ADnote::computecurrentparameters() /****************/ auto *voiceFilter = NoteVoicePar[nvoice].Filter; if(voiceFilter) { - voiceFilter->update(ctl.filtercutoff.relfreq, - ctl.filterq.relq); + voiceFilter->update(relfreq, ctl.filterq.relq); } if(NoteVoicePar[nvoice].noisetype == 0) { //compute only if the voice isn't noise @@ -1167,11 +1151,11 @@ void ADnote::computecurrentparameters() if(NoteVoicePar[nvoice].FreqEnvelope) voicepitch += NoteVoicePar[nvoice].FreqEnvelope->envout() / 100.0f; - voicefreq = getvoicebasefreq(nvoice) - * powf(2, (voicepitch + globalpitch) / 12.0f); //Hz frequency + voicefreq = getvoicebasefreq(nvoice, portamentofreqdelta_log2 + + (voicepitch + globalpitch) / 12.0f); //Hz frequency voicefreq *= powf(ctl.pitchwheel.relfreq, NoteVoicePar[nvoice].BendAdjust); //change the frequency by the controller - setfreq(nvoice, voicefreq * portamentofreqrap + NoteVoicePar[nvoice].OffsetHz); + setfreq(nvoice, voicefreq + NoteVoicePar[nvoice].OffsetHz); /***************/ /* Modulator */ @@ -1182,15 +1166,11 @@ void ADnote::computecurrentparameters() FMrelativepitch = NoteVoicePar[nvoice].FMDetune / 100.0f; if(NoteVoicePar[nvoice].FMFreqEnvelope) FMrelativepitch += - NoteVoicePar[nvoice].FMFreqEnvelope->envout() / 100; + NoteVoicePar[nvoice].FMFreqEnvelope->envout() / 100.0f; if (NoteVoicePar[nvoice].FMFreqFixed) - FMfreq = - powf(2.0f, FMrelativepitch - / 12.0f) * 440.0f; + FMfreq = powf(2.0f, FMrelativepitch / 12.0f) * 440.0f; else - FMfreq = - powf(2.0f, FMrelativepitch - / 12.0f) * voicefreq * portamentofreqrap; + FMfreq = powf(2.0f, FMrelativepitch / 12.0f) * voicefreq; setfreqFM(nvoice, FMfreq); FMoldamplitude[nvoice] = FMnewamplitude[nvoice]; diff --git a/src/Synth/ADnote.h b/src/Synth/ADnote.h @@ -37,13 +37,13 @@ class ADnote:public SynthNote /**Constructor. * @param pars Note Parameters * @param spars Synth Engine Agnostic Parameters*/ - ADnote(ADnoteParameters *pars, SynthParams &spars, + ADnote(ADnoteParameters *pars, const SynthParams &spars, WatchManager *wm=0, const char *prefix=0); /**Destructor*/ ~ADnote(); /**Alters the playing note for legato effect*/ - void legatonote(LegatoParams pars); + void legatonote(const LegatoParams &pars); int noteout(float *outl, float *outr); void releasekey(); @@ -77,7 +77,7 @@ class ADnote:public SynthNote /**Deallocate Note resources and voice resources*/ void KillNote(); /**Get the Voice's base frequency*/ - inline float getvoicebasefreq(int nvoice) const; + inline float getvoicebasefreq(int nvoice, float adjust_log2 = 0.0f) const; /**Get modulator's base frequency*/ inline float getFMvoicebasefreq(int nvoice) const; /**Compute the Oscillator's samples. @@ -116,7 +116,7 @@ class ADnote:public SynthNote ADnoteParameters &pars; unsigned char stereo; //if the note is stereo (allows note Panning) float note_log2_freq; - float velocity, basefreq; + float velocity; ONOFFTYPE NoteEnabled; @@ -251,7 +251,7 @@ class ADnote:public SynthNote /* Wave of the Voice */ float *FMSmp; - float FMVolume; + smooth_float FMVolume; float FMDetune; //in cents Envelope *FMFreqEnvelope; diff --git a/src/Synth/ModFilter.cpp b/src/Synth/ModFilter.cpp @@ -30,7 +30,6 @@ ModFilter::ModFilter(const FilterParams &pars_, bool stereo, float notefreq) :pars(pars_), synth(synth_), time(time_), alloc(alloc_), - baseQ(pars.getq()), baseFreq(pars.getfreq()), noteFreq(notefreq), left(nullptr), right(nullptr), @@ -99,7 +98,7 @@ void ModFilter::update(float relfreq, float relq) void ModFilter::updateNoteFreq(float noteFreq_) { noteFreq = noteFreq_; - tracking = pars.getfreqtracking(noteFreq); + tracking = pars.getfreqtracking(noteFreq_); } void ModFilter::updateSense(float velocity, uint8_t scale, diff --git a/src/Synth/ModFilter.h b/src/Synth/ModFilter.h @@ -53,12 +53,11 @@ class ModFilter Allocator &alloc; //RT Memory Pool - - float baseQ; //filter sharpness - float baseFreq; //base filter frequency + smooth_float baseQ; //filter sharpness + smooth_float baseFreq; //base filter frequency float noteFreq; //frequency note was initialized to - float tracking; //shift due to note frequency - float sense; //shift due to note velocity + smooth_float tracking; //shift due to note frequency + smooth_float sense; //shift due to note velocity Filter *left; //left channel filter diff --git a/src/Synth/PADnote.cpp b/src/Synth/PADnote.cpp @@ -26,7 +26,7 @@ namespace zyn { PADnote::PADnote(const PADnoteParameters *parameters, - SynthParams pars, const int& interpolation, WatchManager *wm, + const SynthParams &pars, const int& interpolation, WatchManager *wm, const char *prefix) :SynthNote(pars), watch_int(wm, prefix, "noteout/after_interpolation"), watch_punch(wm, prefix, "noteout/after_punch"), @@ -38,13 +38,12 @@ PADnote::PADnote(const PADnoteParameters *parameters, NoteGlobalPar.FilterLfo = nullptr; firsttime = true; - setup(pars.frequency, pars.velocity, pars.portamento, pars.note_log2_freq, false, wm, prefix); + setup(pars.velocity, pars.portamento, pars.note_log2_freq, false, wm, prefix); } -void PADnote::setup(float freq, - float velocity_, +void PADnote::setup(float velocity_, int portamento_, - float note_log2_freq, + float note_log2_freq_, bool legato, WatchManager *wm, const char *prefix) @@ -53,22 +52,25 @@ void PADnote::setup(float freq, velocity = velocity_; finished_ = false; + if(pars.Pfixedfreq == 0) { + note_log2_freq = note_log2_freq_; + } + else { //the fixed freq is enabled + const int fixedfreqET = pars.PfixedfreqET; + float fixedfreq_log2 = log2f(440.0f); - if(!pars.Pfixedfreq) - basefreq = freq; - else { - basefreq = 440.0f; - int fixedfreqET = pars.PfixedfreqET; if(fixedfreqET != 0) { //if the frequency varies according the keyboard note - float tmp = - (note_log2_freq - (69.0f / 12.0f)) - * (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); + float tmp_log2 = (note_log2_freq_ - fixedfreq_log2) * + (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); if(fixedfreqET <= 64) - basefreq *= powf(2.0f, tmp); + fixedfreq_log2 += tmp_log2; else - basefreq *= powf(3.0f, tmp); + fixedfreq_log2 += tmp_log2 * log2f(3.0f); } + note_log2_freq = fixedfreq_log2; } + const float basefreq = powf(2.0f, note_log2_freq); + int BendAdj = pars.PBendAdjust - 64; if (BendAdj % 24 == 0) BendAdjust = BendAdj / 24; @@ -84,13 +86,13 @@ void PADnote::setup(float freq, //find out the closest note - float logfreq = logf(basefreq * powf(2.0f, NoteGlobalPar.Detune / 1200.0f)); - float mindist = fabsf(logfreq - logf(pars.sample[0].basefreq + 0.0001f)); + const float log2freq = note_log2_freq + NoteGlobalPar.Detune / 1200.0f; + float mindist = fabsf(log2freq - log2f(pars.sample[0].basefreq + 0.0001f)); nsample = 0; for(int i = 1; i < PAD_MAX_SAMPLES; ++i) { if(pars.sample[i].smp == NULL) break; - float dist = fabsf(logfreq - logf(pars.sample[i].basefreq + 0.0001f)); + const float dist = fabsf(log2freq - log2f(pars.sample[i].basefreq + 0.0001f)); if(dist < mindist) { nsample = i; @@ -113,10 +115,10 @@ void PADnote::setup(float freq, } - if(pars.PPanning == 0) - NoteGlobalPar.Panning = RND; - else + if(pars.PPanning) NoteGlobalPar.Panning = pars.PPanning / 128.0f; + else if(!legato) + NoteGlobalPar.Panning = RND; if(!legato) { NoteGlobalPar.Fadein_adjustment = @@ -131,6 +133,7 @@ void PADnote::setup(float freq, pars.PPunchVelocitySensing)); const float time = powf(10, 3.0f * pars.PPunchTime / 127.0f) / 10000.0f; //0.1f .. 100 ms + const float freq = powf(2.0f, note_log2_freq_); const float stretch = powf(440.0f / freq, pars.PPunchStretch / 64.0f); NoteGlobalPar.Punch.dt = 1.0f / (time * synth.samplerate_f * stretch); @@ -199,18 +202,18 @@ void PADnote::setup(float freq, SynthNote *PADnote::cloneLegato(void) { - SynthParams sp{memory, ctl, synth, time, legato.param.freq, velocity, + SynthParams sp{memory, ctl, synth, time, velocity, (bool)portamento, legato.param.note_log2_freq, true, legato.param.seed}; return memory.alloc<PADnote>(&pars, sp, interpolation); } -void PADnote::legatonote(LegatoParams pars) +void PADnote::legatonote(const LegatoParams &pars) { // Manage legato stuff if(legato.update(pars)) return; - setup(pars.frequency, pars.velocity, pars.portamento, pars.note_log2_freq, true); + setup(pars.velocity, pars.portamento, pars.note_log2_freq, true); } @@ -251,6 +254,7 @@ inline void PADnote::fadein(float *smps) void PADnote::computecurrentparameters() { + const float relfreq = getFilterCutoffRelFreq(); const float globalpitch = 0.01f * (NoteGlobalPar.FreqEnvelope->envout() + NoteGlobalPar.FreqLfo->lfoout() * ctl.modwheel.relmod + NoteGlobalPar.Detune); @@ -259,20 +263,19 @@ void PADnote::computecurrentparameters() * NoteGlobalPar.AmpEnvelope->envout_dB() * NoteGlobalPar.AmpLfo->amplfoout(); - NoteGlobalPar.GlobalFilter->update(ctl.filtercutoff.relfreq, - ctl.filterq.relq); + NoteGlobalPar.GlobalFilter->update(relfreq, ctl.filterq.relq); //compute the portamento, if it is used by this note - float portamentofreqrap = 1.0f; + float portamentofreqdelta_log2 = 0.0f; if(portamento) { //this voice use portamento - portamentofreqrap = ctl.portamento.freqrap; + portamentofreqdelta_log2 = ctl.portamento.freqdelta_log2; if(ctl.portamento.used == 0) //the portamento has finished portamento = false; //this note is no longer "portamented" } - realfreq = basefreq * portamentofreqrap - * powf(2.0f, globalpitch / 12.0f) - * powf(ctl.pitchwheel.relfreq, BendAdjust) + OffsetHz; + realfreq = + powf(2.0f, note_log2_freq + globalpitch / 12.0f + portamentofreqdelta_log2) * + powf(ctl.pitchwheel.relfreq, BendAdjust) + OffsetHz; } diff --git a/src/Synth/PADnote.h b/src/Synth/PADnote.h @@ -24,12 +24,12 @@ namespace zyn { class PADnote:public SynthNote { public: - PADnote(const PADnoteParameters *parameters, SynthParams pars, + PADnote(const PADnoteParameters *parameters, const SynthParams &pars, const int &interpolation, WatchManager *wm=0, const char *prefix=0); ~PADnote(); SynthNote *cloneLegato(void); - void legatonote(LegatoParams pars); + void legatonote(const LegatoParams &pars); int noteout(float *outl, float *outr); bool finished() const; @@ -39,7 +39,7 @@ class PADnote:public SynthNote void releasekey(); private: - void setup(float freq, float velocity, int portamento_, + void setup(float velocity, int portamento_, float note_log2_freq, bool legato = false, WatchManager *wm=0, const char *prefix=0); void fadein(float *smps); void computecurrentparameters(); @@ -49,7 +49,7 @@ class PADnote:public SynthNote int poshi_l, poshi_r; float poslo; - float basefreq; + float note_log2_freq; float BendAdjust; float OffsetHz; bool firsttime; diff --git a/src/Synth/SUBnote.cpp b/src/Synth/SUBnote.cpp @@ -35,7 +35,7 @@ namespace zyn { -SUBnote::SUBnote(const SUBnoteParameters *parameters, SynthParams &spars, +SUBnote::SUBnote(const SUBnoteParameters *parameters, const SynthParams &spars, WatchManager *wm, const char *prefix) : SynthNote(spars), watch_filter(wm, prefix, "noteout/filter"), watch_amp_int(wm,prefix,"noteout/amp_int"), @@ -47,12 +47,13 @@ SUBnote::SUBnote(const SUBnoteParameters *parameters, SynthParams &spars, GlobalFilter(nullptr), GlobalFilterEnvelope(nullptr), NoteEnabled(true), - lfilter(nullptr), rfilter(nullptr) + lfilter(nullptr), rfilter(nullptr), + filterupdate(false) { - setup(spars.frequency, spars.velocity, spars.portamento, spars.note_log2_freq, false, wm, prefix); + setup(spars.velocity, spars.portamento, spars.note_log2_freq, false, wm, prefix); } -float SUBnote::setupFilters(int *pos, bool automation) +float SUBnote::setupFilters(float basefreq, int *pos, bool automation) { //how much the amplitude is normalised (because the harmonics) float reduceamp = 0.0f; @@ -91,22 +92,21 @@ float SUBnote::setupFilters(int *pos, bool automation) return reduceamp; } -void SUBnote::setup(float freq, - float velocity, +void SUBnote::setup(float velocity_, int portamento_, - float note_log2_freq, + float note_log2_freq_, bool legato, WatchManager *wm, const char *prefix) { - this->velocity = velocity; + velocity = velocity_; portamento = portamento_; NoteEnabled = ON; volume = powf(10.0, pars.Volume / 20.0f); - volume *= VelF(velocity, pars.AmpVelocityScaleFunction); + volume *= VelF(velocity_, pars.AmpVelocityScaleFunction); if(pars.PPanning != 0) panning = pars.PPanning / 127.0f; - else + else if (!legato) panning = RND; if(!legato) { //normal note @@ -116,33 +116,38 @@ void SUBnote::setup(float freq, firsttick = 1; } + if(pars.Pfixedfreq == 0) { + note_log2_freq = note_log2_freq_; + } + else { //the fixed freq is enabled + const int fixedfreqET = pars.PfixedfreqET; + float fixedfreq_log2 = log2f(440.0f); - if(pars.Pfixedfreq == 0) - basefreq = freq; - else { - basefreq = 440.0f; - int fixedfreqET = pars.PfixedfreqET; - if(fixedfreqET) { //if the frequency varies according the keyboard note - float tmp = (note_log2_freq - (69.0f / 12.0f)) - * (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); + if(fixedfreqET != 0) { //if the frequency varies according the keyboard note + float tmp_log2 = (note_log2_freq_ - fixedfreq_log2) * + (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); if(fixedfreqET <= 64) - basefreq *= powf(2.0f, tmp); + fixedfreq_log2 += tmp_log2; else - basefreq *= powf(3.0f, tmp); + fixedfreq_log2 += tmp_log2 * log2f(3.0f); } + note_log2_freq = fixedfreq_log2; } + int BendAdj = pars.PBendAdjust - 64; if (BendAdj % 24 == 0) BendAdjust = BendAdj / 24; else BendAdjust = BendAdj / 24.0f; - float offset_val = (pars.POffsetHz - 64)/64.0f; + const float offset_val = (pars.POffsetHz - 64)/64.0f; OffsetHz = 15.0f*(offset_val * sqrtf(fabsf(offset_val))); - float detune = getdetune(pars.PDetuneType, + const float detune = getdetune(pars.PDetuneType, pars.PCoarseDetune, pars.PDetune); - basefreq *= powf(2.0f, detune / 1200.0f); //detune -// basefreq*=ctl.pitchwheel.relfreq;//pitch wheel + + note_log2_freq += detune / 1200.0f; //detune + + const float basefreq = powf(2.0f, note_log2_freq); int pos[MAX_SUB_HARMONICS]; int harmonics; @@ -172,12 +177,14 @@ void SUBnote::setup(float freq, } //how much the amplitude is normalised (because the harmonics) - float reduceamp = setupFilters(pos, legato); + const float reduceamp = setupFilters(basefreq, pos, legato); oldreduceamp = reduceamp; volume /= reduceamp; oldpitchwheel = 0; oldbandwidth = 64; + + const float freq = powf(2.0f, note_log2_freq_); if(!legato) { //normal note if(pars.Pfixedfreq == 0) initparameters(basefreq, wm, prefix); @@ -185,13 +192,12 @@ void SUBnote::setup(float freq, initparameters(basefreq / 440.0f * freq, wm, prefix); } else { - if(pars.Pfixedfreq == 0) - freq = basefreq; - else - freq *= basefreq / 440.0f; - - if(GlobalFilter) - GlobalFilter->updateNoteFreq(basefreq); + if(GlobalFilter) { + if(pars.Pfixedfreq == 0) + GlobalFilter->updateNoteFreq(basefreq); + else + GlobalFilter->updateNoteFreq(basefreq / 440.0f * freq); + } } oldamplitude = newamplitude; @@ -199,20 +205,19 @@ void SUBnote::setup(float freq, SynthNote *SUBnote::cloneLegato(void) { - SynthParams sp{memory, ctl, synth, time, legato.param.freq, velocity, + SynthParams sp{memory, ctl, synth, time, velocity, portamento, legato.param.note_log2_freq, true, legato.param.seed}; return memory.alloc<SUBnote>(&pars, sp); } -void SUBnote::legatonote(LegatoParams pars) +void SUBnote::legatonote(const LegatoParams &pars) { // Manage legato stuff if(legato.update(pars)) return; try { - setup(pars.frequency, pars.velocity, pars.portamento, pars.note_log2_freq, - true, wm); + setup(pars.velocity, pars.portamento, pars.note_log2_freq, true, wm); } catch (std::bad_alloc &ba) { std::cerr << "failed to set legato note parameter in SUBnote: " << ba.what() << std::endl; } @@ -310,7 +315,11 @@ void SUBnote::initfilter(bpfilter &filter, filter.amp = amp; filter.freq = freq; filter.bw = bw; - computefiltercoefs(filter, freq, bw, 1.0f); + + if (!automation) + computefiltercoefs(filter, freq, bw, 1.0f); + else + filterupdate = true; } /* @@ -432,7 +441,8 @@ void SUBnote::computecurrentparameters() rfilter = memory.valloc<bpfilter>(numstages * numharmonics); } - float reduceamp = setupFilters(pos, !delta_harmonics); + const float basefreq = powf(2.0f, note_log2_freq); + const float reduceamp = setupFilters(basefreq, pos, !delta_harmonics); volume = volume*oldreduceamp/reduceamp; oldreduceamp = reduceamp; } @@ -440,12 +450,13 @@ void SUBnote::computecurrentparameters() if(FreqEnvelope || BandWidthEnvelope || (oldpitchwheel != ctl.pitchwheel.data) || (oldbandwidth != ctl.bandwidth.data) - || portamento) { + || portamento + || filterupdate) { float envfreq = 1.0f; float envbw = 1.0f; if(FreqEnvelope) { - envfreq = FreqEnvelope->envout() / 1200; + envfreq = FreqEnvelope->envout() / 1200.0f; envfreq = powf(2.0f, envfreq); } @@ -454,7 +465,7 @@ void SUBnote::computecurrentparameters() //Update frequency while portamento is converging if(portamento) { - envfreq *= ctl.portamento.freqrap; + envfreq *= powf(2.0f, ctl.portamento.freqdelta_log2); if(!ctl.portamento.used) //the portamento has finished portamento = false; } @@ -481,13 +492,15 @@ void SUBnote::computecurrentparameters() oldbandwidth = ctl.bandwidth.data; oldpitchwheel = ctl.pitchwheel.data; + filterupdate = false; } newamplitude = volume * AmpEnvelope->envout_dB() * 2.0f; //Filter - if(GlobalFilter) - GlobalFilter->update(ctl.filtercutoff.relfreq, - ctl.filterq.relq); + if(GlobalFilter) { + const float relfreq = getFilterCutoffRelFreq(); + GlobalFilter->update(relfreq, ctl.filterq.relq); + } } void SUBnote::computeallfiltercoefs(bpfilter *filters, float envfreq, diff --git a/src/Synth/SUBnote.h b/src/Synth/SUBnote.h @@ -23,12 +23,12 @@ namespace zyn { class SUBnote:public SynthNote { public: - SUBnote(const SUBnoteParameters *parameters, SynthParams &pars, + SUBnote(const SUBnoteParameters *parameters, const SynthParams &pars, WatchManager *wm = 0, const char *prefix = 0); ~SUBnote(); SynthNote *cloneLegato(void); - void legatonote(LegatoParams pars); + void legatonote(const LegatoParams &pars); VecWatchPoint watch_filter,watch_amp_int, watch_legato; int noteout(float *outl, float *outr); //note output,return 0 if the note is finished void releasekey(); @@ -36,12 +36,11 @@ class SUBnote:public SynthNote void entomb(void); private: - void setup(float freq, - float velocity, + void setup(float velocity, int portamento_, - float note_log2_freq, + float note_log2_freq, bool legato = false, WatchManager *wm = 0, const char *prefix = 0); - float setupFilters(int *pos, bool automation); + float setupFilters(float basefreq, int *pos, bool automation); void computecurrentparameters(); /* * Initialize envelopes and global filter @@ -58,7 +57,7 @@ class SUBnote:public SynthNote int numharmonics; //number of harmonics (after the too higher hamonics are removed) int firstnumharmonics; //To keep track of the first note's numharmonics value, useful in legato mode. int start; //how the harmonics start - float basefreq; + float note_log2_freq; float BendAdjust; float OffsetHz; float panning; @@ -104,6 +103,7 @@ class SUBnote:public SynthNote int oldpitchwheel, oldbandwidth; float velocity; + bool filterupdate; }; } diff --git a/src/Synth/SynthNote.cpp b/src/Synth/SynthNote.cpp @@ -10,6 +10,7 @@ of the License, or (at your option) any later version. */ #include "SynthNote.h" +#include "../Params/Controller.h" #include "../Misc/Util.h" #include "../globals.h" #include <cstring> @@ -18,13 +19,13 @@ namespace zyn { -SynthNote::SynthNote(SynthParams &pars) +SynthNote::SynthNote(const SynthParams &pars) :memory(pars.memory), - legato(pars.synth, pars.frequency, pars.velocity, pars.portamento, + legato(pars.synth, pars.velocity, pars.portamento, pars.note_log2_freq, pars.quiet, pars.seed), ctl(pars.ctl), synth(pars.synth), time(pars.time) {} -SynthNote::Legato::Legato(const SYNTH_T &synth_, float freq, float vel, int port, +SynthNote::Legato::Legato(const SYNTH_T &synth_, float vel, int port, float note_log2_freq, bool quiet, prng_t seed) :synth(synth_) { @@ -35,22 +36,20 @@ SynthNote::Legato::Legato(const SYNTH_T &synth_, float freq, float vel, int port fade.length = 1; // (if something's fishy) fade.step = (1.0f / fade.length); decounter = -10; - param.freq = freq; param.vel = vel; param.portamento = port; param.note_log2_freq = note_log2_freq; param.seed = seed; - lastfreq = 0.0f; + lastfreq_log2 = note_log2_freq; silent = quiet; } -int SynthNote::Legato::update(LegatoParams pars) +int SynthNote::Legato::update(const LegatoParams &pars) { if(pars.externcall) msg = LM_Norm; if(msg != LM_CatchUp) { - lastfreq = param.freq; - param.freq = pars.frequency; + lastfreq_log2 = param.note_log2_freq; param.vel = pars.velocity; param.portamento = pars.portamento; param.note_log2_freq = pars.note_log2_freq; @@ -91,7 +90,7 @@ void SynthNote::Legato::apply(SynthNote &note, float *outl, float *outr) // the note to the actual parameters. decounter = -10; msg = LM_ToNorm; - LegatoParams pars{param.freq, param.vel, param.portamento, + LegatoParams pars{param.vel, param.portamento, param.note_log2_freq, false, param.seed}; note.legatonote(pars); break; @@ -132,9 +131,9 @@ void SynthNote::Legato::apply(SynthNote &note, float *outl, float *outr) //This freq should make this now silent note to catch-up/resync //with the heard note for the same length it stayed at the //previous freq during the fadeout. - float catchupfreq = param.freq * (param.freq / lastfreq); - LegatoParams pars{catchupfreq, param.vel, param.portamento, - param.note_log2_freq, false, param.seed}; + const float catchupfreq_log2 = 2.0f * param.note_log2_freq - lastfreq_log2; + LegatoParams pars{param.vel, param.portamento, + catchupfreq_log2, false, param.seed}; note.legatonote(pars); break; } @@ -153,7 +152,7 @@ void SynthNote::Legato::apply(SynthNote &note, float *outl, float *outr) void SynthNote::setVelocity(float velocity_) { legato.setSilent(true); //Let legato.update(...) returns 0. - LegatoParams pars{legato.getFreq(), velocity_, + LegatoParams pars{velocity_, legato.getPortamento(), legato.getNoteLog2Freq(), true, legato.getSeed()}; try { legatonote(pars); @@ -163,6 +162,32 @@ void SynthNote::setVelocity(float velocity_) { legato.setDecounter(0); //avoid chopping sound due fade-in } +void SynthNote::setPitch(float log2_freq_) { + legato.setSilent(true); //Let legato.update(...) return 0. + LegatoParams pars{legato.getVelocity(), + legato.getPortamento(), log2_freq_, true, legato.getSeed()}; + try { + legatonote(pars); + } catch (std::bad_alloc &ba) { + std::cerr << "failed to set velocity to legato note: " << ba.what() << std::endl; + } + legato.setDecounter(0); //avoid chopping sound due fade-in +} + +void SynthNote::setFilterCutoff(float value) +{ + filtercutoff_relfreq = + (value - 64.0f) * ctl.filtercutoff.depth / 4096.0f * 3.321928f; +} + +float SynthNote::getFilterCutoffRelFreq(void) +{ + if (filtercutoff_relfreq.isSet() == false) + return (ctl.filtercutoff.relfreq); + else + return (filtercutoff_relfreq); +} + float SynthNote::getRandomFloat() { return (getRandomUint() / (INT32_MAX * 1.0f)); } diff --git a/src/Synth/SynthNote.h b/src/Synth/SynthNote.h @@ -26,7 +26,6 @@ struct SynthParams const Controller &ctl; const SYNTH_T &synth; const AbsTime &time; - float frequency; //Note base frequency float velocity; //Velocity of the Note bool portamento;//True if portamento is used for this note float note_log2_freq; //Floating point value of the note @@ -36,7 +35,6 @@ struct SynthParams struct LegatoParams { - float frequency; float velocity; bool portamento; float note_log2_freq; //Floating point value of the note @@ -47,7 +45,7 @@ struct LegatoParams class SynthNote { public: - SynthNote(SynthParams &pars); + SynthNote(const SynthParams &pars); virtual ~SynthNote() {} /**Compute Output Samples @@ -65,13 +63,20 @@ class SynthNote /**Make a note die off next buffer compute*/ virtual void entomb(void) = 0; - virtual void legatonote(LegatoParams pars) = 0; + virtual void legatonote(const LegatoParams &pars) = 0; virtual SynthNote *cloneLegato(void) = 0; /* For polyphonic aftertouch needed */ void setVelocity(float velocity_); + /* For per-note pitch */ + void setPitch(float log2_freq_); + + /* For per-note filter cutoff */ + void setFilterCutoff(float); + float getFilterCutoffRelFreq(void); + /* Random numbers with own seed */ float getRandomFloat(); prng_t getRandomUint(); @@ -83,15 +88,15 @@ class SynthNote class Legato { public: - Legato(const SYNTH_T &synth_, float freq, float vel, int port, + Legato(const SYNTH_T &synth_, float vel, int port, float note_log2_freq, bool quiet, prng_t seed); void apply(SynthNote &note, float *outl, float *outr); - int update(LegatoParams pars); + int update(const LegatoParams &pars); private: bool silent; - float lastfreq; + float lastfreq_log2; LegatoMsg msg; int decounter; struct { // Fade In/Out vars @@ -123,6 +128,7 @@ class SynthNote const SYNTH_T &synth; const AbsTime &time; WatchManager *wm; + smooth_float filtercutoff_relfreq; }; } diff --git a/src/Tests/AdNoteTest.h b/src/Tests/AdNoteTest.h @@ -41,7 +41,7 @@ class AdNoteTest:public CxxTest::TestSuite ADnoteParameters *defaultPreset; Controller *controller; Alloc memory; - unsigned char testnote; + float test_freq_log2; WatchManager *w; float *outR, *outL; @@ -91,9 +91,8 @@ class AdNoteTest:public CxxTest::TestSuite controller = new Controller(*synth, time); //lets go with.... 50! as a nice note - testnote = 50; - float freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f); - SynthParams pars{memory, *controller, *synth, *time, freq, 120, 0, testnote / 12.0f, false, prng()}; + test_freq_log2 = log2f(440.0f) + (50.0 - 69.0f) / 12.0f; + SynthParams pars{memory, *controller, *synth, *time, 120, 0, test_freq_log2, false, prng()}; note = new ADnote(defaultPreset, pars,w); diff --git a/src/Tests/ControllerTest.h b/src/Tests/ControllerTest.h @@ -38,31 +38,31 @@ class ControllerTest:public CxxTest::TestSuite //Initialize portamento testCtl->setportamento(127); testCtl->portamento.time = 127; - testCtl->initportamento(40.0f, 400.0f, false); + testCtl->initportamento(log2f(40.0f), log2f(400.0f), false); //Bounds Check while(testCtl->portamento.used) { TS_ASSERT((0.0f <= testCtl->portamento.x) && (testCtl->portamento.x <= 1.0f)); - TS_ASSERT((0.1f <= testCtl->portamento.freqrap) - && (testCtl->portamento.freqrap <= 1.0f)); + TS_ASSERT((log2f(0.1f) <= testCtl->portamento.freqdelta_log2) + && (testCtl->portamento.freqdelta_log2 <= log2f(1.0f))); testCtl->updateportamento(); } TS_ASSERT((0.0f <= testCtl->portamento.x) && (testCtl->portamento.x <= 1.0f)); - TS_ASSERT((0.1f <= testCtl->portamento.freqrap) - && (testCtl->portamento.freqrap <= 1.0f)); + TS_ASSERT((log2f(0.1f) <= testCtl->portamento.freqdelta_log2) + && (testCtl->portamento.freqdelta_log2 <= log2f(1.0f))); } void testPortamentoValue() { testCtl->setportamento(127); testCtl->portamento.time = 127; - testCtl->initportamento(40.0f, 400.0f, false); + testCtl->initportamento(log2f(40.0f), log2f(400.0f), false); int i; for(i = 0; i < 10; ++i) testCtl->updateportamento(); //Assert that the numbers are the same as they were at release TS_ASSERT_DELTA(testCtl->portamento.x, 0.0290249f, 0.000001f) - TS_ASSERT_DELTA(testCtl->portamento.freqrap, 0.126122f, 0.000001f) + TS_ASSERT_DELTA(testCtl->portamento.freqdelta_log2, -3.2255092, 0.000001f) } private: diff --git a/src/Tests/MemoryStressTest.h b/src/Tests/MemoryStressTest.h @@ -85,8 +85,7 @@ class AdNoteTest:public CxxTest::TestSuite void testManySimultaneousNotes() { unsigned char testnote = 42; - float freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f); - SynthParams pars{memory, *controller, *synth, *time, freq, 120, 0, testnote / 12.0f, false, prng()}; + SynthParams pars{memory, *controller, *synth, *time, 120, 0, testnote / 12.0f, false, prng()}; std::vector<ADnote*> notes; diff --git a/src/Tests/PadNoteTest.h b/src/Tests/PadNoteTest.h @@ -48,7 +48,7 @@ class PadNoteTest:public CxxTest::TestSuite FFTwrapper *fft; Controller *controller; AbsTime *time; - unsigned char testnote; + float test_freq_log2; Alloc memory; int interpolation; rtosc::ThreadLink *tr; @@ -107,9 +107,8 @@ class PadNoteTest:public CxxTest::TestSuite controller = new Controller(*synth, time); //lets go with.... 50! as a nice note - testnote = 50; - float freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f); - SynthParams pars_{memory, *controller, *synth, *time, freq, 120, 0, testnote / 12.0f, false, prng()}; + test_freq_log2 = log2f(440.0f) + (50.0 - 69.0f) / 12.0f; + SynthParams pars_{memory, *controller, *synth, *time, 120, 0, test_freq_log2, false, prng()}; note = new PADnote(pars, pars_, interpolation); } diff --git a/src/Tests/SubNoteTest.h b/src/Tests/SubNoteTest.h @@ -41,7 +41,7 @@ class SubNoteTest:public CxxTest::TestSuite Master *master; AbsTime *time; Controller *controller; - unsigned char testnote; + float test_freq_log2; Alloc memory; rtosc::ThreadLink *tr; WatchManager *w; @@ -81,10 +81,9 @@ class SubNoteTest:public CxxTest::TestSuite controller = new Controller(*synth, time); //lets go with.... 50! as a nice note - testnote = 50; - float freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f); + test_freq_log2 = log2f(440.0f) + (50.0 - 69.0f) / 12.0f; - SynthParams pars{memory, *controller, *synth, *time, freq, 120, 0, testnote / 12.0f, false, prng()}; + SynthParams pars{memory, *controller, *synth, *time, 120, 0, test_freq_log2, false, prng()}; note = new SUBnote(defaultPreset, pars, w); this->pars = defaultPreset; } diff --git a/src/Tests/TriggerTest.h b/src/Tests/TriggerTest.h @@ -42,7 +42,7 @@ class TriggerTest:public CxxTest::TestSuite Master *master; AbsTime *time; Controller *controller; - unsigned char testnote; + float test_freq_log2; Alloc memory; rtosc::ThreadLink *tr; WatchManager *w; @@ -74,10 +74,9 @@ class TriggerTest:public CxxTest::TestSuite controller = new Controller(*synth, time); //lets go with.... 50! as a nice note - testnote = 50; - float freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f); + test_freq_log2 = log2f(440.0f) + (50.0 - 69.0f) / 12.0f; - SynthParams pars{memory, *controller, *synth, *time, freq, 120, 0, testnote / 12.0f, false, prng()}; + SynthParams pars{memory, *controller, *synth, *time, 120, 0, test_freq_log2, false, prng()}; note = new SUBnote(defaultPreset, pars, w); this->pars = defaultPreset; } diff --git a/src/Tests/UnisonTest.h b/src/Tests/UnisonTest.h @@ -39,11 +39,10 @@ class AdNoteTest:public CxxTest::TestSuite ADnote *note; FFTwrapper *fft; Controller *controller; - unsigned char testnote; + float test_freq_log2; ADnoteParameters *params; AbsTime *time; Alloc memory; - float freq; float outR[BUF], outL[BUF]; @@ -71,8 +70,7 @@ class AdNoteTest:public CxxTest::TestSuite controller = new Controller(*synth, time); //lets go with.... 50! as a nice note - testnote = 50; - freq = 440.0f * powf(2.0f, (testnote - 69.0f) / 12.0f); + test_freq_log2 = log2f(440.0f) + (50.0 - 69.0f) / 12.0f; } void tearDown() { @@ -98,7 +96,7 @@ class AdNoteTest:public CxxTest::TestSuite params->VoicePar[0].Unison_vibratto_speed = e; params->VoicePar[0].Unison_invert_phase = f; - SynthParams pars{memory, *controller, *synth, *time, freq, 120, 0, testnote / 12.0f, false, prng()}; + SynthParams pars{memory, *controller, *synth, *time, 120, 0, test_freq_log2, false, prng()}; note = new ADnote(params, pars); note->noteout(outL, outR); TS_ASSERT_DELTA(outL[80], values[0], 1.9e-5); diff --git a/src/UI/MicrotonalUI.fl b/src/UI/MicrotonalUI.fl @@ -198,7 +198,7 @@ if (true) { Fl_Counter anotecounter { label {"A" Note} callback { - /*if (microtonal->getnotefreq(o->value(),0)<0.0) o->textcolor(FL_RED); + /*if (microtonal->getnotefreq(o->value() / 12.0f,0)<0.0) o->textcolor(FL_RED); else o->textcolor(FL_BLACK);*/ o->redraw();} diff --git a/src/globals.h b/src/globals.h @@ -237,6 +237,7 @@ enum ONOFFTYPE { enum MidiControllers { C_bankselectmsb = 0, C_pitchwheel = 1000, C_NULL = 1001, + C_aftertouch = 1002, C_pitch = 1003, C_expression = 11, C_panning = 10, C_bankselectlsb = 32, C_filtercutoff = 74, C_filterq = 71, C_bandwidth = 75, C_modwheel = 1, C_fmamp = 76, @@ -346,5 +347,37 @@ struct SYNTH_T { static float numRandom(void); //defined in Util.cpp for now }; +class smooth_float { +private: + bool init; + float curr_value; + float next_value; +public: + smooth_float() { + init = false; + next_value = curr_value = 0.0f; + }; + smooth_float(const float value) { + init = true; + next_value = curr_value = value; + }; + operator float() { + const float delta = (next_value - curr_value) / 128.0f; + curr_value += delta; + return (curr_value); + }; + void operator =(const float value) { + if (init) { + next_value = value; + } else { + next_value = curr_value = value; + init = true; + } + }; + bool isSet() const { + return (init); + }; +}; + } #endif