More MIDI features, filter stability

main
Thor 1 year ago
parent 6cd5e452f5
commit 3419675d84
  1. 13
      CMakeLists.txt
  2. 17
      include/synth/cc.h
  3. 9
      include/synth/dsp/adsr.h
  4. 18
      include/synth/dsp/filter.h
  5. 48
      include/synth/dsp/oscillator.h
  6. 38
      include/synth/globals.h
  7. 30
      include/synth/part.h
  8. 8
      include/synth/preset.h
  9. 10
      include/synth/synth.h
  10. 21
      include/synth/voice.h
  11. 13
      include/synth/voicemanager.h
  12. 18
      include/synthframe.h
  13. 4
      src/synth/dsp/adsr.cpp
  14. 5
      src/synth/dsp/filter.cpp
  15. 5
      src/synth/dsp/oscillator.cpp
  16. 96
      src/synth/part.cpp
  17. 11
      src/synth/synth.cpp
  18. 20
      src/synth/voice.cpp
  19. 20
      src/synth/voicemanager.cpp
  20. 28
      src/synthapp.cpp
  21. 29
      src/synthframe.cpp

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.26)
project(synth)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
set(BUILD_SHARED_LIBS OFF)
add_definitions(-D__LITTLE_ENDIAN__)
@ -10,7 +10,16 @@ add_subdirectory(lib/portaudio)
add_subdirectory(lib/portmidi)
add_subdirectory(lib/wxWidgets)
add_executable(main src/synthapp.cpp src/synthframe.cpp src/synth/synth.cpp src/synth/part.cpp src/synth/dsp/adsr.cpp)
add_executable(main
src/synthapp.cpp
src/synthframe.cpp
src/synth/synth.cpp
src/synth/voicemanager.cpp
src/synth/part.cpp
src/synth/voice.cpp
src/synth/dsp/oscillator.cpp
src/synth/dsp/filter.cpp
src/synth/dsp/adsr.cpp)
target_include_directories(main PRIVATE include)
target_link_libraries(main PRIVATE PortAudio)

@ -0,0 +1,17 @@
#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)
#endif

@ -14,12 +14,15 @@ public:
float releaseStep;
} Envelope;
const Envelope* env;
Envelope const * env;
ADSR() = delete;
ADSR(const Envelope *env);
ADSR() : state(IDLE), gain(0) {};
void assign(Envelope const * env);
void noteOn();
void noteOff();
bool isIdle() {
return state == IDLE;
}
inline float tick() {
float out = curve(gain);

@ -64,23 +64,25 @@ public:
} Settings;
private:
const Settings* settings;
Settings const * settings;
SVF12 first, second;
public:
Filter() = delete;
Filter(const Settings *settings): settings(settings) {}
void assign(Settings const * settings);
inline float tick(float as, float freqMod) {
SVF12::Output in = first.tick(as, settings->freq + freqMod, settings->Q);
float freq = clamp(settings->freq + freqMod, 0, 1);
float Q = fmaxf(0.1, settings->Q);
SVF12::Output in = first.tick(as, freq, Q);
switch(settings->type) {
case TYPE_LP:
return settings->slope == SLOPE_24 ? second.tick(in.lp, settings->freq + freqMod, settings->Q).lp : in.lp;
return settings->slope == SLOPE_24 ? second.tick(in.lp, freq, Q).lp : in.lp;
case TYPE_BP:
return settings->slope == SLOPE_24 ? second.tick(in.bp, settings->freq + freqMod, settings->Q).bp : in.bp;
return settings->slope == SLOPE_24 ? second.tick(in.bp, freq, Q).bp : in.bp;
case TYPE_HP:
return settings->slope == SLOPE_24 ? second.tick(in.hp, settings->freq + freqMod, settings->Q).hp : in.hp;
return settings->slope == SLOPE_24 ? second.tick(in.hp, freq, Q).hp : in.hp;
}
}
};

