mirror of
https://codeberg.org/armin/monostep.git
synced 2026-09-01 20:30:46 +02:00
189 lines
4.3 KiB
C
189 lines
4.3 KiB
C
|
|
#pragma once
|
||
|
|
|
||
|
|
#include <JuceHeader.h>
|
||
|
|
#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<float>& 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<float>::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
|