This commit is contained in:
Armin 2026-07-13 20:57:38 +02:00
commit dca008e860
21 changed files with 3746 additions and 0 deletions

47
Source/DSP/Compressor.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include <cmath>
#include <algorithm>
class Compressor {
public:
void prepare(double sr) {
sampleRate = sr;
envelope = 0.0f;
}
void setParameters(float thresholdDb, float ratio, float attackMs, float releaseMs,
float makeupDb, float mix) {
threshold = std::pow(10.0f, thresholdDb / 20.0f);
compRatio = ratio;
attackCoeff = std::exp(-1.0f / (attackMs * 0.001f * static_cast<float>(sampleRate)));
releaseCoeff = std::exp(-1.0f / (releaseMs * 0.001f * static_cast<float>(sampleRate)));
makeupGain = std::pow(10.0f, makeupDb / 20.0f);
dryWet = mix;
}
float process(float input) {
float absIn = std::abs(input);
float coeff = (absIn > envelope) ? attackCoeff : releaseCoeff;
envelope = coeff * envelope + (1.0f - coeff) * absIn;
float gain = 1.0f;
if (envelope > threshold && compRatio > 1.0f) {
float overDb = 20.0f * std::log10(envelope / threshold);
float compressedDb = overDb / compRatio;
gain = std::pow(10.0f, (compressedDb - overDb) / 20.0f);
}
float wet = input * gain * makeupGain;
return input + (wet - input) * dryWet;
}
private:
double sampleRate = 44100.0;
float threshold = 0.1f;
float compRatio = 4.0f;
float attackCoeff = 0.0f;
float releaseCoeff = 0.0f;
float makeupGain = 1.0f;
float dryWet = 1.0f;
float envelope = 0.0f;
};

76
Source/DSP/Delay.h Normal file
View file

@ -0,0 +1,76 @@
#pragma once
#include <vector>
#include <algorithm>
#include <cmath>
class Delay {
public:
void prepare(double sr) {
sampleRate = sr;
int maxSamples = static_cast<int>(sr * 2.0);
bufferL.resize(maxSamples, 0.0f);
bufferR.resize(maxSamples, 0.0f);
writePos = 0;
}
void setParameters(float timeMs, float fb, float mx, bool pp, float spr) {
timeSamples = static_cast<int>(timeMs * 0.001f * sampleRate);
int bufMax = static_cast<int>(bufferL.size()) - 1;
timeSamples = std::clamp(timeSamples, 1, bufMax);
feedback = std::clamp(fb, 0.0f, 0.95f);
mix = std::clamp(mx, 0.0f, 1.0f);
pingpong = pp;
spread = std::clamp(spr, 0.25f, 0.75f);
}
void process(float& left, float& right) {
int bufSize = static_cast<int>(bufferL.size());
if (pingpong) {
int timeR = std::max(1, static_cast<int>(timeSamples * spread));
int readL = (writePos - timeSamples + bufSize) % bufSize;
int readR = (writePos - timeR + bufSize) % bufSize;
float outL = bufferR[readR];
float outR = bufferL[readL];
bufferL[writePos] = left + outR * feedback;
bufferR[writePos] = right + outL * feedback;
writePos = (writePos + 1) % bufSize;
left = left * (1.0f - mix) + outL * mix;
right = right * (1.0f - mix) + outR * mix;
} else {
int readPos1 = (writePos - timeSamples + bufSize) % bufSize;
int time2 = std::max(1, static_cast<int>(timeSamples * spread));
int readPos2 = (writePos - time2 + bufSize) % bufSize;
float tap1L = bufferL[readPos1];
float tap1R = bufferR[readPos1];
float tap2L = bufferL[readPos2];
float tap2R = bufferR[readPos2];
float outL = tap1L + tap2L * 0.6f;
float outR = tap1R + tap2R * 0.6f;
bufferL[writePos] = left + outL * feedback;
bufferR[writePos] = right + outR * feedback;
writePos = (writePos + 1) % bufSize;
left = left * (1.0f - mix) + outL * mix;
right = right * (1.0f - mix) + outR * mix;
}
}
private:
std::vector<float> bufferL, bufferR;
int writePos = 0;
double sampleRate = 44100;
int timeSamples = 8820;
float feedback = 0.3f;
float mix = 0.25f;
bool pingpong = false;
float spread = 0.5f;
};

