Skip to content

Commit

Permalink
Update DoubleLoopCymbal (WIP)
Browse files Browse the repository at this point in the history
`density` modulation check point.
  • Loading branch information
ryukau committed Sep 6, 2024
1 parent ad2a5a2 commit 4fbc33b
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 84 deletions.
122 changes: 114 additions & 8 deletions DoubleLoopCymbal/source/dsp/delay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// 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/pcg-cpp/pcg_random.hpp"
#include "../../lib/LambertW/LambertW.hpp"

#include <algorithm>
Expand Down Expand Up @@ -44,33 +45,56 @@ template<typename Sample> class ExpDecay {
Sample process() { return value *= alpha; }
};

template<typename Sample> class ExpRise {
private:
static constexpr auto eps = Sample(std::numeric_limits<float>::epsilon());
Sample value = eps;
Sample alpha = 0;

public:
void setTime(Sample decayTimeInSamples)
{
alpha = std::pow(Sample(1) / eps, Sample(1) / decayTimeInSamples);
}

void reset() { value = eps; }
void noteOn(Sample gain = eps) { value = gain; }
Sample process() { return value = std::min(value * alpha, Sample(1)); }
};

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

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

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

void noteOn(Sample gain_ = Sample(1)) { gain = gain_; }
void noteOn(Sample gain_ = Sample(1))
{
gain = gain_ / decaySamples;
counter = int(decaySamples);
}

void noteOff() { counter = 0; }

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

