Initial commit

main
Thor 11 months ago
commit 1383f00b1c
  1. 1
      .gitignore
  2. 31
      CMakeLists.txt
  3. 31
      include/cc.h
  4. 83
      include/channel.h
  5. 85
      include/dsp/adsr.h
  6. 74
      include/dsp/delayline.h
  7. 142
      include/dsp/filter.h
  8. 107
      include/dsp/fm.h
  9. 141
      include/dsp/frame.h
  10. 124
      include/dsp/oscillator.h
  11. 34
      include/dsp/phys.h
  12. 53
      include/dsp/reverb.h
  13. 45
      include/dsp/stereodelayline.h
  14. 28
      include/dsp/stereosap.h
  15. 9
      include/globals.h
  16. 26
      include/luts.h
  17. 37
      include/perf.h
  18. 47
      include/preset.h
  19. 73
      include/synth.h
  20. 69
      include/util.h
  21. 135
      include/voice.h
  22. 34
      include/voicemanager.h
  23. 198
      src/channel.cpp
  24. 22
      src/dsp/adsr.cpp
  25. 7
      src/dsp/filter.cpp
  26. 5
      src/dsp/oscillator.cpp
  27. 16
      src/dsp/reverb.cpp
  28. 19
      src/luts.cpp
  29. 3
      src/perf.cpp
  30. 42
      src/preset.cpp
  31. 18
      src/synth.cpp
  32. 33
      src/voice.cpp
  33. 71
      src/voicemanager.cpp

1
.gitignore vendored

@ -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…
Cancel
Save