zynaddsubfx

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

commit a85b360b38d8913b8ae19dca151773da37bb698a
parent cb975edc5b414f1a8f0b88bb70fa98f93d4af3d6
Author: Christopher A. Oliver <[email protected]>
Date:   Fri, 27 Nov 2015 20:26:26 -0500

PWM support via phase mod

Diffstat:
Msrc/Params/ADnoteParameters.h | 2+-
Msrc/Synth/ADnote.cpp | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/Synth/ADnote.h | 4+++-
Msrc/UI/ADnoteUI.fl | 4++++
4 files changed, 115 insertions(+), 47 deletions(-)

diff --git a/src/Params/ADnoteParameters.h b/src/Params/ADnoteParameters.h @@ -27,7 +27,7 @@ #include "PresetsArray.h" enum FMTYPE { - NONE, MORPH, RING_MOD, PHASE_MOD, FREQ_MOD + NONE, MORPH, RING_MOD, PHASE_MOD, FREQ_MOD, PW_MOD }; /*****************************************************************/ diff --git a/src/Synth/ADnote.cpp b/src/Synth/ADnote.cpp @@ -113,13 +113,24 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) unison_stereo_spread[nvoice] = pars.VoicePar[nvoice].Unison_stereo_spread / 127.0f; + int unison = pars.VoicePar[nvoice].Unison_size; if(unison < 1) unison = 1; - // Since noise unison of greater than two is touch goofy... - if (pars.VoicePar[nvoice].Type != 0 && unison > 2) - unison = 2; + bool is_pwm = pars.VoicePar[nvoice].PFMEnabled == PW_MOD; + + if (pars.VoicePar[nvoice].Type != 0) { + // Since noise unison of greater than two is touch goofy... + if (unison > 2) + unison = 2; + } else if (is_pwm) { + /* Pulse width mod uses pairs of subvoices. */ + unison *= 2; + // This many is likely to sound like noise anyhow. + if (unison > 64) + unison = 64; + } //compute unison unison_size[nvoice] = unison; @@ -133,8 +144,8 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) float unison_vibratto_a = pars.VoicePar[nvoice].Unison_vibratto / 127.0f; //0.0f .. 1.0f - - switch(unison) { + int true_unison = unison / (is_pwm ? 2 : 1); + switch(true_unison) { case 1: unison_base_freq_rap[nvoice][0] = 1.0f; //if the unison is not used, always make the only subvoice to have the default note break; @@ -144,11 +155,11 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) }; break; default: { //unison for more than 2 subvoices - float unison_values[unison]; + float unison_values[true_unison]; float min = -1e-6, max = 1e-6; - for(int k = 0; k < unison; ++k) { - float step = (k / (float) (unison - 1)) * 2.0f - 1.0f; //this makes the unison spread more uniform - float val = step + (RND * 2.0f - 1.0f) / (unison - 1); + for(int k = 0; k < true_unison; ++k) { + float step = (k / (float) (true_unison - 1)) * 2.0f - 1.0f; //this makes the unison spread more uniform + float val = step + (RND * 2.0f - 1.0f) / (true_unison - 1); unison_values[k] = val; if (min > val) { min = val; @@ -158,7 +169,7 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) } } float diff = max - min; - for(int k = 0; k < unison; ++k) { + for(int k = 0; k < true_unison; ++k) { unison_values[k] = (unison_values[k] - (max + min) * 0.5f) / diff; //the lowest value will be -1 and the highest will be 1 unison_base_freq_rap[nvoice][k] = @@ -166,9 +177,16 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) } }; } + if (is_pwm) + for (int i = true_unison - 1; i >= 0; i--) { + unison_base_freq_rap[nvoice][2*i + 1] = + unison_base_freq_rap[nvoice][i]; + unison_base_freq_rap[nvoice][2*i] = + unison_base_freq_rap[nvoice][i]; + } //unison vibrattos - if(unison > 1) + if(unison > 2 || (!is_pwm && unison > 1)) for(int k = 0; k < unison; ++k) //reduce the frequency difference for larger vibrattos unison_base_freq_rap[nvoice][k] = 1.0f + (unison_base_freq_rap[ @@ -192,12 +210,27 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) if(RND < 0.5f) m = -m; unison_vibratto[nvoice].step[k] = m; + + // Ugly, but the alternative is likely uglier. + if (is_pwm) + for (int i = 0; i < unison; i += 2) { + unison_vibratto[nvoice].step[i+1] = + unison_vibratto[nvoice].step[i]; + unison_vibratto[nvoice].position[i+1] = + unison_vibratto[nvoice].position[i]; + } } - if(unison == 1) { //no vibratto for a single voice - unison_vibratto[nvoice].step[0] = 0.0f; - unison_vibratto[nvoice].position[0] = 0.0f; - unison_vibratto[nvoice].amplitude = 0.0f; + if(unison <= 2) { //no vibratto for a single voice + if (is_pwm) { + unison_vibratto[nvoice].step[1] = 0.0f; + unison_vibratto[nvoice].position[1] = 0.0f; + } + if (is_pwm || unison == 1) { + unison_vibratto[nvoice].step[0] = 0.0f; + unison_vibratto[nvoice].position[0] = 0.0f; + unison_vibratto[nvoice].amplitude = 0.0f; + } } //phase invert for unison @@ -314,9 +347,11 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) + i] = NoteVoicePar[nvoice].OscilSmp[i]; - oscposhi_start += + NoteVoicePar[nvoice].phase_offset = (int)((pars.VoicePar[nvoice].Poscilphase - - 64.0f) / 128.0f * synth.oscilsize + synth.oscilsize * 4); + - 64.0f) / 128.0f * synth.oscilsize + + synth.oscilsize * 4); + oscposhi_start += NoteVoicePar[nvoice].phase_offset; int kth_start = oscposhi_start; for(int k = 0; k < unison; ++k) { @@ -347,22 +382,28 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) NoteVoicePar[nvoice].filterbypass = pars.VoicePar[nvoice].Pfilterbypass; - switch(pars.VoicePar[nvoice].PFMEnabled) { - case 1: - NoteVoicePar[nvoice].FMEnabled = MORPH; - break; - case 2: - NoteVoicePar[nvoice].FMEnabled = RING_MOD; - break; - case 3: - NoteVoicePar[nvoice].FMEnabled = PHASE_MOD; - break; - case 4: - NoteVoicePar[nvoice].FMEnabled = FREQ_MOD; - break; - default: - NoteVoicePar[nvoice].FMEnabled = NONE; - } + if (pars.VoicePar[nvoice].Type != 0) + NoteVoicePar[nvoice].FMEnabled = NONE; + else + switch(pars.VoicePar[nvoice].PFMEnabled) { + case 1: + NoteVoicePar[nvoice].FMEnabled = MORPH; + break; + case 2: + NoteVoicePar[nvoice].FMEnabled = RING_MOD; + break; + case 3: + NoteVoicePar[nvoice].FMEnabled = PHASE_MOD; + break; + case 4: + NoteVoicePar[nvoice].FMEnabled = FREQ_MOD; + break; + case 5: + NoteVoicePar[nvoice].FMEnabled = PW_MOD; + break; + default: + NoteVoicePar[nvoice].FMEnabled = NONE; + } NoteVoicePar[nvoice].FMVoice = pars.VoicePar[nvoice].PFMVoice; NoteVoicePar[nvoice].FMFreqEnvelope = NULL; @@ -377,6 +418,7 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) - 1.0f); switch(NoteVoicePar[nvoice].FMEnabled) { case PHASE_MOD: + case PW_MOD: fmvoldamp = powf(440.0f / getvoicebasefreq( nvoice), pars.VoicePar[nvoice].PFMVolumeDamp @@ -553,6 +595,7 @@ void ADnote::legatonote(LegatoParams lpars) switch(NoteVoicePar[nvoice].FMEnabled) { case PHASE_MOD: + case PW_MOD: fmvoldamp = powf(440.0f / getvoicebasefreq( nvoice), pars.VoicePar[nvoice].PFMVolumeDamp @@ -1096,6 +1139,8 @@ void ADnote::computecurrentparameters() /***************/ /* Modulator */ /***************/ + + if(NoteVoicePar[nvoice].FMEnabled != NONE) { FMrelativepitch = NoteVoicePar[nvoice].FMDetune / 100.0f; if(NoteVoicePar[nvoice].FMFreqEnvelope) @@ -1343,7 +1388,11 @@ inline void ADnote::ComputeVoiceOscillatorFrequencyModulation(int nvoice, for(int k = 0; k < unison_size[nvoice]; ++k) { float *tw = tmpwave_unison[k]; const float *smps = NoteVoicePar[NoteVoicePar[nvoice].FMVoice].VoiceOut; - memcpy(tw, smps, synth.bufferbytes); + if (FMmode == PW_MOD && (k & 1)) + for (int i = 0; i < synth.buffersize; ++i) + tw[i] = -smps[i]; + else + memcpy(tw, smps, synth.bufferbytes); } } else { //Compute the modulator and store it in tmpwave_unison[][] @@ -1357,7 +1406,10 @@ inline void ADnote::ComputeVoiceOscillatorFrequencyModulation(int nvoice, for(int i = 0; i < synth.buffersize; ++i) { tw[i] = (smps[poshiFM] * ((1<<24) - posloFM) - + smps[poshiFM + 1] * posloFM) / (1.0f*(1<<24)); + + smps[poshiFM + 1] * posloFM) / (1.0f*(1<<24)); + if (FMmode == PW_MOD && (k & 1)) + tw[i] = -tw[i]; + posloFM += freqloFM; if(posloFM >= (1<<24)) { posloFM &= 0xffffff;//fmod(posloFM, 1.0f); @@ -1391,7 +1443,7 @@ inline void ADnote::ComputeVoiceOscillatorFrequencyModulation(int nvoice, //normalize: makes all sample-rates, oscil_sizes to produce same sound - if(FMmode != 0) { //Frequency modulation + if(FMmode == FREQ_MOD) { //Frequency modulation const float normalize = synth.oscilsize_f / 262144.0f * 44100.0f / synth.samplerate_f; for(int k = 0; k < unison_size[nvoice]; ++k) { @@ -1404,7 +1456,7 @@ inline void ADnote::ComputeVoiceOscillatorFrequencyModulation(int nvoice, FMoldsmp[nvoice][k] = fmold; } } - else { //Phase modulation + else { //Phase or PWM modulation const float normalize = synth.oscilsize_f / 262144.0f; for(int k = 0; k < unison_size[nvoice]; ++k) { float *tw = tmpwave_unison[k]; @@ -1432,6 +1484,8 @@ inline void ADnote::ComputeVoiceOscillatorFrequencyModulation(int nvoice, //carrier int carposhi = poshi + FMmodfreqhi; int carposlo = poslo + FMmodfreqlo; + if (FMmode == PW_MOD && (k & 1)) + carposhi += NoteVoicePar[nvoice].phase_offset; if(carposlo >= (1<<24)) { carposhi++; @@ -1519,11 +1573,11 @@ int ADnote::noteout(float *outl, float *outr) case RING_MOD: ComputeVoiceOscillatorRingModulation(nvoice); break; - case PHASE_MOD: - ComputeVoiceOscillatorFrequencyModulation(nvoice, 0); - break; case FREQ_MOD: - ComputeVoiceOscillatorFrequencyModulation(nvoice, 1); + case PHASE_MOD: + case PW_MOD: + ComputeVoiceOscillatorFrequencyModulation(nvoice, + NoteVoicePar[nvoice].FMEnabled); break; default: ComputeVoiceOscillator_LinearInterpolation(nvoice); @@ -1548,10 +1602,17 @@ int ADnote::noteout(float *outl, float *outr) float *tw = tmpwave_unison[k]; if(stereo) { float stereo_pos = 0; - if(unison_size[nvoice] > 1) + bool is_pwm = NoteVoicePar[nvoice].FMEnabled == PW_MOD; + if (is_pwm) { + if(unison_size[nvoice] > 2) + stereo_pos = k/2 + / (float)(unison_size[nvoice]/2 + - 1) * 2.0f - 1.0f; + } else if(unison_size[nvoice] > 1) { stereo_pos = k - / (float)(unison_size[nvoice] - - 1) * 2.0f - 1.0f; + / (float)(unison_size[nvoice] + - 1) * 2.0f - 1.0f; + } float stereo_spread = unison_stereo_spread[nvoice] * 2.0f; //between 0 and 2.0f if(stereo_spread > 1.0f) { float stereo_pos_1 = (stereo_pos >= 0.0f) ? 1.0f : -1.0f; @@ -1563,7 +1624,8 @@ int ADnote::noteout(float *outl, float *outr) else stereo_pos *= stereo_spread; - if(unison_size[nvoice] == 1) + if(unison_size[nvoice] == 1 || + (is_pwm && unison_size[nvoice] == 2)) stereo_pos = 0.0f; float panning = (stereo_pos + 1.0f) * 0.5f; diff --git a/src/Synth/ADnote.h b/src/Synth/ADnote.h @@ -105,7 +105,6 @@ class ADnote:public SynthNote /**Fadein in a way that removes clicks but keep sound "punchy"*/ inline void fadein(float *smps) const; - //GLOBALS ADnoteParameters &pars; unsigned char stereo; //if the note is stereo (allows note Panning) @@ -187,6 +186,9 @@ class ADnote:public SynthNote /* Waveform of the Voice */ float *OscilSmp; + /* preserved for phase mod PWM emulation. */ + int phase_offset; + /* Range of waveform */ float OscilSmpMin, OscilSmpMax; diff --git a/src/UI/ADnoteUI.fl b/src/UI/ADnoteUI.fl @@ -513,6 +513,10 @@ o->redraw();} label FM xywh {80 80 100 20} labelfont 1 } + MenuItem {} { + label PWM + xywh {80 80 100 20} labelfont 1 + } } Fl_Group {} { label FREQUENCY open