diff --git a/GlitchSprinkler/source/dsp/dspcore.cpp b/GlitchSprinkler/source/dsp/dspcore.cpp index 3f401f3a..95c898d1 100644 --- a/GlitchSprinkler/source/dsp/dspcore.cpp +++ b/GlitchSprinkler/source/dsp/dspcore.cpp @@ -37,6 +37,7 @@ void DSPCore::setup(double sampleRate_) SmootherCommon::setTime(double(0.2)); + terminationLength = uint_fast32_t(double(0.002) * sampleRate); lowpassInterpRate = sampleRate / double(48000 * 64); reset(); @@ -105,6 +106,7 @@ void Voice::reset() arpeggioTie = 1; arpeggioTimer = 0; arpeggioLoopCounter = 0; + terminationCounter = 0; scheduleUpdateNote = false; phasePeriod = 0; @@ -133,6 +135,9 @@ void DSPCore::reset() previousBeatsElapsed = 0; + currentBeat = 0; + pitchModifier = double(1); + polynomial.updateCoefficients(true); isPolynomialUpdated = true; @@ -183,10 +188,15 @@ std::array Voice::processFrame() constexpr double epsf32 = double(std::numeric_limits::epsilon()); if ( (state == State::terminate && phaseCounter == 0) - || (state == State::release && lastGain <= epsf32)) + || (state == State::release && lastGain <= epsf32) || (terminationCounter > 0)) { - state = State::rest; - return {double(0), double(0)}; + if (terminationCounter < core.terminationLength && pv[ID::filterSwitch]->getInt()) { + ++terminationCounter; + } else { + terminationCounter = core.terminationLength; + state = State::rest; + return {double(0), double(0)}; + } } // Oscillator. @@ -206,11 +216,6 @@ std::array Voice::processFrame() // Saturation. sig = std::clamp(saturationGain * sig, double(-1), double(1)); - // Envelope. - decayGain *= decayRatio; - lastGain = decayGain * noteVelocity; - sig *= lastGain; - // Lowpass. if (pv[ID::filterSwitch]->getInt()) { sig = lowpass.process( @@ -220,6 +225,13 @@ std::array Voice::processFrame() filterDecayRatio *= filterDecayGain; } + // Envelope. + decayGain *= decayRatio; + const auto terminationDecay = double(core.terminationLength - terminationCounter) + / double(core.terminationLength); + lastGain = decayGain * noteVelocity * terminationDecay; + sig *= lastGain; + if (++phaseCounter >= phasePeriod) { phaseCounter = 0; if (scheduleUpdateNote) { @@ -314,10 +326,12 @@ void DSPCore::noteOn(NoteInfo &info) vc.unisonRatio = double(idx) / double(nUnison - 1); } - vc.unisonGain = double(1) / double(nUnison); + vc.unisonGain = pv[ID::unisonGainSqrt]->getInt() + ? std::sqrt(double(1) / double(nUnison)) + : double(1) / double(nUnison); vc.rngArpeggio.seed(pv[ID::seed]->getInt() + vc.unisonIndex); - if (vc.state == Voice::State::rest && vc.phaseCounter == 0) { + if (vc.state == Voice::State::rest) { vc.arpeggioTimer = 0; vc.arpeggioLoopCounter = 0; vc.lowpass.reset(); @@ -325,6 +339,7 @@ void DSPCore::noteOn(NoteInfo &info) } else if (!param.value[ParameterID::arpeggioSwitch]->getInt()) { vc.scheduleUpdateNote = true; } + vc.terminationCounter = 0; vc.state = Voice::State::active; } } @@ -364,7 +379,9 @@ template inline double getRandomPitch(Rng &rng, const Scale &scale, const Tuning &tuning) { std::uniform_int_distribution indexDist{0, scale.size() - 1}; - return tuning[scale[indexDist(rng)]]; + const size_t index = scale[indexDist(rng)]; + const size_t octave = size_t(1) << (index / tuning.size()); + return octave * tuning[index % tuning.size()]; } void Voice::updateNote() diff --git a/GlitchSprinkler/source/dsp/dspcore.hpp b/GlitchSprinkler/source/dsp/dspcore.hpp index db83a52b..57e46404 100644 --- a/GlitchSprinkler/source/dsp/dspcore.hpp +++ b/GlitchSprinkler/source/dsp/dspcore.hpp @@ -68,6 +68,7 @@ class Voice { uint_fast32_t arpeggioTie = 1; uint_fast32_t arpeggioTimer = 0; uint_fast32_t arpeggioLoopCounter = 0; + uint_fast32_t terminationCounter = 0; bool scheduleUpdateNote = false; uint_fast32_t phasePeriod = 0; @@ -116,6 +117,7 @@ class DSPCore { uint_fast32_t arpeggioDuration = std::numeric_limits::max(); uint_fast32_t arpeggioLoopLength = std::numeric_limits::max(); + uint_fast32_t terminationLength = 0; double lowpassInterpRate = double(1) / double(64); DecibelScale velocityMap{-60, 0, true}; diff --git a/GlitchSprinkler/source/editor.cpp b/GlitchSprinkler/source/editor.cpp index 02ce7934..6d1b19db 100644 --- a/GlitchSprinkler/source/editor.cpp +++ b/GlitchSprinkler/source/editor.cpp @@ -396,6 +396,9 @@ bool Editor::prepareUI() addCheckbox( unisonLeft0, unisonTop4, labelWidth, labelHeight, uiTextSize, "Scatter Arpeggio", ID::unisonScatterArpeggio); + addCheckbox( + unisonLeft1, unisonTop4, labelWidth, labelHeight, uiTextSize, "Gain Sqrt.", + ID::unisonGainSqrt); // Plugin name. constexpr auto splashMargin = uiMargin; diff --git a/GlitchSprinkler/source/parameter.cpp b/GlitchSprinkler/source/parameter.cpp index e7f655e3..b7e33bf3 100644 --- a/GlitchSprinkler/source/parameter.cpp +++ b/GlitchSprinkler/source/parameter.cpp @@ -60,7 +60,7 @@ UIntScale Scales::arpeggioScale(31); LinearScale Scales::arpeggioPicthDriftCent(0, 100); UIntScale Scales::arpeggioOctave(7); -UIntScale Scales::unisonVoice(127); +UIntScale Scales::unisonVoice(255); LinearScale Scales::unisonDetuneCent(0, 1200); } // namespace Synth diff --git a/GlitchSprinkler/source/parameter.hpp b/GlitchSprinkler/source/parameter.hpp index e211e7f6..58a5bb14 100644 --- a/GlitchSprinkler/source/parameter.hpp +++ b/GlitchSprinkler/source/parameter.hpp @@ -133,6 +133,7 @@ enum ID { unisonDetuneCent, unisonPanSpread, unisonScatterArpeggio, + unisonGainSqrt, reservedParameter0, reservedGuiParameter0 = reservedParameter0 + nReservedParameter, @@ -297,6 +298,8 @@ struct GlobalParameter : public ParameterInterface { Info::kCanAutomate); value[ID::unisonScatterArpeggio] = std::make_unique( 1, Scales::boolScale, "unisonScatterArpeggio", Info::kCanAutomate); + value[ID::unisonGainSqrt] = std::make_unique( + 1, Scales::boolScale, "unisonGainSqrt", Info::kCanAutomate); for (size_t idx = 0; idx < nReservedParameter; ++idx) { auto indexStr = std::to_string(idx);