From 96f2f37495ea954600c9fb07b024fb22e45f84bf Mon Sep 17 00:00:00 2001 From: Takamitsu Endo Date: Sun, 1 Oct 2023 17:59:54 +0900 Subject: [PATCH] Update GenericDrum --- GenericDrum/source/dsp/dspcore.cpp | 342 +++++++++++++++++---------- GenericDrum/source/dsp/dspcore.hpp | 59 +++-- GenericDrum/source/dsp/filter.hpp | 5 +- GenericDrum/source/editor.cpp | 139 ++++++++--- GenericDrum/source/editor.hpp | 7 + GenericDrum/source/parameter.cpp | 1 - GenericDrum/source/parameter.hpp | 49 ++-- GenericDrum/source/plugprocessor.cpp | 9 + GenericDrum/source/version.hpp | 8 +- 9 files changed, 416 insertions(+), 203 deletions(-) diff --git a/GenericDrum/source/dsp/dspcore.cpp b/GenericDrum/source/dsp/dspcore.cpp index 0fc0847d..d2e467a7 100644 --- a/GenericDrum/source/dsp/dspcore.cpp +++ b/GenericDrum/source/dsp/dspcore.cpp @@ -136,13 +136,13 @@ void DSPCore::setup(double sampleRate) SmootherCommon::setTime(double(0.2)); const auto maxDelayTimeSamples = upRate; - noiseAllpass.setup(maxDelayTimeSamples); - wireAllpass.setup(maxDelayTimeSamples); - wireEnergyDecay.setup(upRate * double(0.001)); - membrane1EnergyDecay.setup(upRate * double(0.001)); - membrane2EnergyDecay.setup(upRate * double(0.001)); - membrane1.setup(maxDelayTimeSamples); - membrane2.setup(maxDelayTimeSamples); + for (auto &x : noiseAllpass) x.setup(maxDelayTimeSamples); + for (auto &x : wireAllpass) x.setup(maxDelayTimeSamples); + for (auto &x : wireEnergyDecay) x.setup(upRate * double(0.001)); + for (auto &x : membrane1EnergyDecay) x.setup(upRate * double(0.001)); + for (auto &x : membrane2EnergyDecay) x.setup(upRate * double(0.001)); + for (auto &x : membrane1) x.setup(maxDelayTimeSamples); + for (auto &x : membrane2) x.setup(maxDelayTimeSamples); reset(); startup(); @@ -168,20 +168,17 @@ void DSPCore::setup(double sampleRate) pv[ID::delayTimeModAmount]->getDouble() * upRate / double(48000)); \ secondaryFdnMix.METHOD(pv[ID::secondaryFdnMix]->getDouble()); \ membraneWireMix.METHOD(pv[ID::membraneWireMix]->getDouble()); \ + stereoBalance.METHOD(pv[ID::stereoBalance]->getDouble()); \ + stereoMerge.METHOD(pv[ID::stereoMerge]->getDouble() / double(2)); \ outputGain.METHOD(pv[ID::outputGain]->getDouble()); \ \ - safetyHighpass.METHOD( \ - pv[ID::safetyHighpassHz]->getDouble() / sampleRate, \ - std::numbers::sqrt2_v / double(2)); \ + constexpr auto highpassQ = std::numbers::sqrt2_v / double(2); \ + const auto highpassCut = pv[ID::safetyHighpassHz]->getDouble() / sampleRate; \ + for (size_t ch = 0; ch < 2; ++ch) { \ + safetyHighpass[ch].METHOD(highpassCut, highpassQ); \ + } \ \ - noiseLowpass.METHOD(pv[ID::noiseLowpassHz]->getDouble() / upRate); \ paramRng.seed(pv[ID::seed]->getInt()); \ - noiseAllpass.timeInSamples.METHOD( \ - prepareSerialAllpassTime( \ - upRate, pv[ID::noiseAllpassMaxTimeHz]->getDouble(), paramRng)); \ - wireAllpass.timeInSamples.METHOD( \ - prepareSerialAllpassTime( \ - upRate, pv[ID::wireFrequencyHz]->getDouble(), paramRng)); \ \ for (size_t idx = 0; idx < maxFdnSize; ++idx) { \ const auto crossFeedbackRatio = pv[ID::crossFeedbackRatio0 + idx]->getDouble(); \ @@ -189,6 +186,9 @@ void DSPCore::setup(double sampleRate) } \ feedbackMatrix.constructHouseholder(); \ \ + const auto noiseLowpassHz = pv[ID::noiseLowpassHz]->getDouble() / upRate; \ + const auto noiseAllpassMaxTimeHz = pv[ID::noiseAllpassMaxTimeHz]->getDouble(); \ + const auto wireFrequencyHz = pv[ID::wireFrequencyHz]->getDouble(); \ const auto secondaryPitchOffset = pv[ID::secondaryPitchOffset]->getDouble(); \ const auto delayTimeFreq1 = pv[ID::delayTimeHz]->getDouble() / upRate; \ const auto delayTimeFreq2 = delayTimeFreq1 * std::exp2(secondaryPitchOffset); \ @@ -198,36 +198,58 @@ void DSPCore::setup(double sampleRate) const auto bandpassCutSpread = pv[ID::bandpassCutSpread]->getDouble(); \ const auto pitchRandomCent = pv[ID::pitchRandomCent]->getDouble(); \ const size_t pitchType = pv[ID::pitchType]->getInt(); \ - for (size_t idx = 0; idx < maxFdnSize; ++idx) { \ - const auto pitch = pitchFunc(pitchType, idx); \ + for (size_t drm = 0; drm < nDrum; ++drm) { \ + noiseLowpass[drm].METHOD(noiseLowpassHz); \ + noiseAllpass[drm].timeInSamples.METHOD( \ + prepareSerialAllpassTime(upRate, noiseAllpassMaxTimeHz, paramRng)); \ + wireAllpass[drm].timeInSamples.METHOD( \ + prepareSerialAllpassTime(upRate, wireFrequencyHz, paramRng)); \ \ - const auto delayCutRatio1 \ - = pitchRatio(pitch, delayTimeSpread, pitchRandomCent, paramRng); \ - membrane1.delayTimeSamples[idx] = double(1) / (delayTimeFreq1 * delayCutRatio1); \ + for (size_t idx = 0; idx < maxFdnSize; ++idx) { \ + const auto pitch = pitchFunc(pitchType, idx); \ \ - const auto bpCutRatio1 = bandpassCutRatio \ - * pitchRatio(pitch, bandpassCutSpread, pitchRandomCent, paramRng); \ - membrane1.bandpassCutoff.METHOD##At(idx, delayTimeFreq1 *bpCutRatio1); \ + const auto delayCutRatio1 \ + = pitchRatio(pitch, delayTimeSpread, pitchRandomCent, paramRng); \ + membrane1[drm].delayTimeSamples[idx] \ + = double(1) / (delayTimeFreq1 * delayCutRatio1); \ \ - const auto delayCutRatio2 \ - = pitchRatio(pitch, delayTimeSpread, pitchRandomCent, paramRng); \ - membrane2.delayTimeSamples[idx] = double(1) / (delayTimeFreq2 * delayCutRatio2); \ + const auto bpCutRatio1 = bandpassCutRatio \ + * pitchRatio(pitch, bandpassCutSpread, pitchRandomCent, paramRng); \ + membrane1[drm].bandpassCutoff.METHOD##At(idx, delayTimeFreq1 *bpCutRatio1); \ \ - const auto bpCutRatio2 = bandpassCutRatio \ - * pitchRatio(pitch, bandpassCutSpread, pitchRandomCent, paramRng); \ - membrane2.bandpassCutoff.METHOD##At(idx, delayTimeFreq2 *bpCutRatio2); \ - } \ - const auto bandpassQ = pv[ID::bandpassQ]->getDouble(); \ - membrane1.bandpassQ.METHOD(bandpassQ); \ - membrane2.bandpassQ.METHOD( \ - std::clamp(bandpassQ *std::exp2(secondaryQOffset), double(0.1), double(100))); + const auto delayCutRatio2 \ + = pitchRatio(pitch, delayTimeSpread, pitchRandomCent, paramRng); \ + membrane2[drm].delayTimeSamples[idx] \ + = double(1) / (delayTimeFreq2 * delayCutRatio2); \ + \ + const auto bpCutRatio2 = bandpassCutRatio \ + * pitchRatio(pitch, bandpassCutSpread, pitchRandomCent, paramRng); \ + membrane2[drm].bandpassCutoff.METHOD##At(idx, delayTimeFreq2 *bpCutRatio2); \ + } \ + const auto bandpassQ = pv[ID::bandpassQ]->getDouble(); \ + membrane1[drm].bandpassQ.METHOD(bandpassQ); \ + membrane2[drm].bandpassQ.METHOD( \ + std::clamp(bandpassQ *std::exp2(secondaryQOffset), double(0.1), double(100))); \ + } void DSPCore::updateUpRate() { upRate = sampleRate * fold[overSampling]; SmootherCommon::setSampleRate(upRate); - membrane1.onSampleRateChange(upRate); - membrane2.onSampleRateChange(upRate); + for (auto &x : membrane1) x.onSampleRateChange(upRate); + for (auto &x : membrane2) x.onSampleRateChange(upRate); +} + +void DSPCore::resetCollision() +{ + using ID = ParameterID::ID; + const auto &pv = param.value; + + pv[ID::isWireCollided]->setFromInt(0); + isWireCollided = false; + + pv[ID::isSecondaryCollided]->setFromInt(0); + isSecondaryCollided = false; } void DSPCore::reset() @@ -238,35 +260,36 @@ void DSPCore::reset() ASSIGN_PARAMETER(reset); startup(); + resetCollision(); noteNumber = 69.0; velocity = 0; noiseGain = 0; noiseDecay = 0; - noiseAllpass.reset(); + for (auto &x : noiseAllpass) x.reset(); - wireAllpass.reset(); - wireEnergyDecay.reset(); - wireEnergyNoise.reset(); - wirePosition = 0; - wireVelocity = 0; + for (auto &x : wireAllpass) x.reset(); + for (auto &x : wireEnergyDecay) x.reset(); + for (auto &x : wireEnergyNoise) x.reset(); + wirePosition.fill({}); + wireVelocity.fill({}); wireGain = 0; wireDecay = 0; envelope.reset(); releaseSmoother.reset(); - membrane1Position = 0; - membrane1Velocity = 0; - membrane2Position = 0; - membrane2Velocity = 0; - membrane1EnergyDecay.reset(); - membrane2EnergyDecay.reset(); - membrane1.reset(); - membrane2.reset(); + membrane1Position.fill({}); + membrane1Velocity.fill({}); + membrane2Position.fill({}); + membrane2Velocity.fill({}); + for (auto &x : membrane1EnergyDecay) x.reset(); + for (auto &x : membrane2EnergyDecay) x.reset(); + for (auto &x : membrane1) x.reset(); + for (auto &x : membrane2) x.reset(); - halfbandIir.reset(); + for (auto &x : halfbandIir) x.reset(); } void DSPCore::startup() @@ -284,90 +307,135 @@ void DSPCore::setParameters() updateUpRate(); } ASSIGN_PARAMETER(push); + + isWireCollided = pv[ID::isWireCollided]->getInt(); + isSecondaryCollided = pv[ID::isSecondaryCollided]->getInt(); } // Overwrites `p0` and `p1`. inline void solveCollision(double &p0, double &p1, double v0, double v1, double distance) { - const auto diff = p0 - p1 + distance; + auto diff = p0 - p1 + distance; if (diff >= 0) { p0 = 0; p1 = 0; return; } - auto sum = -diff; const auto r0 = std::abs(v0); const auto r1 = std::abs(v1); - if (r0 + r1 >= std::numeric_limits::epsilon()) sum /= r0 + r1; - p0 = sum * r1; - p1 = -sum * r0; + if (r0 + r1 > std::numeric_limits::epsilon()) diff /= r0 + r1; + p0 = -diff * r1; + p1 = diff * r0; } -double DSPCore::processSample() +double DSPCore::processDrum( + size_t index, double noise, double wireGain, double crossGain, double timeModAmt) { - wireDistance.process(); - wireCollisionTypeMix.process(); - impactWireMix.process(); - secondaryDistance.process(); - const auto crossGain = crossFeedbackGain.process(); - const auto timeModAmt = delayTimeModAmount.process(); - secondaryFdnMix.process(); - membraneWireMix.process(); - outputGain.process(); - + // Impact. constexpr auto eps = std::numeric_limits::epsilon(); double sig = 0; - if (noiseGain > eps) { - std::uniform_real_distribution dist{double(-1), double(1)}; - const auto noise = noiseGain * dist(noiseRng); - noiseGain *= noiseDecay; - sig += noiseLowpass.process(noise); - } - sig = std::tanh(noiseAllpass.process(sig, double(0.95))); + sig += noiseLowpass[index].process(noise); + sig = std::tanh(noiseAllpass[index].process(sig, double(0.95))); + + // Wire and membrane processing goes like: + // 1. solve collision, + // 2. update GUI status, + // 3. process delays, + // 4. update collision parameters. + // Wire. solveCollision( - wirePosition, membrane1Position, wireVelocity, membrane1Velocity, - wireDistance.getValue()); + wirePosition[index], membrane1Position[index], wireVelocity[index], + membrane1Velocity[index], wireDistance.getValue()); - // TODO: Send wire-membrane1 collision status to GUI. + if (!isWireCollided && wirePosition[index] != 0) isWireCollided = true; auto wireCollision = std::lerp( - wireEnergyNoise.process(wirePosition, noiseRng), - wireEnergyDecay.process(wirePosition), wireCollisionTypeMix.getValue()); + wireEnergyNoise[index].process(wirePosition[index], noiseRng), + wireEnergyDecay[index].process(wirePosition[index]), wireCollisionTypeMix.getValue()); wireCollision = double(8) * std::tanh(double(0.125) * wireCollision); const auto wireIn = double(0.995) * (sig + wireCollision); - const auto wirePos = wireAllpass.process(wireIn, double(0.5)) * wireGain; - wireGain *= wireDecay; - wireVelocity = wirePos - wirePosition; - wirePosition = wirePos; + const auto wirePos = wireAllpass[index].process(wireIn, double(0.5)) * wireGain; + wireVelocity[index] = wirePos - wirePosition[index]; + wirePosition[index] = wirePos; - const auto wireOut = std::lerp(sig, wirePosition, impactWireMix.getValue()); + const auto wireOut = std::lerp(sig, wirePosition[index], impactWireMix.getValue()); sig = wireOut; + // Membranes. solveCollision( - membrane1Position, membrane2Position, membrane1Velocity, membrane2Velocity, - secondaryDistance.getValue()); + membrane1Position[index], membrane2Position[index], membrane1Velocity[index], + membrane2Velocity[index], secondaryDistance.getValue()); - // TODO: Send membrane1-membrane2 collision status to GUI. + if (!isSecondaryCollided && membrane2Position[index] != 0) isSecondaryCollided = true; const auto env = std::exp2(envelope.process() + releaseSmoother.process()); const auto pitch = env * interpPitch.process(pitchSmoothingKp); + feedbackMatrix.process(); + + const auto collision1 + = membrane1EnergyDecay[index].process(membrane1Position[index]) / double(maxFdnSize); + const auto p1 = membrane1[index].process( + sig + collision1, crossGain, pitch, timeModAmt, feedbackMatrix); + membrane1Velocity[index] = p1 - membrane1Position[index]; + membrane1Position[index] = p1; + + const auto collision2 + = membrane2EnergyDecay[index].process(membrane2Position[index]) / double(maxFdnSize); + const auto p2 = membrane2[index].process( + sig + collision2, crossGain, pitch, timeModAmt, feedbackMatrix); + membrane2Velocity[index] = p2 - membrane2Position[index]; + membrane2Position[index] = p2; + + // Mix. + sig = std::lerp(p1, p2, secondaryFdnMix.getValue()); + sig = std::lerp(sig, wireOut, membraneWireMix.getValue()); + return sig; +} - const auto collision1 = membrane1EnergyDecay.process(membrane1Position); - const auto p1 = membrane1.process(sig, crossGain, pitch, timeModAmt, feedbackMatrix); - membrane1Velocity = p1 - membrane1Position; - membrane1Position = p1; +#define PROCESS_COMMON \ + wireDistance.process(); \ + wireCollisionTypeMix.process(); \ + impactWireMix.process(); \ + secondaryDistance.process(); \ + const auto crossGain = crossFeedbackGain.process(); \ + const auto timeModAmt = delayTimeModAmount.process(); \ + secondaryFdnMix.process(); \ + membraneWireMix.process(); \ + const auto balance = stereoBalance.process(); \ + const auto merge = stereoMerge.process(); \ + const auto outGain = outputGain.process(); \ + \ + std::uniform_real_distribution dist{double(-1), double(1)}; \ + const auto noise = noiseGain * dist(noiseRng); \ + noiseGain *= noiseDecay; \ + wireGain *= wireDecay; - const auto collision2 = membrane2EnergyDecay.process(membrane2Position); - const auto p2 = membrane2.process(sig, crossGain, pitch, timeModAmt, feedbackMatrix); - membrane2Velocity = p2 - membrane2Position; - membrane2Position = p2; +double DSPCore::processSample() +{ + PROCESS_COMMON; - sig = std::lerp(p1, p2, secondaryFdnMix.getValue()); - sig = std::lerp(sig, wireOut, membraneWireMix.getValue()); + return outGain * processDrum(0, noise, wireGain, crossGain, timeModAmt); +} - return outputGain.getValue() * sig; +std::array DSPCore::processFrame() +{ + PROCESS_COMMON; + + auto drum0 = processDrum(0, noise, wireGain, crossGain, timeModAmt); + auto drum1 = processDrum(1, noise, wireGain, crossGain, timeModAmt); + + constexpr auto eps = std::numeric_limits::epsilon(); + if (balance < -eps) { + drum0 *= double(1) + balance; + } else if (balance > eps) { + drum1 *= double(1) - balance; + } + return { + outGain * std::lerp(drum0, drum1, merge), + outGain * std::lerp(drum1, drum0, merge), + }; } void DSPCore::process(const size_t length, float *out0, float *out1) @@ -380,25 +448,56 @@ void DSPCore::process(const size_t length, float *out0, float *out1) SmootherCommon::setBufferSize(double(length)); SmootherCommon::setSampleRate(upRate); + bool isStereo = pv[ID::stereoEnable]->getInt(); bool isSafetyHighpassEnabled = pv[ID::safetyHighpassEnable]->getInt(); - std::array halfbandInput{}; + std::array frame{}; for (size_t i = 0; i < length; ++i) { processMidiNote(i); - if (overSampling) { - for (size_t j = 0; j < upFold; ++j) halfbandInput[j] = processSample(); - auto sig = float(halfbandIir.process(halfbandInput)); - if (isSafetyHighpassEnabled) sig = safetyHighpass.process(sig); - out0[i] = sig; - out1[i] = sig; + if (isStereo) { + if (overSampling) { + for (size_t j = 0; j < upFold; ++j) { + frame = processFrame(); + halfbandInput[0][j] = frame[0]; + halfbandInput[1][j] = frame[1]; + } + frame[0] = halfbandIir[0].process(halfbandInput[0]); + frame[1] = halfbandIir[1].process(halfbandInput[1]); + if (isSafetyHighpassEnabled) { + frame[0] = safetyHighpass[0].process(frame[0]); + frame[1] = safetyHighpass[1].process(frame[1]); + } + out0[i] = float(frame[0]); + out1[i] = float(frame[1]); + } else { + frame = processFrame(); + if (isSafetyHighpassEnabled) { + frame[0] = safetyHighpass[0].process(frame[0]); + frame[1] = safetyHighpass[1].process(frame[1]); + } + out0[i] = float(frame[0]); + out1[i] = float(frame[1]); + } } else { - auto sig = float(processSample()); - if (isSafetyHighpassEnabled) sig = safetyHighpass.process(sig); - out0[i] = sig; - out1[i] = sig; + if (overSampling) { + for (size_t j = 0; j < upFold; ++j) halfbandInput[0][j] = processSample(); + frame[0] = halfbandIir[0].process(halfbandInput[0]); + if (isSafetyHighpassEnabled) frame[0] = safetyHighpass[0].process(frame[0]); + out0[i] = float(frame[0]); + out1[i] = float(frame[0]); + } else { + frame[0] = processSample(); + if (isSafetyHighpassEnabled) frame[0] = safetyHighpass[0].process(frame[0]); + out0[i] = float(frame[0]); + out1[i] = float(frame[0]); + } } } + + // Send a value to GUI. + if (isWireCollided) pv[ID::isWireCollided]->setFromInt(1); + if (isSecondaryCollided) pv[ID::isSecondaryCollided]->setFromInt(1); } void DSPCore::noteOn(NoteInfo &info) @@ -414,14 +513,12 @@ void DSPCore::noteOn(NoteInfo &info) auto notePitch = calcNotePitch(info.noteNumber); auto pitchBend = calcPitch(pv[ID::pitchBendRange]->getDouble() * pv[ID::pitchBend]->getDouble()); - if (pv[ID::slideAtNoteOn]->getInt()) { - interpPitch.push(notePitch); - } else { - interpPitch.reset(notePitch); - } + interpPitch.push(notePitch); velocity = velocityMap.map(info.velocity); + if (pv[ID::resetSeedAtNoteOn]->getInt()) noiseRng.seed(pv[ID::seed]->getInt()); + noiseGain = velocity; noiseDecay = std::pow( double(1e-3), double(1) / (upRate * pv[ID::noiseDecaySeconds]->getDouble())); @@ -435,9 +532,10 @@ void DSPCore::noteOn(NoteInfo &info) pv[ID::envelopeAttackSeconds]->getDouble() * upRate, pv[ID::envelopeDecaySeconds]->getDouble() * upRate); - const auto crossFeedbackGain = pv[ID::crossFeedbackGain]->getDouble(); - membrane1.noteOn(); - membrane2.noteOn(); + // membrane1.noteOn(); + // membrane2.noteOn(); + + resetCollision(); } void DSPCore::noteOff(int_fast32_t noteId) @@ -451,7 +549,7 @@ void DSPCore::noteOff(int_fast32_t noteId) if (it == noteStack.end()) return; noteStack.erase(it); - if (!noteStack.empty() && pv[ID::slideAtNoteOff]->getInt()) { + if (!noteStack.empty()) { noteNumber = noteStack.back().noteNumber; interpPitch.push(calcNotePitch(noteNumber)); } @@ -464,6 +562,6 @@ double DSPCore::calcNotePitch(double note) auto semitone = pv[ID::tuningSemitone]->getInt() - double(semitoneOffset + 57); auto cent = pv[ID::tuningCent]->getDouble() / double(100); - auto equalTemperament = pv[ID::tuningET]->getInt() + 1; - return std::exp2((note + semitone + cent) / equalTemperament); + auto notePitchAmount = pv[ID::notePitchAmount]->getDouble(); + return std::exp2(notePitchAmount * (note + semitone + cent) / double(12)); } diff --git a/GenericDrum/source/dsp/dspcore.hpp b/GenericDrum/source/dsp/dspcore.hpp index 5f5d52e3..5035425c 100644 --- a/GenericDrum/source/dsp/dspcore.hpp +++ b/GenericDrum/source/dsp/dspcore.hpp @@ -93,9 +93,20 @@ class DSPCore { } private: + void updateUpRate(); + void resetCollision(); + double calcNotePitch(double note); + double processSample(); + std::array processFrame(); + double processDrum( + size_t index, double noise, double wireGain, double crossGain, double timeModAmt); + std::vector midiNotes; std::vector noteStack; + bool isWireCollided = false; + bool isSecondaryCollided = false; + DecibelScale velocityMap{-60, 0, true}; DecibelScale velocityToCouplingDecayMap{-40, 0, false}; double velocity = 0; @@ -118,39 +129,41 @@ class DSPCore { ExpSmoother delayTimeModAmount; ExpSmoother secondaryFdnMix; ExpSmoother membraneWireMix; + ExpSmoother stereoBalance; + ExpSmoother stereoMerge; ExpSmoother outputGain; + static constexpr size_t nDrum = 2; + static constexpr size_t nAllpass = 4; + std::minstd_rand noiseRng{0}; std::minstd_rand paramRng{0}; double noiseGain = 0; double noiseDecay = 0; - ComplexLowpass noiseLowpass; - SerialAllpass noiseAllpass; - - SerialAllpass wireAllpass; - EnergyStoreDecay wireEnergyDecay; - EnergyStoreNoise wireEnergyNoise; - double wirePosition = 0; - double wireVelocity = 0; + std::array, nDrum> noiseLowpass; + std::array, nDrum> noiseAllpass; + + std::array, 2> wireAllpass; + std::array, 2> wireEnergyDecay; + std::array, 2> wireEnergyNoise; + std::array wirePosition{}; + std::array wireVelocity{}; double wireGain = 0; double wireDecay = 0; DoubleEmaADEnvelope envelope; TransitionReleaseSmoother releaseSmoother; FeedbackMatrix feedbackMatrix; - double membrane1Position = 0; - double membrane1Velocity = 0; - double membrane2Position = 0; - double membrane2Velocity = 0; - EnergyStoreDecay membrane1EnergyDecay; - EnergyStoreDecay membrane2EnergyDecay; - EasyFDN membrane1; - EasyFDN membrane2; - - HalfBandIIR> halfbandIir; - SVFHighpass safetyHighpass; - - void updateUpRate(); - double calcNotePitch(double note); - double processSample(); + std::array membrane1Position{}; + std::array membrane1Velocity{}; + std::array membrane2Position{}; + std::array membrane2Velocity{}; + std::array, 2> membrane1EnergyDecay; + std::array, 2> membrane2EnergyDecay; + std::array, 2> membrane1; + std::array, 2> membrane2; + + std::array, 2> halfbandInput{}; + std::array>, 2> halfbandIir; + std::array, 2> safetyHighpass; }; diff --git a/GenericDrum/source/dsp/filter.hpp b/GenericDrum/source/dsp/filter.hpp index e467c3e9..a22960c6 100644 --- a/GenericDrum/source/dsp/filter.hpp +++ b/GenericDrum/source/dsp/filter.hpp @@ -502,13 +502,10 @@ template class EasyFDN { auto &front = buf[bufIndex]; auto &back = buf[bufIndex ^ 1]; front.fill({}); - // feedbackMatrix.process(); for (size_t i = 0; i < length; ++i) { - feedbackMatrix.matrix[i].process(); for (size_t j = 0; j < length; ++j) front[i] += feedbackMatrix.at(i, j) * back[j]; } - // input /= Sample(length); const auto feedbackGain = safetyGain * crossGain; for (size_t i = 0; i < length; ++i) front[i] = input + feedbackGain * front[i]; @@ -521,6 +518,8 @@ template class EasyFDN { const auto sum = std::accumulate(front.begin(), front.end(), Sample(0)); if (Sample(length) < sum) { safetyGain *= sum <= Sample(100) ? crossDecayGentle : crossDecaySteep; + } else { + safetyGain = std::min(safetyGain + Sample(0.001), Sample(1)); } return sum; } diff --git a/GenericDrum/source/editor.cpp b/GenericDrum/source/editor.cpp index 7927dc92..0068685f 100644 --- a/GenericDrum/source/editor.cpp +++ b/GenericDrum/source/editor.cpp @@ -48,7 +48,8 @@ constexpr int_least32_t defaultWidth = int_least32_t(4 * uiMargin + 3 * groupLab constexpr int_least32_t defaultHeight = int_least32_t(2 * uiMargin + 20 * labelY - 2 * margin); -enum tabIndex { tabBatter, tabSnare }; +constexpr const char *wireDidntCollidedText = "Wire didn't collide."; +constexpr const char *membraneDidntCollidedText = "Membrane didn't collide."; namespace Steinberg { namespace Vst { @@ -63,6 +64,80 @@ Editor::Editor(void *controller) : PlugEditor(controller) setRect(viewRect); } +ParamValue Editor::getPlainValue(ParamID id) +{ + auto normalized = controller->getParamNormalized(id); + return controller->normalizedParamToPlain(id, normalized); +} + +template +inline void resetStatusText( + ControllerPtr controller, LabelPtr label, Synth::ParameterID::ID id, const char *text) +{ + controller->setParamNormalized(id, 0.0); + controller->performEdit(id, 0.0); + if (label.get()) { + label->setText(text); + label->setDirty(); + } +} + +void Editor::valueChanged(CControl *pControl) +{ + using ID = Synth::ParameterID::ID; + + ParamID id = pControl->getTag(); + + if (id != ID::isWireCollided && id != ID::isSecondaryCollided) { + // controller->setParamNormalized(ID::isWireCollided, 0.0); + // controller->performEdit(ID::isWireCollided, 0.0); + // if (labelWireCollision.get()) { + // labelWireCollision->setText(wireDidntCollidedText); + // labelWireCollision->setDirty(); + // } + + // controller->setParamNormalized(ID::isSecondaryCollided, 0.0); + // controller->performEdit(ID::isSecondaryCollided, 0.0); + // if (labelMembraneCollision.get()) { + // labelMembraneCollision->setText(membraneDidntCollidedText); + // labelMembraneCollision->setDirty(); + // } + + resetStatusText( + controller, labelWireCollision, ID::isWireCollided, wireDidntCollidedText); + resetStatusText( + controller, labelMembraneCollision, ID::isSecondaryCollided, + membraneDidntCollidedText); + } + + ParamValue value = pControl->getValueNormalized(); + controller->setParamNormalized(id, value); + controller->performEdit(id, value); +} + +void Editor::updateUI(ParamID id, ParamValue normalized) +{ + using ID = Synth::ParameterID::ID; + + PlugEditor::updateUI(id, normalized); + + if (labelWireCollision.get() && id == ID::isWireCollided) { + if (getPlainValue(ID::isWireCollided)) { + labelWireCollision->setText("Wire collided."); + } else { + labelWireCollision->setText(wireDidntCollidedText); + } + labelWireCollision->setDirty(); + } else if (labelMembraneCollision.get() && id == ID::isSecondaryCollided) { + if (getPlainValue(ID::isSecondaryCollided)) { + labelMembraneCollision->setText("Membrane collided."); + } else { + labelMembraneCollision->setText(membraneDidntCollidedText); + } + labelMembraneCollision->setDirty(); + } +} + bool Editor::prepareUI() { using ID = Synth::ParameterID::ID; @@ -102,6 +177,9 @@ bool Editor::prepareUI() constexpr auto mixTop1 = mixTop0 + 1 * labelY; constexpr auto mixTop2 = mixTop0 + 2 * labelY; constexpr auto mixTop3 = mixTop0 + 3 * labelY; + constexpr auto mixTop4 = mixTop0 + 4 * labelY; + constexpr auto mixTop5 = mixTop0 + 5 * labelY; + constexpr auto mixTop6 = mixTop0 + 6 * labelY; constexpr auto mixLeft0 = left0; constexpr auto mixLeft1 = mixLeft0 + labelWidth + 2 * margin; addGroupLabel(mixLeft0, mixTop0, groupLabelWidth, labelHeight, uiTextSize, "Mix"); @@ -116,35 +194,49 @@ bool Editor::prepareUI() addTextKnob( mixLeft1, mixTop2, labelWidth, labelHeight, uiTextSize, ID::safetyHighpassHz, Scales::safetyHighpassHz, false, 5); + addCheckbox( + mixLeft0, mixTop3, labelWidth, labelHeight, uiTextSize, "Reset Seed at Note-on", + ID::resetSeedAtNoteOn); addCheckbox( mixLeft1, mixTop3, labelWidth, labelHeight, uiTextSize, "2x Sampling", ID::overSampling); + addLabel(mixLeft0, mixTop4, labelWidth, labelHeight, uiTextSize, "Channel"); + addOptionMenu( + mixLeft1, mixTop4, labelWidth, labelHeight, uiTextSize, ID::stereoEnable, + {"Mono", "Stereo"}); + addLabel(mixLeft0, mixTop5, labelWidth, labelHeight, uiTextSize, "Stereo Balance"); + addTextKnob( + mixLeft1, mixTop5, labelWidth, labelHeight, uiTextSize, ID::stereoBalance, + Scales::bipolarScale, false, 5); + addLabel(mixLeft0, mixTop6, labelWidth, labelHeight, uiTextSize, "Stereo Merge"); + addTextKnob( + mixLeft1, mixTop6, labelWidth, labelHeight, uiTextSize, ID::stereoMerge, + Scales::defaultScale, false, 5); // Tuning. - constexpr auto tuningTop0 = top0 + 4 * labelY; + constexpr auto tuningTop0 = top0 + 7 * labelY; constexpr auto tuningTop1 = tuningTop0 + 1 * labelY; constexpr auto tuningTop2 = tuningTop0 + 2 * labelY; constexpr auto tuningTop3 = tuningTop0 + 3 * labelY; constexpr auto tuningTop4 = tuningTop0 + 4 * labelY; constexpr auto tuningTop5 = tuningTop0 + 5 * labelY; - constexpr auto tuningTop6 = tuningTop0 + 6 * labelY; constexpr auto tuningLeft0 = left0; constexpr auto tuningLeft1 = tuningLeft0 + labelWidth + 2 * margin; addGroupLabel( tuningLeft0, tuningTop0, groupLabelWidth, labelHeight, uiTextSize, "Tuning"); - addLabel(tuningLeft0, tuningTop1, labelWidth, labelHeight, uiTextSize, "Semitone"); + addLabel(tuningLeft0, tuningTop1, labelWidth, labelHeight, uiTextSize, "Note -> Pitch"); + addTextKnob( + tuningLeft1, tuningTop1, labelWidth, labelHeight, uiTextSize, ID::notePitchAmount, + Scales::bipolarScale, false, 5); + addLabel(tuningLeft0, tuningTop2, labelWidth, labelHeight, uiTextSize, "Semitone"); addTextKnob( - tuningLeft1, tuningTop1, labelWidth, labelHeight, uiTextSize, ID::tuningSemitone, + tuningLeft1, tuningTop2, labelWidth, labelHeight, uiTextSize, ID::tuningSemitone, Scales::semitone, false, 0, -semitoneOffset); - addLabel(tuningLeft0, tuningTop2, labelWidth, labelHeight, uiTextSize, "Cent"); + addLabel(tuningLeft0, tuningTop3, labelWidth, labelHeight, uiTextSize, "Cent"); addTextKnob( - tuningLeft1, tuningTop2, labelWidth, labelHeight, uiTextSize, ID::tuningCent, + tuningLeft1, tuningTop3, labelWidth, labelHeight, uiTextSize, ID::tuningCent, Scales::cent, false, 5); - addLabel(tuningLeft0, tuningTop3, labelWidth, labelHeight, uiTextSize, "Equal Temp."); - addTextKnob( - tuningLeft1, tuningTop3, labelWidth, labelHeight, uiTextSize, ID::tuningET, - Scales::equalTemperament, false, 0, 1); addLabel( tuningLeft0, tuningTop4, labelWidth, labelHeight, uiTextSize, "Pitch Bend Range [st.]"); @@ -156,16 +248,6 @@ bool Editor::prepareUI() addTextKnob( tuningLeft1, tuningTop5, labelWidth, labelHeight, uiTextSize, ID::noteSlideTimeSecond, Scales::noteSlideTimeSecond, false, 5); - constexpr auto slideAtWidth = int(groupLabelWidth / 3); - constexpr auto slideAtLeft1 = tuningLeft0 + 1 * slideAtWidth; - constexpr auto slideAtLeft2 = tuningLeft0 + 2 * slideAtWidth; - addLabel(tuningLeft0, tuningTop6, slideAtWidth, labelHeight, uiTextSize, "Slide at"); - addCheckbox( - slideAtLeft1, tuningTop6, slideAtWidth, labelHeight, uiTextSize, "Note-on", - ID::slideAtNoteOn); - addCheckbox( - slideAtLeft2, tuningTop6, slideAtWidth, labelHeight, uiTextSize, "Note-off", - ID::slideAtNoteOff); // Impact. constexpr auto impactTop0 = top0 + 0 * labelY; @@ -239,7 +321,7 @@ bool Editor::prepareUI() addTextKnob( wireLeft1, wireTop6, labelWidth, labelHeight, uiTextSize, ID::wireCollisionTypeMix, Scales::defaultScale, false, 5); - addLabel( + labelWireCollision = addLabel( wireLeft0, wireTop7, 2 * labelWidth, labelHeight, uiTextSize, "Wire collision status."); @@ -326,17 +408,6 @@ bool Editor::prepareUI() mainLeft0, mainTop0, groupLabelWidth, labelHeight, uiTextSize, "Pitch Main"); addLabel(mainLeft0, mainTop1, labelWidth, labelHeight, uiTextSize, "Pitch Type"); - std::vector pitchTypeItems{ - "Harmonic", - "Harmonic+12", - "Harmonic*5", - "Harmonic Cycle(1, 5)", - "Harmonic Odd", - "Semitone (1, 2, 7, 9)", - "Circular Membrane Mode", - "Prime Number", - "Octave", - }; addOptionMenu( mainLeft1, mainTop1, labelWidth, labelHeight, uiTextSize, ID::pitchType, {"Harmonic", "Harmonic+12", "Harmonic*5", "Harmonic Cycle(1, 5)", "Harmonic Odd", @@ -394,7 +465,7 @@ bool Editor::prepareUI() addTextKnob( secondaryLeft1, secondaryTop4, labelWidth, labelHeight, uiTextSize, ID::secondaryDistance, Scales::collisionDistance, false, 5); - addLabel( + labelMembraneCollision = addLabel( secondaryLeft0, secondaryTop5, 2 * labelWidth, labelHeight, uiTextSize, "Membrane collision status."); diff --git a/GenericDrum/source/editor.hpp b/GenericDrum/source/editor.hpp index 9f67f219..4b54784e 100644 --- a/GenericDrum/source/editor.hpp +++ b/GenericDrum/source/editor.hpp @@ -33,10 +33,17 @@ class Editor : public PlugEditor { public: Editor(void *controller); + virtual void valueChanged(CControl *pControl) override; + void updateUI(Vst::ParamID id, ParamValue normalized) override; + DELEGATE_REFCOUNT(VSTGUIEditor); private: + ParamValue getPlainValue(ParamID id); bool prepareUI() override; + + SharedPointer