Various changes

main
Thor 1 year ago
parent 9e07cb5103
commit 2ca29337c3
  1. 3
      CMakeLists.txt
  2. 7
      include/synth/cc.h
  3. 8
      include/synth/channel.h
  4. 36
      include/synth/dsp/delayline.h
  5. 50
      include/synth/dsp/filter.h
  6. 141
      include/synth/dsp/frame.h
  7. 10
      include/synth/dsp/oscillator.h
  8. 55
      include/synth/dsp/reverb.h
  9. 28
      include/synth/dsp/sap.h
  10. 6
      include/synth/dsp/util.h
  11. 41
      include/synth/frame.h
  12. 2
      include/synth/preset.h
  13. 9
      include/synth/synth.h
  14. 23
      include/synth/voice.h
  15. 10
      include/synth/voicemanager.h
  16. 17
      include/synthframe.h
  17. 2
      src/genluts.cpp
  18. 88
      src/synth/channel.cpp
  19. 16
      src/synth/dsp/reverb.cpp
  20. 12
      src/synth/preset.cpp
  21. 5
      src/synth/voice.cpp
  22. 45
      src/synth/voicemanager.cpp
  23. 4
      src/synthapp.cpp
  24. 48
      src/synthframe.cpp

@ -38,7 +38,8 @@ add_executable(main
src/synth/dsp/oscillator.cpp
src/synth/dsp/filter.cpp
src/synth/dsp/filterlut.cpp
src/synth/dsp/adsr.cpp)
src/synth/dsp/adsr.cpp
src/synth/dsp/reverb.cpp)
target_include_directories(main PRIVATE include)
add_executable(genluts src/genluts.cpp)

@ -19,5 +19,10 @@
#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
#endif

