Skip to content

Commit

Permalink
Add external input to GenericDrum
Browse files Browse the repository at this point in the history
  • Loading branch information
ryukau committed Oct 4, 2023
1 parent 3d0f24c commit e41acce
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 48 deletions.
113 changes: 83 additions & 30 deletions GenericDrum/source/dsp/dspcore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ void DSPCore::setup(double sampleRate)

SmootherCommon<double>::setTime(double(0.2));

triggerDetector.setup(upRate * double(0.125));

const auto maxDelayTimeSamples = upRate;
for (auto &x : noiseAllpass) x.setup(maxDelayTimeSamples);
for (auto &x : wireAllpass) x.setup(maxDelayTimeSamples);
Expand All @@ -153,6 +155,8 @@ void DSPCore::setup(double sampleRate)
using ID = ParameterID::ID; \
const auto &pv = param.value; \
\
useExternalInput = pv[ID::useExternalInput]->getInt(); \
useAutomaticTrigger = pv[ID::useAutomaticTrigger]->getInt() && useExternalInput; \
preventBlowUp = pv[ID::preventBlowUp]->getInt(); \
\
pitchSmoothingKp \
Expand All @@ -162,6 +166,7 @@ void DSPCore::setup(double sampleRate)
auto notePitch = calcNotePitch(pitchBend * noteNumber); \
interpPitch.METHOD(notePitch); \
\
externalInputGain.METHOD(pv[ID::externalInputGain]->getDouble()); \
wireDistance.METHOD(pv[ID::wireDistance]->getDouble()); \
wireCollisionTypeMix.METHOD(pv[ID::wireCollisionTypeMix]->getDouble()); \
impactWireMix.METHOD(pv[ID::impactWireMix]->getDouble()); \
Expand All @@ -180,15 +185,25 @@ void DSPCore::setup(double sampleRate)
safetyHighpass[ch].METHOD(highpassCut, highpassQ); \
} \
\
triggerDetector.prepare(pv[ID::automaticTriggerThreshold]->getDouble()); \
\
paramRng.seed(pv[ID::seed]->getInt()); \
\
const auto noiseLowpassFreq = pv[ID::noiseLowpassHz]->getDouble() / upRate; \
noiseLowpass.METHOD(noiseLowpassFreq); \
\
auto gain = pv[ID::outputGain]->getDouble(); \
if (pv[ID::normalizeGainWrtNoiseLowpassHz]->getInt()) { \
gain *= approxNormalizeGain(noiseLowpassFreq) / interpPitch.getValue(); \
} \
outputGain.METHOD(gain); \
\
for (size_t idx = 0; idx < maxFdnSize; ++idx) { \
const auto crossFeedbackRatio = pv[ID::crossFeedbackRatio0 + idx]->getDouble(); \
feedbackMatrix.seed[idx] = crossFeedbackRatio * crossFeedbackRatio; \
} \
feedbackMatrix.constructHouseholder(); \
\
const auto noiseLowpassFreq = 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(); \
Expand All @@ -201,14 +216,7 @@ void DSPCore::setup(double sampleRate)
const auto pitchRandomCent = pv[ID::pitchRandomCent]->getDouble(); \
const size_t pitchType = pv[ID::pitchType]->getInt(); \
\
auto gain = pv[ID::outputGain]->getDouble(); \
if (pv[ID::normalizeGainWrtNoiseLowpassHz]->getInt()) { \
gain *= approxNormalizeGain(noiseLowpassFreq) / interpPitch.getValue(); \
} \
outputGain.METHOD(gain); \
\
for (size_t drm = 0; drm < nDrum; ++drm) { \
noiseLowpass[drm].METHOD(noiseLowpassFreq); \
noiseAllpass[drm].timeInSamples.METHOD( \
prepareSerialAllpassTime<nAllpass>(upRate, noiseAllpassMaxTimeHz, paramRng)); \
wireAllpass[drm].timeInSamples.METHOD( \
Expand Down Expand Up @@ -274,6 +282,8 @@ void DSPCore::reset()
noteNumber = 69.0;
velocity = 0;

triggerDetector.reset();

noiseGain = 0;
noiseDecay = 0;
for (auto &x : noiseAllpass) x.reset();
Expand All @@ -289,6 +299,7 @@ void DSPCore::reset()
envelope.reset();
releaseSmoother.reset();

feedbackMatrix.reset();
membrane1Position.fill({});
membrane1Velocity.fill({});
membrane2Position.fill({});
Expand All @@ -298,6 +309,7 @@ void DSPCore::reset()
for (auto &x : membrane1) x.reset();
for (auto &x : membrane2) x.reset();

for (auto &x : halfbandInput) x.fill({});
for (auto &x : halfbandIir) x.reset();
}

Expand Down Expand Up @@ -340,17 +352,14 @@ inline void solveCollision(double &p0, double &p1, double v0, double v1, double

double DSPCore::processDrum(
size_t index,
double noise,
double excitation,
double wireGain,
double pitchEnv,
double crossGain,
double timeModAmt)
{
// Impact.
constexpr auto eps = std::numeric_limits<double>::epsilon();
double sig = 0;
sig += noiseLowpass[index].process(noise);
sig = std::tanh(noiseAllpass[index].process(sig, double(0.95)));
// Impact & Echo.
double sig = std::tanh(noiseAllpass[index].process(excitation, double(0.95)));

// Wire.
solveCollision(
Expand All @@ -365,7 +374,8 @@ double DSPCore::processDrum(
wireCollisionTypeMix.getValue());
wireCollision = double(8) * std::tanh(double(0.125) * wireCollision);
const auto wireIn = double(0.995) * (sig + wireCollision);
const auto wirePos = wireAllpass[index].process(wireIn, double(0.5)) * wireGain;
auto wirePos = wireAllpass[index].process(wireIn, double(0.5)) * wireGain;
if (preventBlowUp) wirePos /= double(nAllpass);
wireVelocity[index] = wirePos - wirePosition[index];
wirePosition[index] = wirePos;

Expand Down Expand Up @@ -405,6 +415,7 @@ double DSPCore::processDrum(
}

#define PROCESS_COMMON \
externalInputGain.process(); \
wireDistance.process(); \
wireCollisionTypeMix.process(); \
impactWireMix.process(); \
Expand All @@ -418,25 +429,45 @@ double DSPCore::processDrum(
const auto outGain = outputGain.process(); \
\
std::uniform_real_distribution<double> dist{double(-0.5), double(0.5)}; \
const auto noise = noiseGain * (dist(noiseRng) + dist(noiseRng)); \
const auto noise \
= noiseLowpass.process(noiseGain * (dist(noiseRng) + dist(noiseRng))); \
noiseGain *= noiseDecay; \
wireGain *= wireDecay; \
\
const auto pitchEnv = std::exp2(envelope.process() + releaseSmoother.process());

double DSPCore::processSample()
inline void DSPCore::processExternalInput(double absed)
{
if (maxExtInAmplitude < absed) maxExtInAmplitude = absed;
if (useAutomaticTrigger && triggerDetector.process(absed)) wireGain = double(2);
}

double DSPCore::processSample(double externalInput)
{
PROCESS_COMMON;

return outGain * processDrum(0, noise, wireGain, pitchEnv, crossGain, timeModAmt);
const auto excitation
= useExternalInput ? externalInput * externalInputGain.getValue() : noise;
if (useExternalInput) {
processExternalInput(std::abs(excitation));
}

return outGain * processDrum(0, excitation, wireGain, pitchEnv, crossGain, timeModAmt);
}

std::array<double, 2> DSPCore::processFrame()
std::array<double, 2> DSPCore::processFrame(const std::array<double, 2> &externalInput)
{
PROCESS_COMMON;

auto drum0 = processDrum(0, noise, wireGain, pitchEnv, crossGain, timeModAmt);
auto drum1 = processDrum(1, noise, wireGain, pitchEnv, crossGain, timeModAmt);
const auto &extGain = externalInputGain.getValue();
const auto excitation0 = useExternalInput ? externalInput[0] * extGain : noise;
const auto excitation1 = useExternalInput ? externalInput[1] * extGain : noise;
if (useExternalInput) {
processExternalInput(double(0.5) * (std::abs(excitation0) + std::abs(excitation1)));
}

auto drum0 = processDrum(0, excitation0, wireGain, pitchEnv, crossGain, timeModAmt);
auto drum1 = processDrum(1, excitation1, wireGain, pitchEnv, crossGain, timeModAmt);

constexpr auto eps = std::numeric_limits<double>::epsilon();
if (balance < -eps) {
Expand All @@ -450,7 +481,8 @@ std::array<double, 2> DSPCore::processFrame()
};
}

void DSPCore::process(const size_t length, float *out0, float *out1)
void DSPCore::process(
const size_t length, const float *in0, const float *in1, float *out0, float *out1)
{
ScopedNoDenormals scopedDenormals;

Expand All @@ -463,17 +495,29 @@ void DSPCore::process(const size_t length, float *out0, float *out1)
bool isStereo = pv[ID::stereoUnison]->getInt();
bool isSafetyHighpassEnabled = pv[ID::safetyHighpassEnable]->getInt();

maxExtInAmplitude = 0;

std::array<double, 2> prevExtIn = halfbandInput[0];
std::array<double, 2> frame{};
for (size_t i = 0; i < length; ++i) {
processMidiNote(i);

const double extIn0 = in0 == nullptr ? 0 : in0[i];
const double extIn1 = in1 == nullptr ? 0 : in1[i];

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 = processFrame({
double(0.5) * (prevExtIn[0] + extIn0),
double(0.5) * (prevExtIn[1] + extIn1),
});
halfbandInput[0][0] = frame[0];
halfbandInput[1][0] = frame[1];

frame = processFrame({extIn0, extIn1});
halfbandInput[0][1] = frame[0];
halfbandInput[1][1] = frame[1];

frame[0] = halfbandIir[0].process(halfbandInput[0]);
frame[1] = halfbandIir[1].process(halfbandInput[1]);
if (isSafetyHighpassEnabled) {
Expand All @@ -483,7 +527,7 @@ void DSPCore::process(const size_t length, float *out0, float *out1)
out0[i] = float(frame[0]);
out1[i] = float(frame[1]);
} else {
frame = processFrame();
frame = processFrame({extIn0, extIn1});
if (isSafetyHighpassEnabled) {
frame[0] = safetyHighpass[0].process(frame[0]);
frame[1] = safetyHighpass[1].process(frame[1]);
Expand All @@ -492,22 +536,31 @@ void DSPCore::process(const size_t length, float *out0, float *out1)
out1[i] = float(frame[1]);
}
} else {
const double extInMixed = double(0.5) * (extIn0 + extIn1);
if (overSampling) {
for (size_t j = 0; j < upFold; ++j) halfbandInput[0][j] = processSample();
halfbandInput[0][0]
= processSample(extInMixed + double(0.5) * (prevExtIn[0] + prevExtIn[1]));
halfbandInput[0][1] = processSample(extInMixed);
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();
frame[0] = processSample(extInMixed);
if (isSafetyHighpassEnabled) frame[0] = safetyHighpass[0].process(frame[0]);
out0[i] = float(frame[0]);
out1[i] = float(frame[0]);
}
}

prevExtIn = {extIn0, extIn1};
}

// Propagate last input to next cycle.
halfbandInput[0] = prevExtIn;

// Send a value to GUI.
pv[ID::externalInputAmplitudeMeter]->setFromFloat(maxExtInAmplitude);
if (isWireCollided) pv[ID::isWireCollided]->setFromInt(1);
if (isSecondaryCollided) pv[ID::isSecondaryCollided]->setFromInt(1);
}
Expand Down
16 changes: 12 additions & 4 deletions GenericDrum/source/dsp/dspcore.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class DSPCore {
void reset();
void startup();
void setParameters();
void process(const size_t length, float *out0, float *out1);
void process(
const size_t length, const float *in0, const float *in1, float *out0, float *out1);
void noteOn(NoteInfo &info);
void noteOff(int_fast32_t noteId);

Expand Down Expand Up @@ -96,19 +97,21 @@ class DSPCore {
void updateUpRate();
void resetCollision();
double calcNotePitch(double note);
double processSample();
std::array<double, 2> processFrame();
double processSample(double externalInput);
std::array<double, 2> processFrame(const std::array<double, 2> &externalInput);
double processDrum(
size_t index,
double noise,
double wireGain,
double pitchEnv,
double crossGain,
double timeModAmt);
inline void processExternalInput(double absed);

std::vector<NoteInfo> midiNotes;
std::vector<NoteInfo> noteStack;

double maxExtInAmplitude = 0;
bool isWireCollided = false;
bool isSecondaryCollided = false;

Expand All @@ -126,6 +129,7 @@ class DSPCore {
double pitchSmoothingKp = 1.0;
ExpSmootherLocal<double> interpPitch;

ExpSmoother<double> externalInputGain;
ExpSmoother<double> wireDistance;
ExpSmoother<double> wireCollisionTypeMix;
ExpSmoother<double> impactWireMix;
Expand All @@ -141,11 +145,15 @@ class DSPCore {
static constexpr size_t nDrum = 2;
static constexpr size_t nAllpass = 4;

bool useExternalInput = false;
bool useAutomaticTrigger = false;
TriggerDetector<double> triggerDetector;

std::minstd_rand noiseRng{0};
std::minstd_rand paramRng{0};
double noiseGain = 0;
double noiseDecay = 0;
std::array<ComplexLowpass<double>, nDrum> noiseLowpass;
ComplexLowpass<double> noiseLowpass;
std::array<SerialAllpass<double, nAllpass>, nDrum> noiseAllpass;

bool preventBlowUp = false;
Expand Down
30 changes: 27 additions & 3 deletions GenericDrum/source/dsp/filter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "../../../common/dsp/smoother.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <limits>
#include <numbers>
Expand All @@ -29,6 +30,29 @@

namespace SomeDSP {

template<typename Sample> class TriggerDetector {
private:
Sample v0 = 0;
Sample decay = 0;
Sample threshold = 0;

public:
void setup(Sample decayTimeSamples)
{
decay = std::pow(Sample(1e-3), Sample(1) / decayTimeSamples);
}

void reset() { v0 = 0; }
void prepare(Sample newThreshold) { threshold = newThreshold; }

bool process(Sample absed)
{
const auto v1 = v0;
v0 = v0 < absed ? absed : v0 * decay;
return v0 >= threshold && v1 < threshold;
}
};

// Normalize gain for `ComplexLowpass`.
// `x` is normalzied cutoff in [0, 0.5).
template<typename Sample> inline Sample approxNormalizeGain(Sample x)
Expand Down Expand Up @@ -329,7 +353,7 @@ template<typename Sample> class EnergyStoreDecay {
{
const auto absed = std::abs(value);
if (absed > eps) sum = (sum + value) * decay;
if (preventBlowUp) sum = std::min(Sample(1) / Sample(4), sum);
if (preventBlowUp) sum = std::min(Sample(1) / Sample(8), sum);
return sum *= gain;
}
};
Expand All @@ -344,8 +368,8 @@ template<typename Sample, typename Rng> class EnergyStoreNoise {
Sample process(Sample value, bool preventBlowUp, Rng &rng)
{
sum += std::abs(value);
const auto range = preventBlowUp ? std::min(Sample(1) / Sample(64), sum) : sum;
std::uniform_real_distribution<Sample> dist{Sample(-range), Sample(range)};
if (preventBlowUp) sum = std::min(Sample(1) / Sample(4), sum);
std::uniform_real_distribution<Sample> dist{Sample(-sum), Sample(sum)};
const auto out = dist(rng);
sum -= std::abs(out);
return out;
Expand Down
Loading

0 comments on commit e41acce

Please sign in to comment.