Skip to content

Commit

Permalink
Update GlitchSprinkler
Browse files Browse the repository at this point in the history
  • Loading branch information
ryukau committed Jul 31, 2024
1 parent 40bfc76 commit 40ca237
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 66 deletions.
73 changes: 64 additions & 9 deletions GlitchSprinkler/source/dsp/dspcore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ void DSPCore::setup(double sampleRate_)

terminationLength = uint_fast32_t(double(0.002) * sampleRate);
lowpassInterpRate = sampleRate / double(48000 * 64);
softAttackEmaRatio = EMAFilter<double>::cutoffToP(double(50) / sampleRate);

for (auto &x : safetyFilter) x.setup(sampleRate);

reset();
startup();
Expand Down Expand Up @@ -67,13 +70,15 @@ void DSPCore::setup(double sampleRate_)
\
const auto samplesPerBeat = (double(60) * sampleRate) / tempo; \
const auto arpeggioNotesPerBeat = pv[ID::arpeggioNotesPerBeat]->getInt() + 1; \
arpeggioDuration = int_fast32_t(samplesPerBeat / double(arpeggioNotesPerBeat)); \
arpeggioNoteDuration = int_fast32_t(samplesPerBeat / double(arpeggioNotesPerBeat)); \
arpeggioLoopLength \
= (pv[ID::arpeggioLoopLengthInBeat]->getInt()) * arpeggioNotesPerBeat; \
if (arpeggioLoopLength == 0) { \
arpeggioLoopLength = std::numeric_limits<decltype(arpeggioLoopLength)>::max(); \
} \
\
pwmRatio = double(1) - pv[ID::pulseWidthRatio]->getDouble(); \
safetyFilterMix.METHOD(pv[ID::safetyFilterMix]->getDouble()); \
outputGain.METHOD(pv[ID::outputGain]->getDouble()); \
\
for (size_t idx = 0; idx < nPolyOscControl; ++idx) { \
Expand Down Expand Up @@ -109,13 +114,19 @@ void Voice::reset()
terminationCounter = 0;

scheduleUpdateNote = false;
pwmLower = 0;
pwmPoint = 0;
pwmBitMask = 0;
pwmDirection = 1;
phasePeriod = 0;
phaseCounter = 0;
oscSync = double(1);
fmIndex = double(0);
saturationGain = double(1);
decayRatio = double(1);
decayGain = double(0);
decayEmaRatio = double(1);
decayEmaValue = double(0);
polynomialCoefficients.fill({});

filterDecayRatio = double(1);
Expand Down Expand Up @@ -144,6 +155,8 @@ void DSPCore::reset()
nextSteal = 0;
for (auto &x : voices) x.reset();

for (auto &x : safetyFilter) x.reset();

startup();
}

Expand Down Expand Up @@ -171,7 +184,7 @@ std::array<double, 2> Voice::processFrame()

