140 lines
4.1 KiB
C
140 lines
4.1 KiB
C
#include "synth.h"
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
void synthInit(Synth *s, float sampleRate)
|
|
{
|
|
s->sampleRate = sampleRate;
|
|
s->attackSec = 0.005f;
|
|
s->releaseSec = 0.050f;
|
|
s->dutyCycle = 0.5f;
|
|
s->waveform = WAVE_SINE;
|
|
s->lastStolenVoice = 0;
|
|
|
|
for (int i = 0; i < VOICE_COUNT; i++) {
|
|
s->voices[i].phase = 0.0f;
|
|
s->voices[i].freqHz = 440.0f;
|
|
s->voices[i].amp = 0.0f;
|
|
s->voices[i].targetAmp = 0.0f;
|
|
s->voices[i].active = 0;
|
|
s->voices[i].midiNote = -1;
|
|
s->pitchBend = 0.0f;
|
|
s->pitchBendRange = 2.0f;
|
|
}
|
|
}
|
|
|
|
float waveformSample(Waveform w, float phase, float dutyCycle)
|
|
{
|
|
switch (w) {
|
|
case WAVE_SINE:
|
|
return sinf(2.0f * (float)M_PI * phase);
|
|
case WAVE_TRIANGLE:
|
|
return (phase < 0.5f)
|
|
? ( 4.0f * phase - 1.0f)
|
|
: (-4.0f * phase + 3.0f);
|
|
case WAVE_SAW:
|
|
return 2.0f * phase - 1.0f;
|
|
case WAVE_RAMP:
|
|
return 1.0f - 2.0f * phase;
|
|
case WAVE_PULSE:
|
|
return (phase < dutyCycle) ? 1.0f : -1.0f;
|
|
case WAVE_NOISE:
|
|
return ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
|
|
default:
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
const char *waveformName(Waveform w)
|
|
{
|
|
switch (w) {
|
|
case WAVE_SINE: return "Sine";
|
|
case WAVE_TRIANGLE: return "Triangle";
|
|
case WAVE_SAW: return "Saw";
|
|
case WAVE_RAMP: return "Ramp";
|
|
case WAVE_PULSE: return "Pulse";
|
|
case WAVE_NOISE: return "Noise";
|
|
default: return "???";
|
|
}
|
|
}
|
|
|
|
void synthNoteOn(Synth *s, int midiNote)
|
|
{
|
|
float hz = 440.0f * powf(2.0f, (midiNote - 69) / 12.0f);
|
|
|
|
for (int i = 0; i < VOICE_COUNT; i++) {
|
|
if (!s->voices[i].active) {
|
|
s->voices[i].freqHz = hz;
|
|
s->voices[i].midiNote = midiNote;
|
|
s->voices[i].targetAmp = 1.0f;
|
|
s->voices[i].active = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// No free voice — steal round-robin
|
|
int i = s->lastStolenVoice % VOICE_COUNT;
|
|
s->lastStolenVoice++;
|
|
s->voices[i].freqHz = hz;
|
|
s->voices[i].midiNote = midiNote;
|
|
s->voices[i].targetAmp = 1.0f;
|
|
s->voices[i].phase = 0.0f;
|
|
s->voices[i].active = 1;
|
|
}
|
|
|
|
void synthNoteOff(Synth *s, int midiNote)
|
|
{
|
|
for (int i = 0; i < VOICE_COUNT; i++) {
|
|
if (s->voices[i].active && s->voices[i].midiNote == midiNote)
|
|
s->voices[i].targetAmp = 0.0f;
|
|
}
|
|
}
|
|
|
|
void synthFillBuffer(Synth *s, int16_t *out, int frames)
|
|
{
|
|
const float sr = s->sampleRate;
|
|
const float attackInc = (s->attackSec <= 0.0f) ? 1.0f : (1.0f / (s->attackSec * sr));
|
|
const float releaseInc = (s->releaseSec <= 0.0f) ? 1.0f : (1.0f / (s->releaseSec * sr));
|
|
|
|
for (int i = 0; i < frames; i++) {
|
|
float mix = 0.0f;
|
|
|
|
for (int v = 0; v < VOICE_COUNT; v++) {
|
|
Voice *vv = &s->voices[v];
|
|
if (!vv->active) continue;
|
|
|
|
if (vv->targetAmp > vv->amp) {
|
|
vv->amp += attackInc;
|
|
if (vv->amp > vv->targetAmp) vv->amp = vv->targetAmp;
|
|
} else if (vv->targetAmp < vv->amp) {
|
|
vv->amp -= releaseInc;
|
|
if (vv->amp < vv->targetAmp) vv->amp = vv->targetAmp;
|
|
}
|
|
|
|
if (vv->amp <= 0.0f && vv->targetAmp == 0.0f) {
|
|
vv->active = 0;
|
|
continue;
|
|
}
|
|
|
|
//vv->phase += vv->freqHz / sr;
|
|
float bendMultiplier = powf(2.0f, (s->pitchBend * s->pitchBendRange) / 12.0f);
|
|
vv->phase += (vv->freqHz * bendMultiplier) / sr;
|
|
if (vv->phase >= 1.0f) vv->phase -= 1.0f;
|
|
|
|
mix += waveformSample(s->waveform, vv->phase, s->dutyCycle) * vv->amp;
|
|
}
|
|
|
|
mix *= (0.2f / VOICE_COUNT) * 4.0f;
|
|
|
|
int32_t sample = (int32_t)lrintf(mix * 32767.0f);
|
|
if (sample > 32767) sample = 32767;
|
|
if (sample < -32768) sample = -32768;
|
|
out[i] = (int16_t)sample;
|
|
}
|
|
}
|