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;
};

968
Source/PluginEditor.cpp Normal file
View file

@ -0,0 +1,968 @@
#include "PluginEditor.h"
#include <BinaryData.h>
void KnobLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle,
float rotaryEndAngle, juce::Slider& slider) {
auto bounds = juce::Rectangle<int>(x, y, width, height).toFloat();
float labelH = 20.0f;
auto dialBounds = bounds.withTrimmedTop(labelH);
auto radius = juce::jmin(dialBounds.getWidth(), dialBounds.getHeight()) / 2.0f - 4.0f;
auto centreX = dialBounds.getCentreX();
auto centreY = dialBounds.getCentreY();
auto rx = centreX - radius;
auto ry = centreY - radius;
auto rw = radius * 2.0f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
// Drop shadow
g.setColour(juce::Colour(0x40000000));
g.fillEllipse(rx + 2.0f, ry + 3.0f, rw, rw);
// Outer bevel (dark bottom-right, light top-left)
auto bevelPath = juce::Path();
bevelPath.addEllipse(rx, ry, rw, rw);
// Knob body gradient — light top-left to dark bottom-right for 3D bulge
juce::ColourGradient bodyGrad(juce::Colour(0xff404040), rx, ry,
juce::Colour(0xff1a1a1a), rx + rw, ry + rw, true);
g.setGradientFill(bodyGrad);
g.fillEllipse(rx, ry, rw, rw);
// Soft inner shadow ring (bottom-right dark edge, subtle)
g.setColour(juce::Colour(0x18000000));
auto shadowArc = juce::Path();
float innerR = radius - 2.0f;
shadowArc.addCentredArc(centreX, centreY, innerR, innerR, 0.0f,
0.5f, 2.7f, true);
g.strokePath(shadowArc, juce::PathStrokeType(1.5f));
// Outer rim
g.setColour(juce::Colour(0xff555555));
g.drawEllipse(rx, ry, rw, rw, 1.5f);
// Pointer line with glow — green (left) → yellow (mid) → red (right)
auto indicatorColour = juce::Colour::fromHSV(0.33f * (1.0f - sliderPos), 0.85f, 0.9f, 1.0f);
auto glowColour = juce::Colour::fromHSV(0.33f * (1.0f - sliderPos), 0.85f, 0.9f, 0.4f);
g.setColour(glowColour);
juce::Path pointerGlow;
pointerGlow.addRoundedRectangle(-2.5f, -radius + 9, 5.0f, radius * 0.5f, 2.0f);
g.fillPath(pointerGlow, juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(indicatorColour);
juce::Path pointer;
pointer.addRoundedRectangle(-1.5f, -radius + 9, 3.0f, radius * 0.5f, 1.5f);
g.fillPath(pointer, juce::AffineTransform::rotation(angle).translated(centreX, centreY));
// Label above knob
auto name = slider.getName();
if (name.isNotEmpty()) {
g.setColour(juce::Colour(0xff999999));
g.setFont(juce::Font(13.0f).boldened());
g.drawText(name, bounds.getX(), bounds.getY(), bounds.getWidth(), labelH,
juce::Justification::centredBottom);
}
}
void ComboBoxLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height,
bool isButtonDown, int /*buttonX*/, int /*buttonY*/,
int /*buttonW*/, int /*buttonH*/, juce::ComboBox& /*box*/) {
auto bounds = juce::Rectangle<int>(0, 0, width, height).toFloat();
// Drop shadow
g.setColour(juce::Colour(0x40000000));
g.fillRoundedRectangle(bounds.getX() + 1.5f, bounds.getY() + 2.0f,
bounds.getWidth(), bounds.getHeight(), 4.0f);
// Body gradient — light top to dark bottom for 3D raised look
juce::ColourGradient bodyGrad(juce::Colour(0xff404040), bounds.getCentreX(), bounds.getY(),
juce::Colour(0xff1a1a1a), bounds.getCentreX(), bounds.getBottom(), true);
g.setGradientFill(bodyGrad);
g.fillRoundedRectangle(bounds, 4.0f);
// Top highlight edge
g.setColour(juce::Colour(0x30ffffff));
g.drawRoundedRectangle(bounds.getX() + 0.5f, bounds.getY() + 0.5f,
bounds.getWidth() - 1.0f, bounds.getHeight() - 1.0f, 4.0f, 1.0f);
// Bottom shadow edge
g.setColour(juce::Colour(0x30000000));
g.drawLine(bounds.getX() + 4.0f, bounds.getBottom() - 0.5f,
bounds.getRight() - 4.0f, bounds.getBottom() - 0.5f, 1.0f);
// Outline
g.setColour(juce::Colour(0xff555555));
g.drawRoundedRectangle(bounds, 4.0f, 1.0f);
// Arrow (gold triangle on right)
float arrowSize = 6.0f;
float arrowX = bounds.getRight() - 16.0f;
float arrowY = bounds.getCentreY();
juce::Path arrow;
arrow.addTriangle(arrowX, arrowY - arrowSize / 2,
arrowX + arrowSize, arrowY,
arrowX, arrowY + arrowSize / 2);
g.setColour(juce::Colour(0xffccaa44));
g.fillPath(arrow);
}
void ComboBoxLookAndFeel::drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle<int>& area,
bool isSeparator, bool /*isActive*/,
bool isHighlighted, bool /*isTicked*/,
bool /*hasSubMenu*/, const juce::String& text,
const juce::String& /*shortcutKeyText*/,
const juce::Drawable* /*icon*/,
const juce::Colour* /*textColour*/) {
if (isSeparator) {
g.setColour(juce::Colour(0xff444444));
g.drawLine(area.getX() + 8.0f, area.getCentreY(),
area.getRight() - 8.0f, area.getCentreY(), 1.0f);
return;
}
if (isHighlighted) {
g.setColour(juce::Colour(0xffccaa44));
g.fillRoundedRectangle(area.reduced(2).toFloat(), 3.0f);
}
g.setColour(isHighlighted ? juce::Colour(0xff1a1a1a) : juce::Colour(0xffcccccc));
g.setFont(juce::Font(13.0f));
g.drawText(text, area.reduced(8, 0), juce::Justification::centredLeft, true);
}
// --- VU Meter ---
void VuMeter::paint(juce::Graphics& g) {
auto bounds = getLocalBounds().toFloat();
juce::ColourGradient bgGrad(juce::Colour(0xff222222), bounds.getCentreX(), bounds.getY(),
juce::Colour(0xff111111), bounds.getCentreX(), bounds.getBottom(), false);
g.setGradientFill(bgGrad);
g.fillRoundedRectangle(bounds, 3.0f);
float level = processor.getRmsLevel();
float clamped = juce::jlimit(0.0f, 1.0f, level);
float barH = bounds.getHeight() * clamped;
auto barBounds = juce::Rectangle<float>(bounds.getX(), bounds.getBottom() - barH, bounds.getWidth(), barH);
auto innerBar = barBounds.reduced(1.0f);
{
juce::Graphics::ScopedSaveState saved(g);
g.setOpacity(0.7f);
juce::ColourGradient grad(juce::Colour(0xff00cc44), 0.0f, bounds.getBottom(),
juce::Colour(0xffcc2200), 0.0f, bounds.getY(), false);
g.setGradientFill(grad);
g.fillRoundedRectangle(innerBar, 2.0f);
}
g.setColour(juce::Colour(0xff444444));
g.drawRoundedRectangle(bounds, 3.0f, 1.0f);
g.setColour(juce::Colour(0xffccaa44));
g.setFont(juce::Font(13.0f).boldened());
g.drawText("VU", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}
// --- Waveform Display ---
void WaveformDisplay::paint(juce::Graphics& g) {
auto bounds = getLocalBounds().toFloat();
juce::ColourGradient bgGrad(juce::Colour(0xff222222), bounds.getCentreX(), bounds.getY(),
juce::Colour(0xff111111), bounds.getCentreX(), bounds.getBottom(), false);
g.setGradientFill(bgGrad);
g.fillRoundedRectangle(bounds, 3.0f);
g.setColour(juce::Colour(0xff555555));
g.drawRoundedRectangle(bounds, 3.0f, 1.0f);
g.setColour(juce::Colour(0xff333333));
g.drawLine(bounds.getX(), bounds.getCentreY(), bounds.getRight(), bounds.getCentreY(), 1.0f);
int writePos = processor.scopeWritePos.load(std::memory_order_acquire);
int bufSize = ChromaFlockProcessor::scopeBufferSize;
float w = bounds.getWidth();
float h = bounds.getHeight();
float midY = bounds.getY() + h * 0.5f;
juce::Path wavePath;
bool started = false;
for (int i = 0; i < bufSize; ++i) {
int idx = (writePos + i) % bufSize;
float x = bounds.getX() + (static_cast<float>(i) / static_cast<float>(bufSize)) * w;
float sample = processor.scopeBuffer[idx];
float y = midY - sample * h * 0.45f;
if (!started) {
wavePath.startNewSubPath(x, y);
started = true;
} else {
wavePath.lineTo(x, y);
}
}
juce::Path filledPath(wavePath);
filledPath.lineTo(bounds.getRight(), midY);
filledPath.lineTo(bounds.getX(), midY);
filledPath.closeSubPath();
{
juce::Graphics::ScopedSaveState saved(g);
g.setOpacity(0.7f);
juce::ColourGradient waveGrad(juce::Colour(0xff00ff88), 0.0f, bounds.getY(),
juce::Colour(0xff005522), 0.0f, bounds.getBottom(), false);
g.setGradientFill(waveGrad);
g.fillPath(filledPath);
g.setColour(juce::Colour(0xff00ff88).withAlpha(0.8f));
g.strokePath(wavePath, juce::PathStrokeType(1.5f));
}
g.setColour(juce::Colour(0xffccaa44));
g.setFont(juce::Font(13.0f).boldened());
g.drawText("WAVE", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}
// --- Spectrum Analyzer ---
void SpectrumAnalyzer::paint(juce::Graphics& g) {
auto bounds = getLocalBounds().toFloat();
juce::ColourGradient bgGrad(juce::Colour(0xff222222), bounds.getCentreX(), bounds.getY(),
juce::Colour(0xff111111), bounds.getCentreX(), bounds.getBottom(), false);
g.setGradientFill(bgGrad);
g.fillRoundedRectangle(bounds, 3.0f);
g.setColour(juce::Colour(0xff555555));
g.drawRoundedRectangle(bounds, 3.0f, 1.0f);
std::array<float, ChromaFlockProcessor::fftSize * 2> fftData{};
int writePos = processor.fftWritePos.load(std::memory_order_acquire);
int fftSize = ChromaFlockProcessor::fftSize;
for (int i = 0; i < fftSize; ++i) {
int idx = (writePos + i) % fftSize;
fftData[i] = processor.fftInput[idx];
}
for (int i = 0; i < fftSize; ++i) {
float window = 0.5f - 0.5f * std::cos(2.0f * 3.14159265f * static_cast<float>(i) / static_cast<float>(fftSize));
fftData[i] *= window;
}
fft->performFrequencyOnlyForwardTransform(fftData.data());
int numBars = 48;
float w = bounds.getWidth();
float h = bounds.getHeight() - 22.0f;
float barBottom = bounds.getBottom() - 2.0f;
float barW = w / static_cast<float>(numBars);
int maxBin = fftSize / 4;
for (int bar = 0; bar < numBars; ++bar) {
float t = static_cast<float>(bar) / static_cast<float>(numBars);
float tNext = static_cast<float>(bar + 1) / static_cast<float>(numBars);
int binStart = static_cast<int>(std::pow(t, 2.0f) * static_cast<float>(maxBin));
int binEnd = static_cast<int>(std::pow(tNext, 2.0f) * static_cast<float>(maxBin));
if (binEnd <= binStart) binEnd = binStart + 1;
if (binEnd > maxBin) binEnd = maxBin;
float mag = 0.0f;
int count = 0;
for (int b = binStart; b < binEnd; ++b) {
mag += fftData[b];
++count;
}
mag = count > 0 ? mag / static_cast<float>(count) : 0.0f;
mag = mag / static_cast<float>(fftSize);
mag = std::sqrt(mag) * 6.0f;
mag = juce::jlimit(0.0f, 1.0f, mag);
float barH = mag * h;
float x = bounds.getX() + static_cast<float>(bar) * barW;
float bw = barW - 2.0f;
float bx = x + 1.0f;
float by = barBottom - barH;
{
juce::Graphics::ScopedSaveState saved(g);
g.setOpacity(0.7f);
juce::ColourGradient barGrad(juce::Colour(0xff00ff66), 0.0f, by,
juce::Colour(0xff005522), 0.0f, barBottom, false);
g.setGradientFill(barGrad);
g.fillRect(bx, by, bw, barH);
}
}
g.setColour(juce::Colour(0xffccaa44));
g.setFont(juce::Font(13.0f).boldened());
g.drawText("SPECTRUM", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}
MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
: processorRef(p), vuMeter(p), waveformDisplay(p), spectrumAnalyzer(p), pianoRoll(p) {
auto setupParam = [&](juce::Slider& knob, std::unique_ptr<SliderAttachment>& attach,
const juce::String& paramId, const juce::String& name) {
setupKnob(knob, name);
attach = std::make_unique<SliderAttachment>(processorRef.apvts, paramId, knob);
};
auto setupCB = [&](juce::ComboBox& box, std::unique_ptr<ComboBoxAttachment>& attach,
const juce::String& paramId, const juce::StringArray& items) {
setupCombo(box);
box.addItemList(items, 1);
attach = std::make_unique<ComboBoxAttachment>(processorRef.apvts, paramId, box);
};
setupLabel(osc1Label, "OSC 1");
setupCB(osc1WaveBox, osc1WaveAttach, "osc1Wave", {"Sine", "Saw", "Square", "Triangle", "Noise"});
setupParam(osc1OctKnob, osc1OctAttach, "osc1Oct", "OCT");
setupParam(osc1SemiKnob, osc1SemiAttach, "osc1Semi", "SEMI");
setupParam(osc1FineKnob, osc1FineAttach, "osc1Fine", "FINE");
setupParam(osc1LevelKnob, osc1LevelAttach, "osc1Level", "LEVEL");
setupLabel(osc2Label, "OSC 2");
setupCB(osc2WaveBox, osc2WaveAttach, "osc2Wave", {"Sine", "Saw", "Square", "Triangle", "Noise"});
setupParam(osc2OctKnob, osc2OctAttach, "osc2Oct", "OCT");
setupParam(osc2SemiKnob, osc2SemiAttach, "osc2Semi", "SEMI");
setupParam(osc2FineKnob, osc2FineAttach, "osc2Fine", "FINE");
setupParam(osc2LevelKnob, osc2LevelAttach, "osc2Level", "LEVEL");
setupParam(phaseOffsetKnob, phaseOffsetAttach, "phaseOffset", "PHASE");
setupLabel(filterLabel, "FILTER");
setupCB(filterTypeBox, filterTypeAttach, "filterType", {"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"});
setupParam(filterCutoffKnob, filterCutoffAttach, "filterCutoff", "CUTOFF");
setupParam(filterResKnob, filterResAttach, "filterRes", "RES");
setupParam(filterEnvAmtKnob, filterEnvAmtAttach, "filterEnvAmt", "ENV AMT");
setupParam(keyTrackKnob, keyTrackAttach, "keyTrack", "KEY TRK");
setupLabel(envLabel, "AMP ENV");
setupParam(envAttackKnob, envAttackAttach, "envAttack", "ATTACK");
setupParam(envDecayKnob, envDecayAttach, "envDecay", "DECAY");
setupParam(envSustainKnob, envSustainAttach, "envSustain", "SUSTAIN");
setupParam(envReleaseKnob, envReleaseAttach, "envRelease", "RELEASE");
setupLabel(fEnvLabel, "FILTER ENV");
setupParam(fEnvAttackKnob, fEnvAttackAttach, "fEnvAttack", "ATTACK");
setupParam(fEnvDecayKnob, fEnvDecayAttach, "fEnvDecay", "DECAY");
setupParam(fEnvSustainKnob, fEnvSustainAttach, "fEnvSustain", "SUSTAIN");
setupParam(fEnvReleaseKnob, fEnvReleaseAttach, "fEnvRelease", "RELEASE");
setupLabel(globalLabel, "MASTER");
setupParam(panKnob, panAttach, "pan", "PAN");
setupParam(driveKnob, driveAttach, "drive", "DRIVE");
setupParam(masterKnob, masterAttach, "masterLevel", "LEVEL");
setupParam(pbRangeKnob, pbRangeAttach, "pitchBendRange", "PB RANGE");
// FX
setupCB(distTypeBox, distTypeAttach, "distType", {"Soft Clip", "Hard Clip", "Foldback", "Overdrive"});
setupParam(distAmountKnob, distAmountAttach, "distAmount", "AMOUNT");
setupParam(distMixKnob, distMixAttach, "distMix", "MIX");
setupParam(compThresholdKnob, compThresholdAttach, "compThreshold", "THRESH");
setupParam(compRatioKnob, compRatioAttach, "compRatio", "RATIO");
setupParam(compAttackKnob, compAttackAttach, "compAttack", "ATTACK");
setupParam(compReleaseKnob, compReleaseAttach, "compRelease", "RELEASE");
setupParam(compMakeupKnob, compMakeupAttach, "compMakeup", "MAKEUP");
setupParam(compMixKnob, compMixAttach, "compMix", "MIX");
setupParam(autoPanRateKnob, autoPanRateAttach, "autoPanRate", "RATE");
setupParam(autoPanDepthKnob, autoPanDepthAttach, "autoPanDepth", "DEPTH");
// LFO
setupParam(lfo1RateKnob, lfo1RateAttach, "lfo1Rate", "RATE");
setupParam(lfo1DepthKnob, lfo1DepthAttach, "lfo1Depth", "DEPTH");
setupCB(lfo1ShapeBox, lfo1ShapeAttach, "lfo1Shape", {"Sine", "Triangle", "Saw", "Square"});
setupCB(lfo1DestBox, lfo1DestAttach, "lfo1Dest", {"Filter", "OSC1", "OSC2", "Both OSC"});
setupParam(lfo2RateKnob, lfo2RateAttach, "lfo2Rate", "RATE");
setupParam(lfo2DepthKnob, lfo2DepthAttach, "lfo2Depth", "DEPTH");
setupCB(lfo2ShapeBox, lfo2ShapeAttach, "lfo2Shape", {"Sine", "Triangle", "Saw", "Square"});
setupCB(lfo2DestBox, lfo2DestAttach, "lfo2Dest", {"Filter", "OSC1", "OSC2", "Both OSC"});
// FX2
setupParam(delayTimeKnob, delayTimeAttach, "delayTime", "TIME");
setupParam(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
setupCB(delayPingPongBox, delayPingPongAttach, "delayPingPong", {"Off", "On"});
setupParam(delaySpreadKnob, delaySpreadAttach, "delaySpread", "SPREAD");
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
setupParam(reverbMixKnob, reverbMixAttach, "reverbMix", "MIX");
// Preset button
presetButton.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2a2a2a));
presetButton.setColour(juce::TextButton::textColourOffId, juce::Colour(0xffccaa44));
presetButton.setLookAndFeel(&comboLaf);
presetButton.onClick = [this]() { showPresetMenu(); };
addAndMakeVisible(presetButton);
scaleLabel.setText("SCALE", juce::dontSendNotification);
scaleLabel.setFont(juce::Font(9.0f));
scaleLabel.setColour(juce::Label::textColourId, juce::Colour(0xff888888));
scaleLabel.setJustificationType(juce::Justification::centredRight);
addAndMakeVisible(scaleLabel);
addAndMakeVisible(vuMeter);
addAndMakeVisible(waveformDisplay);
addAndMakeVisible(spectrumAnalyzer);
addAndMakeVisible(pianoRoll);
setupCombo(uiScaleBox);
uiScaleBox.addItem("100%", 1);
uiScaleBox.addItem("125%", 2);
uiScaleBox.addItem("150%", 3);
uiScaleBox.addItem("200%", 4);
uiScaleBox.setSelectedId(1, juce::dontSendNotification);
uiScaleBox.onChange = [this]() {
static const float scales[] = {1.0f, 1.25f, 1.5f, 2.0f};
int idx = uiScaleBox.getSelectedId() - 1;
if (idx >= 0 && idx < 4 && onScaleChanged)
onScaleChanged(scales[idx]);
};
}
void MainContentComponent::setupLabel(juce::Label& label, const juce::String& text) {
label.setText(text, juce::dontSendNotification);
label.setFont(juce::Font(13.0f).boldened());
label.setColour(juce::Label::textColourId, juce::Colour(0xffccaa44));
label.setJustificationType(juce::Justification::centred);
addAndMakeVisible(label);
}
void MainContentComponent::setupKnob(juce::Slider& knob, const juce::String& name) {
knob.setSliderStyle(juce::Slider::RotaryVerticalDrag);
knob.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 55, 16);
knob.setLookAndFeel(&knobLaf);
knob.setName(name);
knob.setColour(juce::Slider::textBoxTextColourId, juce::Colour(0xffcccccc));
knob.setColour(juce::Slider::textBoxBackgroundColourId, juce::Colour(0xff1a1a1a));
knob.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentBlack);
addAndMakeVisible(knob);
}
void MainContentComponent::setupCombo(juce::ComboBox& combo) {
combo.setEditableText(false);
combo.setLookAndFeel(&comboLaf);
combo.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2a2a2a));
combo.setColour(juce::ComboBox::textColourId, juce::Colour(0xffcccccc));
combo.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff555555));
combo.setColour(juce::ComboBox::arrowColourId, juce::Colour(0xffccaa44));
combo.setColour(juce::PopupMenu::backgroundColourId, juce::Colour(0xff2a2a2a));
combo.setColour(juce::PopupMenu::textColourId, juce::Colour(0xffcccccc));
combo.setColour(juce::PopupMenu::highlightedBackgroundColourId, juce::Colour(0xffccaa44));
combo.setColour(juce::PopupMenu::highlightedTextColourId, juce::Colour(0xff1a1a1a));
addAndMakeVisible(combo);
}
void MainContentComponent::showPresetMenu() {
juce::PopupMenu menu;
auto categories = processorRef.presetManager.getCategories();
int menuId = 1;
for (auto& cat : categories) {
juce::PopupMenu subMenu;
auto presets = processorRef.presetManager.getPresetsInCategory(cat);
for (auto& preset : presets) {
subMenu.addItem(menuId++, preset.name);
}
menu.addSubMenu(cat, subMenu);
}
menu.showMenuAsync(juce::PopupMenu::Options(), [this](int result) {
if (result > 0) {
auto& presets = processorRef.presetManager.getPresets();
int idx = 0;
for (int i = 0; i < result - 1 && i < static_cast<int>(presets.size()); ++i)
++idx;
if (idx < static_cast<int>(presets.size()))
processorRef.presetManager.applyPreset(presets[idx].name, processorRef.apvts);
}
});
}
void MainContentComponent::paint(juce::Graphics& g) {
g.fillAll(juce::Colour(0xff1a1a1a));
// Draw background image tiled
if (!bgImageLoaded) {
bgImage = juce::ImageFileFormat::loadFrom(
BinaryData::chromaflockbg5_png, BinaryData::chromaflockbg5_pngSize);
bgImageLoaded = true;
}
if (bgImage.isValid()) {
int imgW = bgImage.getWidth();
int imgH = bgImage.getHeight();
int compW = getWidth();
int compH = getHeight();
g.setOpacity(0.35f);
for (int x = 0; x < compW; x += imgW)
for (int y = 0; y < compH; y += imgH)
g.drawImageAt(bgImage, x, y);
g.setOpacity(1.0f);
}
// Load logo
if (!logoLoaded) {
logoImage = juce::ImageFileFormat::loadFrom(
BinaryData::cfnew_png, BinaryData::cfnew_pngSize);
logoLoaded = true;
}
if (logoImage.isValid()) {
int logoH = 124;
int logoW = static_cast<int>(logoH * 805.0 / 119.0);
int logoX = (getWidth() - logoW) / 2;
auto logoArea = juce::Rectangle<int>(logoX, 0, logoW, logoH);
g.drawImage(logoImage, logoArea.toFloat(), juce::RectanglePlacement::centred);
}
g.setColour(juce::Colour(0xffccaa44));
g.drawHorizontalLine(124, 10.0f, 1650.0f);
auto drawSection = [&](int x, int y, int w, int h) {
auto rect = juce::Rectangle<float>(static_cast<float>(x), static_cast<float>(y),
static_cast<float>(w), static_cast<float>(h));
// 3D fill gradient: slight light top, dark bottom
juce::ColourGradient fillGrad(juce::Colour(0xff222222), rect.getCentreX(), rect.getY(),
juce::Colour(0xff111111), rect.getCentreX(), rect.getBottom(), false);
g.setGradientFill(fillGrad);
g.fillRoundedRectangle(rect, 4.0f);
// Border
g.setColour(juce::Colour(0xff333333));
g.drawRoundedRectangle(rect, 4.0f, 1.0f);
};
drawSection(10, 128, 498, 200);
drawSection(520, 128, 620, 200);
drawSection(1152, 128, 498, 200);
drawSection(75, 338, 498, 150);
drawSection(585, 338, 498, 150);
drawSection(1095, 338, 490, 150);
drawSection(10, 900, 1640, 128);
drawSection(10, 1038, 1640, 136);
// FX sub-sections
auto drawSubSection = [&](int x, int y, int w, int h, const juce::String& title, int titlePadY = 2) {
auto rect = juce::Rectangle<float>(static_cast<float>(x), static_cast<float>(y),
static_cast<float>(w), static_cast<float>(h));
juce::ColourGradient fillGrad(juce::Colour(0xff222222), rect.getCentreX(), rect.getY(),
juce::Colour(0xff111111), rect.getCentreX(), rect.getBottom(), false);
g.setGradientFill(fillGrad);
g.fillRoundedRectangle(rect, 3.0f);
g.setColour(juce::Colour(0xff333333));
g.drawRoundedRectangle(rect, 3.0f, 1.0f);
g.setColour(juce::Colour(0xffccaa44));
g.setFont(juce::Font(11.0f).boldened());
g.drawText(title, x + 6, y + titlePadY, w - 12, 14, juce::Justification::centred);
};
drawSubSection(15, 524, 395, 140, "DISTORTION", 6);
drawSubSection(420, 524, 660, 140, "COMPRESSOR", 6);
drawSubSection(1090, 524, 240, 140, "AUTO-PAN", 6);
// LFO sub-sections
drawSubSection(15, 690, 530, 150, "LFO 1", 4);
drawSubSection(560, 690, 530, 150, "LFO 2", 4);
// FX2 sub-sections
drawSubSection(1100, 690, 265, 200, "DELAY", 4);
drawSubSection(1375, 690, 265, 200, "REVERB", 4);
}
void MainContentComponent::resized() {
auto sectionY = 132;
auto knobSize = 112;
auto knobSpacing = 10;
auto labelH = 18;
auto comboH = 48;
osc1Label.setBounds(10, sectionY, 498, labelH);
osc1WaveBox.setBounds(20, sectionY + labelH + 4, 200, comboH);
int knobY1 = sectionY + labelH + comboH + 8;
osc1OctKnob.setBounds(20, knobY1, knobSize, knobSize);
osc1SemiKnob.setBounds(20 + knobSize + knobSpacing, knobY1, knobSize, knobSize);
osc1FineKnob.setBounds(20 + (knobSize + knobSpacing) * 2, knobY1, knobSize, knobSize);
osc1LevelKnob.setBounds(20 + (knobSize + knobSpacing) * 3, knobY1, knobSize, knobSize);
osc2Label.setBounds(520, sectionY, 620, labelH);
osc2WaveBox.setBounds(530, sectionY + labelH + 4, 200, comboH);
int knobY2 = sectionY + labelH + comboH + 8;
osc2OctKnob.setBounds(530, knobY2, knobSize, knobSize);
osc2SemiKnob.setBounds(530 + knobSize + knobSpacing, knobY2, knobSize, knobSize);
osc2FineKnob.setBounds(530 + (knobSize + knobSpacing) * 2, knobY2, knobSize, knobSize);
osc2LevelKnob.setBounds(530 + (knobSize + knobSpacing) * 3, knobY2, knobSize, knobSize);
phaseOffsetKnob.setBounds(530 + (knobSize + knobSpacing) * 4, knobY2, knobSize, knobSize);
filterLabel.setBounds(1152, sectionY, 498, labelH);
filterTypeBox.setBounds(1162, sectionY + labelH + 4, 200, comboH);
int knobYF = sectionY + labelH + comboH + 8;
filterCutoffKnob.setBounds(1162, knobYF, knobSize, knobSize);
filterResKnob.setBounds(1162 + knobSize + knobSpacing, knobYF, knobSize, knobSize);
filterEnvAmtKnob.setBounds(1162 + (knobSize + knobSpacing) * 2, knobYF, knobSize, knobSize);
keyTrackKnob.setBounds(1162 + (knobSize + knobSpacing) * 3, knobYF, knobSize, knobSize);
envLabel.setBounds(75, 352, 498, labelH);
int knobYA = 352 + labelH + 6;
envAttackKnob.setBounds(85, knobYA, knobSize, knobSize);
envDecayKnob.setBounds(85 + knobSize + knobSpacing, knobYA, knobSize, knobSize);
envSustainKnob.setBounds(85 + (knobSize + knobSpacing) * 2, knobYA, knobSize, knobSize);
envReleaseKnob.setBounds(85 + (knobSize + knobSpacing) * 3, knobYA, knobSize, knobSize);
fEnvLabel.setBounds(585, 352, 498, labelH);
int knobYFE = 352 + labelH + 6;
fEnvAttackKnob.setBounds(595, knobYFE, knobSize, knobSize);
fEnvDecayKnob.setBounds(595 + knobSize + knobSpacing, knobYFE, knobSize, knobSize);
fEnvSustainKnob.setBounds(595 + (knobSize + knobSpacing) * 2, knobYFE, knobSize, knobSize);
fEnvReleaseKnob.setBounds(595 + (knobSize + knobSpacing) * 3, knobYFE, knobSize, knobSize);
globalLabel.setBounds(1095, 352, 490, labelH);
int knobYM = 352 + labelH + 6;
panKnob.setBounds(1105, knobYM, knobSize, knobSize);
driveKnob.setBounds(1105 + knobSize + knobSpacing, knobYM, knobSize, knobSize);
masterKnob.setBounds(1105 + (knobSize + knobSpacing) * 2, knobYM, knobSize, knobSize);
pbRangeKnob.setBounds(1105 + (knobSize + knobSpacing) * 3, knobYM, knobSize, knobSize);
// --- FX SECTION ---
int fxY = 496;
int fxKnobSize = 100;
int fxLabelH = 16;
int fxKnobY = fxY + 54; // sub-section y(476) + titlePad(6) + titleH(14) + bottomPad(6) + 4
fxLabel.setBounds(10, fxY + 8, 1640, fxLabelH);
// Distortion sub-section (X=15, Y=476, W=395, H=140)
distTypeBox.setBounds(22, fxKnobY + 2, 140, 36);
distAmountKnob.setBounds(170, fxKnobY, fxKnobSize, fxKnobSize);
distMixKnob.setBounds(280, fxKnobY, fxKnobSize, fxKnobSize);
// Compressor sub-section (X=420, Y=476, W=660, H=140)
compThresholdKnob.setBounds(428, fxKnobY, fxKnobSize, fxKnobSize);
compRatioKnob.setBounds(538, fxKnobY, fxKnobSize, fxKnobSize);
compAttackKnob.setBounds(648, fxKnobY, fxKnobSize, fxKnobSize);
compReleaseKnob.setBounds(758, fxKnobY, fxKnobSize, fxKnobSize);
compMakeupKnob.setBounds(868, fxKnobY, fxKnobSize, fxKnobSize);
compMixKnob.setBounds(978, fxKnobY, fxKnobSize, fxKnobSize);
// Auto-Pan sub-section (X=1090, Y=476, W=240, H=140)
autoPanRateKnob.setBounds(1098, fxKnobY, fxKnobSize, fxKnobSize);
autoPanDepthKnob.setBounds(1098 + fxKnobSize + 8, fxKnobY, fxKnobSize, fxKnobSize);
// --- LFO SECTION ---
int lfoY = 670;
int lfoKnobSize = 80;
int lfoKnobY = 690 + 4 + 14 + 6; // subSectionY + titlePad + titleH + bottomPad
lfoLabel.setBounds(10, lfoY, 1080, fxLabelH);
fx2Label.setBounds(1100, lfoY, 540, fxLabelH);
// LFO 1 sub-section (X=15, Y=642, W=530, H=110)
lfo1RateKnob.setBounds(22, lfoKnobY, lfoKnobSize, lfoKnobSize);
lfo1DepthKnob.setBounds(132, lfoKnobY, lfoKnobSize, lfoKnobSize);
lfo1ShapeBox.setBounds(242, lfoKnobY + 2, 130, 36);
lfo1DestBox.setBounds(382, lfoKnobY + 2, 155, 36);
// LFO 2 sub-section (X=560, Y=642, W=530, H=110)
lfo2RateKnob.setBounds(568, lfoKnobY, lfoKnobSize, lfoKnobSize);
lfo2DepthKnob.setBounds(678, lfoKnobY, lfoKnobSize, lfoKnobSize);
lfo2ShapeBox.setBounds(788, lfoKnobY + 2, 130, 36);
lfo2DestBox.setBounds(928, lfoKnobY + 2, 155, 36);
// --- FX2 SECTION ---
int fx2KnobY = lfoKnobY;
// Delay sub-section (X=1100, Y=690, W=265, H=130)
// Row 1: TIME, FBACK, MIX
delayTimeKnob.setBounds(1110, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayFbKnob.setBounds(1198, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayMixKnob.setBounds(1286, fx2KnobY, lfoKnobSize, lfoKnobSize);
// Row 2: PINGPONG combo, SPREAD knob
int delayRow2Y = fx2KnobY + lfoKnobSize + 4;
delayPingPongBox.setBounds(1110, delayRow2Y, 100, 36);
delaySpreadKnob.setBounds(1220, delayRow2Y, lfoKnobSize, lfoKnobSize);
// Reverb sub-section (X=1375, Y=690, W=265, H=130)
reverbSizeKnob.setBounds(1383, fx2KnobY, lfoKnobSize, lfoKnobSize);
reverbDampKnob.setBounds(1468, fx2KnobY, lfoKnobSize, lfoKnobSize);
reverbMixKnob.setBounds(1553, fx2KnobY, lfoKnobSize, lfoKnobSize);
// --- PRESET + SCALE ---
presetButton.setBounds(20, 8, 120, 28);
scaleLabel.setBounds(1520, 10, 42, 20);
uiScaleBox.setBounds(1566, 8, 80, 24);
// Visualizer area
int vizY = 900;
int vizH = 128;
vuMeter.setBounds(14, vizY, 40, vizH);
waveformDisplay.setBounds(60, vizY, 780, vizH);
spectrumAnalyzer.setBounds(850, vizY, 794, vizH);
// Piano roll
pianoRoll.setBounds(10, 1038, 1640, 136);
}
// ===== PianoRollComponent =====
int PianoRollComponent::midiNoteForKey(int index) const {
// Map key index to MIDI note, skipping black keys in the index
// White keys: C D E F G A B (indices 0,2,4,5,7,9,11 in chromatic scale)
static const int whiteToChromatic[] = {0, 2, 4, 5, 7, 9, 11};
int octave = index / 7;
int noteInOctave = index % 7;
return firstMidiNote + octave * 12 + whiteToChromatic[noteInOctave];
}
bool PianoRollComponent::isBlackKey(int midiNote) const {
int noteInOctave = midiNote % 12;
return noteInOctave == 1 || noteInOctave == 3 || noteInOctave == 6 ||
noteInOctave == 8 || noteInOctave == 10;
}
int PianoRollComponent::keyAtPosition(juce::Point<int> pos) const {
int pbRight = pitchBendWidth;
int keysX = pbRight + 8;
int keysWidth = getWidth() - keysX - 6;
int numWhiteKeys = (numKeys * 7) / 12 + 1;
int localWhiteKeyWidth = keysWidth / numWhiteKeys;
// Check black keys first (they're on top)
for (int i = 0; i < numKeys; ++i) {
int note = firstMidiNote + i;
if (!isBlackKey(note)) continue;
int chroma = note % 12;
int octave = (note - firstMidiNote) / 12;
static const int blackToLeftWhite[] = { 0,0,1,1,2,3,3,4,4,5,5,6 };
int leftWhite = blackToLeftWhite[chroma];
int whiteIndexBefore = octave * 7 + leftWhite;
float bx = static_cast<float>(keysX + (whiteIndexBefore + 1) * localWhiteKeyWidth) - static_cast<float>(blackKeyWidth) * 0.5f;
auto blackRect = juce::Rectangle<int>(static_cast<int>(bx), 0, blackKeyWidth, blackKeyHeight);
if (blackRect.contains(pos))
return note;
}
// Check white keys
for (int i = 0; i < numKeys; ++i) {
int note = firstMidiNote + i;
if (isBlackKey(note)) continue;
int chroma = note % 12;
int octave = (note - firstMidiNote) / 12;
int whiteIndex = octave * 7;
if (chroma == 2) whiteIndex += 1;
else if (chroma == 4) whiteIndex += 2;
else if (chroma == 5) whiteIndex += 3;
else if (chroma == 7) whiteIndex += 4;
else if (chroma == 9) whiteIndex += 5;
else if (chroma == 11) whiteIndex += 6;
auto whiteRect = juce::Rectangle<int>(keysX + whiteIndex * localWhiteKeyWidth, 0, localWhiteKeyWidth, whiteKeyHeight);
if (whiteRect.contains(pos))
return note;
}
return -1;
}
void PianoRollComponent::triggerNote(int note, bool on) {
if (on)
processor.noteOn(note, 0.8f);
else
processor.noteOff(note);
}
void PianoRollComponent::paint(juce::Graphics& g) {
int pbRight = pitchBendWidth;
int keysX = pbRight + 8;
int keysWidth = getWidth() - keysX - 6;
int numWhiteKeys = (numKeys * 7) / 12 + 1;
whiteKeyWidth = keysWidth / numWhiteKeys;
// Draw pitch bend strip background
g.setColour(juce::Colour(0xff222222));
g.fillRect(10, 0, pbRight - 10, whiteKeyHeight);
// Right edge border to separate from keys
g.setColour(juce::Colour(0xff444444));
g.drawVerticalLine(pbRight - 8, 0.0f, static_cast<float>(whiteKeyHeight));
// Active bend area indicator (shaded region from center to current position)
int pbCenter = whiteKeyHeight / 2;
float pbNorm = static_cast<float>(currentPitchBend - 64) / 64.0f; // -1..+1
int pbY = pbCenter - static_cast<int>(pbNorm * static_cast<float>(pbCenter - 8));
g.setColour(juce::Colour(0x40ccaa44));
if (pbY < pbCenter)
g.fillRect(14, pbY, 18, pbCenter - pbY);
else
g.fillRect(14, pbCenter, 18, pbY - pbCenter);
// Pitch bend center line
g.setColour(juce::Colour(0xffccaa44));
g.drawHorizontalLine(pbCenter, 12.0f, static_cast<float>(pbRight - 14));
// Pitch bend indicator dot
g.setColour(juce::Colour(0xffccaa44));
g.fillEllipse(14, pbY - 5, 18, 10);
// PB labels
g.setColour(juce::Colour(0xff888888));
g.setFont(9.0f);
g.drawText("+", 14, 2, 18, 12, juce::Justification::centred);
g.drawText("-", 14, whiteKeyHeight - 14, 18, 12, juce::Justification::centred);
// Draw white keys first
for (int i = 0; i < numKeys; ++i) {
int note = firstMidiNote + i;
if (isBlackKey(note)) continue;
int chroma = note % 12;
int octave = (note - firstMidiNote) / 12;
int whiteIndex = octave * 7;
if (chroma == 2) whiteIndex += 1;
else if (chroma == 4) whiteIndex += 2;
else if (chroma == 5) whiteIndex += 3;
else if (chroma == 7) whiteIndex += 4;
else if (chroma == 9) whiteIndex += 5;
else if (chroma == 11) whiteIndex += 6;
int kx = keysX + whiteIndex * whiteKeyWidth;
auto rect = juce::Rectangle<int>(kx, 0, whiteKeyWidth - 1, whiteKeyHeight);
bool pressed = processor.isNoteActive(note);
if (pressed)
g.setColour(juce::Colour(0xffccaa44));
else
g.setColour(juce::Colour(0xffdddddd));
g.fillRect(rect);
g.setColour(juce::Colour(0xff555555));
g.drawRect(rect, 1);
// Draw note name on C keys
if (chroma == 0) {
g.setColour(juce::Colour(0xff666666));
g.setFont(9.0f);
g.drawText("C" + juce::String(octave + 3), kx + 2, whiteKeyHeight - 14, whiteKeyWidth - 4, 12,
juce::Justification::centred);
}
}
// Draw black keys on top
for (int i = 0; i < numKeys; ++i) {
int note = firstMidiNote + i;
if (!isBlackKey(note)) continue;
int chroma = note % 12;
int octave = (note - firstMidiNote) / 12;
// chroma → white key index to the LEFT of this black key
static const int blackToLeftWhite[] = { 0,0,1,1,2,3,3,4,4,5,5,6 };
int leftWhite = blackToLeftWhite[chroma];
int whiteIndexBefore = octave * 7 + leftWhite;
float bx = static_cast<float>(keysX + (whiteIndexBefore + 1) * whiteKeyWidth) - static_cast<float>(blackKeyWidth) * 0.5f;
auto rect = juce::Rectangle<int>(static_cast<int>(bx), 0, blackKeyWidth, blackKeyHeight);
bool pressed = processor.isNoteActive(note);
if (pressed)
g.setColour(juce::Colour(0xffccaa44));
else
g.setColour(juce::Colour(0xff333333));
g.fillRect(rect);
g.setColour(juce::Colour(0xff555555));
g.drawRect(rect, 1);
}
}
void PianoRollComponent::mouseDown(const juce::MouseEvent& e) {
auto pos = e.getPosition();
// Pitch bend strip
if (pos.x < pitchBendWidth) {
float pbNorm = 1.0f - static_cast<float>(pos.y) / static_cast<float>(whiteKeyHeight);
pbNorm = std::clamp(pbNorm, -1.0f, 1.0f);
currentPitchBend = 64 + static_cast<int>(pbNorm * 64.0f);
processor.pitchBendValue.store(pbNorm);
return;
}
int note = keyAtPosition(pos);
if (note >= 0) {
if (lastTriggeredNote >= 0 && lastTriggeredNote != note)
triggerNote(lastTriggeredNote, false);
lastTriggeredNote = note;
triggerNote(note, true);
repaint();
}
}
void PianoRollComponent::mouseUp(const juce::MouseEvent& e) {
// Reset pitch bend to center on release
if (e.getPosition().x < pitchBendWidth || lastTriggeredNote < 0) {
if (e.getPosition().x < pitchBendWidth) {
currentPitchBend = 64;
processor.pitchBendValue.store(0.0f);
repaint();
}
if (lastTriggeredNote < 0) return;
}
if (lastTriggeredNote >= 0) {
triggerNote(lastTriggeredNote, false);
lastTriggeredNote = -1;
repaint();
}
}
void PianoRollComponent::mouseDrag(const juce::MouseEvent& e) {
auto pos = e.getPosition();
// Pitch bend drag
if (pos.x < pitchBendWidth) {
float pbNorm = 1.0f - static_cast<float>(pos.y) / static_cast<float>(whiteKeyHeight);
pbNorm = std::clamp(pbNorm, -1.0f, 1.0f);
currentPitchBend = 64 + static_cast<int>(pbNorm * 64.0f);
processor.pitchBendValue.store(pbNorm);
repaint();
return;
}
int note = keyAtPosition(pos);
if (note >= 0 && note != lastTriggeredNote) {
if (lastTriggeredNote >= 0)
triggerNote(lastTriggeredNote, false);
lastTriggeredNote = note;
triggerNote(note, true);
repaint();
}
}
ChromaFlockEditor::ChromaFlockEditor(ChromaFlockProcessor& p)
: AudioProcessorEditor(&p), content(p) {
addAndMakeVisible(content);
content.onScaleChanged = [this](float s) { setUIScale(s); };
setSize(baseWidth, baseHeight);
setResizable(false, false);
}
ChromaFlockEditor::~ChromaFlockEditor() {}
void ChromaFlockEditor::paint(juce::Graphics& g) {
g.fillAll(juce::Colour(0xff1a1a1a));
}
void ChromaFlockEditor::resized() {
content.setBounds(0, 0, baseWidth, baseHeight);
content.setTransform(juce::AffineTransform::scale(currentScale, currentScale));
}
void ChromaFlockEditor::setUIScale(float newScale) {
currentScale = newScale;
setSize(static_cast<int>(baseWidth * newScale), static_cast<int>(baseHeight * newScale));
}

205
Source/PluginEditor.h Normal file
View file

@ -0,0 +1,205 @@
#pragma once
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_gui_basics/juce_gui_basics.h>
#include <juce_dsp/juce_dsp.h>
#include "PluginProcessor.h"
#include <vector>
class KnobLookAndFeel : public juce::LookAndFeel_V4 {
public:
void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle,
float rotaryEndAngle, juce::Slider& slider) override;
};
class ComboBoxLookAndFeel : public juce::LookAndFeel_V4 {
public:
void drawComboBox(juce::Graphics& g, int width, int height,
bool isButtonDown, int buttonX, int buttonY,
int buttonW, int buttonH, juce::ComboBox& box) override;
void drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle<int>& area,
bool isSeparator, bool isActive, bool isHighlighted,
bool isTicked, bool hasSubMenu, const juce::String& text,
const juce::String& shortcutKeyText,
const juce::Drawable* icon, const juce::Colour* textColour) override;
};
class VuMeter : public juce::Component, public juce::Timer {
public:
explicit VuMeter(ChromaFlockProcessor& p) : processor(p) { startTimerHz(30); }
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
private:
ChromaFlockProcessor& processor;
};
class WaveformDisplay : public juce::Component, public juce::Timer {
public:
explicit WaveformDisplay(ChromaFlockProcessor& p) : processor(p) { startTimerHz(30); }
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
private:
ChromaFlockProcessor& processor;
};
class SpectrumAnalyzer : public juce::Component, public juce::Timer {
public:
explicit SpectrumAnalyzer(ChromaFlockProcessor& p) : processor(p) {
fft = std::make_unique<juce::dsp::FFT>(ChromaFlockProcessor::fftOrder);
startTimerHz(30);
}
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
private:
ChromaFlockProcessor& processor;
std::unique_ptr<juce::dsp::FFT> fft;
};
class PianoRollComponent : public juce::Component, public juce::Timer {
public:
explicit PianoRollComponent(ChromaFlockProcessor& p) : processor(p) { startTimerHz(30); }
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
void mouseDown(const juce::MouseEvent& e) override;
void mouseUp(const juce::MouseEvent& e) override;
void mouseDrag(const juce::MouseEvent& e) override;
private:
ChromaFlockProcessor& processor;
static constexpr int firstMidiNote = 48; // C3
static constexpr int numKeys = 49; // C3..C7
int whiteKeyWidth = 54;
int whiteKeyHeight = 130;
int blackKeyWidth = 35;
int blackKeyHeight = 80;
int pitchBendWidth = 40;
int midiNoteForKey(int index) const;
bool isBlackKey(int midiNote) const;
int keyAtPosition(juce::Point<int> pos) const;
void triggerNote(int note, bool on);
int lastTriggeredNote = -1;
int currentPitchBend = 64; // 0..127, 64 = center
};
class MainContentComponent : public juce::Component {
public:
explicit MainContentComponent(ChromaFlockProcessor& p);
void paint(juce::Graphics& g) override;
void resized() override;
std::function<void(float)> onScaleChanged;
private:
ChromaFlockProcessor& processorRef;
KnobLookAndFeel knobLaf;
ComboBoxLookAndFeel comboLaf;
juce::Image bgImage;
bool bgImageLoaded = false;
juce::Image logoImage;
bool logoLoaded = false;
VuMeter vuMeter;
WaveformDisplay waveformDisplay;
SpectrumAnalyzer spectrumAnalyzer;
PianoRollComponent pianoRoll;
juce::Label osc1Label, osc2Label, filterLabel, envLabel, fEnvLabel, globalLabel;
juce::Label fxLabel, lfoLabel, fx2Label;
juce::Label scaleLabel;
juce::ComboBox osc1WaveBox;
juce::Slider osc1OctKnob, osc1SemiKnob, osc1FineKnob, osc1LevelKnob;
juce::ComboBox osc2WaveBox;
juce::Slider osc2OctKnob, osc2SemiKnob, osc2FineKnob, osc2LevelKnob, phaseOffsetKnob;
juce::ComboBox filterTypeBox;
juce::Slider filterCutoffKnob, filterResKnob, filterEnvAmtKnob, keyTrackKnob;
juce::Slider envAttackKnob, envDecayKnob, envSustainKnob, envReleaseKnob;
juce::Slider fEnvAttackKnob, fEnvDecayKnob, fEnvSustainKnob, fEnvReleaseKnob;
juce::Slider panKnob, driveKnob, masterKnob, pbRangeKnob;
// FX controls
juce::ComboBox distTypeBox;
juce::Slider distAmountKnob, distMixKnob;
juce::Slider compThresholdKnob, compRatioKnob, compAttackKnob, compReleaseKnob, compMakeupKnob, compMixKnob;
juce::Slider autoPanRateKnob, autoPanDepthKnob;
// LFO controls
juce::Slider lfo1RateKnob, lfo1DepthKnob;
juce::ComboBox lfo1ShapeBox, lfo1DestBox;
juce::Slider lfo2RateKnob, lfo2DepthKnob;
juce::ComboBox lfo2ShapeBox, lfo2DestBox;
// FX2 controls
juce::Slider delayTimeKnob, delayFbKnob, delayMixKnob;
juce::ComboBox delayPingPongBox;
juce::Slider delaySpreadKnob;
juce::Slider reverbSizeKnob, reverbDampKnob, reverbMixKnob;
// Preset menu
juce::TextButton presetButton{"PRESET"};
juce::ComboBox uiScaleBox;
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
using ComboBoxAttachment = juce::AudioProcessorValueTreeState::ComboBoxAttachment;
std::unique_ptr<SliderAttachment> osc1OctAttach, osc1SemiAttach, osc1FineAttach, osc1LevelAttach;
std::unique_ptr<SliderAttachment> osc2OctAttach, osc2SemiAttach, osc2FineAttach, osc2LevelAttach, phaseOffsetAttach;
std::unique_ptr<ComboBoxAttachment> osc1WaveAttach, osc2WaveAttach, filterTypeAttach;
std::unique_ptr<SliderAttachment> filterCutoffAttach, filterResAttach, filterEnvAmtAttach, keyTrackAttach;
std::unique_ptr<SliderAttachment> envAttackAttach, envDecayAttach, envSustainAttach, envReleaseAttach;
std::unique_ptr<SliderAttachment> fEnvAttackAttach, fEnvDecayAttach, fEnvSustainAttach, fEnvReleaseAttach;
std::unique_ptr<SliderAttachment> panAttach, driveAttach, masterAttach, pbRangeAttach;
// FX attachments
std::unique_ptr<ComboBoxAttachment> distTypeAttach;
std::unique_ptr<SliderAttachment> distAmountAttach, distMixAttach;
std::unique_ptr<SliderAttachment> compThresholdAttach, compRatioAttach, compAttackAttach;
std::unique_ptr<SliderAttachment> compReleaseAttach, compMakeupAttach, compMixAttach;
std::unique_ptr<SliderAttachment> autoPanRateAttach, autoPanDepthAttach;
// LFO attachments
std::unique_ptr<SliderAttachment> lfo1RateAttach, lfo1DepthAttach;
std::unique_ptr<ComboBoxAttachment> lfo1ShapeAttach, lfo1DestAttach;
std::unique_ptr<SliderAttachment> lfo2RateAttach, lfo2DepthAttach;
std::unique_ptr<ComboBoxAttachment> lfo2ShapeAttach, lfo2DestAttach;
// FX2 attachments
std::unique_ptr<SliderAttachment> delayTimeAttach, delayFbAttach, delayMixAttach;
std::unique_ptr<ComboBoxAttachment> delayPingPongAttach;
std::unique_ptr<SliderAttachment> delaySpreadAttach;
std::unique_ptr<SliderAttachment> reverbSizeAttach, reverbDampAttach, reverbMixAttach;
void setupLabel(juce::Label& label, const juce::String& text);
void setupKnob(juce::Slider& knob, const juce::String& name);
void setupCombo(juce::ComboBox& combo);
void showPresetMenu();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainContentComponent)
};
class ChromaFlockEditor : public juce::AudioProcessorEditor {
public:
ChromaFlockEditor(ChromaFlockProcessor& p);
~ChromaFlockEditor() override;
void paint(juce::Graphics&) override;
void resized() override;
void setUIScale(float newScale);
private:
MainContentComponent content;
float currentScale = 1.0f;
static constexpr int baseWidth = 1660;
static constexpr int baseHeight = 1182;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChromaFlockEditor)
};