// Arpeggio.
if (pv[ID::arpeggioSwitch]->getInt()) {
if (arpeggioTimer < arpeggioTie * core.arpeggioDuration) {
if (arpeggioTimer < arpeggioTie * core.arpeggioNoteDuration) {
++arpeggioTimer;
} else if (state == State::active) {
scheduleUpdateNote = true;
Expand Down Expand Up @@ -201,11 +214,12 @@ std::array<double, 2> Voice::processFrame()

// Oscillator.
auto oscFunc = [&](double phase) {
if (phaseCounter > (pwmPoint | ~pwmBitMask)) return double(0);
return computePolynomial<double, PolySolver::nPolynomialPoint>(
phase, polynomialCoefficients);
};

const auto phase = oscSync * double(phaseCounter) / double(phasePeriod);
const auto phase = oscSync * double(phaseCounter & pwmBitMask) / double(phasePeriod);
auto sig = oscFunc(phase);

// FM.
Expand All @@ -227,13 +241,30 @@ std::array<double, 2> Voice::processFrame()

// Envelope.
decayGain *= decayRatio;
decayEmaValue += decayEmaRatio * (decayGain - decayEmaValue);
const auto terminationDecay = double(core.terminationLength - terminationCounter)
/ double(core.terminationLength);
lastGain = decayGain * noteVelocity * terminationDecay;
lastGain = decayEmaValue * noteVelocity * terminationDecay;
sig *= lastGain;

if (++phaseCounter >= phasePeriod) {
phaseCounter = 0;

if (pv[ID::pulseWidthModulation]->getInt()) {
pwmPoint = std::min(pwmPoint + pwmDirection, phasePeriod);
if (pwmPoint <= pwmLower) {
pwmDirection = 1;
} else if (pwmPoint == phasePeriod) {
pwmDirection = -1;
}
} else {
pwmPoint = uint_fast32_t(phasePeriod * core.pwmRatio);
}

pwmBitMask = pv[ID::pulseWidthBitwiseAnd]->getInt()
? pwmPoint
: std::numeric_limits<uint_fast32_t>::max();

if (scheduleUpdateNote) {
scheduleUpdateNote = false;
if (state == State::active) updateNote();
Expand Down Expand Up @@ -277,6 +308,10 @@ void DSPCore::process(const size_t length, float *out0, float *out1)
frame[1] += voiceOut[1];
}

const auto safetyFiltMix = safetyFilterMix.process();
frame[0] = std::lerp(frame[0], safetyFilter[0].process(frame[0]), safetyFiltMix);
frame[1] = std::lerp(frame[1], safetyFilter[1].process(frame[1]), safetyFiltMix);

const auto outGain = outputGain.process();
out0[i] = float(outGain * frame[0]);
out1[i] = float(outGain * frame[1]);
Expand Down Expand Up @@ -334,11 +369,15 @@ void DSPCore::noteOn(NoteInfo &info)
if (vc.state == Voice::State::rest) {
vc.arpeggioTimer = 0;
vc.arpeggioLoopCounter = 0;
vc.decayEmaRatio
= pv[ID::decaySoftAttack]->getInt() ? softAttackEmaRatio : double(1);
vc.decayEmaValue = 0;
vc.lowpass.reset();
vc.updateNote();
} else if (!param.value[ParameterID::arpeggioSwitch]->getInt()) {
vc.scheduleUpdateNote = true;
}

vc.terminationCounter = 0;
vc.state = Voice::State::active;
}
Expand Down Expand Up @@ -475,8 +514,8 @@ void Voice::updateNote()
return core.pitchModifier * transposeRatio * double(440);
};
auto getDiscreteFreq = [&]() {
auto semitones = transposeSemitone + double(noteNumber - 127) / double(12);
auto cents = (transposeCent + noteCent) / double(1200);
auto semitones = double(transposeSemitone + noteNumber - 127) / double(12);
auto cents = (transposeCent + noteCent + unisonDetuneCent) / double(1200);
auto transposeRatio = std::exp2(transposeOctave + semitones + cents);
auto freqHz = core.pitchModifier * transposeRatio * core.sampleRate;
switch (tuning) {
Expand Down Expand Up @@ -504,7 +543,8 @@ void Voice::updateNote()
auto arpRatio
= std::exp2(double(octaveDist(rngArpeggio)) + centDist(rngArpeggio) / double(1200));

if (arpeggioTimer != 0 || arpeggioLoopCounter != 0) {
const auto arpeggioStartFromRoot = bool(pv[ID::arpeggioStartFromRoot]->getInt());
if (!(arpeggioStartFromRoot && arpeggioTimer == 0 && arpeggioLoopCounter == 0)) {
const auto arpeggioScale = pv[ID::arpeggioScale]->getInt();
if (arpeggioScale == PitchScale::et5Chromatic) {
arpRatio *= getRandomPitchFixed(rngArpeggio, scaleEt5, tuningRatioEt5);
Expand Down Expand Up @@ -577,6 +617,19 @@ void Voice::updateNote()
}
polynomialCoefficients = core.polynomial.coefficients;

pwmLower = uint_fast32_t(phasePeriod * core.pwmRatio);
if (
state == State::rest || core.pwmRatio >= 1
|| pv[ID::arpeggioResetModulation]->getInt())
{
pwmPoint = pv[ID::pulseWidthModulation]->getInt() ? phasePeriod : pwmLower;
pwmDirection = 1;

pwmBitMask = pv[ID::pulseWidthBitwiseAnd]->getInt()
? pwmPoint
: std::numeric_limits<uint_fast32_t>::max();
}

oscSync = pv[ID::oscSync]->getDouble();

const auto rndFmIndexOct = pv[ID::randomizeFmIndex]->getDouble();
Expand All @@ -587,9 +640,11 @@ void Voice::updateNote()
// Envelope.
const auto decayTargetGain = pv[ID::decayTargetGain]->getDouble();
decayRatio
= std::pow(decayTargetGain, double(1) / (arpeggioTie * core.arpeggioDuration));
= std::pow(decayTargetGain, double(1) / (arpeggioTie * core.arpeggioNoteDuration));

const auto restChance = pv[ID::arpeggioRestChance]->getDouble();
const auto restChance = arpeggioTimer == 0 && arpeggioLoopCounter == 0
? double(0)
: pv[ID::arpeggioRestChance]->getDouble();
std::uniform_real_distribution<double> restDist{double(0), double(1)};
decayGain = useArpeggiator && restDist(rngArpeggio) < restChance || freqHz == 0
? double(0)
Expand Down
13 changes: 12 additions & 1 deletion GlitchSprinkler/source/dsp/dspcore.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ class Voice {
uint_fast32_t terminationCounter = 0;

bool scheduleUpdateNote = false;
uint_fast32_t pwmLower = 0;
uint_fast32_t pwmPoint = 0;
uint_fast32_t pwmBitMask = 0;
int_fast32_t pwmDirection = 1;
uint_fast32_t phasePeriod = 0;
uint_fast32_t phaseCounter = 0;
double oscSync = double(1);
double fmIndex = double(0);
double saturationGain = double(1);
double decayRatio = double(1);
double decayGain = double(0);
double decayEmaRatio = double(1);
double decayEmaValue = double(0);
std::array<double, PolySolver::nPolynomialPoint> polynomialCoefficients{};

double filterDecayRatio = double(1);
Expand Down Expand Up @@ -115,12 +121,15 @@ class DSPCore {
bool isPolyphonic = true;
Voice::State noteOffState = Voice::State::terminate;

uint_fast32_t arpeggioDuration = std::numeric_limits<uint_fast32_t>::max();
uint_fast32_t arpeggioNoteDuration = std::numeric_limits<uint_fast32_t>::max();
uint_fast32_t arpeggioLoopLength = std::numeric_limits<uint_fast32_t>::max();
uint_fast32_t terminationLength = 0;

double lowpassInterpRate = double(1) / double(64);
double softAttackEmaRatio = double(1);
double pwmRatio = double(1);
DecibelScale<double> velocityMap{-60, 0, true};
ExpSmoother<double> safetyFilterMix;
ExpSmoother<double> outputGain;

bool isPolynomialUpdated = false;
Expand All @@ -137,6 +146,8 @@ class DSPCore {
unsigned nextSteal = 0;
std::vector<Voice> voices;

std::array<SafetyFilter<double>, 2> safetyFilter;

DSPCore()
{
midiNotes.reserve(2048);
Expand Down
43 changes: 41 additions & 2 deletions GlitchSprinkler/source/dsp/polynomial.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ template<typename Sample, size_t nControlPoint> class PolynomialCoefficientSolve

template<typename Sample> class ResonantEmaLowpass1A1 {
private:
static constexpr Sample nyquist = Sample(0.4999);

Sample cutValue = Sample(1);
Sample apsValue = Sample(1);
Sample resValue = Sample(0);
Expand Down Expand Up @@ -249,13 +251,13 @@ template<typename Sample> class ResonantEmaLowpass1A1 {
cutValue += interpRate * (cutoffNormalized - cutValue);

constexpr Sample pi = std::numbers::pi_v<Sample>;
const auto freq = pi * std::clamp(cutValue, Sample(0), Sample(0.4999));
const auto freq = pi * std::clamp(cutValue, Sample(0), nyquist);

const auto s = Sample(1) - std::cos(Sample(2) * freq);
const auto c1 = std::sqrt(s * s + Sample(2) * s) - s;

apsValue += interpRate * (apScale - apsValue);
const auto t = std::tan(std::clamp(apsValue * freq, Sample(0), Sample(0.4999)));
const auto t = std::tan(std::clamp(apsValue * freq, Sample(0), nyquist));
const auto c2 = (t - 1) / (t + 1);

resValue += interpRate * (resonance - resValue);
Expand All @@ -270,4 +272,41 @@ template<typename Sample> class ResonantEmaLowpass1A1 {
}
};

// Bilinear transformed 1-pole highpass.
template<typename Sample> class SafetyFilter {
private:
Sample b = Sample(1);
Sample a1 = Sample(1);
Sample x1 = Sample(0);
Sample y1 = Sample(0);

public:
void reset()
{
x1 = 0;
y1 = 0;
}

void setup(Sample sampleRate)
{
constexpr Sample pi = std::numbers::pi_v<Sample>;
constexpr Sample minCutoff = Sample(0.00001);
constexpr Sample nyquist = Sample(0.49998);
constexpr Sample highpassCutoffHz = Sample(16);

const auto k = Sample(1)
/ std::tan(pi * std::clamp(highpassCutoffHz / sampleRate, minCutoff, nyquist));
const auto a0 = Sample(1) + k;
b = k / a0;
a1 = (Sample(1) - k) / a0;
}

Sample process(Sample input)
{
y1 = b * (input - x1) - a1 * y1;
x1 = input;
return y1;
}
};

} // namespace SomeDSP
Loading

0 comments on commit 40ca237

Please sign in to comment.