Expand All @@ -97,6 +121,11 @@ template<typename Sample> class Delay {

void reset() { std::fill(buf.begin(), buf.end(), Sample(0)); }

void applyGain(Sample gain)
{
for (auto &x : buf) x *= gain;
}

Sample process(Sample input, Sample timeInSamples)
{
const int size = int(buf.size());
Expand Down Expand Up @@ -201,6 +230,32 @@ template<typename Sample> class NyquistLowpass {
}
};

// Unused. `SerialAllpass` became unstable when used.
template<typename Sample> class FixedHighpass {
private:
static constexpr Sample g = Sample(0.02); // ~= tan(pi * 300 / 48000).
static constexpr Sample k = Sample(0.7071067811865476); // 2 / sqrt(2).

Sample ic1eq = 0;
Sample ic2eq = 0;

public:
void reset()
{
ic1eq = 0;
ic2eq = 0;
}

Sample process(Sample input)
{
const auto v1 = (ic1eq + g * (input - ic2eq)) / (1 + g * (g + k));
const auto v2 = ic2eq + g * v1;
ic1eq = Sample(2) * v1 - ic1eq;
ic2eq = Sample(2) * v2 - ic2eq;
return input - k * v1 - v2;
}
};

template<typename Sample> class EmaHighShelf {
private:
Sample value = 0;
Expand Down Expand Up @@ -407,6 +462,11 @@ template<typename Sample, size_t nAllpass, size_t nAdaptiveNotch> class SerialAl
for (auto &x : notch) x.reset();
}

void applyGain(Sample gain)
{
for (auto &x : delay) x.applyGain(gain);
}

Sample sum(Sample altSignMix)
{
Sample sumAlt = Sample(0);
Expand Down Expand Up @@ -488,6 +548,8 @@ template<typename Sample> class ExpADEnvelope {
Sample alphaD = 0;

public:
bool isTerminated() { return valueD <= Sample(1e-3); }

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

void reset()
Expand Down Expand Up @@ -537,4 +599,48 @@ template<typename Sample> class ExpADEnvelope {
}
};

template<typename Sample> class HalfClosedNoise {
private:
Sample phase = 0;
Sample gain = Sample(1);
Sample decay = 0;

FixedHighpass<Sample> highpass;

public:
void reset()
{
phase = 0;
gain = Sample(1);

highpass.reset();
}

void setDecay(Sample timeInSample)
{
constexpr Sample eps = std::numeric_limits<Sample>::epsilon();
decay = timeInSample < Sample(1) ? 0 : std::pow(eps, Sample(1) / timeInSample);
}

// `density` is inverse of average samples between impulses.
// `randomGain` is in [0, 1].
Sample process(Sample density, Sample randomGain, pcg64 &rng)
{
std::uniform_real_distribution<Sample> jitter(Sample(0), Sample(1));
phase += jitter(rng) * density;
if (phase >= Sample(1)) {
phase -= std::floor(phase);

std::normal_distribution<Sample> distGain(Sample(0), Sample(1) / Sample(3));
gain = Sample(1) + randomGain * (distGain(rng) - Sample(1));
} else {
gain *= decay;
}

std::uniform_real_distribution<Sample> distNoise(-Sample(1), Sample(1));
const auto noise = distNoise(rng);
return highpass.process(noise * noise * noise * gain);
}
};

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

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

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

releaseSmoother.setup(double(2) * upRate);
envelopeClose.setup(EMAFilter<double>::secondToP(upRate, double(0.004)));
for (auto &x : halfClosedNoise) x.setDecay(double(0.1) * upRate);

const auto maxDelayTimeSamples = upRate * 2 * Scales::delayTimeSecond.getMax();
for (auto &x : serialAllpass1) x.setup(maxDelayTimeSamples);
Expand All @@ -180,7 +181,8 @@ void DSPCore::setup(double sampleRate)
interpPitch.METHOD(notePitch); \
\
externalInputGain.METHOD(pv[ID::externalInputGain]->getDouble()); \
noiseSustainLevel.METHOD(pv[ID::noiseSustainLevel]->getDouble()); \
halfClosedGain.METHOD(pv[ID::halfClosedGain]->getDouble()); \
halfClosedDensity.METHOD(pv[ID::halfClosedDensity]->getDouble() / upRate); \
delayTimeModAmount.METHOD( \
pv[ID::delayTimeModAmount]->getDouble() * upRate / double(48000)); \
allpassFeed1.METHOD( \
Expand All @@ -204,6 +206,7 @@ void DSPCore::setup(double sampleRate)
outputGain.METHOD(gain); \
\
envelopeNoise.setTime(pv[ID::noiseDecaySeconds]->getDouble() * upRate); \
envelopeHalfClosed.setTime(pv[ID::halfClosedDecaySeconds]->getDouble() * upRate); \
envelopeClose.update( \
upRate, pv[ID::closeAttackSeconds]->getDouble(), closeReleaseSecond, \
pv[ID::closeGain]->getDouble()); \
Expand Down Expand Up @@ -259,7 +262,9 @@ void DSPCore::reset()
releaseSmoother.reset();
envelopeNoise.reset();
envelopeRelease.reset();
envelopeHalfClosed.reset();
envelopeClose.reset();
for (auto &x : halfClosedNoise) x.reset();
feedbackBuffer1.fill(double(0));
feedbackBuffer2.fill(double(0));
for (auto &x : serialAllpass1) x.reset();
Expand Down Expand Up @@ -292,10 +297,11 @@ std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externa
const auto terminationGain = envRelease < double(1e-3) ? double(0) : double(1);

const auto extGain = externalInputGain.process();
const auto noiseSustain = noiseSustainLevel.process();
const auto hcGain = halfClosedGain.process();
const auto hcDensity = halfClosedDensity.process();

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

auto apGain1 = allpassFeed1.process();
auto apGain2 = allpassFeed2.process();
Expand All @@ -317,7 +323,6 @@ std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externa

std::uniform_real_distribution<double> dist{double(-1), double(1)};
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();
Expand All @@ -334,6 +339,12 @@ std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externa
const auto ipow = [](double v) { return std::copysign(v * v * v, v); };
excitation[0] += noiseEnv * ipow(dist(noiseRng));
excitation[1] += noiseEnv * ipow(dist(noiseRng));

const auto hcEnv = envelopeHalfClosed.process();
const auto density = hcDensity * std::exp2(double(8) - double(8) * hcEnv);
const auto gn = hcGain * velocity * hcEnv * hcEnv;
excitation[0] += gn * halfClosedNoise[0].process(density, double(1), noiseRng);
excitation[1] += gn * halfClosedNoise[1].process(density, double(1), noiseRng);
}

if (useExternalInput) {
Expand Down Expand Up @@ -446,17 +457,24 @@ void DSPCore::noteOn(NoteInfo &info)

if (pv[ID::resetSeedAtNoteOn]->getInt()) noiseRng.seed(pv[ID::seed]->getInt());

// if (envelopeClose.isTerminated() && !pv[ID::release]->getInt()) {
const auto lossGain = pv[ID::lossGain]->getDouble();
for (auto &x : serialAllpass1) x.applyGain(lossGain);
for (auto &x : serialAllpass2) x.applyGain(lossGain);
// }

const auto oscGain = velocity * pv[ID::noiseGain]->getDouble();
releaseSmoother.prepare(
double(0.1) * envelopeNoise.process(),
upRate * pv[ID::noiseDecaySeconds]->getDouble());
impulse = oscGain;
envelopeNoise.noteOn(oscGain);
envelopeHalfClosed.noteOn();
envelopeRelease.noteOn(double(1));
envelopeRelease.setTime(1, true);
envelopeClose.noteOn(
upRate, pv[ID::closeAttackSeconds]->getDouble(), closeReleaseSecond,
pv[ID::closeGain]->getDouble());
velocity * pv[ID::closeGain]->getDouble());
}

void DSPCore::noteOff(int_fast32_t noteId)
Expand All @@ -474,7 +492,11 @@ void DSPCore::noteOff(int_fast32_t noteId)
noteNumber = noteStack.back().noteNumber;
interpPitch.push(calcNotePitch(noteNumber));
} else {
if (!pv[ID::release]->getInt()) envelopeRelease.setTime(releaseTimeSecond * upRate);
if (!pv[ID::release]->getInt()) {
envelopeRelease.setTime(releaseTimeSecond * upRate);
} else {
envelopeHalfClosed.noteOff();
}
}
}