387
Source/PluginProcessor.cpp Normal file
View file

@ -0,0 +1,387 @@
#include "PluginProcessor.h"
#include "PluginEditor.h"
ChromaFlockProcessor::ChromaFlockProcessor()
: AudioProcessor(BusesProperties()
.withOutput("Output", juce::AudioChannelSet::stereo(), true)),
apvts(*this, nullptr, "Parameters", createParameterLayout()) {
synth.addSound(new SubtractiveSound());
for (int i = 0; i < maxVoices; ++i)
synth.addVoice(new SubtractiveVoice());
}
ChromaFlockProcessor::~ChromaFlockProcessor() {}
juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::createParameterLayout() {
juce::AudioProcessorValueTreeState::ParameterLayout layout;
auto zeroOne = juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f);
// OSC 1
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"osc1Wave", 1}, "OSC1 Wave",
juce::StringArray{"Sine", "Saw", "Square", "Triangle", "Noise"}, 1));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc1Oct", 1}, "OSC1 Octave",
juce::NormalisableRange<float>(-3.0f, 3.0f, 1.0f), 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc1Semi", 1}, "OSC1 Semi",
juce::NormalisableRange<float>(-12.0f, 12.0f, 1.0f), 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc1Fine", 1}, "OSC1 Fine",
juce::NormalisableRange<float>(-100.0f, 100.0f, 1.0f), 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc1Level", 1}, "OSC1 Level", zeroOne, 0.7f));
// OSC 2
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"osc2Wave", 1}, "OSC2 Wave",
juce::StringArray{"Sine", "Saw", "Square", "Triangle", "Noise"}, 2));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc2Oct", 1}, "OSC2 Octave",
juce::NormalisableRange<float>(-3.0f, 3.0f, 1.0f), -1.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc2Semi", 1}, "OSC2 Semi",
juce::NormalisableRange<float>(-12.0f, 12.0f, 1.0f), 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc2Fine", 1}, "OSC2 Fine",
juce::NormalisableRange<float>(-100.0f, 100.0f, 1.0f), 7.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"osc2Level", 1}, "OSC2 Level", zeroOne, 0.7f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"phaseOffset", 1}, "Phase Offset", zeroOne, 0.5f));
// FILTER
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"filterType", 1}, "Filter Type",
juce::StringArray{"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"}, 0));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"filterCutoff", 1}, "Filter Cutoff",
juce::NormalisableRange<float>(25.0f, 1200.0f, 1.0f, 0.25f), 800.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"filterRes", 1}, "Filter Resonance", zeroOne, 0.7f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"filterEnvAmt", 1}, "Filter Env Amount", zeroOne, 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"keyTrack", 1}, "Key Tracking", zeroOne, 0.5f));
// FILTER ENVELOPE
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"fEnvAttack", 1}, "Filter Attack",
juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.01f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"fEnvDecay", 1}, "Filter Decay",
juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.3f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"fEnvSustain", 1}, "Filter Sustain", zeroOne, 0.5f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"fEnvRelease", 1}, "Filter Release",
juce::NormalisableRange<float>(0.001f, 10.0f, 0.001f, 0.3f), 0.5f));
// AMP ENVELOPE
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"envAttack", 1}, "Attack",
juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.01f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"envDecay", 1}, "Decay",
juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.3f), 0.3f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"envSustain", 1}, "Sustain", zeroOne, 0.7f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"envRelease", 1}, "Release",
juce::NormalisableRange<float>(0.001f, 10.0f, 0.001f, 0.3f), 0.5f));
// GLOBAL
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"pan", 1}, "Pan",
juce::NormalisableRange<float>(-1.0f, 1.0f, 0.01f), 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"drive", 1}, "Drive",
juce::NormalisableRange<float>(1.0f, 5.0f, 0.01f, 0.5f), 1.5f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"masterLevel", 1}, "Master Level", zeroOne, 0.8f));
// PITCH BEND
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"pitchBendRange", 1}, "Pitch Bend Range",
juce::NormalisableRange<float>(1.0f, 12.0f, 1.0f), 2.0f));
// DISTORTION
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"distType", 1}, "Dist Type",
juce::StringArray{"Soft Clip", "Hard Clip", "Foldback", "Overdrive"}, 0));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"distAmount", 1}, "Dist Amount", zeroOne, 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"distMix", 1}, "Dist Mix", zeroOne, 0.0f));
// COMPRESSOR
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"compThreshold", 1}, "Comp Threshold",
juce::NormalisableRange<float>(-60.0f, 0.0f, 0.1f), -20.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"compRatio", 1}, "Comp Ratio",
juce::NormalisableRange<float>(1.0f, 20.0f, 0.1f), 4.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"compAttack", 1}, "Comp Attack",
juce::NormalisableRange<float>(0.1f, 100.0f, 0.1f), 10.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"compRelease", 1}, "Comp Release",
juce::NormalisableRange<float>(10.0f, 1000.0f, 1.0f), 100.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"compMakeup", 1}, "Comp Makeup",
juce::NormalisableRange<float>(0.0f, 24.0f, 0.1f), 0.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"compMix", 1}, "Comp Mix", zeroOne, 1.0f));
// AUTO-PAN
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"autoPanRate", 1}, "Pan Rate",
juce::NormalisableRange<float>(0.05f, 20.0f, 0.01f, 0.4f), 2.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"autoPanDepth", 1}, "Pan Depth", zeroOne, 0.0f));
// LFO 1
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"lfo1Rate", 1}, "LFO1 Rate",
juce::NormalisableRange<float>(0.05f, 20.0f, 0.01f, 0.4f), 2.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"lfo1Depth", 1}, "LFO1 Depth", zeroOne, 0.0f));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"lfo1Shape", 1}, "LFO1 Shape",
juce::StringArray{"Sine", "Triangle", "Saw", "Square"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"lfo1Dest", 1}, "LFO1 Dest",
juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0));
// LFO 2
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"lfo2Rate", 1}, "LFO2 Rate",
juce::NormalisableRange<float>(0.05f, 20.0f, 0.01f, 0.4f), 2.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"lfo2Depth", 1}, "LFO2 Depth", zeroOne, 0.0f));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"lfo2Shape", 1}, "LFO2 Shape",
juce::StringArray{"Sine", "Triangle", "Saw", "Square"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"lfo2Dest", 1}, "LFO2 Dest",
juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0));
// DELAY
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"delayTime", 1}, "Delay Time",
juce::NormalisableRange<float>(10.0f, 1000.0f, 1.0f, 0.3f), 200.0f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"delayFeedback", 1}, "Delay Feedback", zeroOne, 0.3f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"delayMix", 1}, "Delay Mix", zeroOne, 0.25f));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"delayPingPong", 1}, "Delay PingPong",
juce::StringArray{"Off", "On"}, 0));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"delaySpread", 1}, "Delay Spread",
juce::NormalisableRange<float>(0.25f, 0.75f, 0.01f), 0.5f));
// REVERB
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"reverbSize", 1}, "Reverb Size", zeroOne, 0.5f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"reverbDamping", 1}, "Reverb Damping", zeroOne, 0.5f));
layout.add(std::make_unique<juce::AudioParameterFloat>(
juce::ParameterID{"reverbMix", 1}, "Reverb Mix", zeroOne, 0.2f));
return layout;
}
void ChromaFlockProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
currentSampleRate = sampleRate;
synth.setCurrentPlaybackSampleRate(sampleRate);
distortion.prepare(sampleRate);
compressor.prepare(sampleRate);
delay.prepare(sampleRate);
reverbFX.prepare(sampleRate);
autoPanPhase = 0.0f;
updateVoiceParameters();
}
void ChromaFlockProcessor::releaseResources() {}
void ChromaFlockProcessor::updateVoiceParameters() {
auto getParam = [&](const juce::String& id) -> float {
auto* p = apvts.getRawParameterValue(id);
return p != nullptr ? p->load() : 0.0f;
};
for (int i = 0; i < synth.getNumVoices(); ++i) {
if (auto* voice = dynamic_cast<SubtractiveVoice*>(synth.getVoice(i))) {
float pbRange = getParam("pitchBendRange");
voice->setPitchBend(getPitchBend() * pbRange);
voice->setParameters(
static_cast<Waveform>(static_cast<int>(getParam("osc1Wave"))),
static_cast<Waveform>(static_cast<int>(getParam("osc2Wave"))),
getParam("osc1Oct"),
getParam("osc2Oct"),
getParam("osc1Semi"),
getParam("osc2Semi"),
getParam("osc1Fine"),
getParam("osc2Fine"),
getParam("osc1Level"),
getParam("osc2Level"),
getParam("phaseOffset"),
getParam("filterCutoff"),
getParam("filterRes"),
static_cast<FilterType>(static_cast<int>(getParam("filterType"))),
getParam("filterEnvAmt"),
getParam("keyTrack"),
getParam("envAttack"),
getParam("envDecay"),
getParam("envSustain"),
getParam("envRelease"),
getParam("fEnvAttack"),
getParam("fEnvDecay"),
getParam("fEnvSustain"),
getParam("fEnvRelease"),
getParam("pan"),
getParam("drive"),
getParam("masterLevel"),
getParam("lfo1Rate"),
getParam("lfo1Depth"),
static_cast<int>(getParam("lfo1Shape")),
static_cast<int>(getParam("lfo1Dest")),
getParam("lfo2Rate"),
getParam("lfo2Depth"),
static_cast<int>(getParam("lfo2Shape")),
static_cast<int>(getParam("lfo2Dest"))
);
}
}
}
void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
juce::ScopedNoDenormals noDenormals;
buffer.clear();
updateVoiceParameters();
for (const auto metadata : midiMessages)
if (metadata.getMessage().isNoteOn())
activeNotes[metadata.getMessage().getNoteNumber()].store(true, std::memory_order_relaxed);
else if (metadata.getMessage().isNoteOff())
activeNotes[metadata.getMessage().getNoteNumber()].store(false, std::memory_order_relaxed);
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
// Read FX params
auto getParam = [&](const juce::String& id) -> float {
auto* p = apvts.getRawParameterValue(id);
return p != nullptr ? p->load() : 0.0f;
};
distortion.setParameters(
static_cast<DistortionType>(static_cast<int>(getParam("distType"))),
getParam("distAmount"),
getParam("distMix"));
compressor.setParameters(
getParam("compThreshold"),
getParam("compRatio"),
getParam("compAttack"),
getParam("compRelease"),
getParam("compMakeup"),
getParam("compMix"));
float panRate = getParam("autoPanRate");
float panDepth = getParam("autoPanDepth");
delay.setParameters(getParam("delayTime"), getParam("delayFeedback"), getParam("delayMix"),
static_cast<int>(getParam("delayPingPong")) == 1, getParam("delaySpread"));
reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, getParam("reverbMix"));
// FX processing: distortion → compression → auto-pan
int numSamples = buffer.getNumSamples();
float* leftData = buffer.getWritePointer(0);
float* rightData = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : leftData;
float twoPi = 6.2831853f;
for (int i = 0; i < numSamples; ++i) {
float left = leftData[i];
float right = rightData[i];
// Distortion (stereo)
left = distortion.process(left);
right = distortion.process(right);
// Compression (stereo)
left = compressor.process(left);
right = compressor.process(right);
// Auto-pan
if (panDepth > 0.001f) {
float panLfo = std::sin(autoPanPhase * twoPi);
float leftGain = 1.0f - panDepth * 0.5f * (1.0f + panLfo);
float rightGain = 1.0f - panDepth * 0.5f * (1.0f - panLfo);
left *= leftGain;
right *= rightGain;
autoPanPhase += panRate / static_cast<float>(currentSampleRate);
if (autoPanPhase >= 1.0f) autoPanPhase -= 1.0f;
}
// Delay
delay.process(left, right);
leftData[i] = left;
rightData[i] = right;
}
// Reverb (block-based)
reverbFX.process(leftData, rightData, numSamples);
// Metering
float sumSquares = 0.0f;
float peak = 0.0f;
for (int i = 0; i < numSamples; ++i) {
float mix = (leftData[i] + rightData[i]) * 0.5f;
int writePos = scopeWritePos.load(std::memory_order_relaxed);
scopeBuffer[writePos] = mix;
scopeWritePos.store((writePos + 1) % scopeBufferSize, std::memory_order_release);
int fftPos = fftWritePos.load(std::memory_order_relaxed);
fftInput[fftPos] = mix;
fftWritePos.store((fftPos + 1) % fftSize, std::memory_order_release);
float absMix = std::abs(mix);
if (absMix > peak) peak = absMix;
sumSquares += mix * mix;
}
float rms = std::sqrt(sumSquares / static_cast<float>(numSamples));
float level = juce::jmax(rms, peak);
level = std::min(level * 5.0f, 1.0f);
float prev = rmsLevel.load(std::memory_order_relaxed);
if (level > prev)
rmsLevel.store(level, std::memory_order_relaxed);
else
rmsLevel.store(prev * 0.95f, std::memory_order_relaxed);
}
juce::AudioProcessorEditor* ChromaFlockProcessor::createEditor() {
return new ChromaFlockEditor(*this);
}
void ChromaFlockProcessor::getStateInformation(juce::MemoryBlock& destData) {
auto state = apvts.copyState();
std::unique_ptr<juce::XmlElement> xml(state.createXml());
copyXmlToBinary(*xml, destData);
}
void ChromaFlockProcessor::setStateInformation(const void* data, int sizeInBytes) {
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
if (xml && xml->hasTagName(apvts.state.getType()))
apvts.replaceState(juce::ValueTree::fromXml(*xml));
}
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {
return new ChromaFlockProcessor();
}

