parent
f860ebfef1
commit
09df1b535f
42 changed files with 927 additions and 524 deletions
@ -1,54 +0,0 @@ |
|||||||
#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" |
|
||||||
|
|
||||||
class Channel { |
|
||||||
public: |
|
||||||
VoiceManager::channel_t number; |
|
||||||
VoiceManager* voiceManager; |
|
||||||
Voice::Settings settings; |
|
||||||
|
|
||||||
Channel() : volume(1), pitchBend(0), modulation(0), lfoPhase(randPhase()) {} |
|
||||||
|
|
||||||
void loadPreset(const Preset* preset); |
|
||||||
|
|
||||||
void noteOn(int note, int vel); |
|
||||||
void noteOff(int note); |
|
||||||
void control(int cc, int val); |
|
||||||
|
|
||||||
frame tick() { |
|
||||||
frame out{0, 0}; |
|
||||||
float lfo = sin(lfoPhase); |
|
||||||
Voice** voices = voiceManager->getChannelVoices(number); |
|
||||||
for(Voice** voice = voices; *voice != NULL; ++voice) { |
|
||||||
float voiceOut = (*voice)->tick(pitchBend + settings.lfoPitchMod * lfo, settings.lfoFltMod * lfo); |
|
||||||
out.l += sinf((1 - (*voice)->pan) * (float) M_PI_2) * voiceOut; |
|
||||||
out.r += sinf((*voice)->pan * (float) M_PI_2) * voiceOut; |
|
||||||
} |
|
||||||
out *= volume; |
|
||||||
|
|
||||||
lfoPhase += settings.lfoStep; |
|
||||||
if(lfoPhase >= (float) PIx2) { |
|
||||||
lfoPhase -= PIx2; |
|
||||||
} |
|
||||||
|
|
||||||
return out; |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
float volume; |
|
||||||
float pitchBend; |
|
||||||
float modulation; |
|
||||||
|
|
||||||
float lfoPhase; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif |
|
@ -1,128 +0,0 @@ |
|||||||
#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" |
|
||||||
|
|
||||||
#define FILTER_OVERSAMPLE 4 |
|
||||||
#define FILTER_K_SCALE (2 * CV_FREQ_MIN / (FILTER_OVERSAMPLE * SAMPLE_RATE)) |
|
||||||
#define FILTER_CV_MAX 9.25 // MIDI note "135" (19912.126958213178287 Hz)
|
|
||||||
|
|
||||||
inline float cvToK(float cv) { |
|
||||||
return tanf((float) M_PI_2 * std::min(0.5f, std::min(1.f / FILTER_OVERSAMPLE, (float) FILTER_K_SCALE * exp2f(cv)))); |
|
||||||
} |
|
||||||
|
|
||||||
class SVF12 { |
|
||||||
public: |
|
||||||
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) {} |
|
||||||
|
|
||||||
inline Output tick(float in, float kK, float kQ) { |
|
||||||
Output out; |
|
||||||
float kdiv = 1 + kK/kQ + kK*kK; |
|
||||||
out.hp = (in - (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; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
class Oversampler { |
|
||||||
private: |
|
||||||
float kK, kQ = (float) M_SQRT1_2; |
|
||||||
SVF12 aa1, aa2; |
|
||||||
|
|
||||||
public: |
|
||||||
Oversampler() : kK(tanf((float) M_PI_2 * 1.0f / FILTER_OVERSAMPLE)) {} |
|
||||||
|
|
||||||
inline float tick(float in, std::function<float(float)> process) { |
|
||||||
if(FILTER_OVERSAMPLE < 2) { |
|
||||||
return process(in); |
|
||||||
} else { |
|
||||||
// float out = aa2.tick(aa1.tick(times * process(in), kK, kQ).lp, kK, kQ).lp;
|
|
||||||
// for(int i = 1; i < times; ++i) {
|
|
||||||
// aa2.tick(aa1.tick(process(0), kK, kQ).lp, kK, kQ);
|
|
||||||
// }
|
|
||||||
float out = process(aa2.tick(aa1.tick(FILTER_OVERSAMPLE * in, kK, kQ).lp, kK, kQ).lp); |
|
||||||
for(int i = 1; i < FILTER_OVERSAMPLE; ++i) { |
|
||||||
process(aa2.tick(aa1.tick(0, kK, kQ).lp, kK, kQ).lp); |
|
||||||
} |
|
||||||
|
|
||||||
return out; |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// 12 and 24 dB/oct
|
|
||||||
class Filter { |
|
||||||
public: |
|
||||||
enum Type { |
|
||||||
TYPE_LP = 0, |
|
||||||
TYPE_BP, |
|
||||||
TYPE_HP |
|
||||||
}; |
|
||||||
|
|
||||||
enum Slope { |
|
||||||
SLOPE_24 = 0, |
|
||||||
SLOPE_12, |
|
||||||
}; |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
float freq; |
|
||||||
float res; |
|
||||||
Type type; |
|
||||||
Slope slope; |
|
||||||
} Settings; |
|
||||||
|
|
||||||
private: |
|
||||||
Settings const * settings; |
|
||||||
SVF12 fltA, fltB; |
|
||||||
Oversampler os; |
|
||||||
|
|
||||||
public: |
|
||||||
void assign(Settings const * settings); |
|
||||||
|
|
||||||
inline float tick(float in, float freqMod) { |
|
||||||
float kK = cvToK(settings->freq + freqMod); |
|
||||||
float kQ = (float) M_SQRT1_2 + settings->res; |
|
||||||
|
|
||||||
return os.tick(in, [this, kK, kQ](float oin) { |
|
||||||
SVF12::Output outA = fltA.tick(oin, kK, kQ); |
|
||||||
|
|
||||||
switch(settings->type) { |
|
||||||
case TYPE_LP: |
|
||||||
return settings->slope == SLOPE_24 ? fltB.tick(outA.lp, kK, kQ).lp : outA.lp; |
|
||||||
case TYPE_BP: |
|
||||||
return settings->slope == SLOPE_24 ? fltB.tick(outA.bp, kK, kQ).bp : outA.bp;
|
|
||||||
case TYPE_HP: |
|
||||||
return settings->slope == SLOPE_24 ? fltB.tick(outA.hp, kK, kQ).hp : outA.hp; |
|
||||||
default: |
|
||||||
return oin; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
#endif |
|
@ -1,82 +0,0 @@ |
|||||||
#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" |
|
||||||
|
|
||||||
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(randPhase()), value(0), driftAmount(0.001f), driftValue(0) {} |
|
||||||
|
|
||||||
void assign(Mode const * mode); |
|
||||||
|
|
||||||
// Generate next output sample and advance the phase angle
|
|
||||||
inline float tick(float cv) { |
|
||||||
const float phaseStep = (float) (2 * CV_FREQ_MIN) * exp2f(cv) * (float) SAMPLE_RATE_INV; |
|
||||||
|
|
||||||
const float t = phase * (float) PIx2_INV; // Define half phase
|
|
||||||
const float dt = phaseStep * (float) PIx2_INV; |
|
||||||
|
|
||||||
if (*mode == MODE_SINE) { |
|
||||||
value = sinf(phase); // No harmonics in sine so no aliasing!! No Poly BLEPs needed!
|
|
||||||
} else if (*mode == MODE_SAW) { |
|
||||||
value = (2.0f * t) - 1.0f; // Render naive waveshape
|
|
||||||
value -= polyBlep(t, dt); // Layer output of Poly BLEP on top
|
|
||||||
} else if (*mode == MODE_SQUARE) { |
|
||||||
if (phase < (float) M_PI) {
|
|
||||||
value = 1.0; // Flip
|
|
||||||
} else {
|
|
||||||
value = -1.0; // Flop
|
|
||||||
} |
|
||||||
value += polyBlep(t, dt); // Layer output of Poly BLEP on top (flip)
|
|
||||||
value -= polyBlep(fmodf(t + 0.5f, 1.0f), dt); // Layer output of Poly BLEP on top (flop)
|
|
||||||
} |
|
||||||
|
|
||||||
driftValue += 0.005f * whiteNoise() - 0.00001f * driftValue; |
|
||||||
phase += phaseStep * (1.0f + driftAmount * driftValue); |
|
||||||
|
|
||||||
if(phase >= (float) PIx2) { // wrap if phase angle >=360º
|
|
||||||
phase -= PIx2; |
|
||||||
} |
|
||||||
|
|
||||||
return value; |
|
||||||
} |
|
||||||
|
|
||||||
inline float polyBlep(float t, float dt) { |
|
||||||
// t-t^2/2 +1/2
|
|
||||||
// 0 < t <= 1
|
|
||||||
// discontinuities between 0 & 1
|
|
||||||
if (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 (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 |
|
@ -1,7 +0,0 @@ |
|||||||
#ifndef __GLOBALS_H__ |
|
||||||
#define __GLOBALS_H__ |
|
||||||
|
|
||||||
#define SAMPLE_RATE 44100 |
|
||||||
#define SAMPLE_RATE_INV (1.0 / SAMPLE_RATE) |
|
||||||
|
|
||||||
#endif |
|
@ -1,45 +0,0 @@ |
|||||||
#ifndef __SYNTH_H__ |
|
||||||
#define __SYNTH_H__ |
|
||||||
|
|
||||||
#include "voicemanager.h" |
|
||||||
#include "channel.h" |
|
||||||
#include "dsp/frame.h" |
|
||||||
#include "dsp/reverb.h" |
|
||||||
|
|
||||||
class Synth { |
|
||||||
public: |
|
||||||
Synth() { |
|
||||||
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 frame tick() { |
|
||||||
frame out{}; |
|
||||||
frame reverbBus{}; |
|
||||||
|
|
||||||
for(auto& channel : channels) { |
|
||||||
frame channelOut = channel.tick(); |
|
||||||
out += channelOut; |
|
||||||
reverbBus += channel.settings.reverb * channelOut; |
|
||||||
} |
|
||||||
|
|
||||||
out += reverb.tick(reverbBus); |
|
||||||
out *= 0.125f; |
|
||||||
|
|
||||||
return out; |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
VoiceManager voiceManager{}; |
|
||||||
Channel channels[16]; |
|
||||||
Reverb reverb; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif |
|
@ -1,74 +0,0 @@ |
|||||||
#ifndef __VOICE_H__ |
|
||||||
#define __VOICE_H__ |
|
||||||
|
|
||||||
#include "globals.h" |
|
||||||
#include "preset.h" |
|
||||||
|
|
||||||
#include "dsp/oscillator.h" |
|
||||||
#include "dsp/filter.h" |
|
||||||
#include "dsp/adsr.h" |
|
||||||
|
|
||||||
class Voice { |
|
||||||
public: |
|
||||||
typedef struct { |
|
||||||
float unison; |
|
||||||
|
|
||||||
Oscillator::Mode osc1Mode; |
|
||||||
Oscillator::Mode osc2Mode; |
|
||||||
float oscMix; |
|
||||||
float oscDetune; |
|
||||||
float osc2Pitch; |
|
||||||
|
|
||||||
float noiseMix; |
|
||||||
|
|
||||||
Filter::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 osc1; |
|
||||||
Oscillator osc2; |
|
||||||
Filter filter; |
|
||||||
|
|
||||||
public: |
|
||||||
int note; |
|
||||||
float gain; |
|
||||||
float pan; |
|
||||||
float detune; |
|
||||||
|
|
||||||
void reset(); |
|
||||||
void assign(Settings const * settings); |
|
||||||
void noteOn(int note, float detune = 0, float gain = 1.0, float pan = 0.5); |
|
||||||
void noteOff(); |
|
||||||
inline bool isIdle() { return adsrAmp.isIdle(); } |
|
||||||
|
|
||||||
inline float tick(float pitchMod, float fltMod) { |
|
||||||
const float basePitch = note + pitchMod + detune; |
|
||||||
const float osc1CV = noteToCV(basePitch - settings->oscDetune); |
|
||||||
const float osc2CV = noteToCV(basePitch + settings->oscDetune + settings->osc2Pitch); |
|
||||||
float out = 0; |
|
||||||
out += (1 - settings->oscMix) * osc1.tick(osc1CV); |
|
||||||
out += settings->oscMix * osc2.tick(osc2CV); |
|
||||||
out += settings->noiseMix * ((1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()) - out); |
|
||||||
const float keyTrackVal = settings->keyTrack * (note - 72) / 12.f; |
|
||||||
out = filter.tick(out, settings->modEnvFltGain * adsrMod.tick() + keyTrackVal + fltMod); |
|
||||||
out *= gain * adsrAmp.tick(); |
|
||||||
return out; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
#endif |
|
@ -0,0 +1,75 @@ |
|||||||
|
#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" |
||||||
|
|
||||||
|
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) { |
||||||
|
Voice** voices = voiceManager->getChannelVoices(number); |
||||||
|
if(*voices == NULL) { |
||||||
|
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[numFrames]; |
||||||
|
(*voice)->tick(voiceOut, numFrames, pitchMod, fltMod); |
||||||
|
|
||||||
|
for(size_t i = 0; i < bufferSize; ++i) { |
||||||
|
out[i] += voiceOut[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
float volume; |
||||||
|
float pitchBend; |
||||||
|
float modulation; |
||||||
|
|
||||||
|
float lfoPhase; |
||||||
|
}; |
||||||
|
|
||||||
|
#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,124 @@ |
|||||||
|
#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" |
||||||
|
|
||||||
|
#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 SVF12 { |
||||||
|
protected: |
||||||
|
float hp, bp, lp; |
||||||
|
float as1, as2; |
||||||
|
|
||||||
|
public: |
||||||
|
SVF12() : hp(0), bp(0), lp(0), as1(0), as2(0) {} |
||||||
|
|
||||||
|
inline void tick(float *in, float *hpo, float *bpo, float *lpo, size_t bufferSize, float *kK, float *kQ) { |
||||||
|
float kQ_1[bufferSize]; |
||||||
|
for(size_t i = 0; i < bufferSize; ++i) { |
||||||
|
kQ_1[i] = 1.f / kQ[i]; |
||||||
|
} |
||||||
|
float kMul[bufferSize]; |
||||||
|
for(size_t i = 0; i < bufferSize; ++i) { |
||||||
|
kMul[i] = 1.f / (1.f + kK[i]*kQ_1[i] + kK[i]*kK[i]); |
||||||
|
} |
||||||
|
for(size_t i = 0; i < bufferSize; ++i) { |
||||||
|
hp = (in[i] - (kQ_1[i] + kK[i]) * as1 - as2) * kMul[i]; |
||||||
|
float au = hp * kK[i]; |
||||||
|
bp = au + as1; |
||||||
|
as1 = au + bp; |
||||||
|
au = bp * kK[i]; |
||||||
|
lp = au + as2; |
||||||
|
as2 = au + lp; |
||||||
|
|
||||||
|
hpo && (hpo[i] = hp); |
||||||
|
bpo && (bpo[i] = bp); |
||||||
|
lpo && (lpo[i] = lp); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 12 and 24 dB/oct
|
||||||
|
class Filter { |
||||||
|
public: |
||||||
|
enum Type { |
||||||
|
TYPE_LP = 0, |
||||||
|
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; |
||||||
|
SVF12 fltA, fltB; |
||||||
|
float freq, res; |
||||||
|
|
||||||
|
public: |
||||||
|
void assign(Settings const * settings); |
||||||
|
|
||||||
|
inline void tick(float *in, float *out, size_t bufferSize, float *freqMod) { |
||||||
|
float dt = 1.f / (float) bufferSize; |
||||||
|
|
||||||
|
float kK[bufferSize]; |
||||||
|
float dFreq = dt * (settings->freq - freq); |
||||||
|
for(size_t i = 0; i < bufferSize; i++) { |
||||||
|
kK[i] = fastCVtoK(freq + freqMod[i]); |
||||||
|
freq += dFreq; |
||||||
|
} |
||||||
|
float kQ[bufferSize]; |
||||||
|
float dRes = dt * (settings->res - res); |
||||||
|
for(size_t i = 0; i < bufferSize; i++) { |
||||||
|
kQ[i] = (float) M_SQRT1_2 + res; |
||||||
|
res += dRes; |
||||||
|
} |
||||||
|
|
||||||
|
switch(settings->type) { |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
#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,101 @@ |
|||||||
|
#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" |
||||||
|
|
||||||
|
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) { |
||||||
|
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]; |
||||||
|
for(size_t i = 0; i < frameCount; ++i) { |
||||||
|
phaseStepBuf[i] = cvToStep(pitch[i]) * driftBuf[i]; |
||||||
|
} |
||||||
|
|
||||||
|
float phaseBuf[frameCount]; |
||||||
|
for(size_t i = 0; i < frameCount; ++i) { |
||||||
|
phaseBuf[i] = phase; |
||||||
|
phase += phaseStepBuf[i]; |
||||||
|
phase -= floorf(phase); |
||||||
|
} |
||||||
|
|
||||||
|
if(*mode == MODE_SINE) { |
||||||
|
for(size_t i = 0, j = 0; i < frameCount; ++i) { |
||||||
|
float value = fastSin(phaseBuf[i]); |
||||||
|
out[j++] += gain[j & 1] * value; |
||||||
|
out[j++] += gain[j & 1] * value; |
||||||
|
} |
||||||
|
} else if(*mode == MODE_SAW) { |
||||||
|
for(size_t i = 0, j = 0; i < frameCount; ++i) { |
||||||
|
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[j & 1] * value; |
||||||
|
out[j++] += gain[j & 1] * value; |
||||||
|
} |
||||||
|
} else if (*mode == MODE_SQUARE) { |
||||||
|
for(size_t i = 0, j = 0; i < frameCount; ++i) { |
||||||
|
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(fmodf(phaseBuf[i] + 0.5f, 1.0f), phaseStepBuf[i]); // Layer output of Poly BLEP on top (flop)
|
||||||
|
|
||||||
|
out[j++] = gain[j & 1] * value; |
||||||
|
out[j++] = gain[j & 1] * value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inline float polyBlep(float t, float dt) { |
||||||
|
// t-t^2/2 +1/2
|
||||||
|
// 0 < t <= 1
|
||||||
|
// discontinuities between 0 & 1
|
||||||
|
if(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(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 |
@ -1,19 +1,28 @@ |
|||||||
#ifndef __DELAYLINE_H__ |
#ifndef __STEREODELAYLINE_H__ |
||||||
#define __DELAYLINE_H__ |
#define __STEREODELAYLINE_H__ |
||||||
|
|
||||||
#include <cstddef> |
#include <cstddef> |
||||||
|
|
||||||
#include "frame.h" |
#include "frame.h" |
||||||
|
|
||||||
template <size_t CAPACITY> |
template <size_t CAPACITY> |
||||||
class DelayLine { |
class StereoDelayLine { |
||||||
private: |
private: |
||||||
const size_t size; |
|
||||||
frame samples[CAPACITY] = {{0, 0}}; |
frame samples[CAPACITY] = {{0, 0}}; |
||||||
size_t cursor; |
size_t cursor; |
||||||
|
|
||||||
public: |
public: |
||||||
DelayLine(size_t size = CAPACITY) : size(size) {} |
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) { |
inline frame tick(frame in) { |
||||||
samples[cursor++] = in; |
samples[cursor++] = in; |
@ -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,65 @@ |
|||||||
|
#ifndef __SYNTH_H__ |
||||||
|
#define __SYNTH_H__ |
||||||
|
|
||||||
|
#include "voicemanager.h" |
||||||
|
#include "channel.h" |
||||||
|
#include "dsp/frame.h" |
||||||
|
#include "dsp/reverb.h" |
||||||
|
#include "luts.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) { |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
VoiceManager voiceManager{}; |
||||||
|
Channel channels[16]; |
||||||
|
Reverb reverb; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,127 @@ |
|||||||
|
#ifndef __VOICE_H__ |
||||||
|
#define __VOICE_H__ |
||||||
|
|
||||||
|
#include "globals.h" |
||||||
|
#include "preset.h" |
||||||
|
|
||||||
|
#include "dsp/oscillator.h" |
||||||
|
#include "dsp/filter.h" |
||||||
|
#include "dsp/adsr.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; |
||||||
|
|
||||||
|
Filter::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]; |
||||||
|
Filter 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) { |
||||||
|
const size_t frameCount = bufferSize / 2; |
||||||
|
|
||||||
|
float basePitchBuf[frameCount]; |
||||||
|
for(size_t i = 0; i < frameCount; ++i) { |
||||||
|
basePitchBuf[i] = note + pitchMod[i]; |
||||||
|
} |
||||||
|
|
||||||
|
float oscCVBuf[MAX_OSCS][frameCount]; |
||||||
|
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; |
||||||
|
for(size_t j = 0; j < frameCount; ++j) { |
||||||
|
oscCVBuf[i][j] = noteToCV(basePitchBuf[i] + detune + transpose); |
||||||
|
} |
||||||
|
detune += settings->oscDetune; |
||||||
|
} |
||||||
|
|
||||||
|
float pan, panStep; |
||||||
|
if(settings->unison > 1) { |
||||||
|
pan = 0.f; |
||||||
|
panStep = 1.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 = (i & 1) ? settings->oscMix : (1 - settings->oscMix); |
||||||
|
float gain[2]; |
||||||
|
gain[0] = sinf((1.f - pan) * (float) M_PI_2); |
||||||
|
gain[1] = sinf(pan * (float) M_PI_2); |
||||||
|
pan += panStep; |
||||||
|
osc[i].tick(oscBuf, bufferSize, oscCVBuf[i], gain); |
||||||
|
} |
||||||
|
|
||||||
|
float noiseBuf[bufferSize]; |
||||||
|
if(settings->unison > 1) { // 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; ) { |
||||||
|
float noise = (1.f / 3.f) * (whiteNoise() + whiteNoise() + whiteNoise()); |
||||||
|
noiseBuf[i++] = noise; |
||||||
|
noiseBuf[i++] = noise; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for(size_t i = 0; i < bufferSize; ++i) { |
||||||
|
oscBuf[i] += settings->noiseMix * (noiseBuf[i] - oscBuf[i]); |
||||||
|
} |
||||||
|
|
||||||
|
const float keyTrackVal = settings->keyTrack * (note - 24) / 12.f; |
||||||
|
|
||||||
|
float fltModBuf[frameCount]; |
||||||
|
for(size_t i = 0; i < frameCount; ++i) { |
||||||
|
fltModBuf[i] = settings->modEnvFltGain * adsrMod.tick() + keyTrackVal + fltMod[i]; |
||||||
|
} |
||||||
|
filter.tick(oscBuf, out, bufferSize, fltModBuf); |
||||||
|
for(size_t i = 0; i < bufferSize; ++i) { |
||||||
|
out[i] *= gain * adsrAmp.tick(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -1,4 +1,4 @@ |
|||||||
#include "synth/dsp/adsr.h" |
#include "dsp/adsr.h" |
||||||
|
|
||||||
void ADSR::reset() { |
void ADSR::reset() { |
||||||
state = IDLE; |
state = IDLE; |
@ -1,5 +1,7 @@ |
|||||||
#include "synth/dsp/filter.h" |
#include "dsp/filter.h" |
||||||
|
|
||||||
void Filter::assign(Settings const * settings) { |
void Filter::assign(Settings const * settings) { |
||||||
this->settings = settings; |
this->settings = settings; |
||||||
|
freq = settings->freq; |
||||||
|
res = settings->res; |
||||||
} |
} |
@ -1,4 +1,4 @@ |
|||||||
#include "synth/dsp/oscillator.h" |
#include "dsp/oscillator.h" |
||||||
|
|
||||||
void Oscillator::assign(Mode const * mode) { |
void Oscillator::assign(Mode const * mode) { |
||||||
this->mode = mode; |
this->mode = mode; |
@ -1,6 +1,6 @@ |
|||||||
#include <cmath> |
#include <cmath> |
||||||
|
|
||||||
#include "synth/dsp/reverb.h" |
#include "dsp/reverb.h" |
||||||
|
|
||||||
Reverb::Reverb() { |
Reverb::Reverb() { |
||||||
size_t interval = REVERB_FRAMES / REVERB_TAPS - 4096 / REVERB_TAPS; |
size_t interval = REVERB_FRAMES / REVERB_TAPS - 4096 / REVERB_TAPS; |
@ -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)); |
||||||
|
} |
||||||
|
} |
@ -1,18 +1,18 @@ |
|||||||
#include <cstdio> |
#include <cstdio> |
||||||
|
|
||||||
#include "synth/synth.h" |
#include "synth.h" |
||||||
|
|
||||||
void Synth::noteOn(int ch, int note, int vel) { |
void Synth::noteOn(int ch, int note, int vel) { |
||||||
channels[ch].noteOn(note, vel); |
|
||||||
printf("Note On: ch=%d note=%d vel=%d\n", ch, note, 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) { |
void Synth::noteOff(int ch, int note) { |
||||||
channels[ch].noteOff(note); |
|
||||||
printf("Note Off: ch=%d note=%d\n", ch, note); |
printf("Note Off: ch=%d note=%d\n", ch, note); |
||||||
|
channels[ch].noteOff(note); |
||||||
} |
} |
||||||
|
|
||||||
void Synth::control(int ch, int cc, int val) { |
void Synth::control(int ch, int cc, int val) { |
||||||
channels[ch].control(cc, val); |
|
||||||
printf("Controller: ch=%d cc=%d val=%d\n", ch, cc, val); |
printf("Controller: ch=%d cc=%d val=%d\n", ch, cc, val); |
||||||
|
channels[ch].control(cc, val); |
||||||
} |
} |
@ -1,7 +1,7 @@ |
|||||||
#include <cstdio> |
#include <cstdio> |
||||||
#include <algorithm> |
#include <algorithm> |
||||||
|
|
||||||
#include "synth/voicemanager.h" |
#include "voicemanager.h" |
||||||
|
|
||||||
#define VOICE_SCORE_MSB (8 * sizeof(score_t) - 1) |
#define VOICE_SCORE_MSB (8 * sizeof(score_t) - 1) |
||||||
|
|
@ -1,18 +0,0 @@ |
|||||||
float centLUT[] = { |
|
||||||
1.0000000, 1.0004549, 1.0009100, 1.0013654, 1.0018209, 1.0022767, 1.0027326, 1.0031888,
|
|
||||||
1.0036452, 1.0041018, 1.0045586, 1.0050156, 1.0054728, 1.0059302, 1.0063878, 1.0068456,
|
|
||||||
1.0073037, 1.0077620, 1.0082204, 1.0086790, 1.0091379, 1.0095969, 1.0100563, 1.0105158,
|
|
||||||
1.0109755, 1.0114354, 1.0118955, 1.0123559, 1.0128164, 1.0132772, 1.0137382, 1.0141994,
|
|
||||||
1.0146607, 1.0151223, 1.0155841, 1.0160462, 1.0165083, 1.0169708, 1.0174334, 1.0178963,
|
|
||||||
1.0183593, 1.0188227, 1.0192862, 1.0197498, 1.0202137, 1.0206778, 1.0211421, 1.0216067,
|
|
||||||
1.0220715, 1.0225364, 1.0230016, 1.0234669, 1.0239326, 1.0243984, 1.0248644, 1.0253307,
|
|
||||||
1.0257971, 1.0262637, 1.0267307, 1.0271977, 1.0276650, 1.0281326, 1.0286002, 1.0290682,
|
|
||||||
1.0295364, 1.0300047, 1.0304732, 1.0309421, 1.0314111, 1.0318803, 1.0323497, 1.0328194,
|
|
||||||
1.0332892, 1.0337592, 1.0342295, 1.0347000, 1.0351708, 1.0356417, 1.0361128, 1.0365841,
|
|
||||||
1.0370557, 1.0375276, 1.0379995, 1.0384717, 1.0389441, 1.0394168, 1.0398897, 1.0403627,
|
|
||||||
1.0408360, 1.0413095, 1.0417832, 1.0422572, 1.0427313, 1.0432056, 1.0436803, 1.0441550,
|
|
||||||
1.0446301, 1.0451053, 1.0455807, 1.0460564, 1.0465323, 1.0470084, 1.0474846, 1.0479612,
|
|
||||||
1.0484380, 1.0489149, 1.0493921, 1.0498694, 1.0503471, 1.0508249, 1.0513029, 1.0517812,
|
|
||||||
1.0522597, 1.0527384, 1.0532173, 1.0536964, 1.0541759, 1.0546554, 1.0551351, 1.0556152,
|
|
||||||
1.0560954, 1.0565759, 1.0570565, 1.0575374, 1.0580184, 1.0584998, 1.0589813, 1.0594631 |
|
||||||
}; |
|
@ -1,10 +0,0 @@ |
|||||||
float kLUT[] = { |
|
||||||
0.0015708, 0.0017336, 0.0019134, 0.0021118, 0.0023307, 0.0025723, 0.0028390, 0.0031333,
|
|
||||||
0.0034582, 0.0038167, 0.0042124, 0.0046491, 0.0051311, 0.0056631, 0.0062502, 0.0068982,
|
|
||||||
0.0076134, 0.0084028, 0.0092740, 0.0102355, 0.0112968, 0.0124681, 0.0137608, 0.0151877,
|
|
||||||
0.0167625, 0.0185007, 0.0204193, 0.0225369, 0.0248743, 0.0274544, 0.0303024, 0.0334462,
|
|
||||||
0.0369167, 0.0407480, 0.0449779, 0.0496483, 0.0548053, 0.0605004, 0.0667905, 0.0737388,
|
|
||||||
0.0814158, 0.0898997, 0.0992782, 0.1096492, 0.1211226, 0.1338222, 0.1478880, 0.1634790,
|
|
||||||
0.1807770, 0.1999912, 0.2213640, 0.2451794, 0.2717728, 0.3015462, 0.3349871, 0.3726965,
|
|
||||||
0.4154281, 0.4641461, 0.5201120, 0.5850177, 0.6611994, 0.7519926, 0.8623521, 1.0000000 |
|
||||||
}; |
|
@ -1,18 +0,0 @@ |
|||||||
float noteLUT[] = { |
|
||||||
8.1757994, 8.6619568, 9.1770239, 9.7227182, 10.3008614, 10.9133825, 11.5623255, 12.2498569,
|
|
||||||
12.9782715, 13.7500000, 14.5676174, 15.4338531, 16.3515987, 17.3239136, 18.3540478, 19.4454365,
|
|
||||||
20.6017227, 21.8267651, 23.1246510, 24.4997139, 25.9565430, 27.5000000, 29.1352348, 30.8677063,
|
|
||||||
32.7031975, 34.6478271, 36.7080956, 38.8908730, 41.2034454, 43.6535301, 46.2493019, 48.9994278,
|
|
||||||
51.9130859, 55.0000000, 58.2704697, 61.7354126, 65.4063950, 69.2956543, 73.4161911, 77.7817459,
|
|
||||||
82.4068909, 87.3070602, 92.4986038, 97.9988556, 103.8261719, 110.0000000, 116.5409393, 123.4708252,
|
|
||||||
130.8127899, 138.5913086, 146.8323822, 155.5634918, 164.8137817, 174.6141205, 184.9972076, 195.9977112,
|
|
||||||
207.6523438, 220.0000000, 233.0818787, 246.9416504, 261.6255798, 277.1826172, 293.6647644, 311.1269836,
|
|
||||||
329.6275635, 349.2282410, 369.9944153, 391.9954224, 415.3046875, 440.0000000, 466.1637573, 493.8833008,
|
|
||||||
523.2511597, 554.3652344, 587.3295288, 622.2539673, 659.2551270, 698.4564819, 739.9888306, 783.9908447,
|
|
||||||
830.6093750, 880.0000000, 932.3275146, 987.7666016, 1046.5023193, 1108.7304688, 1174.6590576, 1244.5079346,
|
|
||||||
1318.5102539, 1396.9129639, 1479.9776611, 1567.9816895, 1661.2187500, 1760.0000000, 1864.6550293, 1975.5332031,
|
|
||||||
2093.0046387, 2217.4609375, 2349.3181152, 2489.0158691, 2637.0205078, 2793.8259277, 2959.9553223, 3135.9633789,
|
|
||||||
3322.4375000, 3520.0000000, 3729.3100586, 3951.0664062, 4186.0092773, 4434.9218750, 4698.6362305, 4978.0317383,
|
|
||||||
5274.0410156, 5587.6518555, 5919.9106445, 6271.9267578, 6644.8750000, 7040.0000000, 7458.6201172, 7902.1328125,
|
|
||||||
8372.0185547, 8869.8437500, 9397.2724609, 9956.0634766, 10548.0820312, 11175.3037109, 11839.8212891, 12543.8535156 |
|
||||||
}; |
|
Loading…
Reference in new issue