@ -13,35 +13,15 @@ class Oscillator {
public:
enum Mode { MODE_SINE, MODE_SAW, MODE_SQUARE };
const Mode *mode;
Mode const * mode;
float phase; // current waveform phase angle (radians)
float value; // current amplitude value
float driftAmount;
float driftValue;
Oscillator(const Mode *mode) : mode(mode), phase(0), value(0), driftAmount(0.001), driftValue(0) {}
Oscillator() : phase(0), value(0), driftAmount(0.001), driftValue(0) {}
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.0;
}
// t^2/2 +t +1/2
// -1 <= t <= 0
// discontinuities between -1 & 0
else if (t > 1.0 - dt) {
t = (t - 1.0) / dt;
return t * t + t + t + 1.0;
}
// no discontinuities
// 0 otherwise
else return 0.0;
}
void assign(Mode const * mode);
// Generate next output sample and advance the phase angle
inline float tick(float phaseStep) {
@ -71,6 +51,28 @@ public:
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.0;
}
// t^2/2 +t +1/2
// -1 <= t <= 0
// discontinuities between -1 & 0
else if (t > 1.0 - dt) {
t = (t - 1.0) / dt;
return t * t + t + t + 1.0;
}
// no discontinuities
// 0 otherwise
else return 0.0;
}
};
#endif

@ -3,4 +3,42 @@
#define SAMPLE_RATE 48000
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

@ -1,9 +1,10 @@
#ifndef __PART_H__
#define __PART_H__
#include <map>
#include <unordered_map>
#include <unordered_set>
#include "globals.h"
#include "voicemanager.h"
#include "voice.h"
#include "preset.h"
@ -14,20 +15,33 @@ public:
Voice::Settings settings;
float pitchBend;
float modulation;
std::map<uint8_t, Voice*> notes;
Part() : volume(1), pitchBend(0), modulation(0) {}
void loadPreset(Preset* preset);
void loadPreset(const Preset* preset);
void noteOn(int note, int vel);
void noteOff(int note);
void control(int cc, int val);
float tick() {
return 0;
frame tick() {
frame out{0};
for(auto& kv : notes) {
int note = kv.first;
const float osc1PhaseStep = noteToFreq(note - settings.oscDetune + pitchBend) * PIx2 / SAMPLE_RATE;
const float osc2PhaseStep = noteToFreq(note + settings.oscDetune + pitchBend) * PIx2 / SAMPLE_RATE;
for(auto& voice : kv.second) {
out += voice->tick(osc1PhaseStep, osc2PhaseStep);
}
}
out *= volume;
return out;
}
private:
float volume;
float pitchBend;
float modulation;
std::unordered_map<uint8_t, std::unordered_set<Voice*>> notes;
};
#endif

