Skip to content

Commit

Permalink
Update DoubleLoopCymbal (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryukau committed Sep 3, 2024
1 parent 5012c20 commit df7c549
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 13 deletions.
101 changes: 97 additions & 4 deletions DoubleLoopCymbal/source/dsp/delay.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// (c) 2023 Takamitsu Endo
// (c) 2024 Takamitsu Endo
//
// This file is part of DoubleLoopCymbal.
//
Expand All @@ -15,6 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with DoubleLoopCymbal. If not, see <https://www.gnu.org/licenses/>.

#include "../../lib/LambertW/LambertW.hpp"

#include <algorithm>
#include <array>
#include <cmath>
Expand All @@ -27,8 +29,8 @@ namespace SomeDSP {

template<typename Sample> class ExpDecay {
private:
Sample value = Sample(0);
Sample alpha = Sample(0);
Sample value = 0;
Sample alpha = 0;

public:
void setTime(Sample decayTimeInSamples, bool sustain = false)
Expand All @@ -37,11 +39,41 @@ template<typename Sample> class ExpDecay {
alpha = sustain ? Sample(1) : std::pow(eps, Sample(1) / decayTimeInSamples);
}

void reset() { value = Sample(0); }
void reset() { value = 0; }
void noteOn(Sample gain = Sample(1)) { value = gain; }
Sample process() { return value *= alpha; }
};

template<typename Sample> class LinearDecay {
private:
Sample gain = Sample(1);
Sample counter = 0;
Sample decayRatio = Sample(1);

public:
void setTime(Sample decayTimeInSamples, bool sustain = false)
{
constexpr auto eps = Sample(std::numeric_limits<float>::epsilon());
counter = decayTimeInSamples;
decayRatio = Sample(1) / std::max(Sample(1), decayTimeInSamples);
}

void reset()
{
gain = Sample(1);
counter = 0;
decayRatio = Sample(1);
}

void noteOn(Sample gain_ = Sample(1)) { gain = gain_; }

Sample process()
{
if (counter <= 0) return 0;
return gain * (--counter * decayRatio);
}
};

template<typename T> inline T lagrange3Interp(T y0, T y1, T y2, T y3, T t)
{
auto u = T(1) + t;
Expand Down Expand Up @@ -444,4 +476,65 @@ template<typename Sample> class TransitionReleaseSmoother {
Sample process() { return v0 *= decay; }
};

template<typename Sample> class ExpADEnvelope {
private:
static constexpr Sample epsilon = std::numeric_limits<Sample>::epsilon();
Sample targetGain = 0;
Sample gain = Sample(1);
Sample smoo = Sample(1);
Sample valueA = 0;
Sample alphaA = 0;
Sample valueD = 0;
Sample alphaD = 0;

public:
void setup(Sample smoothingKp) { smoo = smoothingKp; }

void reset()
{
targetGain = 0;
gain = Sample(1);
valueA = 0;
alphaA = 0;
valueD = 0;
alphaD = 0;
}

void
update(Sample sampleRate, Sample peakSeconds, Sample releaseSeconds, Sample peakGain)
{
const auto decaySeconds = releaseSeconds - std::log(epsilon) * peakSeconds;
const auto d_ = std::log(epsilon) / decaySeconds;
const auto x_ = d_ * peakSeconds;
const auto a_ = Sample(utl::LambertW(-1, x_ * std::exp(x_))) / peakSeconds - d_;

const auto attackSeconds = -std::log(epsilon) / std::log(-a_);
alphaA = std::exp(a_ / sampleRate);
alphaD = std::exp(d_ / sampleRate);

// `area` is obtained by solving `integrate((1-%e^(-a*t))*%e^(-d*t), t, 0, +inf);`.
const auto area = -a_ / (d_ * (d_ + a_));

// targetGain = peakGain
// / ((Sample(1) - std::exp(a_ * peakSeconds)) * std::exp(d_ * peakSeconds));
targetGain = Sample(1e-3) * peakGain / area;
}

void
noteOn(Sample sampleRate, Sample peakSeconds, Sample releaseSeconds, Sample peakGain)
{
valueA = Sample(1);
valueD = Sample(1);
update(sampleRate, peakSeconds, releaseSeconds, peakGain);
}

Sample process()
{
gain += smoo * (targetGain - gain);
valueA *= alphaA;
valueD *= alphaD;
return gain * (Sample(1) - (valueA)) * valueD;
}
};

} // namespace SomeDSP
39 changes: 30 additions & 9 deletions DoubleLoopCymbal/source/dsp/dspcore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

constexpr double defaultTempo = double(120);
constexpr double releaseTimeSecond = double(4.0);
constexpr double closeReleaseSecond = double(1.0);

constexpr const std::array<double, 256> circularModes = {
double(1.000000000000000), double(1.5933405056951118), double(2.135548786649403),
Expand Down Expand Up @@ -157,6 +158,7 @@ void DSPCore::setup(double sampleRate)
SmootherCommon<double>::setTime(double(0.2));

releaseSmoother.setup(double(2) * upRate);
envelopeClose.setup(EMAFilter<double>::secondToP(upRate, double(0.004)));

const auto maxDelayTimeSamples = upRate * 2 * Scales::delayTimeSecond.getMax();
for (auto &x : serialAllpass1) x.setup(maxDelayTimeSamples);
Expand All @@ -178,6 +180,7 @@ void DSPCore::setup(double sampleRate)
interpPitch.METHOD(notePitch); \
\
externalInputGain.METHOD(pv[ID::externalInputGain]->getDouble()); \
noiseSustainLevel.METHOD(pv[ID::noiseSustainLevel]->getDouble()); \
delayTimeModAmount.METHOD( \
pv[ID::delayTimeModAmount]->getDouble() * upRate / double(48000)); \
allpassFeed1.METHOD( \
Expand All @@ -201,8 +204,12 @@ void DSPCore::setup(double sampleRate)
outputGain.METHOD(gain); \
\
envelopeNoise.setTime(pv[ID::noiseDecaySeconds]->getDouble() * upRate); \
envelopeClose.update( \
upRate, pv[ID::closeAttackSeconds]->getDouble(), closeReleaseSecond, \
pv[ID::closeGain]->getDouble()); \
if (!pv[ID::release]->getInt() && noteStack.empty()) { \
envelopeRelease.setTime(releaseTimeSecond *upRate); \
envelopeRelease.setTime( \
double(8) * pv[ID::closeAttackSeconds]->getDouble() * upRate); \
} \
\
updateDelayTime();
Expand Down Expand Up @@ -252,6 +259,7 @@ void DSPCore::reset()
releaseSmoother.reset();
envelopeNoise.reset();
envelopeRelease.reset();
envelopeClose.reset();
feedbackBuffer1.fill(double(0));
feedbackBuffer2.fill(double(0));
for (auto &x : serialAllpass1) x.reset();
Expand Down Expand Up @@ -281,10 +289,13 @@ void DSPCore::setParameters()
std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externalInput)
{
const auto envRelease = envelopeRelease.process();
const auto terminationGain = envRelease < double(1e-3) ? double(0) : double(1);

const auto extGain = externalInputGain.process();
const auto noiseSustain = noiseSustainLevel.process();

auto timeModAmt = delayTimeModAmount.process();
timeModAmt += (double(1) - envRelease) * (double(1000) * upRate / double(48000));
// timeModAmt += (double(1) - envRelease) * (double(200) * upRate / double(48000));

auto apGain1 = allpassFeed1.process();
auto apGain2 = allpassFeed2.process();
Expand All @@ -294,7 +305,7 @@ std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externa
const auto apMixSpike = allpassMixSpike.process();
const auto apMixSign = allpassMixAltSign.process();
const auto hsCut = highShelfCutoff.process();
const auto hsGain = highShelfGain.process() * envRelease;
const auto hsGain = highShelfGain.process(); //* envRelease;
const auto lsCut = lowShelfCutoff.process();
const auto lsGain = lowShelfGain.process();
const size_t nNotch = param.value[ParameterID::nAdaptiveNotch]->getInt();
Expand All @@ -305,9 +316,16 @@ std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externa
const auto outGain = outputGain.process() * envRelease;

std::uniform_real_distribution<double> dist{double(-1), double(1)};
const auto noiseEnv = releaseSmoother.process() + envelopeNoise.process();
auto noiseEnv = releaseSmoother.process() + envelopeNoise.process();
if (!noteStack.empty()) noiseEnv += noiseSustain;
if (!param.value[ParameterID::release]->getInt() && noteStack.empty()) {
// TODO: simplify condition.
noiseEnv += envelopeClose.process();
}

std::array<double, 2> excitation{
-apGain1 * feedbackBuffer1[0], -apGain1 * feedbackBuffer1[1]};
-terminationGain * apGain1 * feedbackBuffer1[0],
-terminationGain * apGain1 * feedbackBuffer1[1]};
if (impulse != 0) {
excitation[0] += impulse;
excitation[1] += impulse;
Expand Down Expand Up @@ -348,11 +366,11 @@ std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externa
= std::lerp(serialAllpass2[1].sum(apMixSign), feedbackBuffer2[1], apMixSpike)
* normalizeGain;
feedbackBuffer2[0] = serialAllpass2[0].process(
ap1Out0 - apGain2 * feedbackBuffer2[0], hsCut, hsGain, lsCut, lsGain, apGain2,
pitchRatio, timeModAmt, nNotch, ntMix, ntNarrowness);
ap1Out0 - terminationGain * apGain2 * feedbackBuffer2[0], hsCut, hsGain, lsCut,
lsGain, apGain2, pitchRatio, timeModAmt, nNotch, ntMix, ntNarrowness);
feedbackBuffer2[1] = serialAllpass2[1].process(
ap1Out1 - apGain2 * feedbackBuffer2[1], hsCut, hsGain, lsCut, lsGain, apGain2,
pitchRatio, timeModAmt, nNotch, ntMix, ntNarrowness);
ap1Out1 - terminationGain * apGain2 * feedbackBuffer2[1], hsCut, hsGain, lsCut,
lsGain, apGain2, pitchRatio, timeModAmt, nNotch, ntMix, ntNarrowness);

constexpr auto eps = std::numeric_limits<double>::epsilon();
if (balance < -eps) {
Expand Down Expand Up @@ -436,6 +454,9 @@ void DSPCore::noteOn(NoteInfo &info)
envelopeNoise.noteOn(oscGain);
envelopeRelease.noteOn(double(1));
envelopeRelease.setTime(1, true);
envelopeClose.noteOn(
upRate, pv[ID::closeAttackSeconds]->getDouble(), closeReleaseSecond,
pv[ID::closeGain]->getDouble());
}

void DSPCore::noteOff(int_fast32_t noteId)
Expand Down
2 changes: 2 additions & 0 deletions DoubleLoopCymbal/source/dsp/dspcore.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class DSPCore {
ExpSmootherLocal<double> interpPitch;

ExpSmoother<double> externalInputGain;
ExpSmoother<double> noiseSustainLevel;
ExpSmoother<double> delayTimeModAmount;
ExpSmoother<double> allpassFeed1;
ExpSmoother<double> allpassFeed2;
Expand All @@ -139,6 +140,7 @@ class DSPCore {
TransitionReleaseSmoother<double> releaseSmoother;
ExpDecay<double> envelopeNoise;
ExpDecay<double> envelopeRelease;
ExpADEnvelope<double> envelopeClose;
std::array<double, 2> feedbackBuffer1{};
std::array<double, 2> feedbackBuffer2{};
std::array<SerialAllpass<double, nAllpass, nNotch>, 2> serialAllpass1;
Expand Down
15 changes: 15 additions & 0 deletions DoubleLoopCymbal/source/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,21 @@ bool Editor::prepareUI()
addTextKnob(
impactLeft1, impactTop3, labelWidth, labelHeight, uiTextSize, ID::noiseDecaySeconds,
Scales::noiseDecaySeconds, false, 5);
addLabel(
impactLeft0, impactTop4, labelWidth, labelHeight, uiTextSize, "Noise Sustain [dB]");
addTextKnob(
impactLeft1, impactTop4, labelWidth, labelHeight, uiTextSize, ID::noiseSustainLevel,
Scales::gain, true, 5);
addLabel(
impactLeft0, impactTop5, labelWidth, labelHeight, uiTextSize, "Close Gain [dB]");
addTextKnob(
impactLeft1, impactTop5, labelWidth, labelHeight, uiTextSize, ID::closeGain,
Scales::gain, true, 5);
addLabel(
impactLeft0, impactTop6, labelWidth, labelHeight, uiTextSize, "Close Attack [s]");
addTextKnob(
impactLeft1, impactTop6, labelWidth, labelHeight, uiTextSize, ID::closeAttackSeconds,
Scales::noiseDecaySeconds, false, 5);

addLabel(
impactLeft0, impactTop8, labelWidth, labelHeight, uiTextSize,
Expand Down
11 changes: 11 additions & 0 deletions DoubleLoopCymbal/source/parameter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ enum ID {
seed,
noiseGain,
noiseDecaySeconds,
noiseSustainLevel,

closeGain,
closeAttackSeconds,

delayTimeShape,
delayTimeBaseSecond,
Expand Down Expand Up @@ -172,6 +176,13 @@ struct GlobalParameter : public ParameterInterface {
value[ID::noiseDecaySeconds] = std::make_unique<DecibelValue>(
Scales::noiseDecaySeconds.invmap(0.001), Scales::noiseDecaySeconds,
"noiseDecaySeconds", Info::kCanAutomate);
value[ID::noiseSustainLevel] = std::make_unique<DecibelValue>(
Scales::gain.invmap(1e-3), Scales::gain, "noiseSustainLevel", Info::kCanAutomate);
value[ID::closeGain] = std::make_unique<DecibelValue>(
Scales::gain.invmap(1.0), Scales::gain, "closeGain", Info::kCanAutomate);
value[ID::closeAttackSeconds] = std::make_unique<DecibelValue>(
Scales::noiseDecaySeconds.invmap(0.05), Scales::noiseDecaySeconds,
"closeAttackSeconds", Info::kCanAutomate);

value[ID::delayTimeShape] = std::make_unique<LinearValue>(
Scales::defaultScale.invmap(0.0), Scales::defaultScale, "delayTimeShape",
Expand Down

0 comments on commit df7c549

Please sign in to comment.