71
Source/DSP/Distortion.h Normal file
View file

@ -0,0 +1,71 @@
#pragma once
#include <cmath>
#include <algorithm>
enum class DistortionType { SoftClip = 0, HardClip, Foldback, Overdrive, NumTypes };
class Distortion {
public:
void prepare(double /*sr*/) {}
void setParameters(DistortionType type, float amount, float mix) {
distType = type;
distAmount = amount;
dryWet = mix;
}
float process(float input) {
float driven = input * (1.0f + distAmount * 9.0f);
float wet = 0.0f;
switch (distType) {
case DistortionType::SoftClip:
wet = std::tanh(driven);
break;
case DistortionType::HardClip: {
float threshold = 1.0f / (1.0f + distAmount * 9.0f);
wet = std::clamp(driven, -threshold, threshold);
break;
}
case DistortionType::Foldback: {
float threshold = 1.0f + distAmount * 3.0f;
wet = driven;
while (wet > threshold || wet < -threshold) {
if (wet > threshold)
wet = 2.0f * threshold - wet;
else if (wet < -threshold)
wet = -2.0f * threshold - wet;
else
break;
}
if (threshold > 0.0f)
wet /= threshold;
break;
}
case DistortionType::Overdrive: {
float t = driven;
if (t > 1.0f) t = 1.0f;
else if (t < -1.0f) t = -1.0f;
wet = t * (1.5f - 0.5f * t * t);
break;
}
default:
wet = driven;
break;
}
if (distAmount < 0.001f)
return input;
return input + (wet - input) * dryWet;
}
private:
DistortionType distType = DistortionType::SoftClip;
float distAmount = 0.0f;
float dryWet = 0.0f;
};

87
Source/DSP/Envelope.h Normal file
View file

@ -0,0 +1,87 @@
#pragma once
#include <cmath>
#include <algorithm>
enum class ADSRStage { Idle = 0, Attack, Decay, Sustain, Release };
class ADSREnvelope {
public:
void prepare(double sr) {
sampleRate = sr;
stage = ADSRStage::Idle;
level = 0.0f;
}
void setAttack(float a) { attackTime = std::max(a, 0.001f); }
void setDecay(float d) { decayTime = std::max(d, 0.001f); }
void setSustain(float s) { sustainLevel = std::clamp(s, 0.0f, 1.0f); }
void setRelease(float r) { releaseTime = std::max(r, 0.001f); }
void noteOn() {
stage = ADSRStage::Attack;
level = 0.0f;
}
void noteOff() {
if (stage != ADSRStage::Idle)
stage = ADSRStage::Release;
}
bool isActive() const { return stage != ADSRStage::Idle; }
float getNextSample() {
switch (stage) {
case ADSRStage::Idle:
level = 0.0f;
break;
case ADSRStage::Attack: {
float attackInc = 1.0f / (attackTime * static_cast<float>(sampleRate));
level += attackInc;
if (level >= 1.0f) {
level = 1.0f;
stage = ADSRStage::Decay;
}
break;
}
case ADSRStage::Decay: {
float decayInc = (1.0f - sustainLevel) / (decayTime * static_cast<float>(sampleRate));
level -= decayInc;
if (level <= sustainLevel) {
level = sustainLevel;
stage = ADSRStage::Sustain;
}
break;
}
case ADSRStage::Sustain:
level = sustainLevel;
break;
case ADSRStage::Release: {
float releaseInc = level / (releaseTime * static_cast<float>(sampleRate));
level -= releaseInc;
if (level <= 0.0f) {
level = 0.0f;
stage = ADSRStage::Idle;
}
break;
}
}
return level;
}
float getCurrentLevel() const { return level; }
ADSRStage getCurrentStage() const { return stage; }
private:
double sampleRate = 44100.0;
float attackTime = 0.01f;
float decayTime = 0.3f;
float sustainLevel = 0.7f;
float releaseTime = 0.5f;
float level = 0.0f;
ADSRStage stage = ADSRStage::Idle;
};