Expand Down
10 changes: 7 additions & 3 deletions DoubleLoopCymbal/source/dsp/dspcore.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "../../../common/dsp/constants.hpp"
#include "../../../common/dsp/multirate.hpp"
#include "../../../common/dsp/smoother.hpp"
#include "../../../lib/pcg-cpp/pcg_random.hpp"
#include "../parameter.hpp"
#include "delay.hpp"

Expand Down Expand Up @@ -116,7 +117,8 @@ class DSPCore {
ExpSmootherLocal<double> interpPitch;

ExpSmoother<double> externalInputGain;
ExpSmoother<double> noiseSustainLevel;
ExpSmoother<double> halfClosedGain;
ExpSmoother<double> halfClosedDensity;
ExpSmoother<double> delayTimeModAmount;
ExpSmoother<double> allpassFeed1;
ExpSmoother<double> allpassFeed2;
Expand All @@ -134,13 +136,15 @@ class DSPCore {

bool useExternalInput = false;

std::minstd_rand noiseRng{0};
std::minstd_rand paramRng{0};
pcg64 noiseRng{0};
pcg64 paramRng{0};
double impulse = 0;
TransitionReleaseSmoother<double> releaseSmoother;
ExpDecay<double> envelopeNoise;
ExpDecay<double> envelopeRelease;
LinearDecay<double> envelopeHalfClosed;
ExpADEnvelope<double> envelopeClose;
std::array<HalfClosedNoise<double>, 2> halfClosedNoise;
std::array<double, 2> feedbackBuffer1{};
std::array<double, 2> feedbackBuffer2{};
std::array<SerialAllpass<double, nAllpass, nNotch>, 2> serialAllpass1;
Expand Down
Loading

0 comments on commit 4fbc33b

Please sign in to comment.