New MIDI controllers, better ADSR algo, polyphony bugfixes

main
Thor 1 year ago
parent e2e05b0520
commit 7b7341c7c6
  1. 13
      CMakeLists.txt
  2. 4
      include/synth/cc.h
  3. 35
      include/synth/dsp/adsr.h
  4. 4
      include/synth/dsp/filter.h
  5. 1
      include/synth/dsp/oscillator.h
  6. 8
      include/synth/part.h
  7. 33
      include/synth/preset.h
  8. 3
      include/synth/synth.h
  9. 18
      include/synth/voice.h
  10. 18
      include/synthframe.h
  11. 2
      src/synth/dsp/adsr.cpp
  12. 39
      src/synth/part.cpp
  13. 32
      src/synth/preset.cpp
  14. 8
      src/synth/voice.cpp
  15. 4
      src/synth/voicemanager.cpp
  16. 46
      src/synthapp.cpp
  17. 47
      src/synthframe.cpp

@ -4,23 +4,34 @@ project(synth)
set(CMAKE_CXX_STANDARD 20)
set(BUILD_SHARED_LIBS OFF)
set(PA_USE_ASIO ON CACHE BOOL "Enable support for ASIO")
if(WIN32)
option(PA_USE_ASIO "" ON)
option(PA_USE_WDMKS "" OFF)
endif()
add_subdirectory(lib/portaudio)
add_subdirectory(lib/portmidi)
add_subdirectory(lib/wxWidgets)
if(MSVC)
add_compile_definitions(_USE_MATH_DEFINES)
endif()
add_executable(main
src/synthapp.cpp
src/synthframe.cpp
src/synth/synth.cpp
src/synth/voicemanager.cpp
src/synth/part.cpp
src/synth/preset.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_compile_options(main PRIVATE -fsanitize=thread)
#target_link_options(main PRIVATE -fsanitize=thread)
target_link_libraries(main PRIVATE PortAudio)
target_link_libraries(main PRIVATE portmidi)
target_include_directories(main PRIVATE lib/portmidi/porttime)

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

@ -16,7 +16,7 @@ public:
Envelope const * env;
ADSR() : state(IDLE), gain(0) {};
ADSR() : state(IDLE), t(0), last(0) {};
void assign(Envelope const * env);
void noteOn();
void noteOff();
@ -25,27 +25,29 @@ public:
}
inline float tick() {
float out = curve(gain);
switch(state) {
case IDLE:
break;
return 0;
case ATTACK:
if(gain < 1) {
gain += env->attackStep;
if(t < 1) {
t += env->attackStep;
last = curve(t);
} else {
state = DECAY;
gain = 1;
t = 1;
last = 1;
}
break;
case DECAY:
if(gain > env->sustain) {
gain -= env->decayStep;
if(t > 0) {
t -= env->decayStep;
last = env->sustain + (1 - env->sustain) * curve(t);
} else {
state = SUSTAIN;
gain = env->sustain;
t = 1;
last = env->sustain;
}
break;
@ -53,21 +55,24 @@ public:
break;
case RELEASE:
if(gain > 0) {
gain -= env->releaseStep;
if(t > 0) {
t -= env->releaseStep;
return last * curve(t);
} else {
state = IDLE;
gain = 0;
t = 0;
last = 0;
}
break;
}
return out;
return last;
}
private:
enum { IDLE, ATTACK, DECAY, SUSTAIN, RELEASE } state;
float gain;
float t;
float last;
float curve(float gain) {
return 0.5 - 0.5 * cos(clamp(gain * M_PI, 0, M_PI));

@ -70,8 +70,8 @@ private:
public:
void assign(Settings const * settings);
inline float tick(float as, float freqMod) {
float freq = clamp(settings->freq + freqMod, 0, 1);
inline float tick(float as, float freqAdd, float freqScale) {
float freq = clamp(freqScale * settings->freq + freqAdd, 0, 1);
float Q = fmaxf(0.1, settings->Q);
SVF12::Output in = first.tick(as, freq, Q);

@ -5,6 +5,7 @@
* Further info about BLEP at: https://pbat.ch/sndkit/blep/
*/
#include <cstdlib>
#include <math.h>
#define PIx2 (2 * M_PI)

@ -2,7 +2,7 @@
#define __PART_H__
#include <unordered_map>
#include <unordered_set>
#include <set>
#include "globals.h"
#include "voicemanager.h"
@ -27,10 +27,8 @@ public:
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 += voice->tick(note, pitchBend, modulation);
}
}
out *= volume;
@ -41,7 +39,7 @@ private:
float volume;
float pitchBend;
float modulation;
std::unordered_map<uint8_t, std::unordered_set<Voice*>> notes;
std::unordered_map<uint8_t, std::set<Voice*>> notes;
};
#endif

@ -26,35 +26,12 @@ struct Preset {
} filter;
Envelope ampEnv;
Envelope fltEnv;
};
Envelope modEnv;
static const Preset DEFAULT_PRESET = {
.osc1Mode = Oscillator::MODE_SAW,
.osc2Mode = Oscillator::MODE_SAW,
.oscMix = 64,
.oscDetune = 70,
.filter = {
.type = Filter::TYPE_LP,
.slope = Filter::SLOPE_24,
.freq = 0,
.Q = 22,
},
.ampEnv = {
.attack = 0,
.decay = 0,
.sustain = 127,
.release = 0
},
.fltEnv = {
.attack = 0,
.decay = 0,
.sustain = 127,
.release = 0
}
uint8_t modEnvFltGain;
uint8_t keyTrack;
};
extern const Preset DEFAULT_PRESET;
#endif

@ -22,7 +22,8 @@ public:
for(auto& part : parts) {
out += part.tick();
}
return out * 0.125;
out *= 0.100;
return out;
}
private:

@ -1,6 +1,7 @@
#ifndef __VOICE_H__
#define __VOICE_H__
#include "globals.h"
#include "preset.h"
#include "dsp/oscillator.h"
@ -18,29 +19,38 @@ public:
float oscDetune;
ADSR::Envelope ampEnv;
ADSR::Envelope fltEnv;
ADSR::Envelope modEnv;
float modEnvFltGain;
float keyTrack;
} Settings;
private:
Settings const * settings;
ADSR adsrAmp;
ADSR adsrFlt;
ADSR adsrMod;
Oscillator osc1;
Oscillator osc2;
Filter filter;
public:
bool keyed;
Voice(): keyed(false) {}
void assign(Settings const * settings);
void noteOn();
void noteOff();
bool isIdle() { return adsrAmp.isIdle(); }
inline float tick(float osc1PhaseStep, float osc2PhaseStep) {
inline float tick(int note, float pitchBend, float modulation) {
const float keyTrackMul = 1 + settings->keyTrack * noteToFreq(note + pitchBend) / 440.0;
const float osc1PhaseStep = noteToFreq(note - settings->oscDetune + pitchBend) * PIx2 / SAMPLE_RATE;
const float osc2PhaseStep = noteToFreq(note + settings->oscDetune + pitchBend) * PIx2 / SAMPLE_RATE;
float out = 0;
out += (1 - settings->oscMix) * osc1.tick(osc1PhaseStep);
out += settings->oscMix * osc2.tick(osc2PhaseStep);
out = filter.tick(out, adsrFlt.tick());
out = filter.tick(out, settings->modEnvFltGain * adsrMod.tick(), keyTrackMul);
out *= adsrAmp.tick();
return out;
}

@ -18,13 +18,17 @@ private:
AMP_SUSTAIN_SLIDER,
AMP_RELEASE_SLIDER,
FLT_ATTACK_SLIDER,
FLT_DECAY_SLIDER,
FLT_SUSTAIN_SLIDER,
FLT_RELEASE_SLIDER,
MOD_ATTACK_SLIDER,
MOD_DECAY_SLIDER,
MOD_SUSTAIN_SLIDER,
MOD_RELEASE_SLIDER,
FLT_FREQ_SLIDER,
FLT_Q_SLIDER
FLT_Q_SLIDER,
KEY_TRACK_SLIDER,
MOD_ENV_FLT_GAIN
};
void OnExit(wxCommandEvent& event);
@ -33,6 +37,8 @@ private:
void OnFltFreqScroll(wxScrollEvent& event);
void OnFltQScroll(wxScrollEvent& event);
void OnKeyTrackScroll(wxScrollEvent& event);
void OnAmpAttackScroll(wxScrollEvent& event);
void OnAmpDecayScroll(wxScrollEvent& event);
void OnAmpSustainScroll(wxScrollEvent& event);
@ -43,6 +49,8 @@ private:
void OnFltSustainScroll(wxScrollEvent& event);
void OnFltReleaseScroll(wxScrollEvent& event);
void OnModEnvFltGainScroll(wxScrollEvent& event);
wxDECLARE_EVENT_TABLE();
};

