parent
5f2086e829
commit
6cd5e452f5
17 changed files with 329 additions and 214 deletions
@ -0,0 +1,22 @@ |
|||||||
|
{ |
||||||
|
"configurations": [ |
||||||
|
{ |
||||||
|
"name": "Mac", |
||||||
|
"includePath": [ |
||||||
|
"${workspaceFolder}/**" |
||||||
|
], |
||||||
|
"defines": [ |
||||||
|
"__LITTLE_ENDIAN__" |
||||||
|
], |
||||||
|
"macFrameworkPath": [ |
||||||
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks" |
||||||
|
], |
||||||
|
"compilerPath": "/usr/bin/clang", |
||||||
|
"cStandard": "c17", |
||||||
|
"cppStandard": "c++17", |
||||||
|
"intelliSenseMode": "macos-clang-x64", |
||||||
|
"configurationProvider": "ms-vscode.cmake-tools" |
||||||
|
} |
||||||
|
], |
||||||
|
"version": 4 |
||||||
|
} |
@ -0,0 +1,88 @@ |
|||||||
|
#ifndef __SVF_H__ |
||||||
|
#define __SVF_H__ |
||||||
|
|
||||||
|
/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ |
||||||
|
|
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
class SVF12 { |
||||||
|
public: |
||||||
|
float frequency; |
||||||
|
float resonance; |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
float lp; // low-pass
|
||||||
|
float bp; // band-pass
|
||||||
|
float hp; // high-pass
|
||||||
|
} Output; |
||||||
|
|
||||||
|
protected: |
||||||
|
float as1, as2; |
||||||
|
|
||||||
|
public: |
||||||
|
SVF12() : as1(0), as2(0) {} |
||||||
|
|
||||||
|
Output tick(float as, float freq, float Q) { |
||||||
|
Output out; |
||||||
|
float kK = tan(M_PI_4 * clamp(freq, 0, 1)); |
||||||
|
float kQ = fmaxf(0, Q); |
||||||
|
|
||||||
|
float kdiv = 1 + kK/kQ + kK*kK; |
||||||
|
out.hp = (as - (1/kQ + kK) * as1 - as2) / kdiv; |
||||||
|
float au = out.hp * kK; |
||||||
|
out.bp = au + as1; |
||||||
|
as1 = au + out.bp; |
||||||
|
au = out.bp * kK; |
||||||
|
out.lp = au + as2; |
||||||
|
as2 = au + out.lp; |
||||||
|
|
||||||
|
return out; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 12 and 24 dB/oct
|
||||||
|
class Filter { |
||||||
|
public: |
||||||
|
enum Type { |
||||||
|
TYPE_LP = 0, |
||||||
|
TYPE_BP = 42, |
||||||
|
TYPE_HP = 84 |
||||||
|
}; |
||||||
|
|
||||||
|
enum Slope { |
||||||
|
SLOPE_24 = 0, |
||||||
|
SLOPE_12 = 64, |
||||||
|
}; |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
float freq; |
||||||
|
float Q; |
||||||
|
Type type; |
||||||
|
Slope slope; |
||||||
|
} Settings; |
||||||
|
|
||||||
|
private: |
||||||
|
const Settings* settings; |
||||||
|
SVF12 first, second; |
||||||
|
|
||||||
|
public: |
||||||
|
Filter() = delete; |
||||||
|
Filter(const Settings *settings): settings(settings) {} |
||||||
|
|
||||||
|
inline float tick(float as, float freqMod) { |
||||||
|
SVF12::Output in = first.tick(as, settings->freq + freqMod, settings->Q); |
||||||
|
|
||||||
|
switch(settings->type) { |
||||||
|
case TYPE_LP: |
||||||
|
return settings->slope == SLOPE_24 ? second.tick(in.lp, settings->freq + freqMod, settings->Q).lp : in.lp; |
||||||
|
case TYPE_BP: |
||||||
|
return settings->slope == SLOPE_24 ? second.tick(in.bp, settings->freq + freqMod, settings->Q).bp : in.bp;
|
||||||
|
case TYPE_HP: |
||||||
|
return settings->slope == SLOPE_24 ? second.tick(in.hp, settings->freq + freqMod, settings->Q).hp : in.hp; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -1,117 +0,0 @@ |
|||||||
#ifndef __SVF_H__ |
|
||||||
#define __SVF_H__ |
|
||||||
|
|
||||||
/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ |
|
||||||
|
|
||||||
#include <math.h> |
|
||||||
|
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
class SVF12 { |
|
||||||
protected: |
|
||||||
float as1, as2; |
|
||||||
float kdiv; |
|
||||||
float kK, kQ; |
|
||||||
|
|
||||||
inline void updateCoeffs() { |
|
||||||
kdiv = 1 + kK/kQ + kK*kK; |
|
||||||
} |
|
||||||
|
|
||||||
public: |
|
||||||
typedef struct { |
|
||||||
float hp; // high-pass
|
|
||||||
float bp; // band-pass
|
|
||||||
float lp; // low-pass
|
|
||||||
} Output; |
|
||||||
|
|
||||||
SVF12() { |
|
||||||
as1 = 0; |
|
||||||
as2 = 0; |
|
||||||
kK = tan(M_PI_4); |
|
||||||
kQ = M_SQRT1_2; |
|
||||||
updateCoeffs(); |
|
||||||
} |
|
||||||
|
|
||||||
void setFrequency(float f) { |
|
||||||
kK = tan(M_PI_4 * clamp(f, 0, 1)); |
|
||||||
updateCoeffs(); |
|
||||||
} |
|
||||||
|
|
||||||
void setQ(float q) { |
|
||||||
kQ = q; |
|
||||||
updateCoeffs(); |
|
||||||
} |
|
||||||
|
|
||||||
Output tick(float as) { |
|
||||||
Output out; |
|
||||||
out.hp = (as - (1/kQ + kK) * as1 - as2) / kdiv; |
|
||||||
float au = out.hp * kK; |
|
||||||
out.bp = au + as1; |
|
||||||
as1 = au + out.bp; |
|
||||||
au = out.bp * kK; |
|
||||||
out.lp = au + as2; |
|
||||||
as2 = au + out.lp; |
|
||||||
return out; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// 12 and 24 dB/oct
|
|
||||||
class SVF { |
|
||||||
public: |
|
||||||
enum Mode {
|
|
||||||
MODE_HP_12, |
|
||||||
MODE_HP_24, |
|
||||||
MODE_BP_12, |
|
||||||
MODE_BP_24, |
|
||||||
MODE_LP_12, |
|
||||||
MODE_LP_24, |
|
||||||
}; |
|
||||||
|
|
||||||
private: |
|
||||||
Mode mode; |
|
||||||
SVF12 first, second; |
|
||||||
|
|
||||||
public: |
|
||||||
SVF() { |
|
||||||
this->mode = MODE_LP_24; |
|
||||||
} |
|
||||||
|
|
||||||
void setMode(Mode mode) { |
|
||||||
this->mode = mode; |
|
||||||
} |
|
||||||
|
|
||||||
void setFrequency(float f) { |
|
||||||
first.setFrequency(f); |
|
||||||
second.setFrequency(f); |
|
||||||
} |
|
||||||
|
|
||||||
void setQ(float q) { |
|
||||||
first.setQ(q); |
|
||||||
second.setQ(q); |
|
||||||
} |
|
||||||
|
|
||||||
float tick(float as) { |
|
||||||
SVF12::Output in = first.tick(as); |
|
||||||
switch(mode) { |
|
||||||
case MODE_HP_12: |
|
||||||
return in.hp; |
|
||||||
|
|
||||||
case MODE_HP_24: |
|
||||||
return second.tick(in.hp).hp; |
|
||||||
|
|
||||||
case MODE_BP_12: |
|
||||||
return in.bp; |
|
||||||
|
|
||||||
case MODE_BP_24: |
|
||||||
return second.tick(in.bp).bp; |
|
||||||
|
|
||||||
case MODE_LP_12: |
|
||||||
return in.lp; |
|
||||||
|
|
||||||
case MODE_LP_24: |
|
||||||
return second.tick(in.lp).lp; |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
#endif |
|
@ -0,0 +1,6 @@ |
|||||||
|
#ifndef __GLOBALS_H__ |
||||||
|
#define __GLOBALS_H__ |
||||||
|
|
||||||
|
#define SAMPLE_RATE 48000 |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,33 @@ |
|||||||
|
#ifndef __PART_H__ |
||||||
|
#define __PART_H__ |
||||||
|
|
||||||
|
#include <map> |
||||||
|
#include <unordered_set> |
||||||
|
|
||||||
|
#include "voicemanager.h" |
||||||
|
#include "voice.h" |
||||||
|
#include "preset.h" |
||||||
|
|
||||||
|
class Part { |
||||||
|
public: |
||||||
|
VoiceManager* voiceManager; |
||||||
|
|
||||||
|
Voice::Settings settings; |
||||||
|
|
||||||
|
float pitchBend; |
||||||
|
float modulation; |
||||||
|
|
||||||
|
std::map<uint8_t, Voice*> notes; |
||||||
|
|
||||||
|
void loadPreset(Preset* preset); |
||||||
|
|
||||||
|
void noteOn(int note, int vel); |
||||||
|
void noteOff(int note); |
||||||
|
void control(int cc, int val); |
||||||
|
|
||||||
|
float tick() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -1,13 +1,58 @@ |
|||||||
#ifndef __PRESET_H__ |
#ifndef __PRESET_H__ |
||||||
#define __PRESET_H__ |
#define __PRESET_H__ |
||||||
|
|
||||||
|
#include "dsp/oscillator.h" |
||||||
|
#include "dsp/filter.h" |
||||||
#include "dsp/adsr.h" |
#include "dsp/adsr.h" |
||||||
|
|
||||||
struct Preset { |
struct Preset { |
||||||
float cutoff; |
typedef struct { |
||||||
float resonance; |
uint8_t attack; |
||||||
|
uint8_t decay; |
||||||
|
uint8_t sustain; |
||||||
|
uint8_t release; |
||||||
|
} Envelope; |
||||||
|
|
||||||
ADSR::Envelope ampEnv, fltEnv; |
uint8_t osc1Mode; |
||||||
|
uint8_t osc2Mode; |
||||||
|
uint8_t oscMix; |
||||||
|
|
||||||
|
struct { |
||||||
|
uint8_t type; |
||||||
|
uint8_t slope; |
||||||
|
uint8_t freq; |
||||||
|
uint8_t Q; |
||||||
|
} filter; |
||||||
|
|
||||||
|
Envelope ampEnv; |
||||||
|
Envelope fltEnv; |
||||||
|
}; |
||||||
|
|
||||||
|
static const Preset DEFAULT_PRESET = { |
||||||
|
.osc1Mode = Oscillator::MODE_SAW, |
||||||
|
.osc2Mode = Oscillator::MODE_SAW, |
||||||
|
.oscMix = 0, |
||||||
|
|
||||||
|
.filter = { |
||||||
|
.type = Filter::TYPE_LP, |
||||||
|
.slope = Filter::SLOPE_24, |
||||||
|
.freq = 127, |
||||||
|
.Q = 0, |
||||||
|
}, |
||||||
|
|
||||||
|
.ampEnv = { |
||||||
|
.attack = 0, |
||||||
|
.decay = 0, |
||||||
|
.sustain = 127, |
||||||
|
.release = 0 |
||||||
|
}, |
||||||
|
|
||||||
|
.fltEnv = { |
||||||
|
.attack = 0, |
||||||
|
.decay = 0, |
||||||
|
.sustain = 127, |
||||||
|
.release = 0 |
||||||
|
} |
||||||
}; |
}; |
||||||
|
|
||||||
#endif |
#endif |
@ -1,19 +1,23 @@ |
|||||||
#ifndef __SYNTH_H__ |
#ifndef __SYNTH_H__ |
||||||
#define __SYNTH_H__ |
#define __SYNTH_H__ |
||||||
|
|
||||||
#include "voice.h" |
#include "voicemanager.h" |
||||||
|
#include "part.h" |
||||||
const int SAMPLE_RATE = 48000; |
|
||||||
const int NUM_VOICES = 72; |
|
||||||
|
|
||||||
class Synth { |
class Synth { |
||||||
public:
|
public: |
||||||
void noteOn(int note, int velocity); |
Synth() { |
||||||
void noteOff(int note); |
for(int i = 0; i < 16; ++i) { |
||||||
void control(int code, int value); |
parts[i].voiceManager = &voiceManager; |
||||||
|
} |
||||||
|
} |
||||||
|
void noteOn(int ch, int note, int vel); |
||||||
|
void noteOff(int ch, int note); |
||||||
|
void control(int ch, int cc, int val); |
||||||
|
|
||||||
private: |
private: |
||||||
Voice voices[NUM_VOICES]; |
VoiceManager voiceManager{}; |
||||||
|
Part parts[16]; |
||||||
}; |
}; |
||||||
|
|
||||||
#endif |
#endif |
@ -0,0 +1,10 @@ |
|||||||
|
#ifndef __VOICEMANAGER_H__ |
||||||
|
#define __VOICEMANAGER_H__ |
||||||
|
|
||||||
|
class VoiceManager { |
||||||
|
public: |
||||||
|
private: |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,44 @@ |
|||||||
|
#include "synth/globals.h" |
||||||
|
|
||||||
|
#include "synth/part.h" |
||||||
|
|
||||||
|
float ccToA(int x) { |
||||||
|
return 5.0 * exp(9.2 * (x / 127.0) - 9.2); |
||||||
|
} |
||||||
|
|
||||||
|
float ccToDR(int x) { |
||||||
|
return 5.0 * exp(7.0 * (x / 127.0) - 7.0); |
||||||
|
} |
||||||
|
|
||||||
|
void Part::loadPreset(Preset* preset) { |
||||||
|
settings.osc1Mode = Oscillator::Mode(preset->osc1Mode); |
||||||
|
settings.osc2Mode = Oscillator::Mode(preset->osc2Mode); |
||||||
|
settings.oscMix = preset->oscMix / 127.0; |
||||||
|
|
||||||
|
settings.filter.type = Filter::Type(preset->filter.type); |
||||||
|
settings.filter.slope = Filter::Slope(preset->filter.slope); |
||||||
|
settings.filter.freq = preset->filter.freq / 127.0; |
||||||
|
settings.filter.Q = preset->filter.Q / 127.0; |
||||||
|
|
||||||
|
settings.ampEnv.attackStep = (1.0 / SAMPLE_RATE) / ccToA(preset->ampEnv.attack);; |
||||||
|
settings.ampEnv.decayStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->ampEnv.decay); |
||||||
|
settings.ampEnv.sustain = preset->ampEnv.sustain / 127.0; |
||||||
|
settings.ampEnv.releaseStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->ampEnv.release); |
||||||
|
|
||||||
|
settings.fltEnv.attackStep = (1.0 / SAMPLE_RATE) / ccToA(preset->fltEnv.attack);; |
||||||
|
settings.fltEnv.decayStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->fltEnv.decay); |
||||||
|
settings.fltEnv.sustain = preset->fltEnv.sustain / 127.0; |
||||||
|
settings.fltEnv.releaseStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->fltEnv.release); |
||||||
|
} |
||||||
|
|
||||||
|
void Part::noteOn(int note, int velocity) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void Part::noteOff(int note) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void Part::control(int code, int value) { |
||||||
|
|
||||||
|
} |
@ -1,13 +1,13 @@ |
|||||||
#include "synth/synth.h" |
#include "synth/synth.h" |
||||||
|
|
||||||
void Synth::noteOn(int note, int velocity) { |
void Synth::noteOn(int ch, int note, int vel) { |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
void Synth::noteOff(int note) { |
void Synth::noteOff(int ch, int note) { |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
void Synth::control(int code, int value) { |
void Synth::control(int ch, int cc, int val) { |
||||||
|
|
||||||
} |
} |
Loading…
Reference in new issue