110
Source/DSP/Filter.h Normal file
View file

@ -0,0 +1,110 @@
#pragma once
#include <cmath>
#include <algorithm>
enum class FilterType { LowPass12 = 0, LowPass24, BandPass, HighPass, Notch, NumTypes };
class Filter {
public:
void prepare(double sr) {
sampleRate = sr;
reset();
}
void reset() {
for (int i = 0; i < 4; ++i) {
stage[i] = 0.0f;
}
}
void setCoefficients(float cutoff, float res, FilterType type) {
cutoff = std::clamp(cutoff, 20.0f, static_cast<float>(sampleRate * 0.49));
resonance = std::clamp(res, 0.0f, 1.0f);
filterType = type;
float g = std::tan(static_cast<float>(pi) * cutoff / static_cast<float>(sampleRate));
float k = 2.0f - 2.0f * resonance;
float a1 = 1.0f / (1.0f + g * (g + k));
float a2 = g * a1;
gCoeff = g;
kCoeff = k;
m0 = 0.0f;
m1 = 0.0f;
m2 = 0.0f;
m3 = 0.0f;
switch (filterType) {
case FilterType::LowPass12:
m0 = 0.0f; m1 = 0.0f; m2 = a2; m3 = 0.0f;
break;
case FilterType::LowPass24:
m0 = 0.0f; m1 = 0.0f; m2 = 0.0f; m3 = a2 * a2;
break;
case FilterType::BandPass:
m0 = 0.0f; m1 = a2; m2 = 0.0f; m3 = 0.0f;
break;
case FilterType::HighPass:
m0 = 1.0f; m1 = -a1; m2 = -a2; m3 = 0.0f;
break;
case FilterType::Notch:
m0 = 1.0f; m1 = -a1 * k; m2 = -a2; m3 = 0.0f;
break;
case FilterType::NumTypes:
break;
}
}
float process(float input) {
float denom = 1.0f + kCoeff * gCoeff + gCoeff * gCoeff;
float hp = (input - (kCoeff + gCoeff) * stage[0] - stage[1]) / denom;
float bp = gCoeff * hp + stage[0];
float lp = gCoeff * bp + stage[1];
stage[0] = gCoeff * hp + bp;
stage[1] = gCoeff * bp + lp;
float hp2 = (lp - (kCoeff + gCoeff) * stage[2] - stage[3]) / denom;
float bp2 = gCoeff * hp2 + stage[2];
float lp2 = gCoeff * bp2 + stage[3];
stage[2] = gCoeff * hp2 + bp2;
stage[3] = gCoeff * bp2 + lp2;
float output = 0.0f;
switch (filterType) {
case FilterType::LowPass12:
output = lp;
break;
case FilterType::LowPass24:
output = lp2;
break;
case FilterType::BandPass:
output = bp;
break;
case FilterType::HighPass:
output = hp;
break;
case FilterType::Notch:
output = input - bp;
break;
case FilterType::NumTypes:
output = input;
break;
}
return output;
}
private:
static constexpr float pi = 3.14159265358979323846f;
double sampleRate = 44100.0;
float stage[4] = {0.0f, 0.0f, 0.0f, 0.0f};
float gCoeff = 0.0f, kCoeff = 0.0f;
float m0 = 0.0f, m1 = 0.0f, m2 = 0.0f, m3 = 0.0f;
float resonance = 0.0f;
FilterType filterType = FilterType::LowPass12;
};

54
Source/DSP/LFO.h Normal file
View file

