mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 04:10:47 +02:00
init
This commit is contained in:
commit
dca008e860
21 changed files with 3746 additions and 0 deletions
47
Source/DSP/Compressor.h
Normal file
47
Source/DSP/Compressor.h
Normal 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
76
Source/DSP/Delay.h
Normal 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
71
Source/DSP/Distortion.h
Normal 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
87
Source/DSP/Envelope.h
Normal 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
110
Source/DSP/Filter.h
Normal 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
54
Source/DSP/LFO.h
Normal 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
80
Source/DSP/Oscillator.h
Normal 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
25
Source/DSP/Reverb.h
Normal 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
231
Source/DSP/Voice.h
Normal 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;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue