diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fd87c4..f727a23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/synth/cc.h b/include/synth/cc.h new file mode 100644 index 0000000..3481d68 --- /dev/null +++ b/include/synth/cc.h @@ -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 \ No newline at end of file diff --git a/include/synth/dsp/adsr.h b/include/synth/dsp/adsr.h index 55e5784..9658bcc 100644 --- a/include/synth/dsp/adsr.h +++ b/include/synth/dsp/adsr.h @@ -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); diff --git a/include/synth/dsp/filter.h b/include/synth/dsp/filter.h index afd090f..f1fead1 100644 --- a/include/synth/dsp/filter.h +++ b/include/synth/dsp/filter.h @@ -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; } } }; diff --git a/include/synth/dsp/oscillator.h b/include/synth/dsp/oscillator.h index ac41d2f..34ca0c7 100644 --- a/include/synth/dsp/oscillator.h +++ b/include/synth/dsp/oscillator.h @@ -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 \ No newline at end of file diff --git a/include/synth/globals.h b/include/synth/globals.h index 44aba82..03787cf 100644 --- a/include/synth/globals.h +++ b/include/synth/globals.h @@ -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 \ No newline at end of file diff --git a/include/synth/part.h b/include/synth/part.h index 167da48..5449241 100644 --- a/include/synth/part.h +++ b/include/synth/part.h @@ -1,9 +1,10 @@ #ifndef __PART_H__ #define __PART_H__ -#include +#include #include +#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 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> notes; }; #endif \ No newline at end of file diff --git a/include/synth/preset.h b/include/synth/preset.h index a124513..0b4a4f3 100644 --- a/include/synth/preset.h +++ b/include/synth/preset.h @@ -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 = { diff --git a/include/synth/synth.h b/include/synth/synth.h index 39b27b0..7286636 100644 --- a/include/synth/synth.h +++ b/include/synth/synth.h @@ -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{}; diff --git a/include/synth/voice.h b/include/synth/voice.h index 561da6e..e8e6c9d 100644 --- a/include/synth/voice.h +++ b/include/synth/voice.h @@ -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; diff --git a/include/synth/voicemanager.h b/include/synth/voicemanager.h index 1fb7ca4..589db65 100644 --- a/include/synth/voicemanager.h +++ b/include/synth/voicemanager.h @@ -1,10 +1,21 @@ #ifndef __VOICEMANAGER_H__ #define __VOICEMANAGER_H__ +#include + +#include "synth/voice.h" + class VoiceManager { public: -private: + VoiceManager(); + Voice * allocate(); + void free(Voice * voice); + +private: + Voice voices[64]; + std::unordered_set idle; + std::unordered_set active; }; #endif \ No newline at end of file diff --git a/include/synthframe.h b/include/synthframe.h index af99aa1..0c8590a 100644 --- a/include/synthframe.h +++ b/include/synthframe.h @@ -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); diff --git a/src/synth/dsp/adsr.cpp b/src/synth/dsp/adsr.cpp index c7a15a0..b9881e7 100644 --- a/src/synth/dsp/adsr.cpp +++ b/src/synth/dsp/adsr.cpp @@ -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() { diff --git a/src/synth/dsp/filter.cpp b/src/synth/dsp/filter.cpp new file mode 100644 index 0000000..a5b3dd0 --- /dev/null +++ b/src/synth/dsp/filter.cpp @@ -0,0 +1,5 @@ +#include "synth/dsp/filter.h" + +void Filter::assign(Settings const * settings) { + this->settings = settings; +} \ No newline at end of file diff --git a/src/synth/dsp/oscillator.cpp b/src/synth/dsp/oscillator.cpp new file mode 100644 index 0000000..9d1e371 --- /dev/null +++ b/src/synth/dsp/oscillator.cpp @@ -0,0 +1,5 @@ +#include "synth/dsp/oscillator.h" + +void Oscillator::assign(Mode const * mode) { + this->mode = mode; +} \ No newline at end of file diff --git a/src/synth/part.cpp b/src/synth/part.cpp index a99df58..20f6f2d 100644 --- a/src/synth/part.cpp +++ b/src/synth/part.cpp @@ -1,7 +1,14 @@ +#include + #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}); + } } 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; + } } \ No newline at end of file diff --git a/src/synth/synth.cpp b/src/synth/synth.cpp index bad9330..3ecc0a2 100644 --- a/src/synth/synth.cpp +++ b/src/synth/synth.cpp @@ -1,13 +1,18 @@ +#include + #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; } \ No newline at end of file diff --git a/src/synth/voice.cpp b/src/synth/voice.cpp new file mode 100644 index 0000000..4dc39d3 --- /dev/null +++ b/src/synth/voice.cpp @@ -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(); +} \ No newline at end of file diff --git a/src/synth/voicemanager.cpp b/src/synth/voicemanager.cpp new file mode 100644 index 0000000..5d5bd8e --- /dev/null +++ b/src/synth/voicemanager.cpp @@ -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); +} \ No newline at end of file diff --git a/src/synthapp.cpp b/src/synthapp.cpp index 2010ced..56dd098 100644 --- a/src/synthapp.cpp +++ b/src/synthapp.cpp @@ -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; + */ } } diff --git a/src/synthframe.cpp b/src/synthframe.cpp index b37a762..2ae8974 100644 --- a/src/synthframe.cpp +++ b/src/synthframe.cpp @@ -1,5 +1,7 @@ #include +#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)