/* ZynAddSubFX - a software synthesizer SUBnote.cpp - The "subtractive" synthesizer Copyright (C) 2002-2005 Nasca Octavian Paul Author: Nasca Octavian Paul This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include "../globals.h" #include "SUBnote.h" #include "Envelope.h" #include "ModFilter.h" #include "../Containers/ScratchString.h" #include "../Params/Controller.h" #include "../Params/SUBnoteParameters.h" #include "../Params/FilterParams.h" #include "../Misc/Time.h" #include "../Misc/Util.h" #include "../Misc/Allocator.h" #ifndef M_PI # define M_PI 3.14159265358979323846 /* pi */ #endif namespace zyncarla { SUBnote::SUBnote(const SUBnoteParameters *parameters, SynthParams &spars) :SynthNote(spars), pars(*parameters), AmpEnvelope(nullptr), FreqEnvelope(nullptr), BandWidthEnvelope(nullptr), GlobalFilter(nullptr), GlobalFilterEnvelope(nullptr), NoteEnabled(true), lfilter(nullptr), rfilter(nullptr), wm(nullptr) { setup(spars.frequency, spars.velocity, spars.portamento, spars.note); } float SUBnote::setupFilters(int *pos, bool automation) { //how much the amplitude is normalised (because the harmonics) float reduceamp = 0.0f; for(int n = 0; n < numharmonics; ++n) { const float freq = basefreq * pars.POvertoneFreqMult[pos[n]]; overtone_freq[n] = freq; overtone_rolloff[n] = computerolloff(freq); //the bandwidth is not absolute(Hz); it is relative to frequency const float bw = SUBnoteParameters::convertBandwidth(pars.Pbandwidth, numstages, freq, pars.Pbwscale, pars.Phrelbw[pos[n]]); //try to keep same amplitude on all freqs and bw. (empirically) const float hgain = SUBnoteParameters::convertHarmonicMag(pars.Phmag[pos[n]], pars.Phmagtype); const float gain = hgain * sqrt(1500.0f / (bw * freq)); reduceamp += hgain; for(int nph = 0; nph < numstages; ++nph) { float amp = 1.0f; if(nph == 0) amp = gain; initfilter(lfilter[nph + n * numstages], freq + OffsetHz, bw, amp, hgain, automation); if(stereo) initfilter(rfilter[nph + n * numstages], freq + OffsetHz, bw, amp, hgain, automation); } } if(reduceamp < 0.001f) reduceamp = 1.0f; return reduceamp; } void SUBnote::setup(float freq, float velocity, int portamento_, int midinote, bool legato) { this->velocity = velocity; portamento = portamento_; NoteEnabled = ON; volume = powf(0.1f, 3.0f * (1.0f - pars.PVolume / 96.0f)); //-60 dB .. 0 dB volume *= VelF(velocity, pars.PAmpVelocityScaleFunction); if(pars.PPanning != 0) panning = pars.PPanning / 127.0f; else panning = RND; if(!legato) { //normal note numstages = pars.Pnumstages; stereo = pars.Pstereo; start = pars.Pstart; firsttick = 1; } 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 = (midinote - 69.0f) / 12.0f * (powf(2.0f, (fixedfreqET - 1) / 63.0f) - 1.0f); if(fixedfreqET <= 64) basefreq *= powf(2.0f, tmp); else basefreq *= powf(3.0f, tmp); } } 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; OffsetHz = 15.0f*(offset_val * sqrtf(fabsf(offset_val))); float detune = getdetune(pars.PDetuneType, pars.PCoarseDetune, pars.PDetune); basefreq *= powf(2.0f, detune / 1200.0f); //detune // basefreq*=ctl.pitchwheel.relfreq;//pitch wheel int pos[MAX_SUB_HARMONICS]; int harmonics; pars.activeHarmonics(pos, harmonics); if(!legato) //normal note firstnumharmonics = numharmonics = harmonics; else { if(harmonics > firstnumharmonics) numharmonics = firstnumharmonics; else numharmonics = harmonics; } if(numharmonics == 0) { NoteEnabled = false; return; } if(!legato) { //normal note lfilter = memory.valloc(numstages * numharmonics); if(stereo) rfilter = memory.valloc(numstages * numharmonics); } //how much the amplitude is normalised (because the harmonics) float reduceamp = setupFilters(pos, false); oldreduceamp = reduceamp; volume /= reduceamp; oldpitchwheel = 0; oldbandwidth = 64; if(!legato) { //normal note if(pars.Pfixedfreq == 0) initparameters(basefreq, wm); else initparameters(basefreq / 440.0f * freq, wm); } else { if(pars.Pfixedfreq == 0) freq = basefreq; else freq *= basefreq / 440.0f; if(GlobalFilter) GlobalFilter->updateNoteFreq(basefreq); } oldamplitude = newamplitude; } SynthNote *SUBnote::cloneLegato(void) { SynthParams sp{memory, ctl, synth, time, legato.param.freq, velocity, portamento, legato.param.midinote, true}; return memory.alloc(&pars, sp); } void SUBnote::legatonote(LegatoParams pars) { // Manage legato stuff if(legato.update(pars)) return; try { setup(pars.frequency, pars.velocity, pars.portamento, pars.midinote, true); } catch (std::bad_alloc &ba) { std::cerr << "failed to set legato note parameter in SUBnote: " << ba.what() << std::endl; } } SUBnote::~SUBnote() { if(NoteEnabled) KillNote(); } /* * Kill the note */ void SUBnote::KillNote() { if(NoteEnabled) { memory.devalloc(numstages * numharmonics, lfilter); if(stereo) memory.devalloc(numstages * numharmonics, rfilter); memory.dealloc(AmpEnvelope); memory.dealloc(FreqEnvelope); memory.dealloc(BandWidthEnvelope); memory.dealloc(GlobalFilter); memory.dealloc(GlobalFilterEnvelope); NoteEnabled = false; } } /* * Compute the filters coefficients */ void SUBnote::computefiltercoefs(bpfilter &filter, float freq, float bw, float gain) { if(freq > synth.samplerate_f / 2.0f - 200.0f) freq = synth.samplerate_f / 2.0f - 200.0f; float omega = 2.0f * PI * freq / synth.samplerate_f; float sn = sinf(omega); float cs = cosf(omega); float alpha = sn * sinh(LOG_2 / 2.0f * bw * omega / sn); if(alpha > 1) alpha = 1; if(alpha > bw) alpha = bw; filter.b0 = alpha / (1.0f + alpha) * filter.amp * gain; filter.b2 = -alpha / (1.0f + alpha) * filter.amp * gain; filter.a1 = -2.0f * cs / (1.0f + alpha); filter.a2 = (1.0f - alpha) / (1.0f + alpha); } /* * Initialise the filters */ void SUBnote::initfilter(bpfilter &filter, float freq, float bw, float amp, float mag, bool automation) { if(!automation) { filter.xn1 = 0.0f; filter.xn2 = 0.0f; if(start == 0) { filter.yn1 = 0.0f; filter.yn2 = 0.0f; } else { float a = 0.1f * mag; //empirically float p = RND * 2.0f * PI; if(start == 1) a *= RND; filter.yn1 = a * cosf(p); filter.yn2 = a * cosf(p + freq * 2.0f * PI / synth.samplerate_f); //correct the error of computation the start amplitude //at very high frequencies if(freq > synth.samplerate_f * 0.96f) { filter.yn1 = 0.0f; filter.yn2 = 0.0f; } } } filter.amp = amp; filter.freq = freq; filter.bw = bw; computefiltercoefs(filter, freq, bw, 1.0f); } /* * Do the filtering */ inline void SubFilterA(const float coeff[4], float &src, float work[4]) { work[3] = src*coeff[0]+work[1]*coeff[1]+work[2]*coeff[2]+work[3]*coeff[3]; work[1] = src; src = work[3]; } inline void SubFilterB(const float coeff[4], float &src, float work[4]) { work[2] = src*coeff[0]+work[0]*coeff[1]+work[3]*coeff[2]+work[2]*coeff[3]; work[0] = src; src = work[2]; } //This dance is designed to minimize unneeded memory operations which can result //in quite a bit of wasted time void SUBnote::filter(bpfilter &filter, float *smps) { assert(synth.buffersize % 8 == 0); float coeff[4] = {filter.b0, filter.b2, -filter.a1, -filter.a2}; float work[4] = {filter.xn1, filter.xn2, filter.yn1, filter.yn2}; for(int i = 0; i < synth.buffersize; i += 8) { SubFilterA(coeff, smps[i + 0], work); SubFilterB(coeff, smps[i + 1], work); SubFilterA(coeff, smps[i + 2], work); SubFilterB(coeff, smps[i + 3], work); SubFilterA(coeff, smps[i + 4], work); SubFilterB(coeff, smps[i + 5], work); SubFilterA(coeff, smps[i + 6], work); SubFilterB(coeff, smps[i + 7], work); } filter.xn1 = work[0]; filter.xn2 = work[1]; filter.yn1 = work[2]; filter.yn2 = work[3]; } /* * Init Parameters */ void SUBnote::initparameters(float freq, WatchManager *wm) { //TODO populate this base string ScratchString pre; AmpEnvelope = memory.alloc(*pars.AmpEnvelope, freq, synth.dt(), wm, (pre+"AmpEnvelope/").c_str); if(pars.PFreqEnvelopeEnabled) FreqEnvelope = memory.alloc(*pars.FreqEnvelope, freq, synth.dt(), wm, (pre+"FreqEnvelope/").c_str); if(pars.PBandWidthEnvelopeEnabled) BandWidthEnvelope = memory.alloc(*pars.BandWidthEnvelope, freq, synth.dt(), wm, (pre+"BandWidthEnvelope/").c_str); if(pars.PGlobalFilterEnabled) { GlobalFilterEnvelope = memory.alloc(*pars.GlobalFilterEnvelope, freq, synth.dt(), wm, (pre+"GlobalFilterEnvelope/").c_str); GlobalFilter = memory.alloc(*pars.GlobalFilter, synth, time, memory, stereo, freq); GlobalFilter->updateSense(velocity, pars.PGlobalFilterVelocityScale, pars.PGlobalFilterVelocityScaleFunction); GlobalFilter->addMod(*GlobalFilterEnvelope); } computecurrentparameters(); } /* * Compute how much to reduce amplitude near nyquist or subaudible frequencies. */ float SUBnote::computerolloff(float freq) { const float lower_limit = 10.0f; const float lower_width = 10.0f; const float upper_width = 200.0f; float upper_limit = synth.samplerate / 2.0f; if (freq > lower_limit + lower_width && freq < upper_limit - upper_width) return 1.0f; if (freq <= lower_limit || freq >= upper_limit) return 0.0f; if (freq <= lower_limit + lower_width) return (1.0f - cosf(M_PI * (freq - lower_limit) / lower_width)) / 2.0f; return (1.0f - cosf(M_PI * (freq - upper_limit) / upper_width)) / 2.0f; } /* * Compute Parameters of SUBnote for each tick */ void SUBnote::computecurrentparameters() { //Recompute parameters for realtime automation if(pars.time && pars.last_update_timestamp == pars.time->time()) { //A little bit of copy/paste for now int pos[MAX_SUB_HARMONICS]; int harmonics; pars.activeHarmonics(pos, harmonics); bool delta_harmonics = (harmonics != numharmonics); if(delta_harmonics) { memory.devalloc(lfilter); memory.devalloc(rfilter); firstnumharmonics = numharmonics = harmonics; lfilter = memory.valloc(numstages * numharmonics); if(stereo) rfilter = memory.valloc(numstages * numharmonics); } float reduceamp = setupFilters(pos, !delta_harmonics); volume = volume*oldreduceamp/reduceamp; oldreduceamp = reduceamp; } if(FreqEnvelope || BandWidthEnvelope || (oldpitchwheel != ctl.pitchwheel.data) || (oldbandwidth != ctl.bandwidth.data) || portamento) { float envfreq = 1.0f; float envbw = 1.0f; if(FreqEnvelope) { envfreq = FreqEnvelope->envout() / 1200; envfreq = powf(2.0f, envfreq); } envfreq *= powf(ctl.pitchwheel.relfreq, BendAdjust); //pitch wheel //Update frequency while portamento is converging if(portamento) { envfreq *= ctl.portamento.freqrap; if(!ctl.portamento.used) //the portamento has finished portamento = false; } if(BandWidthEnvelope) { envbw = BandWidthEnvelope->envout(); envbw = powf(2, envbw); } envbw *= ctl.bandwidth.relbw; //bandwidth controller //Recompute High Frequency Dampening Terms for(int n = 0; n < numharmonics; ++n) overtone_rolloff[n] = computerolloff(overtone_freq[n] * envfreq); //Recompute Filter Coefficients float tmpgain = 1.0f / sqrt(envbw * envfreq); computeallfiltercoefs(lfilter, envfreq, envbw, tmpgain); if(stereo) computeallfiltercoefs(rfilter, envfreq, envbw, tmpgain); oldbandwidth = ctl.bandwidth.data; oldpitchwheel = ctl.pitchwheel.data; } newamplitude = volume * AmpEnvelope->envout_dB() * 2.0f; //Filter if(GlobalFilter) GlobalFilter->update(ctl.filtercutoff.relfreq, ctl.filterq.relq); } void SUBnote::computeallfiltercoefs(bpfilter *filters, float envfreq, float envbw, float gain) { for(int n = 0; n < numharmonics; ++n) for(int nph = 0; nph < numstages; ++nph) computefiltercoefs(filters[nph + n * numstages], filters[nph + n * numstages].freq * envfreq, filters[nph + n * numstages].bw * envbw, nph == 0 ? gain : 1.0); } void SUBnote::chanOutput(float *out, bpfilter *bp, int buffer_size) { float tmprnd[buffer_size]; float tmpsmp[buffer_size]; //Initialize Random Input for(int i = 0; i < buffer_size; ++i) tmprnd[i] = RND * 2.0f - 1.0f; //For each harmonic apply the filter on the random input stream //Sum the filter outputs to obtain the output signal for(int n = 0; n < numharmonics; ++n) { float rolloff = overtone_rolloff[n]; memcpy(tmpsmp, tmprnd, synth.bufferbytes); for(int nph = 0; nph < numstages; ++nph) filter(bp[nph + n * numstages], tmpsmp); for(int i = 0; i < synth.buffersize; ++i) out[i] += tmpsmp[i] * rolloff; } } /* * Note Output */ int SUBnote::noteout(float *outl, float *outr) { memcpy(outl, synth.denormalkillbuf, synth.bufferbytes); memcpy(outr, synth.denormalkillbuf, synth.bufferbytes); if(!NoteEnabled) return 0; if(stereo) { chanOutput(outl, lfilter, synth.buffersize); chanOutput(outr, rfilter, synth.buffersize); if(GlobalFilter) GlobalFilter->filter(outl, outr); } else { chanOutput(outl, lfilter, synth.buffersize); if(GlobalFilter) GlobalFilter->filter(outl, 0); memcpy(outr, outl, synth.bufferbytes); } if(firsttick) { int n = 10; if(n > synth.buffersize) n = synth.buffersize; for(int i = 0; i < n; ++i) { float ampfadein = 0.5f - 0.5f * cosf( (float) i / (float) n * PI); outl[i] *= ampfadein; outr[i] *= ampfadein; } firsttick = false; } if(ABOVE_AMPLITUDE_THRESHOLD(oldamplitude, newamplitude)) // Amplitude interpolation for(int i = 0; i < synth.buffersize; ++i) { float tmpvol = INTERPOLATE_AMPLITUDE(oldamplitude, newamplitude, i, synth.buffersize); outl[i] *= tmpvol * panning; outr[i] *= tmpvol * (1.0f - panning); } else for(int i = 0; i < synth.buffersize; ++i) { outl[i] *= newamplitude * panning; outr[i] *= newamplitude * (1.0f - panning); } oldamplitude = newamplitude; computecurrentparameters(); // Apply legato-specific sound signal modifications legato.apply(*this, outl, outr); // Check if the note needs to be computed more if(AmpEnvelope->finished() != 0) { for(int i = 0; i < synth.buffersize; ++i) { //fade-out float tmp = 1.0f - (float)i / synth.buffersize_f; outl[i] *= tmp; outr[i] *= tmp; } KillNote(); } return 1; } /* * Release Key (Note Off) */ void SUBnote::releasekey() { AmpEnvelope->releasekey(); if(FreqEnvelope) FreqEnvelope->releasekey(); if(BandWidthEnvelope) BandWidthEnvelope->releasekey(); if(GlobalFilterEnvelope) GlobalFilterEnvelope->releasekey(); } /* * Check if the note is finished */ bool SUBnote::finished() const { return !NoteEnabled; } void SUBnote::entomb(void) { AmpEnvelope->forceFinish(); } }