93
Source/PluginProcessor.h Normal file
View file

@ -0,0 +1,93 @@
#pragma once
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_audio_basics/juce_audio_basics.h>
#include <juce_dsp/juce_dsp.h>
#include "DSP/Voice.h"
#include "DSP/Distortion.h"
#include "DSP/Compressor.h"
#include "DSP/Delay.h"
#include "DSP/Reverb.h"
#include "PresetManager.h"
#include <array>
#include <atomic>
class ChromaFlockProcessor : public juce::AudioProcessor {
public:
ChromaFlockProcessor();
~ChromaFlockProcessor() override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
juce::AudioProcessorEditor* createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override { return "ChromaFlock"; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram(int) override {}
const juce::String getProgramName(int) override { return {}; }
void changeProgramName(int, const juce::String&) override {}
void getStateInformation(juce::MemoryBlock& destData) override;
void setStateInformation(const void* data, int sizeInBytes) override;
juce::AudioProcessorValueTreeState apvts;
PresetManager presetManager;
static constexpr int scopeBufferSize = 1024;
std::array<float, scopeBufferSize> scopeBuffer{};
std::atomic<int> scopeWritePos{0};
static constexpr int fftOrder = 11;
static constexpr int fftSize = 1 << fftOrder;
std::array<float, fftSize> fftInput{};
std::atomic<int> fftWritePos{0};
float getRmsLevel() const { return rmsLevel.load(); }
void noteOn(int midiNote, float velocity) {
if (midiNote >= 0 && midiNote < 128)
activeNotes[midiNote].store(true, std::memory_order_relaxed);
synth.noteOn(1, midiNote, velocity);
}
void noteOff(int midiNote) {
if (midiNote >= 0 && midiNote < 128)
activeNotes[midiNote].store(false, std::memory_order_relaxed);
synth.noteOff(1, midiNote, 0.0f, true);
}
bool isNoteActive(int midiNote) const {
return midiNote >= 0 && midiNote < 128
&& activeNotes[midiNote].load(std::memory_order_relaxed);
}
std::atomic<float> pitchBendValue{0.0f};
float getPitchBend() const { return pitchBendValue.load(std::memory_order_relaxed); }
private:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void updateVoiceParameters();
juce::Synthesiser synth;
static constexpr int maxVoices = 16;
Distortion distortion;
Compressor compressor;
Delay delay;
ReverbFX reverbFX;
float autoPanPhase = 0.0f;
double currentSampleRate = 44100.0;
std::atomic<float> rmsLevel{0.0f};
std::array<std::atomic<bool>, 128> activeNotes{};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChromaFlockProcessor)
};

