commit
1383f00b1c
33 changed files with 1843 additions and 0 deletions
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,31 @@ |
||||
cmake_minimum_required(VERSION 3.26) |
||||
project(synth) |
||||
|
||||
set(CMAKE_CXX_STANDARD 20) |
||||
if(MSVC) |
||||
set(CMAKE_CXX_FLAGS "/W4") |
||||
set(CMAKE_CXX_FLAGS_DEBUG "/DEBUG:FASTLINK /fp:fast") |
||||
set(CMAKE_CXX_FLAGS_RELEASE "/O2 /fp:fast /DNDEBUG") |
||||
else() |
||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wdouble-promotion -Rpass-analysis=loop-vectorize") |
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -ffast-math") |
||||
set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -DNDEBUG") |
||||
endif() |
||||
|
||||
if(MSVC) |
||||
add_compile_definitions(_USE_MATH_DEFINES) |
||||
endif() |
||||
|
||||
add_library(synth |
||||
src/luts.cpp |
||||
src/synth.cpp |
||||
src/voicemanager.cpp |
||||
src/channel.cpp |
||||
src/preset.cpp |
||||
src/voice.cpp |
||||
src/dsp/oscillator.cpp |
||||
src/dsp/filter.cpp |
||||
src/dsp/adsr.cpp |
||||
src/dsp/reverb.cpp |
||||
src/perf.cpp) |
||||
target_include_directories(synth PUBLIC include) |
@ -0,0 +1,31 @@ |
||||
#ifndef __CC_H__ |
||||
#define __CC_H__ |
||||
|
||||
#define CC_VOLUME 7 // Volume (Standard MIDI)
|
||||
#define CC_FLT_ATK 16 // Filter Attack Time
|
||||
#define CC_FLT_DEC 17 // Filter Decay Time
|
||||
#define CC_FLT_SUS 18 // Filter Sustain
|
||||
#define CC_FLT_REL 19 // Filter Release Time
|
||||
#define CC_FLT_Q 71 // Timbre / Harmonic Content (Standard MIDI)
|
||||
#define CC_AMP_REL 72 // Release Time (Standard MIDI)
|
||||
#define CC_AMP_ATK 73 // Attack Time (Standard MIDI)
|
||||
#define CC_FLT_FRQ 74 // Brightness (Standard MIDI)
|
||||
#define CC_AMP_DEC 75 // Decay Time
|
||||
#define CC_AMP_SUS 76 // Sustain
|
||||
#define CC_OSC_DET 94 // Detune (Standard MIDI)
|
||||
#define CC_MOD_FLT 20 // Mod Envelope Filter Gain
|
||||
#define CC_KEY_TRK 21 // Filter Key Tracking
|
||||
#define CC_OSC2PIT 22 // Oscillator 2 Pitch
|
||||
#define CC_LFO_FRQ 23 // LFO Frequency
|
||||
#define CC_LFO_PIT 24 // LFO Pitch Modulation
|
||||
#define CC_LFO_FLT 25 // LFO Filter Modulation
|
||||
#define CC_UNISON 26 // Unison Amount
|
||||
#define CC_OSC_MIX 27 // Oscillator 1/2 Mix
|
||||
#define CC_OSC1MDE 28 // Oscillator 1 Mode
|
||||
#define CC_OSC2MDE 29 // Oscillator 2 Mode
|
||||
#define CC_FLT_TYP 30 // Filter Type
|
||||
#define CC_FLT_SLP 31 // Filter Slope
|
||||
#define CC_RVB_SND 102 // Reverb Send
|
||||
#define CC_NOI_MIX 103 // Noise Mix
|
||||
|
||||
#endif |
@ -0,0 +1,83 @@ |
||||
#ifndef __PART_H__ |
||||
#define __PART_H__ |
||||
|
||||
#include <unordered_map> |
||||
#include <set> |
||||
|
||||
#include "globals.h" |
||||
#include "voicemanager.h" |
||||
#include "voice.h" |
||||
#include "preset.h" |
||||
#include "dsp/frame.h" |
||||
#include "luts.h" |
||||
#include "perf.h" |
||||
|
||||
class Channel { |
||||
public: |
||||
VoiceManager::channel_t number; |
||||
VoiceManager* voiceManager; |
||||
Voice::Settings settings; |
||||
|
||||
Channel() : volume(1), pitchBend(0), modulation(0), lfoPhase(randFrac()) {} |
||||
|
||||
void loadPreset(const Preset* preset); |
||||
|
||||
void noteOn(int note, int vel); |
||||
void noteOff(int note); |
||||
void control(int cc, int val); |
||||
|
||||
bool tick(float *out, const size_t bufferSize) { |
||||
nano_t started = nanos(); |
||||
|
||||
Voice** voices = voiceManager->getChannelVoices(number); |
||||
if(*voices == NULL) { |
||||
nano_t finished = nanos(); |
||||
perfNanos.channel += finished - started; |
||||
return false; |
||||
} |
||||
|
||||
const size_t numFrames = bufferSize / 2; |
||||
|
||||
float lfo[numFrames]; |
||||
for(size_t i = 0; i < numFrames; ++i) { |
||||
lfo[i] = fastSin(lfoPhase); |
||||
|
||||
lfoPhase += settings.lfoStep; |
||||
lfoPhase -= floorf(lfoPhase); |
||||
} |
||||
|
||||
float pitchMod[numFrames]; |
||||
for(size_t i = 0; i < numFrames; ++i) { |
||||
pitchMod[i] = settings.lfoPitchMod * lfo[i]; |
||||
} |
||||
|
||||
float fltMod[numFrames]; |
||||
for(size_t i = 0; i < numFrames; ++i) { |
||||
fltMod[i] = settings.lfoFltMod * lfo[i]; |
||||
} |
||||
|
||||
std::fill(out, out + bufferSize, 0.f); |
||||
for(Voice** voice = voices; *voice != NULL; ++voice) { |
||||
float voiceOut[bufferSize]; |
||||
(*voice)->tick(voiceOut, bufferSize, pitchMod, fltMod); |
||||
|
||||
for(size_t i = 0; i < bufferSize; ++i) { |
||||
out[i] += voiceOut[i]; |
||||
} |
||||
} |
||||
|
||||
nano_t finished = nanos(); |
||||
perfNanos.channel += finished - started; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private: |
||||
float volume; |
||||
float pitchBend; |
||||
float modulation; |
||||
|
||||
float lfoPhase; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,85 @@ |
||||
#ifndef __ADSR_H__ |
||||
#define __ADSR_H__ |
||||
|
||||
#include <cmath> |
||||
|
||||
#include "../util.h" |
||||
|
||||
class ADSR { |
||||
public: |
||||
typedef struct { |
||||
float attackStep; |
||||
float decayStep; |
||||
float sustain; |
||||
float releaseStep; |
||||
} Envelope; |
||||
|
||||
Envelope const * env; |
||||
|
||||
ADSR() : state(IDLE), t(0), last(0) {}; |
||||
void reset(); |
||||
void assign(Envelope const * env); |
||||
void noteOn(); |
||||
void noteOff(); |
||||
bool isIdle() { |
||||
return state == IDLE; |
||||
} |
||||
|
||||
inline float tick() { |
||||
switch(state) { |
||||
case IDLE: |
||||
return 0; |
||||
|
||||
case ATTACK: |
||||
if(t < 1) { |
||||
t += env->attackStep; |
||||
last = t*t; |
||||
} else { |
||||
state = DECAY; |
||||
t = 1; |
||||
last = 1; |
||||
} |
||||
break; |
||||
|
||||
case DECAY: |
||||
if(t > 0) { |
||||
t -= env->decayStep; |
||||
//last = env->sustain + (1 - env->sustain) * curve(t);
|
||||
last = env->sustain + (1 - env->sustain) * t*t; |
||||
} else { |
||||
state = SUSTAIN; |
||||
t = 1; |
||||
last = env->sustain; |
||||
} |
||||
break; |
||||
|
||||
case SUSTAIN: |
||||
last = env->sustain; |
||||
break; |
||||
|
||||
case RELEASE: |
||||
if(t > 0) { |
||||
t -= env->releaseStep; |
||||
return last * t*t; |
||||
} else { |
||||
state = IDLE; |
||||
t = 0; |
||||
last = 0; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
return last; |
||||
} |
||||
|
||||
private: |
||||
enum { IDLE, ATTACK, DECAY, SUSTAIN, RELEASE } state; |
||||
float t; |
||||
float last; |
||||
|
||||
float curve(float gain) { |
||||
return 0.5f - 0.5f * cosf(clamp(gain * (float) M_PI, 0.f, M_PI)); |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,74 @@ |
||||
#ifndef __DELAYLINE_H__ |
||||
#define __DELAYLINE_H__ |
||||
|
||||
#include <cstddef> |
||||
|
||||
template <size_t CAPACITY> |
||||
class DelayLine { |
||||
private: |
||||
float samples[CAPACITY] = {0}; |
||||
size_t cursor; |
||||
|
||||
public: |
||||
size_t size; |
||||
|
||||
DelayLine(size_t size = CAPACITY) : size(size) {} |
||||
|
||||
inline float get() { |
||||
while(cursor >= size) { |
||||
cursor -= size; |
||||
} |
||||
return samples[cursor]; |
||||
} |
||||
|
||||
inline float tick() { |
||||
++cursor; |
||||
while(cursor >= size) { |
||||
cursor -= size; |
||||
} |
||||
return samples[cursor]; |
||||
} |
||||
|
||||
inline float tick(float in) { |
||||
while(cursor >= size) { |
||||
cursor -= size; |
||||
} |
||||
samples[cursor++] = in; |
||||
if(cursor == size) { |
||||
cursor = 0; |
||||
} |
||||
return samples[cursor]; |
||||
} |
||||
|
||||
inline void inject(float in, size_t pos) { |
||||
size_t index = cursor + pos; |
||||
while(index >= size) { |
||||
index -= size; |
||||
} |
||||
samples[index] = in; |
||||
} |
||||
|
||||
inline void inject(float* in, size_t num, size_t pos = 0) { |
||||
size_t index = cursor + pos; |
||||
while(index >= size) { |
||||
index -= size; |
||||
} |
||||
for(size_t i = 0; i < num; ++i) { |
||||
samples[index++] = *(in++); |
||||
if(index == size) { |
||||
index = 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
inline float tap(size_t n) { |
||||
size_t index = cursor + n; |
||||
if(index < size) { |
||||
return samples[index]; |
||||
} else { |
||||
return samples[index - size]; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,142 @@ |
||||
#ifndef __SVF_H__ |
||||
#define __SVF_H__ |
||||
|
||||
/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ |
||||
|
||||
#include <cmath> |
||||
#include <cfloat> |
||||
#include <iostream> |
||||
#include <algorithm> |
||||
#include <functional> |
||||
|
||||
#include "../globals.h" |
||||
#include "../util.h" |
||||
#include "../luts.h" |
||||
#include "../perf.h" |
||||
|
||||
#define FILTER_K_SCALE (2 * CV_FREQ_MIN / SAMPLE_RATE) |
||||
|
||||
inline float cvToK(float cv) { |
||||
return tanf((float) M_PI_2 * std::min(0.5f, (float) FILTER_K_SCALE * exp2f(cv))); |
||||
} |
||||
|
||||
class SVF12Stereo { |
||||
protected: |
||||
float hp[2], bp[2], lp[2]; |
||||
float as1[2], as2[2]; |
||||
|
||||
public: |
||||
SVF12Stereo() : hp{0, 0}, bp{0, 0}, lp{0, 0}, as1{0, 0}, as2{0, 0} {} |
||||
|
||||
inline void tick(float *in, float *hpo, float *bpo, float *lpo, size_t bufferSize, float *kK, float *kQ) { |
||||
size_t frameCount = bufferSize / 2; |
||||
|
||||
float kQ_1[frameCount]; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
kQ_1[i] = 1.f / kQ[i]; |
||||
} |
||||
float kMul[frameCount]; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
kMul[i] = 1.f / (1.f + kK[i]*kQ_1[i] + kK[i]*kK[i]); |
||||
} |
||||
for(size_t i = 0, j = 0; i < bufferSize; ++i) { |
||||
size_t ch = i & 1; |
||||
|
||||
hp[ch] = (in[i] - (kQ_1[j] + kK[j]) * as1[ch] - as2[ch]) * kMul[j]; |
||||
float au = hp[ch] * kK[j]; |
||||
bp[ch] = au + as1[ch]; |
||||
as1[ch] = au + bp[ch]; |
||||
au = bp[ch] * kK[j]; |
||||
lp[ch] = au + as2[ch]; |
||||
as2[ch] = au + lp[ch]; |
||||
|
||||
hpo && (hpo[i] = hp[ch]); |
||||
bpo && (bpo[i] = bp[ch]); |
||||
lpo && (lpo[i] = lp[ch]); |
||||
|
||||
j += ch; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// 12 and 24 dB/oct
|
||||
class FilterStereo { |
||||
public: |
||||
enum Type { |
||||
TYPE_NONE = 0, |
||||
TYPE_LP, |
||||
TYPE_BP, |
||||
TYPE_HP |
||||
}; |
||||
|
||||
enum Slope { |
||||
SLOPE_12 = 0, |
||||
SLOPE_24, |
||||
}; |
||||
|
||||
typedef struct { |
||||
float freq; |
||||
float res; |
||||
Type type; |
||||
Slope slope; |
||||
} Settings; |
||||
|
||||
private: |
||||
Settings const * settings; |
||||
SVF12Stereo fltA, fltB; |
||||
float freq, res; |
||||
|
||||
public: |
||||
void assign(Settings const * settings); |
||||
|
||||
inline void tick(float *in, float *out, size_t bufferSize, float *freqMod) { |
||||
nano_t started = nanos(); |
||||
|
||||
size_t frameCount = bufferSize / 2; |
||||
|
||||
float dt = 1.f / (float) frameCount; |
||||
|
||||
float kK[frameCount]; |
||||
float dFreq = dt * (settings->freq - freq); |
||||
for(size_t i = 0; i < frameCount; i++) { |
||||
kK[i] = fastCVtoK(freq + freqMod[i]); |
||||
freq += dFreq; |
||||
} |
||||
float kQ[frameCount]; |
||||
float dRes = dt * (settings->res - res); |
||||
for(size_t i = 0; i < frameCount; i++) { |
||||
kQ[i] = (float) M_SQRT1_2 + res; |
||||
res += dRes; |
||||
} |
||||
|
||||
switch(settings->type) { |
||||
case TYPE_NONE: |
||||
std::copy(in, in + bufferSize, out); |
||||
break; |
||||
|
||||
case TYPE_LP: |
||||
fltA.tick(in, NULL, NULL, out, bufferSize, kK, kQ); |
||||
if(settings->slope == SLOPE_24) { |
||||
fltB.tick(out, NULL, NULL, out, bufferSize, kK, kQ); |
||||
} |
||||
break; |
||||
case TYPE_BP: |
||||
fltA.tick(in, NULL, out, NULL, bufferSize, kK, kQ); |
||||
if(settings->slope == SLOPE_24) { |
||||
fltB.tick(out, NULL, out, NULL, bufferSize, kK, kQ); |
||||
} |
||||
break; |
||||
case TYPE_HP: |
||||
fltA.tick(in, out, NULL, NULL, bufferSize, kK, kQ); |
||||
if(settings->slope == SLOPE_24) { |
||||
fltB.tick(out, out, NULL, NULL, bufferSize, kK, kQ); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
nano_t finished = nanos(); |
||||
perfNanos.filter += finished - started; |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,107 @@ |
||||
#ifndef __FM_H__ |
||||
#define __FM_H__ |
||||
|
||||
#include <array> |
||||
|
||||
#include "../util.h" |
||||
|
||||
class FM { |
||||
private: |
||||
float phase1 = randPhase(), phase2 = randPhase(), phase3 = randPhase(), phase4 = randPhase(), phase5 = randPhase(), phase6 = randPhase(); |
||||
float fb1 = 0.f, fb2 = 0.f; |
||||
|
||||
public: |
||||
struct Settings { |
||||
struct { |
||||
float pitch; |
||||
float level; |
||||
} ops[6]; |
||||
}; |
||||
Settings settings; |
||||
|
||||
float tick(float pitch, float fmEnv) { |
||||
float step3 = cvToStep(pitch); |
||||
float step2 = cvToStep(pitch + 3.807354922057604f); // Frequency Coarse = 14
|
||||
float step1 = cvToStep(pitch + 0.007195501404204f); // Detune = +7
|
||||
float step6 = cvToStep(pitch); |
||||
float step5 = cvToStep(pitch - 0.007231569231076f); // Detune = -7
|
||||
float step4 = cvToStep(pitch); |
||||
|
||||
float osc3 = fmEnv * 0.333f * sinf(phase3); |
||||
float osc2 = fmEnv * 0.333f * sinf(phase2 + (float) M_PI * osc3); |
||||
float osc1 = sinf(phase1 + (float) M_PI * osc2); |
||||
|
||||
float osc6 = fmEnv * 1.0f * sinf(phase6 + 0.0f * (float) M_PI * (fb1 + fb2)); |
||||
float osc5 = fmEnv * 1.0f * sinf(phase5 + (float) M_PI * osc6); |
||||
float osc4 = sinf(phase4 + (float) M_PI * osc5); |
||||
fb2 = fb1; |
||||
fb1 = osc4; |
||||
|
||||
phase1 += step1; |
||||
if(phase1 >= (float) PIx2) { |
||||
phase1 -= PIx2; |
||||
} |
||||
phase2 += step2; |
||||
if(phase2 >= (float) PIx2) { |
||||
phase2 -= PIx2; |
||||
} |
||||
phase3 += step3; |
||||
if(phase3 >= (float) PIx2) { |
||||
phase3 -= PIx2; |
||||
} |
||||
phase4 += step4; |
||||
if(phase4 >= (float) PIx2) { |
||||
phase4 -= PIx2; |
||||
} |
||||
phase5 += step5; |
||||
if(phase5 >= (float) PIx2) { |
||||
phase5 -= PIx2; |
||||
} |
||||
phase6 += step6; |
||||
if(phase6 >= (float) PIx2) { |
||||
phase6 -= PIx2; |
||||
} |
||||
|
||||
return osc1 + osc4; |
||||
} |
||||
}; |
||||
|
||||
/*
|
||||
op6.detune 14 |
||||
op6.output_level 79 |
||||
op6.frequency_coarse 1 |
||||
|
||||
op5.detune 0 |
||||
op5.output_level 99 |
||||
op5.frequency_coarse 1 |
||||
|
||||
op4.detune 7 |
||||
op4.output_level 89 |
||||
op4.frequency_coarse 1 |
||||
|
||||
op3.detune 7 |
||||
op3.output_level 99 |
||||
op3.frequency_coarse 1 |
||||
|
||||
op2.detune 7 |
||||
op2.output_level 58 |
||||
op2.frequency_coarse 14 |
||||
|
||||
op1.detune 10 |
||||
op1.output_level 99 |
||||
op1.frequency_coarse 1 |
||||
|
||||
algorithm 4 |
||||
feedback 6 [0..7] |
||||
|
||||
RANGES |
||||
|
||||
opx.output_level [0, 99] |
||||
opX.frequency_coarse [0, 31] 0.50 - 31.00 X |
||||
op6.frequency_fine [0, 99] 1.00 - 1.99 X |
||||
opX.detune [0, 14] [-7, +7] |
||||
feedback [0, 7] |
||||
|
||||
*/ |
||||
|
||||
#endif |
@ -0,0 +1,141 @@ |
||||
#ifndef __FRAME_H__ |
||||
#define __FRAME_H__ |
||||
|
||||
struct frame { |
||||
float l; |
||||
float r; |
||||
|
||||
inline frame operator+(const frame& rhs) { |
||||
return { |
||||
.l = l + rhs.l, |
||||
.r = r + rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator+(float rhs) { |
||||
return { |
||||
.l = l + rhs, |
||||
.r = r + rhs |
||||
}; |
||||
} |
||||
|
||||
inline frame operator-(const frame& rhs) { |
||||
return { |
||||
.l = l - rhs.l, |
||||
.r = r - rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator-(float rhs) { |
||||
return { |
||||
.l = l - rhs, |
||||
.r = r - rhs |
||||
}; |
||||
} |
||||
|
||||
inline frame operator*(const frame& rhs) { |
||||
return { |
||||
.l = l * rhs.l, |
||||
.r = r * rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator*(float rhs) { |
||||
return { |
||||
.l = l * rhs, |
||||
.r = r * rhs |
||||
}; |
||||
} |
||||
|
||||
inline frame operator/(const frame& rhs) { |
||||
return { |
||||
.l = l / rhs.l, |
||||
.r = r / rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator/(float rhs) { |
||||
return { |
||||
.l = l / rhs, |
||||
.r = r / rhs |
||||
}; |
||||
} |
||||
|
||||
inline frame operator+=(const frame& rhs) { |
||||
this->l += rhs.l; |
||||
this->r += rhs.r; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator+=(float rhs) { |
||||
this->l += rhs; |
||||
this->r += rhs; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator-=(const frame& rhs) { |
||||
this->l -= rhs.l; |
||||
this->r -= rhs.r; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator-=(float rhs) { |
||||
this->l -= rhs; |
||||
this->r -= rhs; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator*=(const frame& rhs) { |
||||
this->l *= rhs.l; |
||||
this->r *= rhs.r; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator*=(float rhs) { |
||||
this->l *= rhs; |
||||
this->r *= rhs; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator/=(const frame& rhs) { |
||||
this->l /= rhs.l; |
||||
this->r /= rhs.r; |
||||
return *this; |
||||
} |
||||
|
||||
inline frame operator/=(float rhs) { |
||||
this->l /= rhs; |
||||
this->r /= rhs; |
||||
return *this; |
||||
} |
||||
}; |
||||
|
||||
inline frame operator+(float lhs, const frame& rhs) { |
||||
return { |
||||
lhs + rhs.l, |
||||
lhs + rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator-(float lhs, const frame& rhs) { |
||||
return { |
||||
lhs + rhs.l, |
||||
lhs + rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator*(float lhs, const frame& rhs) { |
||||
return { |
||||
lhs * rhs.l, |
||||
lhs * rhs.r |
||||
}; |
||||
} |
||||
|
||||
inline frame operator/(float lhs, const frame& rhs) { |
||||
return { |
||||
lhs / rhs.l, |
||||
lhs / rhs.r |
||||
}; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,124 @@ |
||||
#ifndef __OSCILLATOR_H__ |
||||
#define __OSCILLATOR_H__ |
||||
|
||||
/* PolyBLEP oscillator inspired by: https://www.metafunction.co.uk/post/all-about-digital-oscillators-part-2-blits-bleps
|
||||
* Further info about BLEP at: https://pbat.ch/sndkit/blep/
|
||||
*/ |
||||
|
||||
#include <cstdlib> |
||||
#include <math.h> |
||||
|
||||
#include "../util.h" |
||||
#include "../luts.h" |
||||
#include "../perf.h" |
||||
|
||||
class Oscillator { |
||||
public: |
||||
enum Mode { MODE_SINE = 0, MODE_SAW, MODE_SQUARE }; |
||||
|
||||
Mode const * mode; |
||||
float phase; // current waveform phase angle (radians)
|
||||
float value; // current amplitude value
|
||||
float driftAmount; |
||||
float driftValue; |
||||
|
||||
Oscillator() : phase(randFrac()), value(0), driftAmount(0.001f), driftValue(0) {} |
||||
|
||||
void assign(Mode const * mode); |
||||
|
||||
// Generate next output sample and advance the phase angle
|
||||
inline void tick(float* out, size_t bufferSize, float *pitch, float* gain) { |
||||
nano_t started = nanos(); |
||||
|
||||
size_t frameCount = bufferSize / 2; |
||||
|
||||
float driftBuf[frameCount]; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
driftValue += 0.005f * whiteNoise() - 0.00001f * driftValue; |
||||
driftBuf[i] = 1.0f + driftAmount * driftValue; |
||||
} |
||||
float phaseStepBuf[frameCount]; |
||||
float step = cvToStep(pitch[0]); |
||||
float stepEnd = cvToStep(pitch[1]); |
||||
float stepStep = (stepEnd - step) / frameCount; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
phaseStepBuf[i] = step * driftBuf[i]; |
||||
step += stepStep; |
||||
} |
||||
|
||||
float phaseBuf[frameCount]; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
phaseBuf[i] = phase; |
||||
phase += phaseStepBuf[i]; |
||||
if(unlikely(phase >= 1.f)) { |
||||
phase -= 1.f; |
||||
} |
||||
} |
||||
|
||||
if(*mode == MODE_SINE) { |
||||
for(size_t i = 0, j = 0; i < frameCount; ++i, j += 2) { |
||||
float value = fastSin(phaseBuf[i]); |
||||
|
||||
out[j] += gain[0] * value; |
||||
out[j + 1] += gain[1] * value; |
||||
} |
||||
} else if(*mode == MODE_SAW) { |
||||
for(size_t i = 0, j = 0; i < frameCount; ++i, j += 2) { |
||||
float value = (2.0f * phaseBuf[i]) - 1.0f; // Render naive waveshape
|
||||
|
||||
value -= polyBlep(phaseBuf[i], phaseStepBuf[i]); // Layer output of Poly BLEP on top
|
||||
|
||||
out[j] += gain[0] * value; |
||||
out[j + 1] += gain[1] * value; |
||||
} |
||||
} else if (*mode == MODE_SQUARE) { |
||||
float antiphaseBuf[frameCount]; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
float antiphase = phaseBuf[i] + 0.5f; |
||||
if(unlikely(antiphase >= 1.f)) { |
||||
antiphase -= 1; |
||||
} |
||||
antiphaseBuf[i] = antiphase; |
||||
} |
||||
|
||||
for(size_t i = 0, j = 0; i < frameCount; ++i, j += 2) { |
||||
float value = phaseBuf[i] < 0.5f ? 1.f : -1.f; // Render naive waveshape
|
||||
|
||||
value += polyBlep(phaseBuf[i], phaseStepBuf[i]); // Layer output of Poly BLEP on top (flip)
|
||||
value -= polyBlep(antiphaseBuf[i], phaseStepBuf[i]); // Layer output of Poly BLEP on top (flop)
|
||||
|
||||
out[j] += gain[0] * value; |
||||
out[j + 1] += gain[1] * value; |
||||
} |
||||
} |
||||
|
||||
nano_t finished = nanos(); |
||||
perfNanos.oscillator += finished - started; |
||||
} |
||||
|
||||
inline float polyBlep(float t, float dt) { |
||||
// t-t^2/2 +1/2
|
||||
// 0 < t <= 1
|
||||
// discontinuities between 0 & 1
|
||||
if(unlikely(t < dt)) { |
||||
t /= dt; |
||||
return t + t - t * t - 1.0f; |
||||
} |
||||
|
||||
// t^2/2 +t +1/2
|
||||
// -1 <= t <= 0
|
||||
// discontinuities between -1 & 0
|
||||
else if(unlikely(t > 1.0f - dt)) { |
||||
t = (t - 1.0f) / dt; |
||||
return t * t + t + t + 1.0f; |
||||
} |
||||
|
||||
// no discontinuities
|
||||
// 0 otherwise
|
||||
else { |
||||
return 0.0; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,34 @@ |
||||
#ifndef __PHYS_H__ |
||||
#define __PHYS_H__ |
||||
|
||||
#include "../util.h" |
||||
#include "delayline.h" |
||||
|
||||
class Physical { |
||||
private: |
||||
DelayLine<MAX_SAMPLE_PERIOD> dl; |
||||
float lp = 0, lp2 = 0; |
||||
|
||||
public: |
||||
inline float tick(float pitch) { |
||||
int period = roundf(cvToPeriod(pitch)); |
||||
dl.size = period; |
||||
float out = dl.get(); |
||||
lp *= 0.99f; |
||||
lp += 0.90f * (out - lp); |
||||
dl.inject(lp, 0); |
||||
dl.tick(); |
||||
lp2 += 0.01f * (out - lp2); |
||||
return lp2; |
||||
} |
||||
|
||||
inline void pluck() { |
||||
float tmp[512]; |
||||
for(int i = 0; i < 512; ++i) { |
||||
tmp[i] = 8.f * (whiteNoise() + whiteNoise() + whiteNoise() + whiteNoise()); |
||||
} |
||||
dl.inject(tmp, 512); |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,53 @@ |
||||
#ifndef __REVERB_H__ |
||||
#define __REVERB_H__ |
||||
|
||||
// Velvet noise reverb ("Late Reverberation Synthesis Using Filtered Velvet Noise")
|
||||
|
||||
#include <algorithm> |
||||
|
||||
#include "globals.h" |
||||
#include "frame.h" |
||||
#include "stereodelayline.h" |
||||
|
||||
#define REVERB_FRAMES (3 * SAMPLE_RATE) |
||||
#define REVERB_TAPS 256 |
||||
|
||||
class Reverb { |
||||
private: |
||||
struct Tap { |
||||
size_t lp, rp; |
||||
float lg, rg; |
||||
}; |
||||
|
||||
StereoDelayLine<REVERB_FRAMES> dl; |
||||
Tap taps[REVERB_TAPS]; |
||||
float lpfL = 0.f, lpfR = 0.f; |
||||
float hpfL = 0.f, hpfR = 0.f; |
||||
|
||||
public: |
||||
Reverb(); |
||||
|
||||
frame tick(frame in) { |
||||
dl.tick(in); |
||||
|
||||
frame out{0, 0}; |
||||
|
||||
for(const Tap& tap : taps) { |
||||
out.l += tap.lg * dl.tap(tap.lp).l; |
||||
out.r += tap.rg * dl.tap(tap.rp).r; |
||||
} |
||||
|
||||
lpfL += 0.1f * (out.l - lpfL); |
||||
lpfR += 0.1f * (out.r - lpfR); |
||||
|
||||
hpfL += 0.05f * (lpfL - hpfL); |
||||
hpfR += 0.05f * (lpfR - hpfR); |
||||
|
||||
out.l = lpfL - hpfL; |
||||
out.r = lpfR - hpfR; |
||||
|
||||
return out; |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,45 @@ |
||||
#ifndef __STEREODELAYLINE_H__ |
||||
#define __STEREODELAYLINE_H__ |
||||
|
||||
#include <cstddef> |
||||
|
||||
#include "frame.h" |
||||
|
||||
template <size_t CAPACITY> |
||||
class StereoDelayLine { |
||||
private: |
||||
frame samples[CAPACITY] = {{0, 0}}; |
||||
size_t cursor; |
||||
|
||||
public: |
||||
size_t size; |
||||
|
||||
StereoDelayLine(size_t size = CAPACITY) : size(size) {} |
||||
|
||||
inline frame tick() { |
||||
++cursor; |
||||
if(cursor == size) { |
||||
cursor = 0; |
||||
} |
||||
return samples[cursor]; |
||||
} |
||||
|
||||
inline frame tick(frame in) { |
||||
samples[cursor++] = in; |
||||
if(cursor == size) { |
||||
cursor = 0; |
||||
} |
||||
return samples[cursor]; |
||||
} |
||||
|
||||
inline frame tap(size_t n) { |
||||
size_t index = cursor + n; |
||||
if(index < size) { |
||||
return samples[index]; |
||||
} else { |
||||
return samples[index - size]; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,28 @@ |
||||
#ifndef __SAP_H__ |
||||
#define __SAP_H__ |
||||
|
||||
// Schroeder All-Pass (delay w/feedback)
|
||||
|
||||
#include <cstddef> |
||||
|
||||
#include "stereodelayline.h" |
||||
|
||||
template <size_t CAPACITY> |
||||
class StereoSAP { |
||||
private: |
||||
const size_t size; |
||||
StereoDelayLine<CAPACITY> dl; |
||||
|
||||
public: |
||||
float gain = 0.7; |
||||
|
||||
StereoSAP(size_t size = CAPACITY) : size(size) {} |
||||
|
||||
inline frame tick(frame in) { |
||||
frame fb = in + dl.tap(0) * gain; |
||||
frame out = dl.tick(fb) + fb * -gain; |
||||
return out; |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,9 @@ |
||||
#ifndef __GLOBALS_H__ |
||||
#define __GLOBALS_H__ |
||||
|
||||
#define SAMPLE_RATE 96000 |
||||
#define SAMPLE_RATE_INV (1.0 / SAMPLE_RATE) |
||||
|
||||
#define FILTER_CV_MAX 9.25 // MIDI note "135" (19912.126958213178287 Hz)
|
||||
|
||||
#endif |
@ -0,0 +1,26 @@ |
||||
#ifndef __LUTS_H__ |
||||
#define __LUTS_H__ |
||||
|
||||
#include <algorithm> |
||||
|
||||
#include "globals.h" |
||||
#include "util.h" |
||||
|
||||
#define SIN_LUT_SIZE 256 |
||||
#define K_LUT_SIZE 256 |
||||
|
||||
extern float sinLUT[SIN_LUT_SIZE]; |
||||
extern float kLUT[K_LUT_SIZE]; |
||||
|
||||
void genLUTs(); |
||||
|
||||
inline float fastSin(float t) { |
||||
t += randFrac() * (1.f / SIN_LUT_SIZE); |
||||
return sinLUT[(size_t) (t * SIN_LUT_SIZE) & (SIN_LUT_SIZE - 1)]; |
||||
} |
||||
|
||||
inline float fastCVtoK(float cv) { |
||||
return kLUT[std::min(K_LUT_SIZE - (size_t) 1, (size_t) (cv * (K_LUT_SIZE - 1) * (1.f / (float) FILTER_CV_MAX)))]; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,37 @@ |
||||
#ifndef __PERF_H__ |
||||
#define __PERF_H__ |
||||
|
||||
#include <cstdint> |
||||
#include <chrono> |
||||
|
||||
#if defined linux || __APPLE__ || _WIN32 |
||||
typedef std::chrono::nanoseconds::rep nano_t; |
||||
#else |
||||
typedef uint32_t nano_t; |
||||
#endif |
||||
|
||||
typedef struct { |
||||
nano_t total; |
||||
nano_t channel; |
||||
nano_t voice; |
||||
nano_t oscillator; |
||||
nano_t filter; |
||||
} PerfNanos; |
||||
|
||||
extern PerfNanos perfNanos; |
||||
|
||||
// Get timestamp in nanoseconds
|
||||
#if defined linux || __APPLE__ || _WIN32 |
||||
inline nano_t nanos() { |
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>( |
||||
std::chrono::high_resolution_clock::now().time_since_epoch()) |
||||
.count(); |
||||
} |
||||
#else |
||||
inline nano_t nanos() { |
||||
// SysTick * 100 / 55
|
||||
return 0; |
||||
} |
||||
#endif |
||||
|
||||
#endif |
@ -0,0 +1,47 @@ |
||||
#ifndef __PRESET_H__ |
||||
#define __PRESET_H__ |
||||
|
||||
#include "dsp/oscillator.h" |
||||
#include "dsp/filter.h" |
||||
#include "dsp/adsr.h" |
||||
|
||||
struct Preset { |
||||
typedef struct { |
||||
uint8_t attack; |
||||
uint8_t decay; |
||||
uint8_t sustain; |
||||
uint8_t release; |
||||
} Envelope; |
||||
|
||||
uint8_t unison; |
||||
|
||||
uint8_t osc1Mode; |
||||
uint8_t osc2Mode; |
||||
uint8_t oscMix; |
||||
uint8_t oscDetune; |
||||
uint8_t osc2Pitch; |
||||
|
||||
uint8_t noiseMix; |
||||
|
||||
struct { |
||||
uint8_t type; |
||||
uint8_t slope; |
||||
uint8_t freq; |
||||
uint8_t Q; |
||||
} filter; |
||||
|
||||
Envelope ampEnv; |
||||
Envelope modEnv; |
||||
|
||||
uint8_t modEnvFltGain; |
||||
uint8_t keyTrack; |
||||
uint8_t lfoFreq; |
||||
uint8_t lfoPitchMod; |
||||
uint8_t lfoFltMod; |
||||
|
||||
uint8_t reverb; |
||||
}; |
||||
|
||||
extern const Preset DEFAULT_PRESET; |
||||
|
||||
#endif |
@ -0,0 +1,73 @@ |
||||
#ifndef __SYNTH_H__ |
||||
#define __SYNTH_H__ |
||||
|
||||
#include "voicemanager.h" |
||||
#include "channel.h" |
||||
#include "dsp/frame.h" |
||||
#include "dsp/reverb.h" |
||||
#include "luts.h" |
||||
#include "perf.h" |
||||
|
||||
class Synth { |
||||
public: |
||||
Synth() { |
||||
genLUTs(); |
||||
|
||||
for(VoiceManager::channel_t i = 0; i < 16; ++i) { |
||||
channels[i].number = i; |
||||
channels[i].voiceManager = &voiceManager; |
||||
channels[i].loadPreset(&DEFAULT_PRESET); |
||||
} |
||||
} |
||||
|
||||
void noteOn(int ch, int note, int vel); |
||||
void noteOff(int ch, int note); |
||||
void control(int ch, int cc, int val); |
||||
|
||||
inline void tick(float* out, const size_t bufferSize) { |
||||
nano_t started = nanos(); |
||||
|
||||
std::fill(out, out + bufferSize, 0.f); |
||||
|
||||
float reverbBus[bufferSize]; |
||||
std::fill(reverbBus, reverbBus + bufferSize, 0.f); |
||||
|
||||
for(auto& channel : channels) { |
||||
float chOut[bufferSize]; |
||||
if(!channel.tick(chOut, bufferSize)) { |
||||
continue; |
||||
} |
||||
|
||||
for(size_t i = 0; i < bufferSize; ++i) {
|
||||
out[i] += chOut[i]; |
||||
} |
||||
|
||||
for(size_t i = 0; i < bufferSize; ++i) {
|
||||
reverbBus[i] += channel.settings.reverb * chOut[i]; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
for(size_t i = 0; i < bufferSize; i += 2) { |
||||
frame f = {reverbBus[i], reverbBus[i + 1]}; |
||||
f = reverb.tick(f); |
||||
out[i] += f.l; |
||||
out[i + 1] += f.r; |
||||
} |
||||
*/ |
||||
|
||||
for(size_t i = 0; i < bufferSize; ++i) { |
||||
out[i] *= 0.125f; |
||||
} |
||||
|
||||
nano_t finished = nanos(); |
||||
perfNanos.total += finished - started; |
||||
} |
||||
|
||||
private: |
||||
VoiceManager voiceManager{}; |
||||
Channel channels[16]; |
||||
Reverb reverb; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,69 @@ |
||||
#ifndef __UTIL_H__ |
||||
#define __UTIL_H__ |
||||
|
||||
#include <cmath> |
||||
#include <cfloat> |
||||
|
||||
#include "globals.h" |
||||
|
||||
#define unlikely(cond) __builtin_expect((cond), 0) |
||||
#define likely(cond) __builtin_expect((cond), 1) |
||||
|
||||
#define PIx2 (2 * M_PI) |
||||
#define PIx2_INV (1.0 / PIx2) |
||||
|
||||
#define CV_FREQ_MIN 32.703195662574829 // MIDI note 24 (C1)
|
||||
#define MAX_SAMPLE_PERIOD ((int) (SAMPLE_RATE / CV_FREQ_MIN + 0.5)) |
||||
|
||||
inline float fakeSin(float x) { |
||||
x = 4.f * (x - 0.5f); |
||||
return x * (2.f - fabsf(x)); |
||||
} |
||||
|
||||
inline float clamp(float x, float a, float b) { |
||||
return fminf(b, fmaxf(a, x)); |
||||
} |
||||
|
||||
inline float whiteNoise() { |
||||
static int32_t x1 = 0x70F4F854; |
||||
static int32_t x2 = 0xE1E9F0A7; |
||||
|
||||
x1 ^= x2; |
||||
float value = x2 * (2.0f / 0xFFFFFFFF); |
||||
x2 += x1; |
||||
|
||||
return value; |
||||
} |
||||
|
||||
inline float randFrac() { |
||||
static uint32_t x1 = 0x70F4F854; |
||||
static uint32_t x2 = 0xE1E9F0A7; |
||||
|
||||
x1 ^= x2; |
||||
float value = x2 * (1.f / 0x100000000); |
||||
x2 += x1; |
||||
|
||||
return value; |
||||
} |
||||
|
||||
inline float triangle(float phase) { |
||||
if(phase < 0.5f) { |
||||
return 2.f * phase - 1; |
||||
} else { |
||||
return 1 - 2.f * (phase - 0.5f); |
||||
} |
||||
} |
||||
|
||||
inline float cvToStep(float cv) { |
||||
return (float) CV_FREQ_MIN * exp2f(cv) * (float) SAMPLE_RATE_INV; |
||||
} |
||||
|
||||
inline float noteToCV(float note) { |
||||
return note / 12.f; |
||||
} |
||||
|
||||
inline float cvToPeriod(float cv) { |
||||
return (float) SAMPLE_RATE / ((float) CV_FREQ_MIN * exp2f(cv)); |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,135 @@ |
||||
#ifndef __VOICE_H__ |
||||
#define __VOICE_H__ |
||||
|
||||
#include "globals.h" |
||||
#include "preset.h" |
||||
|
||||
#include "dsp/oscillator.h" |
||||
#include "dsp/filter.h" |
||||
#include "dsp/adsr.h" |
||||
#include "perf.h" |
||||
|
||||
#define MAX_OSCS 8 |
||||
|
||||
class Voice { |
||||
public: |
||||
typedef struct { |
||||
size_t unison; |
||||
|
||||
Oscillator::Mode osc1Mode; |
||||
Oscillator::Mode osc2Mode; |
||||
float oscMix; |
||||
float oscDetune; |
||||
float osc2Pitch; |
||||
|
||||
float noiseMix; |
||||
|
||||
FilterStereo::Settings filter; |
||||
|
||||
ADSR::Envelope ampEnv; |
||||
ADSR::Envelope modEnv; |
||||
|
||||
float modEnvFltGain; |
||||
float keyTrack; |
||||
float lfoStep; |
||||
float lfoPitchMod; |
||||
float lfoFltMod; |
||||
|
||||
float reverb; |
||||
} Settings; |
||||
|
||||
private: |
||||
Settings const * settings; |
||||
|
||||
ADSR adsrAmp; |
||||
ADSR adsrMod; |
||||
Oscillator osc[MAX_OSCS]; |
||||
FilterStereo filter; |
||||
|
||||
public: |
||||
int note; |
||||
float velocity; |
||||
float gain; |
||||
|
||||
void reset(); |
||||
void assign(Settings const * settings); |
||||
void noteOn(int note, float velocity = 1, float gain = 1.0f); |
||||
void noteOff(); |
||||
inline bool isIdle() { return adsrAmp.isIdle(); } |
||||
|
||||
inline void tick(float *out, const size_t bufferSize, float *pitchMod, float *fltMod) { |
||||
nano_t started = nanos(); |
||||
|
||||
const size_t frameCount = bufferSize / 2; |
||||
|
||||
float basePitchBuf[2]; |
||||
basePitchBuf[0] = note + pitchMod[0]; |
||||
basePitchBuf[1] = note + pitchMod[frameCount - 1]; |
||||
|
||||
float oscPitchBuf[MAX_OSCS][2]; |
||||
float detune = -settings->oscDetune * 0.5f * (settings->unison - 1); |
||||
for(size_t i = 0; i < settings->unison; ++i) { |
||||
float transpose = (i & 1) ? settings->osc2Pitch : 0; |
||||
oscPitchBuf[i][0] = noteToCV(basePitchBuf[0] + detune + transpose); |
||||
oscPitchBuf[i][1] = noteToCV(basePitchBuf[1] + detune + transpose); |
||||
detune += settings->oscDetune; |
||||
} |
||||
|
||||
float pan, panStep; |
||||
if(settings->unison > 2) { |
||||
pan = 0.f; |
||||
panStep = 2.f / (settings->unison - 1); |
||||
} else { |
||||
pan = 0.5f; |
||||
panStep = 0.f; |
||||
} |
||||
|
||||
float oscBuf[bufferSize]; |
||||
std::fill(oscBuf, oscBuf + bufferSize, 0.f); |
||||
for(size_t i = 0; i < settings->unison; ++i) { |
||||
float baseGain = settings->unison > 1 ? ((i & 1) ? settings->oscMix : (1 - settings->oscMix)) : 1.f; |
||||
float gain[2]; |
||||
gain[0] = baseGain * sinf((1.f - pan) * (float) M_PI_2); |
||||
gain[1] = baseGain * sinf(pan * (float) M_PI_2); |
||||
pan += (i & 1) * panStep; |
||||
osc[i].tick(oscBuf, bufferSize, oscPitchBuf[i], gain); |
||||
} |
||||
|
||||
float noiseBuf[bufferSize]; |
||||
if(settings->unison > 2) { // Stereo noise
|
||||
for(size_t i = 0; i < bufferSize; ++i) { |
||||
noiseBuf[i] = (1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()); |
||||
} |
||||
} else { // Mono noise
|
||||
for(size_t i = 0; i < bufferSize; i += 2 ) { |
||||
float noise = (1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()); |
||||
noiseBuf[i] = noise; |
||||
noiseBuf[i + 1] = noise; |
||||
} |
||||
} |
||||
|
||||
for(size_t i = 0; i < bufferSize; ++i) { |
||||
oscBuf[i] += settings->noiseMix * (noiseBuf[i] - oscBuf[i]); |
||||
} |
||||
|
||||
float keyTrack = settings->keyTrack * (basePitchBuf[0] - 24) * (1.f / 12.f); |
||||
float keyTrackEnd = settings->keyTrack * (basePitchBuf[1] - 24) * (1.f / 12.f); |
||||
float keyTrackDelta = (keyTrackEnd - keyTrack) / frameCount; |
||||
|
||||
float fltModBuf[frameCount]; |
||||
for(size_t i = 0; i < frameCount; ++i) { |
||||
fltModBuf[i] = settings->modEnvFltGain * adsrMod.tick() + keyTrack + fltMod[i]; |
||||
keyTrack += keyTrackDelta; |
||||
} |
||||
|
||||
filter.tick(oscBuf, out, bufferSize, fltModBuf); |
||||
for(size_t i = 0; i < bufferSize; ++i) { |
||||
out[i] *= gain * adsrAmp.tick(); |
||||
} |
||||
|
||||
nano_t finished = nanos(); |
||||
perfNanos.voice += finished - started; |
||||
} |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,34 @@ |
||||
#ifndef __VOICEMANAGER_H__ |
||||
#define __VOICEMANAGER_H__ |
||||
|
||||
#include <unordered_set> |
||||
|
||||
#include "voice.h" |
||||
|
||||
#define NUM_VOICES 16 |
||||
|
||||
class VoiceManager { |
||||
public: |
||||
typedef unsigned int channel_t; |
||||
|
||||
VoiceManager(); |
||||
Voice* get(channel_t channel); |
||||
Voice** getChannelVoices(channel_t channel); |
||||
|
||||
private: |
||||
typedef unsigned int serial_t; |
||||
typedef unsigned int score_t; |
||||
|
||||
struct VoiceData { |
||||
channel_t channel; |
||||
serial_t serial; |
||||
Voice voice; |
||||
}; |
||||
|
||||
unsigned int serial; |
||||
VoiceData voices[NUM_VOICES]; |
||||
|
||||
unsigned int scoreVoice(VoiceData& voiceData); |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,198 @@ |
||||
#include <iostream> |
||||
|
||||
#include "globals.h" |
||||
#include "cc.h" |
||||
|
||||
#include "channel.h" |
||||
|
||||
inline int midiToNote(int note) { |
||||
return note - 24; |
||||
} |
||||
|
||||
inline float timeToStep(float t) { |
||||
return (float) SAMPLE_RATE_INV / t; |
||||
} |
||||
|
||||
inline float ccToA(int x) { |
||||
return 5.0 * exp(9.2 * (x / 127.0) - 9.2); |
||||
} |
||||
|
||||
inline float ccToDR(int x) { |
||||
return 5.0 * exp(7.0 * (x / 127.0) - 7.0); |
||||
} |
||||
|
||||
inline float ccToLFOStep(int x) { |
||||
return (float) 0.1f * expf((1 + x) * 6.907755278982137f / 128.0f) * (float) SAMPLE_RATE_INV; |
||||
} |
||||
|
||||
inline float ccToLFOPitchMod(int x) { |
||||
return 6.f * x * x / 16129.f; |
||||
} |
||||
|
||||
inline float ccToLFOFltMod(int x) { |
||||
return (float) FILTER_CV_MAX * (x - 64) / 126.f; |
||||
} |
||||
|
||||
void Channel::loadPreset(const Preset * preset) { |
||||
settings.unison = 2 + 2 * floorf(preset->unison * 4.f / 128.f); |
||||
|
||||
settings.osc1Mode = Oscillator::Mode(preset->osc1Mode); |
||||
settings.osc2Mode = Oscillator::Mode(preset->osc2Mode); |
||||
settings.oscDetune = preset->oscDetune / 254.f; |
||||
settings.osc2Pitch = floorf(preset->osc2Pitch * 13.f / 128.f); |
||||
settings.oscMix = preset->oscMix / 127.f; |
||||
settings.noiseMix = preset->noiseMix / 127.f; |
||||
|
||||
settings.filter.type = FilterStereo::Type(floorf(4.f * preset->filter.type / 128.f)); |
||||
settings.filter.slope = FilterStereo::Slope(floorf(2.f * preset->filter.slope / 128.f)); |
||||
settings.filter.freq = (float) FILTER_CV_MAX * preset->filter.freq / 127.f; |
||||
settings.filter.res = preset->filter.Q / 31.75f; |
||||
|
||||
settings.ampEnv.attackStep = timeToStep(ccToA(preset->ampEnv.attack)); |
||||
settings.ampEnv.decayStep = timeToStep(ccToDR(preset->ampEnv.decay)); |
||||
settings.ampEnv.sustain = preset->ampEnv.sustain / 127.f; |
||||
settings.ampEnv.releaseStep = timeToStep(ccToDR(preset->ampEnv.release)); |
||||
|
||||
settings.modEnv.attackStep = timeToStep(ccToA(preset->modEnv.attack)); |
||||
settings.modEnv.decayStep = timeToStep(ccToDR(preset->modEnv.decay)); |
||||
settings.modEnv.sustain = preset->modEnv.sustain / 127.f; |
||||
settings.modEnv.releaseStep = timeToStep(ccToDR(preset->modEnv.release)); |
||||
|
||||
settings.modEnvFltGain = (float) FILTER_CV_MAX * preset->modEnvFltGain / 127.f; |
||||
settings.keyTrack = preset->keyTrack / 127.f; |
||||
settings.lfoStep = ccToLFOStep(preset->lfoFreq); |
||||
settings.lfoPitchMod = ccToLFOPitchMod(preset->lfoPitchMod); |
||||
settings.lfoFltMod = ccToLFOFltMod(preset->lfoFltMod); |
||||
|
||||
settings.reverb = preset->reverb / 127.f; |
||||
} |
||||
|
||||
void Channel::noteOn(int note, int velocity) { |
||||
//printf("on: midi=%d note=%d\n", note, midiToNote(note));
|
||||
Voice* const voice = voiceManager->get(number); |
||||
voice->assign(&settings); |
||||
voice->noteOn(midiToNote(note), velocity / 127.f, 1.f); |
||||
} |
||||
|
||||
void Channel::noteOff(int note) { |
||||
Voice** voices = voiceManager->getChannelVoices(number); |
||||
for(Voice** voice = voices; *voice != NULL; ++voice) { |
||||
if((*voice)->note == midiToNote(note)) { |
||||
//printf("off: midi=%d note=%d\n", note, midiToNote(note));
|
||||
(*voice)->noteOff(); |
||||
}
|
||||
} |
||||
} |
||||
|
||||
void Channel::control(int code, int value) { |
||||
switch(code) { |
||||
case CC_VOLUME: // Volume (Standard MIDI)
|
||||
volume = value / 127.f; |
||||
break; |
||||
|
||||
case CC_FLT_ATK: // Filter Attack Time
|
||||
settings.modEnv.attackStep = timeToStep(ccToA(value)); |
||||
break; |
||||
|
||||
case CC_FLT_DEC: // Filter Decay Time
|
||||
settings.modEnv.decayStep = timeToStep(ccToDR(value)); |
||||
break; |
||||
|
||||
case CC_FLT_SUS: // Filter Sustain
|
||||
settings.modEnv.sustain = value / 127.f; |
||||
break; |
||||
|
||||
case CC_FLT_REL: // Filter Release Time
|
||||
settings.modEnv.releaseStep = timeToStep(ccToDR(value)); |
||||
break; |
||||
|
||||
case CC_FLT_Q: // Timbre / Harmonic Content (Standard MIDI)
|
||||
settings.filter.res = value / 31.75f; |
||||
break; |
||||
|
||||
case CC_AMP_REL: // Release Time (Standard MIDI)
|
||||
settings.ampEnv.releaseStep = timeToStep(ccToDR(value)); |
||||
break; |
||||
|
||||
case CC_AMP_ATK: // Attack Time (Standard MIDI)
|
||||
settings.ampEnv.attackStep = timeToStep(ccToA(value)); |
||||
break; |
||||
|
||||
case CC_FLT_FRQ: // Brightness (Standard MIDI)
|
||||
settings.filter.freq = (float) FILTER_CV_MAX * value / 127.f; |
||||
break; |
||||
|
||||
case CC_AMP_DEC: // Decay Time
|
||||
settings.ampEnv.decayStep = timeToStep(ccToDR(value)); |
||||
break; |
||||
|
||||
case CC_AMP_SUS: // Sustain
|
||||
settings.ampEnv.sustain = value / 127.f; |
||||
break; |
||||
|
||||
case CC_OSC_DET: // Detune (Standard MIDI)
|
||||
settings.oscDetune = value / 254.f; |
||||
break; |
||||
|
||||
case CC_OSC2PIT: // Oscillator 2 Pitch
|
||||
settings.osc2Pitch = floorf(value * (13.f / 128.f)); |
||||
break; |
||||
|
||||
case CC_MOD_FLT: // Mod Envelope Filter Gain
|
||||
settings.modEnvFltGain = (float) FILTER_CV_MAX * value / 127.f; |
||||
break; |
||||
|
||||
case CC_KEY_TRK: // Filter Key Tracking
|
||||
settings.keyTrack = value / 127.f; |
||||
break; |
||||
|
||||
case CC_LFO_FRQ: // LFO Frequency
|
||||
settings.lfoStep = ccToLFOStep(value); |
||||
break; |
||||
|
||||
case CC_LFO_PIT: // LFO Pitch Modulation
|
||||
settings.lfoPitchMod = ccToLFOPitchMod(value); |
||||
break; |
||||
|
||||
case CC_LFO_FLT: // LFO Filter Modulation
|
||||
settings.lfoFltMod = ccToLFOFltMod(value); |
||||
break; |
||||
|
||||
case CC_UNISON: // Unison Amount
|
||||
settings.unison = 2 + 2 * floorf(value * 4.f / 128.f); |
||||
break; |
||||
|
||||
case CC_OSC_MIX: // Oscillator 1/2 Mix
|
||||
settings.oscMix = value / 127.f; |
||||
break; |
||||
|
||||
case CC_OSC1MDE: // Oscillator 1 Mode
|
||||
settings.osc1Mode = Oscillator::Mode(floorf(3.f * value / 128.f)); |
||||
break; |
||||
|
||||
case CC_OSC2MDE: // Oscillator 2 Mode
|
||||
settings.osc2Mode = Oscillator::Mode(floorf(3.f * value / 128.f)); |
||||
break; |
||||
|
||||
case CC_FLT_TYP: // Filter Type
|
||||
settings.filter.type = FilterStereo::Type(floorf(4.f * value / 128.f)); |
||||
break; |
||||
|
||||
case CC_FLT_SLP: // Filter Slope
|
||||
settings.filter.slope = FilterStereo::Slope(floorf(2.f * value / 128.f)); |
||||
break; |
||||
|
||||
case CC_RVB_SND: |
||||
settings.reverb = value / 127.f; |
||||
break; |
||||
|
||||
case CC_NOI_MIX: |
||||
settings.noiseMix = value / 127.f; |
||||
break; |
||||
|
||||
// LFO delay
|
||||
// Reverb Time
|
||||
// Reverb Brightness
|
||||
// Reverb Density
|
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
#include "dsp/adsr.h" |
||||
|
||||
void ADSR::reset() { |
||||
state = IDLE; |
||||
t = 0; |
||||
} |
||||
|
||||
void ADSR::assign(Envelope const * env) { |
||||
this->env = env; |
||||
} |
||||
|
||||
void ADSR::noteOn() { |
||||
state = ATTACK; |
||||
t = 0; |
||||
} |
||||
|
||||
void ADSR::noteOff() { |
||||
if(state != RELEASE) { |
||||
state = RELEASE; |
||||
t = 1; |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
#include "dsp/filter.h" |
||||
|
||||
void FilterStereo::assign(Settings const * settings) { |
||||
this->settings = settings; |
||||
freq = settings->freq; |
||||
res = settings->res; |
||||
} |
@ -0,0 +1,5 @@ |
||||
#include "dsp/oscillator.h" |
||||
|
||||
void Oscillator::assign(Mode const * mode) { |
||||
this->mode = mode; |
||||
} |
@ -0,0 +1,16 @@ |
||||
#include <cmath> |
||||
|
||||
#include "dsp/reverb.h" |
||||
|
||||
Reverb::Reverb() { |
||||
size_t interval = REVERB_FRAMES / REVERB_TAPS - 4096 / REVERB_TAPS; |
||||
for(size_t tap = 0; tap < REVERB_TAPS; ++tap) { |
||||
float gain = exp2f(-8.f + 8.f * tap / (REVERB_TAPS - 1)); |
||||
taps[tap] = { |
||||
.lp = tap * interval + (rand() % interval), |
||||
.rp = tap * interval + (rand() % interval), |
||||
.lg = (rand() > RAND_MAX / 2 ? 1 : -1) * gain, |
||||
.rg = (rand() > RAND_MAX / 2 ? 1 : -1) * gain |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
#include <cmath> |
||||
//#include <cstdio>
|
||||
|
||||
#include "luts.h" |
||||
|
||||
#include "dsp/filter.h" |
||||
|
||||
float sinLUT[SIN_LUT_SIZE]; |
||||
float kLUT[K_LUT_SIZE]; |
||||
|
||||
void genLUTs() { |
||||
for(size_t i = 0; i < SIN_LUT_SIZE; ++i) { |
||||
sinLUT[i] = sin((i * 2 * M_PI) / SIN_LUT_SIZE); |
||||
} |
||||
|
||||
for(size_t i = 0; i < K_LUT_SIZE; ++i) { |
||||
kLUT[i] = cvToK(i * FILTER_CV_MAX / (K_LUT_SIZE - 1)); |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
#include "perf.h" |
||||
|
||||
PerfNanos perfNanos; |
@ -0,0 +1,42 @@ |
||||
#include "preset.h" |
||||
|
||||
const Preset DEFAULT_PRESET = { |
||||
.unison = 1, |
||||
|
||||
.osc1Mode = Oscillator::MODE_SAW, |
||||
.osc2Mode = Oscillator::MODE_SAW, |
||||
.oscMix = 64, |
||||
.oscDetune = 6, |
||||
.osc2Pitch = 0, |
||||
|
||||
.noiseMix = 0, |
||||
|
||||
.filter = { |
||||
.type = 0, |
||||
.slope = 22, |
||||
.freq = 32, |
||||
.Q = 25, |
||||
}, |
||||
|
||||
.ampEnv = { |
||||
.attack = 0, |
||||
.decay = 56, |
||||
.sustain = 64, |
||||
.release = 56 |
||||
}, |
||||
|
||||
.modEnv = { |
||||
.attack = 0, |
||||
.decay = 106, |
||||
.sustain = 0, |
||||
.release = 106 |
||||
}, |
||||
|
||||
.modEnvFltGain = 56, |
||||
.keyTrack = 127, |
||||
.lfoFreq = 64, |
||||
.lfoPitchMod = 0, |
||||
.lfoFltMod = 64, |
||||
|
||||
.reverb = 16 |
||||
}; |
@ -0,0 +1,18 @@ |
||||
//#include <cstdio>
|
||||
|
||||
#include "synth.h" |
||||
|
||||
void Synth::noteOn(int ch, int note, int vel) { |
||||
//printf("Note On: ch=%d note=%d vel=%d\n", ch, note, vel);
|
||||
channels[ch].noteOn(note, vel); |
||||
} |
||||
|
||||
void Synth::noteOff(int ch, int note) { |
||||
//printf("Note Off: ch=%d note=%d\n", ch, note);
|
||||
channels[ch].noteOff(note); |
||||
} |
||||
|
||||
void Synth::control(int ch, int cc, int val) { |
||||
//printf("Controller: ch=%d cc=%d val=%d\n", ch, cc, val);
|
||||
channels[ch].control(cc, val); |
||||
} |
@ -0,0 +1,33 @@ |
||||
#include "voice.h" |
||||
|
||||
void Voice::reset() { |
||||
adsrAmp.reset(); |
||||
adsrMod.reset(); |
||||
} |
||||
|
||||
void Voice::assign(Settings const * settings) { |
||||
this->settings = settings; |
||||
adsrAmp.assign(&settings->ampEnv); |
||||
adsrMod.assign(&settings->modEnv); |
||||
for(size_t i = 0; i < MAX_OSCS; ++i) { |
||||
if(i & 1) { |
||||
osc[i].assign(&settings->osc2Mode); |
||||
} else { |
||||
osc[i].assign(&settings->osc1Mode); |
||||
} |
||||
} |
||||
filter.assign(&settings->filter); |
||||
} |
||||
|
||||
void Voice::noteOn(int note, float velocity, float gain) { |
||||
this->note = note; |
||||
this->velocity = velocity; |
||||
this->gain = gain; |
||||
adsrAmp.noteOn(); |
||||
adsrMod.noteOn(); |
||||
} |
||||
|
||||
void Voice::noteOff() { |
||||
adsrAmp.noteOff(); |
||||
adsrMod.noteOff(); |
||||
} |
@ -0,0 +1,71 @@ |
||||
//#include <cstdio>
|
||||
#include <algorithm> |
||||
|
||||
#include "voicemanager.h" |
||||
|
||||
#define VOICE_SCORE_MSB (8 * sizeof(score_t) - 1) |
||||
|
||||
#define VOICE_SCORE_ACTIVE ( 1 << (VOICE_SCORE_MSB - 0)) |
||||
#define VOICE_SCORE_CHANNEL(channel) ((score_t) (channel) << (VOICE_SCORE_MSB - 4)) |
||||
#define VOICE_SCORE_SERIAL(serial) ((score_t) serial & ((1 << (VOICE_SCORE_MSB - 4)) - 1)) |
||||
|
||||
VoiceManager::VoiceManager() : serial(0) { |
||||
for(VoiceData& vd : voices) { |
||||
vd.channel = 0; |
||||
vd.serial = 0; |
||||
} |
||||
} |
||||
|
||||
VoiceManager::score_t VoiceManager::scoreVoice(VoiceData& voiceData) { |
||||
return |
||||
(voiceData.voice.isIdle() ? 0 : VOICE_SCORE_ACTIVE) | |
||||
VOICE_SCORE_CHANNEL(voiceData.channel) | |
||||
VOICE_SCORE_SERIAL(voiceData.serial); |
||||
} |
||||
|
||||
Voice * VoiceManager::get(channel_t channel) { |
||||
// Sort idle voices first, then by highest channel and lowest serial
|
||||
std::sort(std::begin(voices), std::end(voices), [this](VoiceData& a, VoiceData& b) { |
||||
return scoreVoice(a) < scoreVoice(b); |
||||
}); |
||||
|
||||
/*
|
||||
putchar('\n'); |
||||
for(VoiceData& vd : voices) { |
||||
printf("channel=%d serial=%d idle=%d note=%d score=%x\n", vd.channel, vd.serial, vd.voice.isIdle(), vd.voice.note, scoreVoice(vd)); |
||||
} |
||||
putchar('\n'); |
||||
*/ |
||||
|
||||
VoiceData& voiceData = voices[0]; |
||||
if(!voiceData.voice.isIdle()) { |
||||
//printf("Out of voices\n");
|
||||
} |
||||
voiceData.channel = channel; |
||||
voiceData.serial = serial++; |
||||
voiceData.voice.reset(); |
||||
|
||||
return &voiceData.voice; |
||||
} |
||||
|
||||
Voice** VoiceManager::getChannelVoices(channel_t channel) { |
||||
static Voice* result[NUM_VOICES + 1]; |
||||
Voice** voice = result; |
||||
bool empty = true; |
||||
for(VoiceData& voiceData : voices) { |
||||
if(!voiceData.voice.isIdle()) { |
||||
empty = false; |
||||
if(voiceData.channel == channel) { |
||||
*voice++ = &voiceData.voice; |
||||
} |
||||
} |
||||
} |
||||
*voice = NULL; |
||||
|
||||
// Reset serial if voice table is empty
|
||||
if(empty) { |
||||
serial = 0; |
||||
} |
||||
|
||||
return result; |
||||
} |
Loading…
Reference in new issue