@ -8,11 +8,11 @@
#include "voicemanager.h"
#include "voice.h"
#include "preset.h"
#include "frame.h"
#include "dsp/frame.h"
class Channel {
public:
channel_t number;
VoiceManager::channel_t number;
VoiceManager* voiceManager;
Voice::Settings settings;
@ -29,7 +29,9 @@ public:
float lfo = sin(lfoPhase);
Voice** voices = voiceManager->getChannelVoices(number);
for(Voice** voice = voices; *voice != NULL; ++voice) {
out += (*voice)->tick(pitchBend + settings.lfoPitchMod * lfo, settings.lfoFltMod * lfo);
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;

@ -0,0 +1,36 @@
#ifndef __DELAYLINE_H__
#define __DELAYLINE_H__
#include <cstddef>
#include "frame.h"
template <size_t CAPACITY>
class DelayLine {
private:
const size_t size;
frame samples[CAPACITY] = {{0, 0}};
size_t cursor;
public:
DelayLine(size_t size = CAPACITY) : size(size) {}
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

@ -6,40 +6,42 @@
#include <cmath>
#include <cfloat>
#include <iostream>
#include <algorithm>
#include <functional>
#include "../globals.h"
#include "util.h"
#define FILTER_OVERSAMPLE 4
#define FILTER_K_MIN_FREQ 20.0f
#define FILTER_K_MAX_FREQ 15000.0f
#define FILTER_K_BASE logf(FILTER_K_MAX_FREQ / FILTER_K_MIN_FREQ)
#define FILTER_K_SCALE (2.f * FILTER_K_MIN_FREQ / (FILTER_OVERSAMPLE * SAMPLE_RATE))
#define FILTER_K_SCALE (2 * CV_FREQ_MIN / (FILTER_OVERSAMPLE * SAMPLE_RATE))
#define FILTER_CV_MAX 9.25 // MIDI note "135" (19912.126958213178287 Hz)
//#define USE_LUTS
#define K_LUT_SIZE 64
#ifdef USE_LUTS
extern float kLUT[K_LUT_SIZE];
inline float freqToK(float x) {
float intPart, fracPart = modf(x * (K_LUT_SIZE - 1), &intPart);
inline float cvToK(float cv) {
float intPart, fracPart = modf(cv * (K_LUT_SIZE - 1) / FILTER_K_LUT_MAX, &intPart);
int index = (int) intPart;
return fracPart < FLT_EPSILON ? kLUT[index] : (1 - fracPart) * kLUT[index] + fracPart * kLUT[index + 1];
if(index < 0) {
return kLUT[0];
} else if(index >= K_LUT_SIZE - 1) {
return kLUT[K_LUT_SIZE - 1];
} else {
return (1 - fracPart) * kLUT[index] + fracPart * kLUT[index + 1];
}
}
#else
inline float freqToK(float x) {
return tanf((float) M_PI_2 * std::min(0.5f, FILTER_K_SCALE * expf(x * FILTER_K_BASE)));
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))));
}
#endif
inline float noteToFltFreq(float note) {
return ((note - 69) / 12.0f) / (FILTER_K_BASE * (float) M_LOG2E);
}
class SVF12 {
public:
typedef struct {
@ -81,10 +83,15 @@ public:
if(times < 2) {
return process(in);
} else {
float out = aa2.tick(aa1.tick(times * process(in), kK, kQ).lp, kK, kQ).lp;
// 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(times * 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);
process(aa2.tick(aa1.tick(0, kK, kQ).lp, kK, kQ).lp);
}
return out;
}
}
@ -95,13 +102,13 @@ class Filter {
public:
enum Type {
TYPE_LP = 0,
TYPE_BP = 42,
TYPE_HP = 84
TYPE_BP,
TYPE_HP
};
enum Slope {
SLOPE_24 = 0,
SLOPE_12 = 64,
SLOPE_12,
};
typedef struct {
@ -119,9 +126,8 @@ private:
public:
void assign(Settings const * settings);
inline float tick(float in, float freqAdd) {
float freq = clamp(settings->freq + freqAdd, 0.f, 1.f);
float kK = freqToK(freq);
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) {

@ -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

@ -12,7 +12,7 @@
class Oscillator {
public:
enum Mode { MODE_SINE, MODE_SAW, MODE_SQUARE };
enum Mode { MODE_SINE = 0, MODE_SAW, MODE_SQUARE };
Mode const * mode;
float phase; // current waveform phase angle (radians)
@ -25,9 +25,11 @@ public:
void assign(Mode const * mode);
// Generate next output sample and advance the phase angle
inline float tick(float phaseStep) {
float t = phase / PIx2; // Define half phase
float dt = phaseStep / PIx2;
inline float tick(float cv) {
const float phaseStep = (float) (2 * CV_FREQ_MIN) * exp2f(cv) / SAMPLE_RATE;
const float t = phase / PIx2; // Define half phase
const float dt = phaseStep / PIx2;
if (*mode == MODE_SINE) {
value = sinf(phase); // No harmonics in sine so no aliasing!! No Poly BLEPs needed!

@ -0,0 +1,55 @@
#ifndef __REVERB_H__
#define __REVERB_H__
// Velvet noise reverb ("Late Reverberation Synthesis Using Filtered Velvet Noise")
#include <algorithm>
#include "frame.h"
#include "delayline.h"
#include "sap.h"
#define REVERB_FRAMES 131072
#define REVERB_TAPS 256
class Reverb {
private:
struct Tap {
size_t lp, rp;
float lg, rg;
};
DelayLine<REVERB_FRAMES> dl;
Tap taps[REVERB_TAPS];
float lpfL = 0.f, lpfR = 0.f;
float hpfL = 0.f, hpfR = 0.f;
public:
float mix = 0.25f;
Reverb();
frame tick(frame in) {
dl.tick(in);
frame out{0, 0};
for(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 mix * out + (1 - mix) * in;
}
};
#endif

@ -0,0 +1,28 @@
#ifndef __SAP_H__
#define __SAP_H__
// Schroeder All-Pass (delay w/feedback)
#include <cstddef>
#include "delayline.h"
template <size_t CAPACITY>
class SAP {
private:
const size_t size;
DelayLine<CAPACITY> dl;
public:
float gain = 0.7;
SAP(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

@ -8,6 +8,8 @@
#define PIx2 ((float )(2 * M_PI))
#define CV_FREQ_MIN 32.703195662574829 // MIDI note 24 (C1)
//#define USE_NOTE_LUTS
#define NOTE_LUT_SIZE 128
#define CENT_LUT_SIZE 128
@ -24,8 +26,8 @@
#else
inline float noteToFreq(float note) {
return 440 * powf(2.f, (note - 69) / 12.0f);
inline float noteToCV(float note) {
return (note - 24) / 12.f;
}
#endif

@ -1,41 +0,0 @@
#ifndef __FRAME_H__
#define __FRAME_H__
struct frame {
float left;
float right;
inline frame operator+(const frame& rhs) {
return {
.left = left + rhs.left,
.right = right + rhs.right
};
}
inline frame operator+=(const frame& rhs) {
this->left += rhs.left;
this->right += rhs.right;
return *this;
}
inline frame operator*(float rhs) {
return {
.left = left * rhs,
.right = right * rhs
};
}
inline frame operator+=(float rhs) {
this->left += rhs;
this->right += rhs;
return *this;
}
inline frame operator*=(float rhs) {
this->left *= rhs;
this->right *= rhs;
return *this;
}
};
#endif

@ -13,6 +13,8 @@ struct Preset {
uint8_t release;
} Envelope;
uint8_t unison;
uint8_t osc1Mode;
uint8_t osc2Mode;
uint8_t oscMix;

@ -3,12 +3,13 @@
#include "voicemanager.h"
#include "channel.h"
#include "frame.h"
#include "dsp/frame.h"
#include "dsp/reverb.h"
class Synth {
public:
Synth() {
for(channel_t i = 0; i < 16; ++i) {
for(VoiceManager::channel_t i = 0; i < 16; ++i) {
channels[i].number = i;
channels[i].voiceManager = &voiceManager;
channels[i].loadPreset(&DEFAULT_PRESET);
@ -24,13 +25,15 @@ public:
for(auto& channel : channels) {
out += channel.tick();
}
out *= 0.100f;
out = reverb.tick(out);
out *= 0.125f;
return out;
}
private:
VoiceManager voiceManager{};
Channel channels[16];
Reverb reverb;
};
#endif

@ -11,6 +11,8 @@
class Voice {
public:
typedef struct {
float unison;
Oscillator::Mode osc1Mode;
Oscillator::Mode osc2Mode;
float oscMix;
@ -40,23 +42,24 @@ private:
public:
int note;
float unisonPosition;
float pan;
float detune;
void reset();
void assign(Settings const * settings);
void noteOn(int note, float unisonPosition = 0);
void noteOn(int note, float detune = 0, float pan = 0.5);
void noteOff();
inline bool isIdle() { return adsrAmp.isIdle(); }
inline float tick(float pitchAdd, float fltFreqAdd) {
const float basePitch = note + pitchAdd + unisonPosition * 4 * settings->oscDetune;
const float osc1PhaseStep = noteToFreq(basePitch - settings->oscDetune) * PIx2 / SAMPLE_RATE;
const float osc2PhaseStep = noteToFreq(basePitch + settings->oscDetune + settings->osc2Pitch) * PIx2 / SAMPLE_RATE;
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(osc1PhaseStep);
out += settings->oscMix * osc2.tick(osc2PhaseStep);
const float keyTrackVal = settings->keyTrack * noteToFltFreq(note);
out = filter.tick(out, settings->modEnvFltGain * adsrMod.tick() + keyTrackVal + fltFreqAdd);
out += (1 - settings->oscMix) * osc1.tick(osc1CV);
out += settings->oscMix * osc2.tick(osc2CV);
const float keyTrackVal = settings->keyTrack * (note - 72) / 12.f;
out = filter.tick(out, settings->modEnvFltGain * adsrMod.tick() + keyTrackVal + fltMod);
out *= adsrAmp.tick();
return out;
}

@ -7,16 +7,18 @@
#define NUM_VOICES 32
typedef unsigned int channel_t;
typedef unsigned int serial_t;
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;
@ -25,6 +27,8 @@ private:
unsigned int serial;
VoiceData voices[NUM_VOICES];
unsigned int scoreVoice(VoiceData& voiceData);
};
#endif

@ -13,9 +13,17 @@ public:
private:
enum {
OSC_DET_SLIDER = wxID_TOP,
UNISON_SLIDER = wxID_TOP,
OSC1_MODE_SLIDER,
OSC2_MODE_SLIDER,
OSC_DET_SLIDER,
OSC2_PIT_SLIDER,
OSC_MIX_SLIDER,
FLT_TYPE_SLIDER,
FLT_SLOPE_SLIDER,
FLT_FREQ_SLIDER,
FLT_Q_SLIDER,
@ -43,9 +51,16 @@ private:
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnUnisonScroll(wxScrollEvent& event);
void OnOsc1ModeScroll(wxScrollEvent& event);
void OnOsc2ModeScroll(wxScrollEvent& event);
void OnOscDetScroll(wxScrollEvent& event);
void OnOsc2PitScroll(wxScrollEvent& event);
void OnOscMixScroll(wxScrollEvent& event);
void OnFltTypeScroll(wxScrollEvent& event);
void OnFltSlopeScroll(wxScrollEvent& event);
void OnFltFreqScroll(wxScrollEvent& event);
void OnFltQScroll(wxScrollEvent& event);

@ -26,7 +26,7 @@ void dumpLUT(const char * filename, const char * identifier, float *lut, size_t
int main(int argc, char** argv) {
float kLUT[K_LUT_SIZE];
for(int i = 0; i < K_LUT_SIZE; ++i) {
kLUT[i] = tanf((float) M_PI_4 * FILTER_K_SCALE * expf(i / (K_LUT_SIZE - 1.0f) * FILTER_K_BASE));
kLUT[i] = tan(std::max(M_PI_4, M_PI_2 * FILTER_K_SCALE * exp2(FILTER_CV_MAX * i / (K_LUT_SIZE - 1.0))));
}
dumpLUT("src/synth/dsp/filterlut.cpp", "kLUT", kLUT, K_LUT_SIZE);

@ -5,6 +5,10 @@
#include "synth/channel.h"
float midiToNote(int note) {
return note + 24;
}
float timeToStep(float t) {
return (1.0f / SAMPLE_RATE) / t;
}
@ -22,50 +26,57 @@ float ccToLFOStep(int x) {
}
float ccToLFOPitchMod(int x) {
return pow(x / 127.0, 2) * 6;
return 6.f * x * x / 16129.f;
}
float ccToLFOFltMod(int x) {
return (float) FILTER_CV_MAX * (x - 64) / 126.f;
}
void Channel::loadPreset(const Preset * preset) {
settings.unison = 1 + floorf(preset->unison * 4.f / 128.f);
settings.osc1Mode = Oscillator::Mode(preset->osc1Mode);
settings.osc2Mode = Oscillator::Mode(preset->osc2Mode);
settings.oscMix = preset->oscMix / 127.0;
settings.oscDetune = preset->oscDetune / 254.0;
settings.osc2Pitch = floorf(preset->osc2Pitch * (13.0 / 128.0));
settings.oscDetune = preset->oscDetune / 254.f;
settings.osc2Pitch = floorf(preset->osc2Pitch * 13.f / 128.f);
settings.oscMix = preset->oscMix / 127.f;
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.res = preset->filter.Q / 31.75;
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.0;
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.0;
settings.modEnv.sustain = preset->modEnv.sustain / 127.f;
settings.modEnv.releaseStep = timeToStep(ccToDR(preset->modEnv.release));
settings.modEnvFltGain = preset->modEnvFltGain / 127.0;
settings.keyTrack = preset->keyTrack / 127.0;
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 = (preset->lfoFltMod - 64) / 126.0;
settings.lfoFltMod = ccToLFOFltMod(preset->lfoFltMod);
}
void Channel::noteOn(int note, int velocity) {
for(int i = -1; i <= 1; ++i) {
int range = 2 * (settings.unison - 1);
for(int i = -range; i <= range; i += 4) {
Voice* const voice = voiceManager->get(number);
voice->assign(&settings);
voice->noteOn(note, i);
voice->noteOn(midiToNote(note), i * settings.oscDetune, range ? 0.5f + 0.5f * i / range : 0.5f);
}
}
void Channel::noteOff(int note) {
Voice** voices = voiceManager->getChannelVoices(number);
for(Voice** voice = voices; *voice != NULL; ++voice) {
if((*voice)->note == note) {
if((*voice)->note == midiToNote(note)) {
(*voice)->noteOff();
}
}
@ -74,7 +85,7 @@ void Channel::noteOff(int note) {
void Channel::control(int code, int value) {
switch(code) {
case CC_VOLUME: // Volume (Standard MIDI)
volume = value / 127.0;
volume = value / 127.f;
break;
case CC_FLT_ATK: // Filter Attack Time
@ -86,7 +97,7 @@ void Channel::control(int code, int value) {
break;
case CC_FLT_SUS: // Filter Sustain
settings.modEnv.sustain = value / 127.0;
settings.modEnv.sustain = value / 127.f;
break;
case CC_FLT_REL: // Filter Release Time
@ -94,7 +105,7 @@ void Channel::control(int code, int value) {
break;
case CC_FLT_Q: // Timbre / Harmonic Content (Standard MIDI)
settings.filter.res = value / 31.75;
settings.filter.res = value / 31.75f;
printf("Q=%f\n", M_SQRT1_2 + (double) settings.filter.res);
break;
@ -107,7 +118,7 @@ void Channel::control(int code, int value) {
break;
case CC_FLT_FRQ: // Brightness (Standard MIDI)
settings.filter.freq = value / 127.0;
settings.filter.freq = (float) FILTER_CV_MAX * value / 127.f;
break;
case CC_AMP_DEC: // Decay Time
@ -115,23 +126,23 @@ void Channel::control(int code, int value) {
break;
case CC_AMP_SUS: // Sustain
settings.ampEnv.sustain = value / 127.0;
settings.ampEnv.sustain = value / 127.f;
break;
case CC_OSC_DET: // Detune (Standard MIDI)
settings.oscDetune = value / 254.0;
settings.oscDetune = value / 254.f;
break;
case CC_OSC2PIT: // Oscillator 2 Pitch
settings.osc2Pitch = floorf(value * (13.0 / 128.0));
settings.osc2Pitch = floorf(value * (13.f / 128.f));
break;
case CC_MOD_FLT: // Mod Envelope Filter Gain
settings.modEnvFltGain = value / 127.0;
settings.modEnvFltGain = (float) FILTER_CV_MAX * value / 127.f;
break;
case CC_KEY_TRK: // Filter Key Tracking
settings.keyTrack = value / 127.0;
settings.keyTrack = value / 127.f;
break;
case CC_LFO_FRQ: // LFO Frequency
@ -143,7 +154,36 @@ void Channel::control(int code, int value) {
break;
case CC_LFO_FLT: // LFO Filter Modulation
settings.lfoFltMod = (value - 64) / 126.0;
settings.lfoFltMod = ccToLFOFltMod(value);
break;
case CC_UNISON: // Unison Amount
settings.unison = 1 + 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 = Filter::Type(floorf(3.f * value / 128.f));
break;
case CC_FLT_SLP: // Filter Slope
settings.filter.slope = Filter::Slope(floorf(2.f * value / 128.f));
break;
// LFO delay
// Reverb Time
// Reverb Brightness
// Reverb Density
}
}

@ -0,0 +1,16 @@
#include <cmath>
#include "synth/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
};
}
}

@ -1,6 +1,8 @@
#include "synth/preset.h"
const Preset DEFAULT_PRESET = {
.unison = 1,
.osc1Mode = Oscillator::MODE_SAW,
.osc2Mode = Oscillator::MODE_SAW,
.oscMix = 64,
@ -8,17 +10,17 @@ const Preset DEFAULT_PRESET = {
.osc2Pitch = 0,
.filter = {
.type = Filter::TYPE_LP,
.slope = Filter::SLOPE_24,
.freq = 64,
.Q = 50,
.type = 0,
.slope = 22,
.freq = 32,
.Q = 25,
},
.ampEnv = {
.attack = 0,
.decay = 80,
.sustain = 64,
.release = 100
.release = 0
},
.modEnv = {

@ -14,9 +14,10 @@ void Voice::assign(Settings const * settings) {
filter.assign(&settings->filter);
}
void Voice::noteOn(int note, float unisonPosition) {
void Voice::noteOn(int note, float detune, float pan) {
this->note = note;
this->unisonPosition = unisonPosition;
this->detune = detune;
this->pan = pan;
adsrAmp.noteOn();
adsrMod.noteOn();
}

@ -3,6 +3,12 @@
#include "synth/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;
@ -10,34 +16,53 @@ VoiceManager::VoiceManager() : 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), [](VoiceData& a, VoiceData& b) {
return (a.voice.isIdle() && !b.voice.isIdle()) || a.channel > b.channel || a.serial < b.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];
voiceData.channel = channel;
voiceData.serial = serial++;
voiceData.voice.reset();
//putchar('\n');
//for(VoiceData& vd : voices) {
// printf("channel=%d serial=%d idle=%d note=%d\n", vd.channel, vd.serial, vd.voice.isIdle(), vd.voice.note);
//}
//putchar('\n');
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.channel == channel && !voiceData.voice.isIdle()) {
*voice++ = &voiceData.voice;
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;
}

@ -52,8 +52,8 @@ static int paCallback(
for(unsigned long i = 0; i < framesPerBuffer; i++) {
frame f = app->synth.tick();
*out++ = f.left;
*out++ = f.right;
*out++ = f.l;
*out++ = f.r;
}
return 0;

@ -31,9 +31,16 @@ SynthFrame::SynthFrame() : wxFrame(NULL, wxID_ANY, "Hello World") {
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
panel->SetSizer(sizer);
sizer->AddStretchSpacer();
sizer->Add(new CCSlider(panel, UNISON_SLIDER, DEFAULT_PRESET.unison, "Uni"), 1, wxEXPAND);
sizer->AddStretchSpacer();
sizer->Add(new CCSlider(panel, OSC1_MODE_SLIDER, DEFAULT_PRESET.osc1Mode, "O1M"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, OSC2_MODE_SLIDER, DEFAULT_PRESET.osc2Mode, "O2M"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, OSC_DET_SLIDER, DEFAULT_PRESET.oscDetune, "Det"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, OSC2_PIT_SLIDER, DEFAULT_PRESET.osc2Pitch, "2nd"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, OSC_MIX_SLIDER, DEFAULT_PRESET.oscMix, "Mix"), 1, wxEXPAND);
sizer->AddStretchSpacer();
sizer->Add(new CCSlider(panel, FLT_TYPE_SLIDER, DEFAULT_PRESET.filter.type, "FTy"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, FLT_SLOPE_SLIDER, DEFAULT_PRESET.filter.slope, "FSl"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, FLT_FREQ_SLIDER, DEFAULT_PRESET.filter.freq, "Frq"), 1, wxEXPAND);
sizer->Add(new CCSlider(panel, FLT_Q_SLIDER, DEFAULT_PRESET.filter.Q, "Res"), 1, wxEXPAND);
sizer->AddStretchSpacer();
@ -71,6 +78,21 @@ void SynthFrame::OnAbout(wxCommandEvent& event) {
"About Hello World", wxOK | wxICON_INFORMATION);
}
void SynthFrame::OnUnisonScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_UNISON, event.GetPosition());
}
void SynthFrame::OnOsc1ModeScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_OSC1MDE, event.GetPosition());
}
void SynthFrame::OnOsc2ModeScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_OSC2MDE, event.GetPosition());
}
void SynthFrame::OnOscDetScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_OSC_DET, event.GetPosition());
@ -81,6 +103,21 @@ void SynthFrame::OnOsc2PitScroll(wxScrollEvent& event) {
app.synth.control(0, CC_OSC2PIT, event.GetPosition());
}
void SynthFrame::OnOscMixScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_OSC_MIX, event.GetPosition());
}
void SynthFrame::OnFltTypeScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_TYP, event.GetPosition());
}
void SynthFrame::OnFltSlopeScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_SLP, event.GetPosition());
}
void SynthFrame::OnFltFreqScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_FRQ, event.GetPosition());
@ -158,9 +195,20 @@ void SynthFrame::OnLFOFilterScroll(wxScrollEvent& event) {
wxBEGIN_EVENT_TABLE(SynthFrame, wxFrame)
EVT_COMMAND_SCROLL(SynthFrame::UNISON_SLIDER, SynthFrame::OnUnisonScroll)
//#define CC_OSC1MDE 28 // Oscillator 1 Mode
//#define CC_OSC2MDE 29 // Oscillator 2 Mode
EVT_COMMAND_SCROLL(SynthFrame::OSC1_MODE_SLIDER, SynthFrame::OnOsc1ModeScroll)
EVT_COMMAND_SCROLL(SynthFrame::OSC2_MODE_SLIDER, SynthFrame::OnOsc2ModeScroll)
EVT_COMMAND_SCROLL(SynthFrame::OSC_DET_SLIDER, SynthFrame::OnOscDetScroll)
EVT_COMMAND_SCROLL(SynthFrame::OSC2_PIT_SLIDER, SynthFrame::OnOsc2PitScroll)
EVT_COMMAND_SCROLL(SynthFrame::OSC_MIX_SLIDER, SynthFrame::OnOscMixScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_TYPE_SLIDER, SynthFrame::OnFltTypeScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_SLOPE_SLIDER, SynthFrame::OnFltSlopeScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_FREQ_SLIDER, SynthFrame::OnFltFreqScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_Q_SLIDER, SynthFrame::OnFltQScroll)

Loading…
Cancel
Save