@ -16,6 +16,7 @@ struct Preset {
uint8_t osc1Mode;
uint8_t osc2Mode;
uint8_t oscMix;
uint8_t oscDetune;
struct {
uint8_t type;
@ -31,13 +32,14 @@ struct Preset {
static const Preset DEFAULT_PRESET = {
.osc1Mode = Oscillator::MODE_SAW,
.osc2Mode = Oscillator::MODE_SAW,
.oscMix = 0,
.oscMix = 64,
.oscDetune = 70,
.filter = {
.type = Filter::TYPE_LP,
.slope = Filter::SLOPE_24,
.freq = 127,
.Q = 0,
.freq = 0,
.Q = 64,
},
.ampEnv = {

@ -9,11 +9,21 @@ public:
Synth() {
for(int i = 0; i < 16; ++i) {
parts[i].voiceManager = &voiceManager;
parts[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);
frame tick() {
frame out{};
for(auto& part : parts) {
out += part.tick();
}
return out * 0.125;
}
private:
VoiceManager voiceManager{};

@ -15,28 +15,31 @@ public:
Oscillator::Mode osc1Mode;
Oscillator::Mode osc2Mode;
float oscMix;
float oscDetune;
ADSR::Envelope ampEnv;
ADSR::Envelope fltEnv;
} Settings;
private:
const Settings* settings;
Settings const * settings;
ADSR adsrAmp{&settings->ampEnv};
ADSR adsrFlt{&settings->fltEnv};
Oscillator osc1{&settings->osc1Mode};
Oscillator osc2{&settings->osc2Mode};
Filter filter{&settings->filter};
ADSR adsrAmp;
ADSR adsrFlt;
Oscillator osc1;
Oscillator osc2;
Filter filter;
public:
Voice() = delete;
Voice(const Settings* settings) : settings(settings) {}
void assign(Settings const * settings);
void noteOn();
void noteOff();
bool isIdle() { return adsrAmp.isIdle(); }
inline float tick(float osc1PhaseStep, float osc2PhaseStep) {
float out = 0;
out += (1 - settings->oscMix) * osc1.tick(osc1PhaseStep);
out += settings->oscMix * osc2.tick(osc2PhaseStep);
out += settings->oscMix * osc2.tick(osc2PhaseStep);
out = filter.tick(out, adsrFlt.tick());
out *= adsrAmp.tick();
return out;

@ -1,10 +1,21 @@
#ifndef __VOICEMANAGER_H__
#define __VOICEMANAGER_H__
#include <unordered_set>
#include "synth/voice.h"
class VoiceManager {
public:
private:
VoiceManager();
Voice * allocate();
void free(Voice * voice);
private:
Voice voices[64];
std::unordered_set<size_t> idle;
std::unordered_set<size_t> active;
};
#endif

@ -21,22 +21,18 @@ private:
FLT_ATTACK_SLIDER,
FLT_DECAY_SLIDER,
FLT_SUSTAIN_SLIDER,
FLT_RELEASE_SLIDER
};
wxSlider* ampAttackSlider;
wxSlider* ampDecaySlider;
wxSlider* ampSustainSlider;
wxSlider* ampReleaseSlider;
FLT_RELEASE_SLIDER,
wxSlider* fltAttackSlider;
wxSlider* fltDecaySlider;
wxSlider* fltSustainSlider;
wxSlider* fltReleaseSlider;
FLT_FREQ_SLIDER,
FLT_Q_SLIDER
};
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnFltFreqScroll(wxScrollEvent& event);
void OnFltQScroll(wxScrollEvent& event);
void OnAmpAttackScroll(wxScrollEvent& event);
void OnAmpDecayScroll(wxScrollEvent& event);
void OnAmpSustainScroll(wxScrollEvent& event);

@ -1,9 +1,7 @@
#include "synth/dsp/adsr.h"
ADSR::ADSR(const Envelope *env) {
void ADSR::assign(Envelope const * env) {
this->env = env;
this->state = IDLE;
this->gain = 0;
}
void ADSR::noteOn() {

@ -0,0 +1,5 @@
#include "synth/dsp/filter.h"
void Filter::assign(Settings const * settings) {
this->settings = settings;
}

@ -0,0 +1,5 @@
#include "synth/dsp/oscillator.h"
void Oscillator::assign(Mode const * mode) {
this->mode = mode;
}

@ -1,7 +1,14 @@
#include <iostream>
#include "synth/globals.h"
#include "synth/cc.h"
#include "synth/part.h"
float timeToStep(float t) {
return (1.0 / SAMPLE_RATE) / t;
}
float ccToA(int x) {
return 5.0 * exp(9.2 * (x / 127.0) - 9.2);
}
@ -10,35 +17,108 @@ float ccToDR(int x) {
return 5.0 * exp(7.0 * (x / 127.0) - 7.0);
}
void Part::loadPreset(Preset* preset) {
void Part::loadPreset(const Preset * preset) {
settings.osc1Mode = Oscillator::Mode(preset->osc1Mode);
settings.osc2Mode = Oscillator::Mode(preset->osc2Mode);
settings.oscMix = preset->oscMix / 127.0;
settings.oscDetune = (preset->oscDetune - 64.0) / 100.0;
settings.filter.type = Filter::Type(preset->filter.type);
settings.filter.slope = Filter::Slope(preset->filter.slope);
settings.filter.freq = preset->filter.freq / 127.0;
settings.filter.Q = preset->filter.Q / 127.0;
settings.ampEnv.attackStep = (1.0 / SAMPLE_RATE) / ccToA(preset->ampEnv.attack);;
settings.ampEnv.decayStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->ampEnv.decay);
settings.ampEnv.attackStep = timeToStep(ccToA(preset->ampEnv.attack));
settings.ampEnv.decayStep = timeToStep(ccToDR(preset->ampEnv.decay));
settings.ampEnv.sustain = preset->ampEnv.sustain / 127.0;
settings.ampEnv.releaseStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->ampEnv.release);
settings.ampEnv.releaseStep = timeToStep(ccToDR(preset->ampEnv.release));
settings.fltEnv.attackStep = (1.0 / SAMPLE_RATE) / ccToA(preset->fltEnv.attack);;
settings.fltEnv.decayStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->fltEnv.decay);
settings.fltEnv.attackStep = timeToStep(ccToA(preset->fltEnv.attack));
settings.fltEnv.decayStep = timeToStep(ccToDR(preset->fltEnv.decay));
settings.fltEnv.sustain = preset->fltEnv.sustain / 127.0;
settings.fltEnv.releaseStep = (1.0 / SAMPLE_RATE) / ccToDR(preset->fltEnv.release);
settings.fltEnv.releaseStep = timeToStep(ccToDR(preset->fltEnv.release));
}
void Part::noteOn(int note, int velocity) {
// Garbage collection - free up idle voices
std::erase_if(notes, [this](auto& note) {
std::erase_if(note.second, [this](auto& voice) {
if(voice->isIdle()) {
voiceManager->free(voice);
return true;
}
return false;
});
return note.second.empty();
});
Voice * const voice = voiceManager->allocate();
voice->assign(&settings);
voice->noteOn();
if(notes.count(note)) {
notes.at(note).insert(voice);
} else {
notes.emplace(note, std::unordered_set<Voice *>{voice});
}
}
void Part::noteOff(int note) {
if(notes.count(note)) {
for(auto& voice : notes.at(note)) {
voice->noteOff();
}
}
}
void Part::control(int code, int value) {
switch(code) {
case CC_VOLUME: // Volume (Standard MIDI)
volume = value / 127.0;
break;
case CC_FLT_ATK: // Filter Attack Time
settings.fltEnv.attackStep = timeToStep(ccToA(value));
break;
case CC_FLT_DEC: // Filter Decay Time
settings.fltEnv.decayStep = timeToStep(ccToDR(value));
break;
case CC_FLT_SUS: // Filter Sustain
settings.fltEnv.sustain = value / 127.0;
break;
case CC_FLT_REL: // Filter Release Time
settings.fltEnv.releaseStep = timeToStep(ccToDR(value));
break;
case CC_FLT_Q: // Timbre / Harmonic Content (Standard MIDI)
settings.filter.Q = value / 31.75;
std::cout << "Q=" << settings.filter.Q << std::endl;
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 = value / 127.0;
break;
case CC_AMP_DEC: // Decay Time
settings.ampEnv.decayStep = timeToStep(ccToDR(value));
break;
case CC_AMP_SUS: // Sustain
settings.ampEnv.sustain = value / 127.0;
break;
case CC_OSC_DET: // Detune (Standard MIDI)
settings.oscDetune = (value - 64.0) / 100.0;
break;
}
}

@ -1,13 +1,18 @@
#include <iostream>
#include "synth/synth.h"
void Synth::noteOn(int ch, int note, int vel) {
parts[ch].noteOn(note, vel);
std::cout << "Note On: ch=" << ch << " note=" << note << " vel=" << vel << std::endl;
}
void Synth::noteOff(int ch, int note) {
parts[ch].noteOff(note);
std::cout << "Note Off: ch=" << ch << " note=" << note << std::endl;
}
void Synth::control(int ch, int cc, int val) {
parts[ch].control(cc, val);
std::cout << "Controller: ch=" << ch << " cc=" << cc << " val=" << val << std::endl;
}

@ -0,0 +1,20 @@
#include "synth/voice.h"
void Voice::assign(Settings const * settings) {
this->settings = settings;
adsrAmp.assign(&settings->ampEnv);
adsrFlt.assign(&settings->fltEnv);
osc1.assign(&settings->osc1Mode);
osc2.assign(&settings->osc2Mode);
filter.assign(&settings->filter);
}
void Voice::noteOn() {
adsrAmp.noteOn();
adsrFlt.noteOn();
}
void Voice::noteOff() {
adsrAmp.noteOff();
adsrFlt.noteOff();
}

@ -0,0 +1,20 @@
#include "synth/voicemanager.h"
VoiceManager::VoiceManager() {
for(int i = 0; i < 64; ++i) {
idle.insert(i);
}
}
Voice * VoiceManager::allocate() {
int index = *idle.begin();
idle.erase(index);
active.insert(index);
return &voices[index];
}
void VoiceManager::free(Voice * voice) {
int index = voice - voices;
active.erase(index);
idle.insert(index);
}

@ -20,14 +20,13 @@ static int paCallback(
PaStreamCallbackFlags statusFlags,
void *userData ) {
Synth *state = (Synth*) userData;
float *out = (float*)outputBuffer;
Synth *synth = (Synth*) userData;
float *out = (float*) outputBuffer;
for(int i = 0; i < framesPerBuffer; i++) {
float value = 0;
*out++ = value;
*out++ = value;
frame f = synth->tick();
*out++ = f.left;
*out++ = f.right;
}
return 0;
@ -35,9 +34,9 @@ static int paCallback(
// PortTimer callback for MIDI I/O
static void ptCallback(PtTimestamp timestamp, void* userData) {
SynthApp* app = (SynthApp*) userData;
SynthApp* synthApp = (SynthApp*) userData;
PmEvent events[8];
int numEvents = Pm_Read(app->pmStream, events, 8);
int numEvents = Pm_Read(synthApp->pmStream, events, 8);
for(int i = 0; i < numEvents; ++i) {
PmMessage msg = events[i].message;
@ -45,25 +44,24 @@ static void ptCallback(PtTimestamp timestamp, void* userData) {
int ch = msg & 0xF;
if(type == 0x8 || (type == 0x9 && Pm_MessageData2(msg) == 0)) { // Note Off
int note = Pm_MessageData1(msg);
synthApp->synth.noteOff(ch, note);
} else if(type == 0x9) { // Note On
int note = Pm_MessageData1(msg);
int vel = Pm_MessageData2(msg);
synthApp->synth.noteOn(ch, note, vel);
} else if(type == 0xB) { // Controller Message
int cc = Pm_MessageData1(msg);
int val = Pm_MessageData2(msg);
if(cc == 1 || cc == 74) { // Sound Controller 5: Brightness
}
synthApp->synth.control(ch, cc, val);
}
/*
cout << "MIDI:";
for(int j = 0; j < 24; j += 8) {
cout << " " << hex << ((msg >> j) & 0xFF);
}
cout << endl;
*/
}
}

@ -1,5 +1,7 @@
#include <iostream>
#include "synth/cc.h"
#include "synthframe.h"
#include "synthapp.h"
@ -26,11 +28,14 @@ SynthFrame::SynthFrame() : wxFrame(NULL, wxID_ANY, "Hello World") {
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
panel->SetSizer(sizer);
sizer->Add(new wxSlider(panel, FLT_FREQ_SLIDER, 127, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_Q_SLIDER, 64, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->AddSpacer(20);
sizer->Add(new wxSlider(panel, AMP_ATTACK_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_DECAY_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_SUSTAIN_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_RELEASE_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->AddSpacer(20);
sizer->Add(new wxSlider(panel, FLT_ATTACK_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_DECAY_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_SUSTAIN_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
@ -49,41 +54,61 @@ void SynthFrame::OnAbout(wxCommandEvent& event) {
"About Hello World", wxOK | wxICON_INFORMATION);
}
void SynthFrame::OnFltFreqScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_FRQ, event.GetPosition());
}
void SynthFrame::OnFltQScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_Q, event.GetPosition());
}
void SynthFrame::OnAmpAttackScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
// event.GetPosition()
app.synth.control(0, CC_AMP_ATK, event.GetPosition());
}
void SynthFrame::OnAmpDecayScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_AMP_DEC, event.GetPosition());
}
void SynthFrame::OnAmpSustainScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_AMP_SUS, event.GetPosition());
}
void SynthFrame::OnAmpReleaseScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_AMP_REL, event.GetPosition());
}
void SynthFrame::OnFltAttackScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_ATK, event.GetPosition());
}
void SynthFrame::OnFltDecayScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_DEC, event.GetPosition());
}
void SynthFrame::OnFltSustainScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_SUS, event.GetPosition());
}
void SynthFrame::OnFltReleaseScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_FLT_REL, event.GetPosition());
}
wxBEGIN_EVENT_TABLE(SynthFrame, wxFrame)
EVT_COMMAND_SCROLL(SynthFrame::FLT_FREQ_SLIDER, SynthFrame::OnFltFreqScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_Q_SLIDER, SynthFrame::OnFltQScroll)
EVT_COMMAND_SCROLL(SynthFrame::AMP_ATTACK_SLIDER, SynthFrame::OnAmpAttackScroll)
EVT_COMMAND_SCROLL(SynthFrame::AMP_DECAY_SLIDER, SynthFrame::OnAmpDecayScroll)
EVT_COMMAND_SCROLL(SynthFrame::AMP_SUSTAIN_SLIDER, SynthFrame::OnAmpSustainScroll)

Loading…
Cancel
Save