@ -6,8 +6,10 @@ void ADSR::assign(Envelope const * env) {
void ADSR::noteOn() {
state = ATTACK;
t = 0;
}
void ADSR::noteOff() {
state = RELEASE;
t = 1;
}

@ -33,10 +33,13 @@ void Part::loadPreset(const Preset * preset) {
settings.ampEnv.sustain = preset->ampEnv.sustain / 127.0;
settings.ampEnv.releaseStep = timeToStep(ccToDR(preset->ampEnv.release));
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 = timeToStep(ccToDR(preset->fltEnv.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.releaseStep = timeToStep(ccToDR(preset->modEnv.release));
settings.modEnvFltGain = preset->modEnvFltGain / 127.0;
settings.keyTrack = preset->keyTrack / 127.0;
}
void Part::noteOn(int note, int velocity) {
@ -58,15 +61,19 @@ void Part::noteOn(int note, int velocity) {
if(notes.count(note)) {
notes.at(note).insert(voice);
} else {
notes.emplace(note, std::unordered_set<Voice *>{voice});
notes.emplace(note, std::set<Voice *>{voice});
}
}
void Part::noteOff(int note) {
if(notes.count(note)) {
for(auto& voice : notes.at(note)) {
voice->noteOff();
}
auto& voices = notes.at(note);
for (auto i = voices.rbegin(); i != voices.rend(); ++i ) {
if((*i)->keyed) {
(*i)->noteOff();
break;
}
}
}
}
@ -77,19 +84,19 @@ void Part::control(int code, int value) {
break;
case CC_FLT_ATK: // Filter Attack Time
settings.fltEnv.attackStep = timeToStep(ccToA(value));
settings.modEnv.attackStep = timeToStep(ccToA(value));
break;
case CC_FLT_DEC: // Filter Decay Time
settings.fltEnv.decayStep = timeToStep(ccToDR(value));
settings.modEnv.decayStep = timeToStep(ccToDR(value));
break;
case CC_FLT_SUS: // Filter Sustain
settings.fltEnv.sustain = value / 127.0;
settings.modEnv.sustain = value / 127.0;
break;
case CC_FLT_REL: // Filter Release Time
settings.fltEnv.releaseStep = timeToStep(ccToDR(value));
settings.modEnv.releaseStep = timeToStep(ccToDR(value));
break;
case CC_FLT_Q: // Timbre / Harmonic Content (Standard MIDI)
@ -120,5 +127,13 @@ void Part::control(int code, int value) {
case CC_OSC_DET: // Detune (Standard MIDI)
settings.oscDetune = (value - 64.0) / 100.0;
break;
case CC_MOD_FLT: // Mod Envelope Filter Gain
settings.modEnvFltGain = value / 127.0;
break;
case CC_KEY_TRK: // Filter Key Tracking
settings.keyTrack = value / 127.0;
break;
}
}

@ -0,0 +1,32 @@
#include "synth/preset.h"
const Preset DEFAULT_PRESET = {
.osc1Mode = Oscillator::MODE_SAW,
.osc2Mode = Oscillator::MODE_SAW,
.oscMix = 64,
.oscDetune = 70,
.filter = {
.type = Filter::TYPE_LP,
.slope = Filter::SLOPE_24,
.freq = 45,
.Q = 50,
},
.ampEnv = {
.attack = 0,
.decay = 80,
.sustain = 64,
.release = 64
},
.modEnv = {
.attack = 48,
.decay = 104,
.sustain = 0,
.release = 64
},
.modEnvFltGain = 40,
.keyTrack = 127
};

@ -3,18 +3,20 @@
void Voice::assign(Settings const * settings) {
this->settings = settings;
adsrAmp.assign(&settings->ampEnv);
adsrFlt.assign(&settings->fltEnv);
adsrMod.assign(&settings->modEnv);
osc1.assign(&settings->osc1Mode);
osc2.assign(&settings->osc2Mode);
filter.assign(&settings->filter);
}
void Voice::noteOn() {
keyed = true;
adsrAmp.noteOn();
adsrFlt.noteOn();
adsrMod.noteOn();
}
void Voice::noteOff() {
keyed = false;
adsrAmp.noteOff();
adsrFlt.noteOff();
adsrMod.noteOff();
}

@ -1,3 +1,5 @@
#include <iostream>
#include "synth/voicemanager.h"
VoiceManager::VoiceManager() {
@ -10,6 +12,7 @@ Voice * VoiceManager::allocate() {
int index = *idle.begin();
idle.erase(index);
active.insert(index);
std::cout << "allocate(): active=" << active.size() << " idle=" << idle.size() << std::endl;
return &voices[index];
}
@ -17,4 +20,5 @@ void VoiceManager::free(Voice * voice) {
int index = voice - voices;
active.erase(index);
idle.insert(index);
std::cout << "free(): active=" << active.size() << " idle=" << idle.size() << std::endl;
}

@ -2,8 +2,6 @@
#include <cmath>
#include <iostream>
#include <porttime.h>
#include "synth/globals.h"
#include "synthapp.h"
@ -20,23 +18,10 @@ static int paCallback(
PaStreamCallbackFlags statusFlags,
void *userData ) {
Synth *synth = (Synth*) userData;
float *out = (float*) outputBuffer;
for(int i = 0; i < framesPerBuffer; i++) {
frame f = synth->tick();
*out++ = f.left;
*out++ = f.right;
}
return 0;
}
SynthApp *app = (SynthApp*) userData;
// PortTimer callback for MIDI I/O
static void ptCallback(PtTimestamp timestamp, void* userData) {
SynthApp* synthApp = (SynthApp*) userData;
PmEvent events[8];
int numEvents = Pm_Read(synthApp->pmStream, events, 8);
PmEvent events[16];
int numEvents = Pm_Read(app->pmStream, events, 16);
for(int i = 0; i < numEvents; ++i) {
PmMessage msg = events[i].message;
@ -45,15 +30,15 @@ static void ptCallback(PtTimestamp timestamp, void* userData) {
if(type == 0x8 || (type == 0x9 && Pm_MessageData2(msg) == 0)) { // Note Off
int note = Pm_MessageData1(msg);
synthApp->synth.noteOff(ch, note);
app->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);
app->synth.noteOn(ch, note, vel);
} else if(type == 0xB) { // Controller Message
int cc = Pm_MessageData1(msg);
int val = Pm_MessageData2(msg);
synthApp->synth.control(ch, cc, val);
app->synth.control(ch, cc, val);
}
/*
cout << "MIDI:";
@ -63,10 +48,19 @@ static void ptCallback(PtTimestamp timestamp, void* userData) {
cout << endl;
*/
}
float *out = (float*) outputBuffer;
for(int i = 0; i < framesPerBuffer; i++) {
frame f = app->synth.tick();
*out++ = f.left;
*out++ = f.right;
}
return 0;
}
void handlePMError(PmError err) {
Pt_Stop();
Pa_Terminate();
Pm_Terminate();
cerr << "PortMidi error" << endl;
@ -76,7 +70,6 @@ void handlePMError(PmError err) {
}
void handlePAError(PaError err) {
Pt_Stop();
Pa_Terminate();
Pm_Terminate();
cerr << "PortAudio error" << endl;
@ -144,8 +137,7 @@ bool SynthApp::OnInit() {
cout << "Invalid selection" << endl;
}
// Start MIDI timer and open MIDI input
Pt_Start(1, ptCallback, this);
// Open MIDI input
PmError pmError = Pm_OpenInput(
&pmStream,
pmDeviceIndex,
@ -172,7 +164,7 @@ bool SynthApp::OnInit() {
0, // frames per buffer
paNoFlag,
paCallback,
&synth);
this);
if(paErr != paNoError) {
handlePAError(paErr);
}
@ -190,8 +182,6 @@ bool SynthApp::OnInit() {
}
int SynthApp::OnExit() {
Pt_Stop();
PaError paErr = Pa_StopStream(paStream);
if(paErr != paNoError) {
handlePAError(paErr);

@ -1,6 +1,7 @@
#include <iostream>
#include "synth/cc.h"
#include "synth/preset.h"
#include "synthframe.h"
#include "synthapp.h"
@ -28,18 +29,22 @@ SynthFrame::SynthFrame() : wxFrame(NULL, wxID_ANY, "Hello World") {
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
panel->SetSizer(sizer);
sizer->Add(new wxSlider(panel, FLT_FREQ_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_Q_SLIDER, 22, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_FREQ_SLIDER, DEFAULT_PRESET.filter.freq, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_Q_SLIDER, DEFAULT_PRESET.filter.Q, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->AddSpacer(20);
sizer->Add(new wxSlider(panel, AMP_ATTACK_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_DECAY_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_SUSTAIN_SLIDER, 127, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_RELEASE_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, KEY_TRACK_SLIDER, DEFAULT_PRESET.keyTrack, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->AddSpacer(20);
sizer->Add(new wxSlider(panel, FLT_ATTACK_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_DECAY_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_SUSTAIN_SLIDER, 127, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, FLT_RELEASE_SLIDER, 0, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_ATTACK_SLIDER, DEFAULT_PRESET.ampEnv.attack, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_DECAY_SLIDER, DEFAULT_PRESET.ampEnv.decay, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_SUSTAIN_SLIDER, DEFAULT_PRESET.ampEnv.sustain, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, AMP_RELEASE_SLIDER, DEFAULT_PRESET.ampEnv.release, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->AddSpacer(20);
sizer->Add(new wxSlider(panel, MOD_ATTACK_SLIDER, DEFAULT_PRESET.modEnv.attack, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, MOD_DECAY_SLIDER, DEFAULT_PRESET.modEnv.decay, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, MOD_SUSTAIN_SLIDER, DEFAULT_PRESET.modEnv.sustain, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->Add(new wxSlider(panel, MOD_RELEASE_SLIDER, DEFAULT_PRESET.modEnv.release, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
sizer->AddSpacer(20);
sizer->Add(new wxSlider(panel, MOD_ENV_FLT_GAIN, DEFAULT_PRESET.modEnvFltGain, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE));
Bind(wxEVT_MENU, &SynthFrame::OnAbout, this, wxID_ABOUT);
Bind(wxEVT_MENU, &SynthFrame::OnExit, this, wxID_EXIT);
@ -64,6 +69,11 @@ void SynthFrame::OnFltQScroll(wxScrollEvent& event) {
app.synth.control(0, CC_FLT_Q, event.GetPosition());
}
void SynthFrame::OnKeyTrackScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_KEY_TRK, event.GetPosition());
}
void SynthFrame::OnAmpAttackScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_AMP_ATK, event.GetPosition());
@ -104,19 +114,28 @@ void SynthFrame::OnFltReleaseScroll(wxScrollEvent& event) {
app.synth.control(0, CC_FLT_REL, event.GetPosition());
}
void SynthFrame::OnModEnvFltGainScroll(wxScrollEvent& event) {
SynthApp& app = wxGetApp();
app.synth.control(0, CC_MOD_FLT, 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::KEY_TRACK_SLIDER, SynthFrame::OnKeyTrackScroll)
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)
EVT_COMMAND_SCROLL(SynthFrame::AMP_RELEASE_SLIDER, SynthFrame::OnAmpReleaseScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_ATTACK_SLIDER, SynthFrame::OnFltAttackScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_DECAY_SLIDER, SynthFrame::OnFltDecayScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_SUSTAIN_SLIDER, SynthFrame::OnFltSustainScroll)
EVT_COMMAND_SCROLL(SynthFrame::FLT_RELEASE_SLIDER, SynthFrame::OnFltReleaseScroll)
EVT_COMMAND_SCROLL(SynthFrame::MOD_ATTACK_SLIDER, SynthFrame::OnFltAttackScroll)
EVT_COMMAND_SCROLL(SynthFrame::MOD_DECAY_SLIDER, SynthFrame::OnFltDecayScroll)
EVT_COMMAND_SCROLL(SynthFrame::MOD_SUSTAIN_SLIDER, SynthFrame::OnFltSustainScroll)
EVT_COMMAND_SCROLL(SynthFrame::MOD_RELEASE_SLIDER, SynthFrame::OnFltReleaseScroll)
EVT_COMMAND_SCROLL(SynthFrame::MOD_ENV_FLT_GAIN, SynthFrame::OnModEnvFltGainScroll)
wxEND_EVENT_TABLE()
Loading…
Cancel
Save