@ -0,0 +1,54 @@
#pragma once
#include <cmath>
enum class LFODest { FilterCutoff = 0, OSC1Freq, OSC2Freq, BothOsc };
enum class LFOShape { Sine = 0, Triangle, Saw, Square };
class LFO {
public:
void prepare(double sr) { sampleRate = sr; }
void setParameters(float rate, float depth, LFOShape s, LFODest d) {
lfoRate = rate;
lfoDepth = depth;
shape = s;
dest = d;
}
float getNextSample() {
float twoPi = 6.2831853f;
phase += lfoRate / static_cast<float>(sampleRate);
if (phase >= 1.0f) phase -= 1.0f;
float out = 0.0f;
float p = phase;
switch (shape) {
case LFOShape::Sine:
out = std::sin(p * twoPi);
break;
case LFOShape::Triangle:
out = 2.0f * std::abs(2.0f * (p - 0.5f)) - 1.0f;
break;
case LFOShape::Saw:
out = 2.0f * p - 1.0f;
break;
case LFOShape::Square:
out = p < 0.5f ? 1.0f : -1.0f;
break;
}
return out * lfoDepth;
}
LFODest getDest() const { return dest; }
void reset() { phase = 0.0f; }
private:
double sampleRate = 44100.0;
float phase = 0.0f;
float lfoRate = 2.0f;
float lfoDepth = 0.0f;
LFOShape shape = LFOShape::Sine;
LFODest dest = LFODest::FilterCutoff;
};

80
Source/DSP/Oscillator.h Normal file
View file

@ -0,0 +1,80 @@
#pragma once
#include <cmath>
#include <algorithm>
enum class Waveform { Sine = 0, Saw, Square, Triangle, Noise, NumWaveforms };
class Oscillator {
public:
void prepare(double sampleRate) {
this->sampleRate = sampleRate;
phase = 0.0;
phaseIncrement = 0.0;
}
void setFrequency(float freq) {
frequency = freq;
if (sampleRate > 0.0)
phaseIncrement = frequency / static_cast<float>(sampleRate);
}
void setWaveform(Waveform wf) { waveform = wf; }
void setLevel(float l) { level = std::clamp(l, 0.0f, 1.0f); }
void setPan(float p) { pan = std::clamp(p, -1.0f, 1.0f); }
void setSemitone(float s) { semitoneOffset = s; }
void setFineTune(float f) { fineTune = f; }
void setPhase(float p) { phase = p; }
float getFrequency() const { return frequency; }
void process(float* leftOut, float* rightOut) {
float output = 0.0f;
switch (waveform) {
case Waveform::Sine:
output = std::sin(phase * 2.0f * pi);
break;
case Waveform::Saw:
output = 2.0f * (phase - 0.5f);
break;
case Waveform::Square:
output = (phase < 0.5f) ? 1.0f : -1.0f;
break;
case Waveform::Triangle:
output = 4.0f * std::abs(phase - 0.5f) - 1.0f;
break;
case Waveform::Noise:
output = random.nextFloat() * 2.0f - 1.0f;
break;
default:
output = 0.0f;
break;
}
phase += phaseIncrement;
if (phase >= 1.0f) phase -= 1.0f;
output *= level;
float leftGain = std::sqrt((1.0f - pan) * 0.5f);
float rightGain = std::sqrt((1.0f + pan) * 0.5f);
*leftOut += output * leftGain;
*rightOut += output * rightGain;
}
float getCurrentPhase() const { return phase; }
private:
static constexpr float pi = 3.14159265358979323846f;
float frequency = 440.0f;
float phase = 0.0f;
float phaseIncrement = 0.0f;
float level = 0.7f;
float pan = 0.0f;
float semitoneOffset = 0.0f;
float fineTune = 0.0f;
double sampleRate = 44100.0;
Waveform waveform = Waveform::Saw;
juce::Random random;
};

25
Source/DSP/Reverb.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <juce_audio_basics/juce_audio_basics.h>
class ReverbFX {
public:
void prepare(double /*sr*/) {}
void setParameters(float size, float damping, float width, float mix) {
juce::Reverb::Parameters params;
params.roomSize = size;
params.damping = damping;
params.width = width;
params.wetLevel = mix;
params.dryLevel = 1.0f - mix * 0.5f;
params.freezeMode = 0.0f;
reverb.setParameters(params);
}
void process(float* left, float* right, int numSamples) {
reverb.processStereo(left, right, numSamples);
}
private:
juce::Reverb reverb;
};

231
Source/DSP/Voice.h Normal file
View file

