#pragma once #include #include "WaveTables.h" namespace monostep { class SynthVoice { public: struct Params { Waveform waveA = Waveform::saw; Waveform waveB = Waveform::square; float detuneRatio = 1.0f; float mix = 0.5f; float cutoff = 7000.0f; float resonance = 0.15f; float attack = 0.005f; float decay = 0.3f; float sustain = 0.7f; float release = 0.4f; float glide = 0.12f; float master = 0.9f; }; void prepare (double sr) { sampleRate = (float) sr; reset(); } void reset() { phaseA = phaseB = 0.0f; currentFreq = targetFreq = 440.0f; smoothing = 0.0f; filterLow = filterBand = 0.0f; env = 0.0f; envStage = Stage::idle; gate = false; } void setParams (const Params& p) { params = p; } bool isActive() const { return gate; } void noteOn (float freqHz, bool legato) { targetFreq = freqHz; if (gate && legato) { const float time = std::max (params.glide, 0.001f); smoothing = 1.0f - std::exp (-1.0f / (time * sampleRate)); } else { currentFreq = targetFreq; smoothing = 0.0f; retriggerEnvelope(); } gate = true; } void noteOff() { if (gate) { gate = false; if (envStage != Stage::idle && envStage != Stage::release) envStage = Stage::release; } } void render (juce::AudioBuffer& buffer, int numSamples) { auto* out = buffer.getWritePointer (0); for (int i = 0; i < numSamples; ++i) out[i] = renderSample(); } private: enum class Stage { idle, attack, decay, sustain, release }; void retriggerEnvelope() { envStage = Stage::attack; } void updateEnvelope() { const float sr = sampleRate; switch (envStage) { case Stage::attack: env += 1.0f / (params.attack * sr); if (env >= 1.0f) { env = 1.0f; envStage = Stage::decay; } break; case Stage::decay: env -= (1.0f - params.sustain) / (params.decay * sr); if (env <= params.sustain) { env = params.sustain; envStage = Stage::sustain; } break; case Stage::sustain: env = params.sustain; break; case Stage::release: env -= 1.0f / (params.release * sr); if (env <= 0.0f) { env = 0.0f; envStage = Stage::idle; } break; case Stage::idle: break; } } void updateGlide() { if (smoothing > 0.0f) { currentFreq += (targetFreq - currentFreq) * smoothing; if (std::fabs (targetFreq - currentFreq) < 0.01f) { currentFreq = targetFreq; smoothing = 0.0f; } } } float processFilter (float input) { const float f = 2.0f * std::sin (juce::MathConstants::pi * params.cutoff / sampleRate); const float q = 1.0f / (1.0f + params.resonance * 9.0f); filterLow += f * filterBand; const float high = input - filterLow - q * filterBand; filterBand += f * high; return filterLow; } float renderSample() { updateEnvelope(); updateGlide(); const float incA = currentFreq / sampleRate; const float incB = (currentFreq * params.detuneRatio) / sampleRate; const float a = renderWave (params.waveA, phaseA, incA); const float b = renderWave (params.waveB, phaseB, incB); phaseA = frac (phaseA + incA); phaseB = frac (phaseB + incB); float out = (1.0f - params.mix) * a + params.mix * b; out = processFilter (out); return out * env * params.master; } float sampleRate = 44100.0f; Params params; float phaseA = 0.0f; float phaseB = 0.0f; float currentFreq = 440.0f; float targetFreq = 440.0f; float smoothing = 0.0f; float filterLow = 0.0f; float filterBand = 0.0f; float env = 0.0f; Stage envStage = Stage::idle; bool gate = false; }; } // namespace monostep