676
Source/PresetManager.h Normal file
View file

@ -0,0 +1,676 @@
#pragma once
#include <juce_audio_processors/juce_audio_processors.h>
#include <vector>
#include <map>
struct Preset {
juce::String name;
juce::String category;
std::map<juce::String, float> params;
};
class PresetManager {
public:
PresetManager() { buildPresets(); }
const std::vector<Preset>& getPresets() const { return presets; }
std::vector<juce::String> getCategories() const {
std::vector<juce::String> cats;
for (auto& p : presets) {
bool found = false;
for (auto& c : cats)
if (c == p.category) { found = true; break; }
if (!found) cats.push_back(p.category);
}
return cats;
}
std::vector<Preset> getPresetsInCategory(const juce::String& cat) const {
std::vector<Preset> result;
for (auto& p : presets)
if (p.category == cat)
result.push_back(p);
return result;
}
void applyPreset(const juce::String& name, juce::AudioProcessorValueTreeState& apvts) {
for (auto& p : presets) {
if (p.name == name) {
for (auto& kv : p.params) {
if (auto* param = apvts.getParameter(kv.first))
param->setValueNotifyingHost(param->convertTo0to1(kv.second));
}
return;
}
}
}
private:
std::vector<Preset> presets;
void add(const juce::String& name, const juce::String& category,
std::map<juce::String, float> params) {
presets.push_back({ name, category, params });
}
void buildPresets() {
// --- DEFAULT ---
add("Init", "Init", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 2}, {"osc2Oct", -1}, {"osc2Semi", 0}, {"osc2Fine", 7}, {"osc2Level", 0.7f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 800}, {"filterRes", 0.7f}, {"filterEnvAmt", 0}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.01f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.01f}, {"envDecay", 0.3f}, {"envSustain", 0.7f}, {"envRelease", 0.5f},
{"pan", 0}, {"drive", 1.5f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -20}, {"compRatio", 4}, {"compAttack", 10}, {"compRelease", 100}, {"compMakeup", 0}, {"compMix", 1},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 200}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.5f}, {"reverbMix", 0}
});
// --- BASS ---
add("Sub Bass", "Bass", {
{"osc1Wave", 0}, {"osc1Oct", -2}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 0}, {"osc2Oct", -1}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 150}, {"filterRes", 0.2f}, {"filterEnvAmt", 0}, {"keyTrack", 0.7f},
{"fEnvAttack", 0.01f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.005f}, {"envDecay", 0.4f}, {"envSustain", 0.7f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 1.2f}, {"masterLevel", 0.85f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -18}, {"compRatio", 6}, {"compAttack", 5}, {"compRelease", 80}, {"compMakeup", 4}, {"compMix", 0.8f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0.2f}, {"lfo1Depth", 0.15f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 200}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.5f}, {"reverbMix", 0}
});
add("Reese Bass", "Bass", {
{"osc1Wave", 1}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 1}, {"osc2Oct", -1}, {"osc2Semi", -7}, {"osc2Fine", 15}, {"osc2Level", 0.7f},
{"phaseOffset", 0.3f},
{"filterType", 0}, {"filterCutoff", 400}, {"filterRes", 0.4f}, {"filterEnvAmt", 0.1f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.01f}, {"fEnvDecay", 0.5f}, {"fEnvSustain", 0.4f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.005f}, {"envDecay", 0.3f}, {"envSustain", 0.8f}, {"envRelease", 0.4f},
{"pan", 0}, {"drive", 2.0f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0.1f}, {"distMix", 0.3f},
{"compThreshold", -15}, {"compRatio", 4}, {"compAttack", 8}, {"compRelease", 100}, {"compMakeup", 3}, {"compMix", 0.7f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0.3f}, {"lfo1Depth", 0.2f}, {"lfo1Shape", 0}, {"lfo1Dest", 3},
{"lfo2Rate", 0.15f}, {"lfo2Depth", 0.1f}, {"lfo2Shape", 1}, {"lfo2Dest", 0},
{"delayTime", 200}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.5f}, {"reverbMix", 0}
});
add("Acid Bass", "Bass", {
{"osc1Wave", 1}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 0}, {"osc2Oct", -2}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 300}, {"filterRes", 0.85f}, {"filterEnvAmt", 0.7f}, {"keyTrack", 0.6f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.2f}, {"fEnvSustain", 0.1f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.001f}, {"envDecay", 0.3f}, {"envSustain", 0.4f}, {"envRelease", 0.2f},
{"pan", 0}, {"drive", 1.8f}, {"masterLevel", 0.85f},
{"distType", 0}, {"distAmount", 0.1f}, {"distMix", 0.2f},
{"compThreshold", -20}, {"compRatio", 5}, {"compAttack", 5}, {"compRelease", 60}, {"compMakeup", 3}, {"compMix", 0.6f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 4.0f}, {"lfo1Depth", 0.3f}, {"lfo1Shape", 2}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 200}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.5f}, {"reverbMix", 0}
});
add("Pluck Bass", "Bass", {
{"osc1Wave", 1}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 2}, {"osc2Oct", -2}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 500}, {"filterRes", 0.3f}, {"filterEnvAmt", 0.5f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.15f}, {"fEnvSustain", 0.1f}, {"fEnvRelease", 0.2f},
{"envAttack", 0.001f}, {"envDecay", 0.15f}, {"envSustain", 0.1f}, {"envRelease", 0.1f},
{"pan", 0}, {"drive", 1.5f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -15}, {"compRatio", 4}, {"compAttack", 3}, {"compRelease", 50}, {"compMakeup", 2}, {"compMix", 0.5f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 200}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.5f}, {"reverbMix", 0}
});
add("Moog Bass", "Bass", {
{"osc1Wave", 2}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.9f},
{"osc2Wave", 2}, {"osc2Oct", -1}, {"osc2Semi", 0}, {"osc2Fine", 8}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 250}, {"filterRes", 0.6f}, {"filterEnvAmt", 0.5f}, {"keyTrack", 0.6f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.25f}, {"fEnvSustain", 0.2f}, {"fEnvRelease", 0.2f},
{"envAttack", 0.001f}, {"envDecay", 0.3f}, {"envSustain", 0.7f}, {"envRelease", 0.15f},
{"pan", 0}, {"drive", 1.6f}, {"masterLevel", 0.85f},
{"distType", 0}, {"distAmount", 0.15f}, {"distMix", 0.2f},
{"compThreshold", -16}, {"compRatio", 5}, {"compAttack", 3}, {"compRelease", 60}, {"compMakeup", 4}, {"compMix", 0.7f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 120}, {"delayFeedback", 0.15f}, {"delayMix", 0.1f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.2f}, {"reverbDamping", 0.7f}, {"reverbMix", 0.05f}
});
add("Gritty Bass", "Bass", {
{"osc1Wave", 1}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 2}, {"osc2Oct", -2}, {"osc2Semi", 0}, {"osc2Fine", 12}, {"osc2Level", 0.5f},
{"phaseOffset", 0.4f},
{"filterType", 0}, {"filterCutoff", 350}, {"filterRes", 0.5f}, {"filterEnvAmt", 0.3f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.2f}, {"fEnvSustain", 0.3f}, {"fEnvRelease", 0.15f},
{"envAttack", 0.001f}, {"envDecay", 0.25f}, {"envSustain", 0.6f}, {"envRelease", 0.15f},
{"pan", 0}, {"drive", 3.5f}, {"masterLevel", 0.75f},
{"distType", 2}, {"distAmount", 0.4f}, {"distMix", 0.35f},
{"compThreshold", -14}, {"compRatio", 6}, {"compAttack", 2}, {"compRelease", 50}, {"compMakeup", 5}, {"compMix", 0.8f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 80}, {"delayFeedback", 0.2f}, {"delayMix", 0.08f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.15f}, {"reverbDamping", 0.8f}, {"reverbMix", 0.05f}
});
add("FM Bass", "Bass", {
{"osc1Wave", 0}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 0}, {"osc2Oct", 2}, {"osc2Semi", 7}, {"osc2Fine", 0}, {"osc2Level", 0.6f},
{"phaseOffset", 0.3f},
{"filterType", 0}, {"filterCutoff", 600}, {"filterRes", 0.2f}, {"filterEnvAmt", 0.4f}, {"keyTrack", 0.7f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.12f}, {"fEnvSustain", 0.0f}, {"fEnvRelease", 0.1f},
{"envAttack", 0.001f}, {"envDecay", 0.2f}, {"envSustain", 0.5f}, {"envRelease", 0.1f},
{"pan", 0}, {"drive", 1.3f}, {"masterLevel", 0.85f},
{"distType", 0}, {"distAmount", 0.1f}, {"distMix", 0.15f},
{"compThreshold", -16}, {"compRatio", 4}, {"compAttack", 2}, {"compRelease", 40}, {"compMakeup", 3}, {"compMix", 0.7f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 100}, {"delayFeedback", 0.1f}, {"delayMix", 0.05f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.3f}, {"reverbDamping", 0.6f}, {"reverbMix", 0.08f}
});
// --- LEAD ---
add("Sync Lead", "Lead", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 2}, {"osc2Oct", 0}, {"osc2Semi", 7}, {"osc2Fine", 0}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 800}, {"filterRes", 0.3f}, {"filterEnvAmt", 0.2f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.005f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.4f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.005f}, {"envDecay", 0.2f}, {"envSustain", 0.7f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 2.0f}, {"masterLevel", 0.75f},
{"distType", 0}, {"distAmount", 0.2f}, {"distMix", 0.3f},
{"compThreshold", -18}, {"compRatio", 3}, {"compAttack", 8}, {"compRelease", 80}, {"compMakeup", 3}, {"compMix", 0.6f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 5.0f}, {"lfo1Depth", 0.15f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 250}, {"delayFeedback", 0.25f}, {"delayMix", 0.15f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.4f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.1f}
});
add("Square Lead", "Lead", {
{"osc1Wave", 2}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.6f},
{"osc2Wave", 2}, {"osc2Oct", 0}, {"osc2Semi", 5}, {"osc2Fine", 10}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 900}, {"filterRes", 0.2f}, {"filterEnvAmt", 0.15f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.005f}, {"fEnvDecay", 0.4f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.005f}, {"envDecay", 0.2f}, {"envSustain", 0.8f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 1.5f}, {"masterLevel", 0.75f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -15}, {"compRatio", 3}, {"compAttack", 10}, {"compRelease", 100}, {"compMakeup", 2}, {"compMix", 0.5f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 3.0f}, {"lfo1Depth", 0.1f}, {"lfo1Shape", 3}, {"lfo1Dest", 2},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 300}, {"delayFeedback", 0.3f}, {"delayMix", 0.2f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.35f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.12f}
});
add("Soft Lead", "Lead", {
{"osc1Wave", 3}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 0}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 700}, {"filterRes", 0.15f}, {"filterEnvAmt", 0.1f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.01f}, {"fEnvDecay", 0.4f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.01f}, {"envDecay", 0.3f}, {"envSustain", 0.7f}, {"envRelease", 0.5f},
{"pan", 0}, {"drive", 1.3f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -20}, {"compRatio", 3}, {"compAttack", 10}, {"compRelease", 120}, {"compMakeup", 2}, {"compMix", 0.4f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0.5f}, {"lfo1Depth", 0.08f}, {"lfo1Shape", 0}, {"lfo1Dest", 1},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 350}, {"delayFeedback", 0.2f}, {"delayMix", 0.15f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.45f}, {"reverbDamping", 0.4f}, {"reverbMix", 0.15f}
});
add("Saw Lead", "Lead", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 1}, {"osc2Oct", 0}, {"osc2Semi", 12}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 850}, {"filterRes", 0.25f}, {"filterEnvAmt", 0.2f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.005f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.005f}, {"envDecay", 0.2f}, {"envSustain", 0.8f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 1.8f}, {"masterLevel", 0.75f},
{"distType", 0}, {"distAmount", 0.15f}, {"distMix", 0.25f},
{"compThreshold", -18}, {"compRatio", 3}, {"compAttack", 8}, {"compRelease", 100}, {"compMakeup", 3}, {"compMix", 0.5f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 6.0f}, {"lfo1Depth", 0.12f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.8f}, {"lfo2Depth", 0.1f}, {"lfo2Shape", 1}, {"lfo2Dest", 3},
{"delayTime", 200}, {"delayFeedback", 0.2f}, {"delayMix", 0.12f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.3f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.1f}
});
// --- PAD ---
add("Warm Pad", "Pad", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.5f},
{"osc2Wave", 3}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 12}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 400}, {"filterRes", 0.2f}, {"filterEnvAmt", 0.1f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.5f}, {"fEnvDecay", 1.0f}, {"fEnvSustain", 0.6f}, {"fEnvRelease", 2.0f},
{"envAttack", 1.5f}, {"envDecay", 1.0f}, {"envSustain", 0.8f}, {"envRelease", 3.0f},
{"pan", 0}, {"drive", 1.2f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -20}, {"compRatio", 2}, {"compAttack", 20}, {"compRelease", 200}, {"compMakeup", 2}, {"compMix", 0.4f},
{"autoPanRate", 0.3f}, {"autoPanDepth", 0.3f},
{"lfo1Rate", 0.25f}, {"lfo1Depth", 0.15f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.1f}, {"lfo2Depth", 0.1f}, {"lfo2Shape", 1}, {"lfo2Dest", 3},
{"delayTime", 400}, {"delayFeedback", 0.3f}, {"delayMix", 0.15f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.7f}, {"reverbDamping", 0.3f}, {"reverbMix", 0.3f}
});
add("Glass Pad", "Pad", {
{"osc1Wave", 0}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.6f},
{"osc2Wave", 3}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 7}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 600}, {"filterRes", 0.15f}, {"filterEnvAmt", 0.05f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.8f}, {"fEnvDecay", 1.5f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 2.5f},
{"envAttack", 2.0f}, {"envDecay", 1.5f}, {"envSustain", 0.7f}, {"envRelease", 4.0f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -25}, {"compRatio", 2}, {"compAttack", 30}, {"compRelease", 250}, {"compMakeup", 3}, {"compMix", 0.3f},
{"autoPanRate", 0.4f}, {"autoPanDepth", 0.25f},
{"lfo1Rate", 0.15f}, {"lfo1Depth", 0.2f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.08f}, {"lfo2Depth", 0.12f}, {"lfo2Shape", 0}, {"lfo2Dest", 1},
{"delayTime", 500}, {"delayFeedback", 0.35f}, {"delayMix", 0.2f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.8f}, {"reverbDamping", 0.2f}, {"reverbMix", 0.35f}
});
add("Dark Pad", "Pad", {
{"osc1Wave", 2}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.5f},
{"osc2Wave", 1}, {"osc2Oct", -1}, {"osc2Semi", 0}, {"osc2Fine", 15}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 250}, {"filterRes", 0.3f}, {"filterEnvAmt", 0.15f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.3f}, {"fEnvDecay", 0.8f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 1.5f},
{"envAttack", 1.0f}, {"envDecay", 0.8f}, {"envSustain", 0.8f}, {"envRelease", 3.0f},
{"pan", 0}, {"drive", 1.3f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -18}, {"compRatio", 3}, {"compAttack", 15}, {"compRelease", 150}, {"compMakeup", 2}, {"compMix", 0.5f},
{"autoPanRate", 0.2f}, {"autoPanDepth", 0.2f},
{"lfo1Rate", 0.2f}, {"lfo1Depth", 0.25f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.35f}, {"lfo2Depth", 0.1f}, {"lfo2Shape", 2}, {"lfo2Dest", 3},
{"delayTime", 600}, {"delayFeedback", 0.4f}, {"delayMix", 0.25f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.85f}, {"reverbDamping", 0.25f}, {"reverbMix", 0.4f}
});
add("Shimmer Pad", "Pad", {
{"osc1Wave", 0}, {"osc1Oct", 1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.4f},
{"osc2Wave", 3}, {"osc2Oct", 0}, {"osc2Semi", 12}, {"osc2Fine", 0}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 500}, {"filterRes", 0.2f}, {"filterEnvAmt", 0.3f}, {"keyTrack", 0.6f},
{"fEnvAttack", 1.0f}, {"fEnvDecay", 2.0f}, {"fEnvSustain", 0.7f}, {"fEnvRelease", 3.0f},
{"envAttack", 2.5f}, {"envDecay", 2.0f}, {"envSustain", 0.9f}, {"envRelease", 5.0f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -25}, {"compRatio", 2}, {"compAttack", 25}, {"compRelease", 200}, {"compMakeup", 3}, {"compMix", 0.3f},
{"autoPanRate", 0.5f}, {"autoPanDepth", 0.4f},
{"lfo1Rate", 0.12f}, {"lfo1Depth", 0.3f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.05f}, {"lfo2Depth", 0.15f}, {"lfo2Shape", 0}, {"lfo2Dest", 1},
{"delayTime", 600}, {"delayFeedback", 0.5f}, {"delayMix", 0.3f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.9f}, {"reverbDamping", 0.15f}, {"reverbMix", 0.5f}
});
add("Evolving Pad", "Pad", {
{"osc1Wave", 3}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", -5}, {"osc1Level", 0.5f},
{"osc2Wave", 0}, {"osc2Oct", 0}, {"osc2Semi", 7}, {"osc2Fine", 8}, {"osc2Level", 0.5f},
{"phaseOffset", 0.2f},
{"filterType", 0}, {"filterCutoff", 350}, {"filterRes", 0.25f}, {"filterEnvAmt", 0.2f}, {"keyTrack", 0.4f},
{"fEnvAttack", 1.5f}, {"fEnvDecay", 2.5f}, {"fEnvSustain", 0.6f}, {"fEnvRelease", 4.0f},
{"envAttack", 3.0f}, {"envDecay", 2.0f}, {"envSustain", 0.85f}, {"envRelease", 5.0f},
{"pan", 0}, {"drive", 1.1f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -22}, {"compRatio", 2}, {"compAttack", 30}, {"compRelease", 300}, {"compMakeup", 2}, {"compMix", 0.3f},
{"autoPanRate", 0.15f}, {"autoPanDepth", 0.5f},
{"lfo1Rate", 0.08f}, {"lfo1Depth", 0.35f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.03f}, {"lfo2Depth", 0.2f}, {"lfo2Shape", 1}, {"lfo2Dest", 1},
{"delayTime", 700}, {"delayFeedback", 0.45f}, {"delayMix", 0.25f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.9f}, {"reverbDamping", 0.1f}, {"reverbMix", 0.45f}
});
add("Choir Pad", "Pad", {
{"osc1Wave", 0}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.6f},
{"osc2Wave", 3}, {"osc2Oct", 0}, {"osc2Semi", 12}, {"osc2Fine", -3}, {"osc2Level", 0.4f},
{"phaseOffset", 0.1f},
{"filterType", 0}, {"filterCutoff", 700}, {"filterRes", 0.1f}, {"filterEnvAmt", 0.05f}, {"keyTrack", 0.6f},
{"fEnvAttack", 0.8f}, {"fEnvDecay", 1.2f}, {"fEnvSustain", 0.7f}, {"fEnvRelease", 2.0f},
{"envAttack", 1.2f}, {"envDecay", 1.0f}, {"envSustain", 0.85f}, {"envRelease", 3.0f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.75f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -22}, {"compRatio", 2}, {"compAttack", 25}, {"compRelease", 250}, {"compMakeup", 3}, {"compMix", 0.3f},
{"autoPanRate", 0.2f}, {"autoPanDepth", 0.35f},
{"lfo1Rate", 0.1f}, {"lfo1Depth", 0.2f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.06f}, {"lfo2Depth", 0.1f}, {"lfo2Shape", 0}, {"lfo2Dest", 1},
{"delayTime", 550}, {"delayFeedback", 0.4f}, {"delayMix", 0.2f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.85f}, {"reverbDamping", 0.2f}, {"reverbMix", 0.4f}
});
// --- KEYS ---
add("Electric Piano", "Keys", {
{"osc1Wave", 0}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 0}, {"osc2Oct", 0}, {"osc2Semi", 12}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 800}, {"filterRes", 0.1f}, {"filterEnvAmt", 0.1f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.5f}, {"fEnvSustain", 0.3f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.001f}, {"envDecay", 0.5f}, {"envSustain", 0.3f}, {"envRelease", 0.8f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -15}, {"compRatio", 3}, {"compAttack", 5}, {"compRelease", 80}, {"compMakeup", 2}, {"compMix", 0.4f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 250}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.4f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.15f}
});
add("Organ", "Keys", {
{"osc1Wave", 2}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.5f},
{"osc2Wave", 2}, {"osc2Oct", 1}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 1000}, {"filterRes", 0.1f}, {"filterEnvAmt", 0}, {"keyTrack", 0.8f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.1f}, {"fEnvSustain", 0.9f}, {"fEnvRelease", 0.1f},
{"envAttack", 0.001f}, {"envDecay", 0.1f}, {"envSustain", 0.9f}, {"envRelease", 0.1f},
{"pan", 0}, {"drive", 1.2f}, {"masterLevel", 0.75f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -18}, {"compRatio", 3}, {"compAttack", 8}, {"compRelease", 80}, {"compMakeup", 2}, {"compMix", 0.5f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 6.0f}, {"lfo1Depth", 0.05f}, {"lfo1Shape", 3}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 150}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.3f}, {"reverbDamping", 0.6f}, {"reverbMix", 0.1f}
});
add("Clav", "Keys", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 2}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 700}, {"filterRes", 0.4f}, {"filterEnvAmt", 0.6f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.1f}, {"fEnvSustain", 0.1f}, {"fEnvRelease", 0.15f},
{"envAttack", 0.001f}, {"envDecay", 0.2f}, {"envSustain", 0.05f}, {"envRelease", 0.1f},
{"pan", 0}, {"drive", 1.5f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -15}, {"compRatio", 4}, {"compAttack", 5}, {"compRelease", 60}, {"compMakeup", 3}, {"compMix", 0.5f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 100}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.25f}, {"reverbDamping", 0.7f}, {"reverbMix", 0.08f}
});
add("Rhodes", "Keys", {
{"osc1Wave", 0}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.65f},
{"osc2Wave", 0}, {"osc2Oct", 0}, {"osc2Semi", 12}, {"osc2Fine", 5}, {"osc2Level", 0.35f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 650}, {"filterRes", 0.15f}, {"filterEnvAmt", 0.15f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.6f}, {"fEnvSustain", 0.25f}, {"fEnvRelease", 0.7f},
{"envAttack", 0.001f}, {"envDecay", 0.6f}, {"envSustain", 0.25f}, {"envRelease", 1.0f},
{"pan", 0}, {"drive", 1.1f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0.05f}, {"distMix", 0.1f},
{"compThreshold", -18}, {"compRatio", 3}, {"compAttack", 8}, {"compRelease", 100}, {"compMakeup", 2}, {"compMix", 0.4f},
{"autoPanRate", 0.8f}, {"autoPanDepth", 0.15f},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 350}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.55f}, {"reverbDamping", 0.4f}, {"reverbMix", 0.25f}
});
add("Bell Keys", "Keys", {
{"osc1Wave", 0}, {"osc1Oct", 1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.6f},
{"osc2Wave", 0}, {"osc2Oct", 1}, {"osc2Semi", 12}, {"osc2Fine", 3}, {"osc2Level", 0.4f},
{"phaseOffset", 0.3f},
{"filterType", 0}, {"filterCutoff", 900}, {"filterRes", 0.05f}, {"filterEnvAmt", 0.05f}, {"keyTrack", 0.7f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.8f}, {"fEnvSustain", 0.0f}, {"fEnvRelease", 1.5f},
{"envAttack", 0.001f}, {"envDecay", 0.8f}, {"envSustain", 0.0f}, {"envRelease", 2.0f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.75f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -20}, {"compRatio", 2}, {"compAttack", 15}, {"compRelease", 150}, {"compMakeup", 2}, {"compMix", 0.3f},
{"autoPanRate", 0.3f}, {"autoPanDepth", 0.2f},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 400}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.75f}, {"reverbDamping", 0.2f}, {"reverbMix", 0.35f}
});
add("Toy Piano", "Keys", {
{"osc1Wave", 0}, {"osc1Oct", 1}, {"osc1Semi", 0}, {"osc1Fine", 12}, {"osc1Level", 0.5f},
{"osc2Wave", 2}, {"osc2Oct", 2}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 1100}, {"filterRes", 0.05f}, {"filterEnvAmt", 0.1f}, {"keyTrack", 0.8f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.15f}, {"fEnvSustain", 0.0f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.001f}, {"envDecay", 0.15f}, {"envSustain", 0.0f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -15}, {"compRatio", 3}, {"compAttack", 3}, {"compRelease", 40}, {"compMakeup", 2}, {"compMix", 0.3f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 180}, {"delayFeedback", 0}, {"delayMix", 0}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.35f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.12f}
});
// --- FX ---
add("Wobble", "FX1", {
{"osc1Wave", 2}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 1}, {"osc2Oct", -1}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 400}, {"filterRes", 0.8f}, {"filterEnvAmt", 0.9f}, {"keyTrack", 0.3f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.005f}, {"envDecay", 0.3f}, {"envSustain", 0.8f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 2.5f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0.3f}, {"distMix", 0.4f},
{"compThreshold", -15}, {"compRatio", 5}, {"compAttack", 5}, {"compRelease", 80}, {"compMakeup", 4}, {"compMix", 0.7f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 4.0f}, {"lfo1Depth", 0.7f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 8.0f}, {"lfo2Depth", 0.3f}, {"lfo2Shape", 3}, {"lfo2Dest", 3},
{"delayTime", 200}, {"delayFeedback", 0.3f}, {"delayMix", 0.15f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.4f}, {"reverbMix", 0.15f}
});
add("Distorted Lead", "FX1", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 1}, {"osc2Oct", 0}, {"osc2Semi", 7}, {"osc2Fine", 0}, {"osc2Level", 0.6f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 600}, {"filterRes", 0.3f}, {"filterEnvAmt", 0.2f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.005f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.005f}, {"envDecay", 0.2f}, {"envSustain", 0.8f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 3.0f}, {"masterLevel", 0.65f},
{"distType", 0}, {"distAmount", 0.5f}, {"distMix", 0.6f},
{"compThreshold", -20}, {"compRatio", 4}, {"compAttack", 8}, {"compRelease", 100}, {"compMakeup", 6}, {"compMix", 0.8f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 5.0f}, {"lfo1Depth", 0.2f}, {"lfo1Shape", 2}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 150}, {"delayFeedback", 0.2f}, {"delayMix", 0.1f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.3f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.1f}
});
add("Auto-Pan Spread", "FX1", {
{"osc1Wave", 0}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.6f},
{"osc2Wave", 3}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 800}, {"filterRes", 0.1f}, {"filterEnvAmt", 0}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.01f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.01f}, {"envDecay", 0.3f}, {"envSustain", 0.7f}, {"envRelease", 0.5f},
{"pan", 0}, {"drive", 1.0f}, {"masterLevel", 0.8f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -20}, {"compRatio", 3}, {"compAttack", 10}, {"compRelease", 100}, {"compMakeup", 2}, {"compMix", 0.4f},
{"autoPanRate", 2.0f}, {"autoPanDepth", 0.8f},
{"lfo1Rate", 0.3f}, {"lfo1Depth", 0.2f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.15f}, {"lfo2Depth", 0.15f}, {"lfo2Shape", 1}, {"lfo2Dest", 3},
{"delayTime", 300}, {"delayFeedback", 0.25f}, {"delayMix", 0.15f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.4f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.15f}
});
add("Filtered Noise", "FX1", {
{"osc1Wave", 4}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 4}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 2}, {"filterCutoff", 400}, {"filterRes", 0.6f}, {"filterEnvAmt", 0.8f}, {"keyTrack", 0},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.5f}, {"fEnvSustain", 0.3f}, {"fEnvRelease", 0.5f},
{"envAttack", 0.01f}, {"envDecay", 0.5f}, {"envSustain", 0.5f}, {"envRelease", 1.0f},
{"pan", 0}, {"drive", 1.5f}, {"masterLevel", 0.7f},
{"distType", 1}, {"distAmount", 0.2f}, {"distMix", 0.3f},
{"compThreshold", -18}, {"compRatio", 4}, {"compAttack", 5}, {"compRelease", 100}, {"compMakeup", 4}, {"compMix", 0.6f},
{"autoPanRate", 0.5f}, {"autoPanDepth", 0.6f},
{"lfo1Rate", 2.5f}, {"lfo1Depth", 0.5f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.4f}, {"lfo2Depth", 0.3f}, {"lfo2Shape", 2}, {"lfo2Dest", 3},
{"delayTime", 250}, {"delayFeedback", 0.35f}, {"delayMix", 0.2f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.6f}, {"reverbDamping", 0.3f}, {"reverbMix", 0.25f}
});
add("Dub Delay", "FX2", {
{"osc1Wave", 1}, {"osc1Oct", -1}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 2}, {"osc2Oct", -1}, {"osc2Semi", 0}, {"osc2Fine", 5}, {"osc2Level", 0.5f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 500}, {"filterRes", 0.7f}, {"filterEnvAmt", 0.4f}, {"keyTrack", 0.3f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.3f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.001f}, {"envDecay", 0.2f}, {"envSustain", 0.7f}, {"envRelease", 0.2f},
{"pan", 0}, {"drive", 1.5f}, {"masterLevel", 0.7f},
{"distType", 0}, {"distAmount", 0.1f}, {"distMix", 0.15f},
{"compThreshold", -16}, {"compRatio", 4}, {"compAttack", 5}, {"compRelease", 80}, {"compMakeup", 3}, {"compMix", 0.5f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 0}, {"lfo1Depth", 0}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 500}, {"delayFeedback", 0.7f}, {"delayMix", 0.4f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.6f}, {"reverbDamping", 0.4f}, {"reverbMix", 0.3f}
});
add("Reverse Reverb", "FX2", {
{"osc1Wave", 3}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.6f},
{"osc2Wave", 0}, {"osc2Oct", 1}, {"osc2Semi", 12}, {"osc2Fine", 0}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 600}, {"filterRes", 0.2f}, {"filterEnvAmt", 0.3f}, {"keyTrack", 0.5f},
{"fEnvAttack", 0.5f}, {"fEnvDecay", 1.0f}, {"fEnvSustain", 0.5f}, {"fEnvRelease", 2.0f},
{"envAttack", 0.3f}, {"envDecay", 0.8f}, {"envSustain", 0.6f}, {"envRelease", 1.5f},
{"pan", 0}, {"drive", 1.2f}, {"masterLevel", 0.65f},
{"distType", 0}, {"distAmount", 0}, {"distMix", 0},
{"compThreshold", -20}, {"compRatio", 2}, {"compAttack", 20}, {"compRelease", 200}, {"compMakeup", 2}, {"compMix", 0.3f},
{"autoPanRate", 0.3f}, {"autoPanDepth", 0.3f},
{"lfo1Rate", 0.1f}, {"lfo1Depth", 0.3f}, {"lfo1Shape", 1}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 800}, {"delayFeedback", 0.6f}, {"delayMix", 0.5f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.95f}, {"reverbDamping", 0.1f}, {"reverbMix", 0.6f}
});
add("Laser Zap", "FX2", {
{"osc1Wave", 1}, {"osc1Oct", 2}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 4}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.3f},
{"phaseOffset", 0.5f},
{"filterType", 0}, {"filterCutoff", 200}, {"filterRes", 0.9f}, {"filterEnvAmt", 1.0f}, {"keyTrack", 0},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.08f}, {"fEnvSustain", 0.0f}, {"fEnvRelease", 0.05f},
{"envAttack", 0.001f}, {"envDecay", 0.1f}, {"envSustain", 0.0f}, {"envRelease", 0.05f},
{"pan", 0}, {"drive", 2.0f}, {"masterLevel", 0.7f},
{"distType", 1}, {"distAmount", 0.3f}, {"distMix", 0.4f},
{"compThreshold", -15}, {"compRatio", 5}, {"compAttack", 1}, {"compRelease", 30}, {"compMakeup", 5}, {"compMix", 0.7f},
{"autoPanRate", 2}, {"autoPanDepth", 0},
{"lfo1Rate", 12.0f}, {"lfo1Depth", 0.8f}, {"lfo1Shape", 2}, {"lfo1Dest", 0},
{"lfo2Rate", 0}, {"lfo2Depth", 0}, {"lfo2Shape", 0}, {"lfo2Dest", 0},
{"delayTime", 100}, {"delayFeedback", 0.4f}, {"delayMix", 0.25f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.3f}, {"reverbDamping", 0.6f}, {"reverbMix", 0.2f}
});
add("Space Drone", "FX2", {
{"osc1Wave", 0}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", -7}, {"osc1Level", 0.5f},
{"osc2Wave", 3}, {"osc2Oct", 0}, {"osc2Semi", 7}, {"osc2Fine", 7}, {"osc2Level", 0.5f},
{"phaseOffset", 0.1f},
{"filterType", 0}, {"filterCutoff", 300}, {"filterRes", 0.5f}, {"filterEnvAmt", 0.2f}, {"keyTrack", 0.3f},
{"fEnvAttack", 2.0f}, {"fEnvDecay", 3.0f}, {"fEnvSustain", 0.7f}, {"fEnvRelease", 5.0f},
{"envAttack", 3.0f}, {"envDecay", 2.0f}, {"envSustain", 0.9f}, {"envRelease", 6.0f},
{"pan", 0}, {"drive", 1.1f}, {"masterLevel", 0.6f},
{"distType", 0}, {"distAmount", 0.05f}, {"distMix", 0.1f},
{"compThreshold", -25}, {"compRatio", 2}, {"compAttack", 40}, {"compRelease", 500}, {"compMakeup", 3}, {"compMix", 0.3f},
{"autoPanRate", 0.1f}, {"autoPanDepth", 0.6f},
{"lfo1Rate", 0.05f}, {"lfo1Depth", 0.4f}, {"lfo1Shape", 0}, {"lfo1Dest", 0},
{"lfo2Rate", 0.02f}, {"lfo2Depth", 0.25f}, {"lfo2Shape", 1}, {"lfo2Dest", 3},
{"delayTime", 900}, {"delayFeedback", 0.75f}, {"delayMix", 0.5f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.95f}, {"reverbDamping", 0.05f}, {"reverbMix", 0.7f}
});
add("Ring Mod", "FX2", {
{"osc1Wave", 1}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.7f},
{"osc2Wave", 0}, {"osc2Oct", 3}, {"osc2Semi", 5}, {"osc2Fine", 0}, {"osc2Level", 0.6f},
{"phaseOffset", 0.5f},
{"filterType", 2}, {"filterCutoff", 800}, {"filterRes", 0.3f}, {"filterEnvAmt", 0.5f}, {"keyTrack", 0.4f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.3f}, {"fEnvSustain", 0.4f}, {"fEnvRelease", 0.3f},
{"envAttack", 0.001f}, {"envDecay", 0.3f}, {"envSustain", 0.7f}, {"envRelease", 0.3f},
{"pan", 0}, {"drive", 2.0f}, {"masterLevel", 0.65f},
{"distType", 3}, {"distAmount", 0.3f}, {"distMix", 0.3f},
{"compThreshold", -15}, {"compRatio", 4}, {"compAttack", 3}, {"compRelease", 50}, {"compMakeup", 4}, {"compMix", 0.6f},
{"autoPanRate", 3.0f}, {"autoPanDepth", 0.5f},
{"lfo1Rate", 7.0f}, {"lfo1Depth", 0.6f}, {"lfo1Shape", 3}, {"lfo1Dest", 0},
{"lfo2Rate", 11.0f}, {"lfo2Depth", 0.4f}, {"lfo2Shape", 2}, {"lfo2Dest", 2},
{"delayTime", 180}, {"delayFeedback", 0.3f}, {"delayMix", 0.2f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.5f}, {"reverbDamping", 0.5f}, {"reverbMix", 0.2f}
});
add("Glitch Stutter", "FX2", {
{"osc1Wave", 2}, {"osc1Oct", 0}, {"osc1Semi", 0}, {"osc1Fine", 0}, {"osc1Level", 0.8f},
{"osc2Wave", 4}, {"osc2Oct", 0}, {"osc2Semi", 0}, {"osc2Fine", 0}, {"osc2Level", 0.4f},
{"phaseOffset", 0.5f},
{"filterType", 1}, {"filterCutoff", 600}, {"filterRes", 0.6f}, {"filterEnvAmt", 0.7f}, {"keyTrack", 0.2f},
{"fEnvAttack", 0.001f}, {"fEnvDecay", 0.05f}, {"fEnvSustain", 0.0f}, {"fEnvRelease", 0.03f},
{"envAttack", 0.001f}, {"envDecay", 0.05f}, {"envSustain", 0.0f}, {"envRelease", 0.03f},
{"pan", 0}, {"drive", 3.0f}, {"masterLevel", 0.6f},
{"distType", 1}, {"distAmount", 0.5f}, {"distMix", 0.5f},
{"compThreshold", -12}, {"compRatio", 8}, {"compAttack", 1}, {"compRelease", 20}, {"compMakeup", 6}, {"compMix", 0.8f},
{"autoPanRate", 8.0f}, {"autoPanDepth", 0.8f},
{"lfo1Rate", 15.0f}, {"lfo1Depth", 1.0f}, {"lfo1Shape", 3}, {"lfo1Dest", 0},
{"lfo2Rate", 0.1f}, {"lfo2Depth", 0.5f}, {"lfo2Shape", 3}, {"lfo2Dest", 3},
{"delayTime", 60}, {"delayFeedback", 0.5f}, {"delayMix", 0.3f}, {"delayPingPong", 0}, {"delaySpread", 0.5f},
{"reverbSize", 0.4f}, {"reverbDamping", 0.3f}, {"reverbMix", 0.15f}
});
}
};