@ -0,0 +1,231 @@
#pragma once
#include "Oscillator.h"
#include "Filter.h"
#include "Envelope.h"
#include "LFO.h"
#include <cmath>
#include <algorithm>
struct SubtractiveSound : public juce::SynthesiserSound {
bool appliesToNote(int /*midiNoteNumber*/) override { return true; }
bool appliesToChannel(int /*midiChannel*/) override { return true; }
};
class SubtractiveVoice : public juce::SynthesiserVoice {
public:
SubtractiveVoice() = default;
bool canPlaySound(juce::SynthesiserSound* sound) override {
return dynamic_cast<SubtractiveSound*>(sound) != nullptr;
}
void startNote(int midiNoteNumber, float velocity,
juce::SynthesiserSound* sound, int currentPitchWheelPosition) override {
currentMidiNote = midiNoteNumber;
noteVelocity = velocity;
double sr = getSampleRate();
if (sr <= 0.0) sr = 44100.0;
osc1.prepare(sr);
osc2.prepare(sr);
env.prepare(sr);
filterEnv.prepare(sr);
filter.prepare(sr);
filterR.prepare(sr);
lfo1.prepare(sr);
lfo2.prepare(sr);
lfo1.reset();
lfo2.reset();
baseFreq1 = 440.0f * std::pow(2.0f, (static_cast<float>(midiNoteNumber) - 69.0f) / 12.0f)
* std::pow(2.0f, semitone1 / 12.0f + fineTune1 / 1200.0f) * std::pow(2.0f, octave1);
baseFreq2 = 440.0f * std::pow(2.0f, (static_cast<float>(midiNoteNumber) - 69.0f) / 12.0f)
* std::pow(2.0f, semitone2 / 12.0f + fineTune2 / 1200.0f) * std::pow(2.0f, octave2);
osc1.setFrequency(baseFreq1);
osc2.setFrequency(baseFreq2);
osc1.setWaveform(waveform1);
osc2.setWaveform(waveform2);
osc1.setPan(pan);
osc2.setPan(pan);
osc1.setPhase(0.0f);
osc2.setPhase(phaseOffset2);
filter.setCoefficients(filterCutoff, filterResonance, filterType);
filterR.setCoefficients(filterCutoff, filterResonance, filterType);
env.noteOn();
filterEnv.noteOn();
}
void stopNote(float velocity, bool allowTailOff) override {
env.noteOff();
filterEnv.noteOff();
if (!allowTailOff || !env.isActive())
clearCurrentNote();
}
void renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
int startSample, int numSamples) override {
if (!isVoiceActive()) return;
for (int i = startSample; i < startSample + numSamples; ++i) {
float envSample = env.getNextSample();
float fEnvSample = filterEnv.getNextSample();
if (!env.isActive()) {
clearCurrentNote();
return;
}
// LFO outputs
float lfo1Out = lfo1.getNextSample();
float lfo2Out = lfo2.getNextSample();
// Apply pitch bend + LFO to oscillator frequencies
float pitchBendMult = std::pow(2.0f, pitchBendSemitones / 12.0f);
float freq1 = baseFreq1 * pitchBendMult;
float freq2 = baseFreq2 * pitchBendMult;
auto applyPitchLfo = [&](float lfoOut, LFODest dest) {
float semitoneShift = lfoOut * 12.0f; // +/- 12 semitones range
float pitchMult = std::pow(2.0f, semitoneShift / 12.0f);
if (dest == LFODest::OSC1Freq || dest == LFODest::BothOsc)
freq1 *= pitchMult;
if (dest == LFODest::OSC2Freq || dest == LFODest::BothOsc)
freq2 *= pitchMult;
};
if (lfo1Depth > 0.001f)
applyPitchLfo(lfo1Out, lfo1Dest);
if (lfo2Depth > 0.001f)
applyPitchLfo(lfo2Out, lfo2Dest);
osc1.setFrequency(freq1);
osc2.setFrequency(freq2);
float left = 0.0f, right = 0.0f;
osc1.process(&left, &right);
osc2.process(&left, &right);
left *= noteVelocity * envSample;
right *= noteVelocity * envSample;
// Filter cutoff with env + keytrack
float modCutoff = filterCutoff * std::pow(2.0f, fEnvSample * filterEnvAmount * 4.0f);
float keyTrackFactor = std::pow(2.0f, (static_cast<float>(currentMidiNote) - 60.0f) / 12.0f * keyTrack);
modCutoff *= keyTrackFactor;
// Apply LFO to filter cutoff
if (lfo1Depth > 0.001f && lfo1Dest == LFODest::FilterCutoff)
modCutoff *= std::pow(2.0f, lfo1Out * 4.0f);
if (lfo2Depth > 0.001f && lfo2Dest == LFODest::FilterCutoff)
modCutoff *= std::pow(2.0f, lfo2Out * 4.0f);
modCutoff = std::clamp(modCutoff, 25.0f, 1200.0f);
filter.setCoefficients(modCutoff, filterResonance, filterType);
filterR.setCoefficients(modCutoff, filterResonance, filterType);
left = filter.process(left);
right = filterR.process(right);
left = std::tanh(left * drive) / std::tanh(drive);
right = std::tanh(right * drive) / std::tanh(drive);
outputBuffer.addSample(0, i, left * outputLevel);
outputBuffer.addSample(1, i, right * outputLevel);
}
}
void pitchWheelMoved(int newPitchWheelValue) override {}
void controllerMoved(int controllerNumber, int newControllerValue) override {}
void setParameters(Waveform wf1, Waveform wf2,
float oct1, float oct2,
float semi1, float semi2,
float fine1, float fine2,
float lvl1, float lvl2,
float phOff2,
float cut, float res, FilterType ft,
float fEnvAmt, float kTrack,
float att, float dec, float sus, float rel,
float fAtt, float fDec, float fSus, float fRel,
float p, float drv, float outLvl,
float l1Rate, float l1Depth, int l1Shape, int l1Dest,
float l2Rate, float l2Depth, int l2Shape, int l2Dest) {
waveform1 = wf1;
waveform2 = wf2;
octave1 = oct1;
octave2 = oct2;
semitone1 = semi1;
semitone2 = semi2;
fineTune1 = fine1;
fineTune2 = fine2;
osc1.setLevel(lvl1);
osc2.setLevel(lvl2);
phaseOffset2 = phOff2;
filterCutoff = cut;
filterResonance = res;
filterType = ft;
filterEnvAmount = fEnvAmt;
keyTrack = kTrack;
pan = p;
drive = std::max(drv, 1.001f);
outputLevel = outLvl;
lfo1Depth = l1Depth;
lfo1Dest = static_cast<LFODest>(l1Dest);
lfo1.setParameters(l1Rate, l1Depth, static_cast<LFOShape>(l1Shape), lfo1Dest);
lfo2Depth = l2Depth;
lfo2Dest = static_cast<LFODest>(l2Dest);
lfo2.setParameters(l2Rate, l2Depth, static_cast<LFOShape>(l2Shape), lfo2Dest);
env.setAttack(att);
env.setDecay(dec);
env.setSustain(sus);
env.setRelease(rel);
filterEnv.setAttack(fAtt);
filterEnv.setDecay(fDec);
filterEnv.setSustain(fSus);
filterEnv.setRelease(fRel);
}
void setPitchBend(float semitones) { pitchBendSemitones = semitones; }
private:
Oscillator osc1, osc2;
Filter filter;
Filter filterR;
ADSREnvelope env;
ADSREnvelope filterEnv;
LFO lfo1, lfo2;
Waveform waveform1 = Waveform::Saw;
Waveform waveform2 = Waveform::Square;
float octave1 = 0.0f, octave2 = -1.0f;
float semitone1 = 0.0f, semitone2 = 0.0f;
float fineTune1 = 0.0f, fineTune2 = 7.0f;
float phaseOffset2 = 0.5f;
float filterCutoff = 800.0f;
float filterResonance = 0.7f;
FilterType filterType = FilterType::LowPass12;
float filterEnvAmount = 0.0f;
float keyTrack = 0.5f;
float pan = 0.0f;
float drive = 1.5f;
float outputLevel = 0.8f;
float baseFreq1 = 440.0f;
float baseFreq2 = 440.0f;
float lfo1Depth = 0.0f;
float lfo2Depth = 0.0f;
LFODest lfo1Dest = LFODest::FilterCutoff;
LFODest lfo2Dest = LFODest::FilterCutoff;
float pitchBendSemitones = 0.0f;
int currentMidiNote = 60;
float noteVelocity = 1.0f;
};