chromaflock/Source/DSP/Voice.h

264 lines
9.4 KiB
C++

#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)
* globalTune;
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)
* globalTune;
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();
quickFade = 1.0f;
quickFadeStep = 0.0f;
}
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;
}
// Per-voice quick fade (triggered on preset change)
if (quickFadeStep > 0.0f)
quickFade = std::max(0.0f, quickFade - quickFadeStep);
// 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);
// Apply LFO to OSC2 phase (vibrato-style phase sweep)
if (lfo1Depth > 0.001f && lfo1Dest == LFODest::OSC2Phase)
osc2.setPhase(phaseOffset2 + lfo1Out * 0.5f);
if (lfo2Depth > 0.001f && lfo2Dest == LFODest::OSC2Phase)
osc2.setPhase(phaseOffset2 + lfo2Out * 0.5f);
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, 20.0f, static_cast<float>(getSampleRate() * 0.49));
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 * quickFade);
outputBuffer.addSample(1, i, right * outputLevel * quickFade);
if (quickFade <= 0.0f) {
clearCurrentNote();
return;
}
}
}
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 * 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; }
// Quickly fade out this voice (e.g. when a preset is switched) so any
// long release tail from the previous sound is silenced over `seconds`.
void triggerQuickRelease(float seconds) {
double sr = getSampleRate();
if (sr <= 0.0) sr = 44100.0;
quickFadeStep = 1.0f / static_cast<float>(seconds * sr);
env.noteOff();
filterEnv.noteOff();
}
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 = 8000.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 globalTune = 0.5f; // synth tuned down one octave (1/2 of normal pitch)
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;
float quickFade = 1.0f;
float quickFadeStep = 0.0f;
};