diff --git a/.gitignore b/.gitignore index e8ec6e2..6e90488 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ CMakeCache.txt CMakeFiles/ *.cmake main -Makefile \ No newline at end of file +Makefile diff --git a/.gitmodules b/.gitmodules index 50e7aec..cf3bd7e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/portmidi"] path = lib/portmidi url = https://github.com/PortMidi/portmidi +[submodule "lib/wxWidgets"] + path = lib/wxWidgets + url = https://github.com/wxWidgets/wxWidgets diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b4d8c35 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index cefa34d..e5e41bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,14 @@ set(BUILD_SHARED_LIBS OFF) set(PA_USE_ASIO ON CACHE BOOL "Enable support for ASIO") add_subdirectory(lib/portaudio) add_subdirectory(lib/portmidi) -add_executable(main src/main.cpp src/oscillator.cpp) +add_subdirectory(lib/wxWidgets) + +add_executable(main src/synthapp.cpp src/synthframe.cpp src/synth/synth.cpp src/synth/dsp/adsr.cpp) target_include_directories(main PRIVATE include) + target_link_libraries(main PRIVATE PortAudio) target_link_libraries(main PRIVATE portmidi) -target_include_directories(main PRIVATE lib/portmidi/porttime) \ No newline at end of file +target_include_directories(main PRIVATE lib/portmidi/porttime) + +target_link_libraries(main PRIVATE wxbase) +target_link_libraries(main PRIVATE wxcore) diff --git a/README.md b/README.md index 9c70a9e..847a60d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Build files are generated for various C++ compilers using CMake. For VS Code, yo PortAudio and PortMidi are included as Git submodules that need to be fetched before building: ``` -git submodule init -git submodule update +git submodule update --init --recursive ``` diff --git a/include/synth/dsp/adsr.h b/include/synth/dsp/adsr.h new file mode 100644 index 0000000..8bb152f --- /dev/null +++ b/include/synth/dsp/adsr.h @@ -0,0 +1,76 @@ +#ifndef __ADSR_H__ +#define __ADSR_H__ + +#include + +#include "util.h" + +class ADSR { +public: + struct Envelope { + float attackStep; + float decayStep; + float sustain; + float releaseStep; + }; + + ADSR(); + void setEnvelope(const Envelope* envelope); + void noteOn(); + void noteOff(); + + float tick() { + float out = curve(gain); + + switch(state) { + case IDLE: + break; + + case ATTACK: + if(gain < 1) { + gain += envelope->attackStep; + } else { + state = DECAY; + gain = 1; + } + break; + + case DECAY: + if(gain > envelope->sustain) { + gain -= envelope->decayStep; + } else { + state = SUSTAIN; + gain = envelope->sustain; + } + break; + + case SUSTAIN: + break; + + case RELEASE: + if(gain > 0) { + gain -= envelope->releaseStep; + } else { + state = IDLE; + gain = 0; + } + break; + } + + return out; + } + +private: + enum { IDLE, ATTACK, DECAY, SUSTAIN, RELEASE } state; + + static const Envelope DEFAULT_ENVELOPE; + + const Envelope* envelope; + float gain; + + float curve(float gain) { + return 0.5 - 0.5 * cos(clamp(gain * M_PI, 0, M_PI)); + } +}; + +#endif \ No newline at end of file diff --git a/include/oscillator.h b/include/synth/dsp/oscillator.h similarity index 78% rename from include/oscillator.h rename to include/synth/dsp/oscillator.h index 31d082e..cf08d2e 100644 --- a/include/oscillator.h +++ b/include/synth/dsp/oscillator.h @@ -1,3 +1,6 @@ +#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/ */ @@ -17,10 +20,17 @@ public: float driftAmount; float driftValue; - Oscillator(); + Oscillator() { + mode = MODE_SAW; + phase = 0; + phaseStep = (440 * 2 * M_PI) / 44100; + value = 0; + driftAmount = 0.001; + driftValue = 0; + } float polyBlep(float t) { - double dt = phaseStep / PIx2; + float dt = phaseStep / PIx2; // t-t^2/2 +1/2 // 0 < t <= 1 @@ -48,7 +58,7 @@ public: float t = phase / PIx2; // Define half phase if (mode == MODE_SINE) { - value = sin(phase); // No harmonics in sine so no aliasing!! No Poly BLEPs needed! + value = sinf(phase); // No harmonics in sine so no aliasing!! No Poly BLEPs needed! } else if (mode == MODE_SAW) { value = (2.0 * phase / PIx2) - 1.0; // Render naive waveshape value -= polyBlep(t); // Layer output of Poly BLEP on top @@ -59,15 +69,17 @@ public: value = -1.0; // Flop } value += polyBlep(t); // Layer output of Poly BLEP on top (flip) - value -= polyBlep(fmod(t + 0.5, 1.0)); // Layer output of Poly BLEP on top (flop) + value -= polyBlep(fmodf(t + 0.5, 1.0)); // Layer output of Poly BLEP on top (flop) } driftValue += 0.01 * ((float) rand() / RAND_MAX - 0.5) - 0.00001 * driftValue; phase += phaseStep * (1.0 + driftAmount * driftValue); - if(phase >= PIx2) { // wrap if phase angle >=360º + while(phase >= PIx2) { // wrap if phase angle >=360º phase -= PIx2; } return value; } -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/include/svf.h b/include/synth/dsp/svf.h similarity index 66% rename from include/svf.h rename to include/synth/dsp/svf.h index 7036990..7cac4f8 100644 --- a/include/svf.h +++ b/include/synth/dsp/svf.h @@ -1,12 +1,13 @@ +#ifndef __SVF_H__ +#define __SVF_H__ + /* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ #include -inline float clamp(float x, float a, float b) { - return fminf(b, fmaxf(a, x)); -} +#include "util.h" -class SVF { +class SVF12 { protected: float as1, as2; float kdiv; @@ -21,11 +22,9 @@ public: float hp; // high-pass float bp; // band-pass float lp; // low-pass - float br; // band-reject - float ap; // all-pass } Output; - SVF() { + SVF12() { as1 = 0; as2 = 0; kK = tan(M_PI_4); @@ -34,7 +33,7 @@ public: } void setFrequency(float f) { - kK = tan(M_PI_2 * clamp(f, 0, 1)); + kK = tan(M_PI_4 * clamp(f, 0, 1)); updateCoeffs(); } @@ -52,24 +51,29 @@ public: au = out.bp * kK; out.lp = au + as2; as2 = au + out.lp; - float abr = out.hp + out.lp; - float aap = out.hp + out.lp + (1/kQ) * out.bp; return out; } }; -// same filter but two of them chained for 24 dB/oct -class SVFx2 { +// 12 and 24 dB/oct +class SVF { public: - enum Mode { MODE_HP, MODE_BP, MODE_LP, MODE_BR, MODE_AP }; + enum Mode { + MODE_HP_12, + MODE_HP_24, + MODE_BP_12, + MODE_BP_24, + MODE_LP_12, + MODE_LP_24, + }; private: Mode mode; - SVF first, second; + SVF12 first, second; public: - SVFx2() { - this->mode = MODE_LP; + SVF() { + this->mode = MODE_LP_24; } void setMode(Mode mode) { @@ -87,22 +91,27 @@ public: } float tick(float as) { - SVF::Output in = first.tick(as); + SVF12::Output in = first.tick(as); switch(mode) { - case MODE_HP: + case MODE_HP_12: + return in.hp; + + case MODE_HP_24: return second.tick(in.hp).hp; - case MODE_BP: + case MODE_BP_12: + return in.bp; + + case MODE_BP_24: return second.tick(in.bp).bp; - case MODE_LP: + case MODE_LP_12: + return in.lp; + + case MODE_LP_24: return second.tick(in.lp).lp; - - case MODE_BR: - return second.tick(in.br).br; - - case MODE_AP: - return second.tick(in.ap).ap; } } -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/include/synth/dsp/util.h b/include/synth/dsp/util.h new file mode 100644 index 0000000..4eb18a3 --- /dev/null +++ b/include/synth/dsp/util.h @@ -0,0 +1,26 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include + +inline float clamp(float x, float a, float b) { + return fminf(b, fmaxf(a, x)); +} + +inline float dBToGain(float dB) { + return pow(10, dB / 20.0); +} + +inline float noteToFreq(float note) { + return 440 * pow(2, (note - 69) / 12.0); +} + +inline float triangle(float phase) { + if(phase < M_PI) { + return phase / M_PI_2 - 1; + } else { + return 1 - (phase - M_PI) / M_PI_2; + } +} + +#endif \ No newline at end of file diff --git a/include/synth/preset.h b/include/synth/preset.h new file mode 100644 index 0000000..affdd68 --- /dev/null +++ b/include/synth/preset.h @@ -0,0 +1,13 @@ +#ifndef __PRESET_H__ +#define __PRESET_H__ + +#include "dsp/adsr.h" + +struct Preset { + float cutoff; + float resonance; + + ADSR::Envelope ampEnv, fltEnv; +}; + +#endif \ No newline at end of file diff --git a/include/synth/synth.h b/include/synth/synth.h new file mode 100644 index 0000000..70ee4eb --- /dev/null +++ b/include/synth/synth.h @@ -0,0 +1,19 @@ +#ifndef __SYNTH_H__ +#define __SYNTH_H__ + +#include "voice.h" + +const int SAMPLE_RATE = 48000; +const int NUM_VOICES = 72; + +class Synth { +public: + void noteOn(int note, int velocity); + void noteOff(int note); + void control(int code, int value); + +private: + Voice voices[NUM_VOICES]; +}; + +#endif \ No newline at end of file diff --git a/include/synth/voice.h b/include/synth/voice.h new file mode 100644 index 0000000..e306475 --- /dev/null +++ b/include/synth/voice.h @@ -0,0 +1,23 @@ +#ifndef __VOICE_H__ +#define __VOICE_H__ + +#include "preset.h" + +#include "dsp/oscillator.h" +#include "dsp/svf.h" +#include "dsp/adsr.h" + +class Voice { +public: + static const int NUM_OSCS = 2; + + void usePreset(Preset* preset); + +private: + ADSR adsrAmp; + ADSR adsrFlt; + Oscillator oscs[NUM_OSCS]; + SVF filter; +}; + +#endif \ No newline at end of file diff --git a/include/synthapp.h b/include/synthapp.h new file mode 100644 index 0000000..b0a9896 --- /dev/null +++ b/include/synthapp.h @@ -0,0 +1,25 @@ +#ifndef __SYNTHAPP_H__ +#define __SYNTHAPP_H__ + +#include + +#ifndef WX_PRECOMP + #include +#endif + +#include +#include + +#include "synth/synth.h" + +class SynthApp : public wxApp { +public: + PaStream* paStream; + PmStream* pmStream; + Synth synth; + + virtual bool OnInit(); + virtual int OnExit(); +}; + +#endif \ No newline at end of file diff --git a/include/synthframe.h b/include/synthframe.h new file mode 100644 index 0000000..af99aa1 --- /dev/null +++ b/include/synthframe.h @@ -0,0 +1,53 @@ +#ifndef __SYNTHFRAME_H__ +#define __SYNTHFRAME_H__ + +#include + +#ifndef WX_PRECOMP + #include +#endif + +class SynthFrame : public wxFrame { +public: + SynthFrame(); + +private: + enum { + AMP_ATTACK_SLIDER = wxID_TOP, + AMP_DECAY_SLIDER, + AMP_SUSTAIN_SLIDER, + AMP_RELEASE_SLIDER, + + FLT_ATTACK_SLIDER, + FLT_DECAY_SLIDER, + FLT_SUSTAIN_SLIDER, + FLT_RELEASE_SLIDER + }; + + wxSlider* ampAttackSlider; + wxSlider* ampDecaySlider; + wxSlider* ampSustainSlider; + wxSlider* ampReleaseSlider; + + wxSlider* fltAttackSlider; + wxSlider* fltDecaySlider; + wxSlider* fltSustainSlider; + wxSlider* fltReleaseSlider; + + void OnExit(wxCommandEvent& event); + void OnAbout(wxCommandEvent& event); + + void OnAmpAttackScroll(wxScrollEvent& event); + void OnAmpDecayScroll(wxScrollEvent& event); + void OnAmpSustainScroll(wxScrollEvent& event); + void OnAmpReleaseScroll(wxScrollEvent& event); + + void OnFltAttackScroll(wxScrollEvent& event); + void OnFltDecayScroll(wxScrollEvent& event); + void OnFltSustainScroll(wxScrollEvent& event); + void OnFltReleaseScroll(wxScrollEvent& event); + + wxDECLARE_EVENT_TABLE(); +}; + +#endif \ No newline at end of file diff --git a/lib/wxWidgets b/lib/wxWidgets new file mode 160000 index 0000000..a242283 --- /dev/null +++ b/lib/wxWidgets @@ -0,0 +1 @@ +Subproject commit a242283456ffd0920a2384f6fee45d56a7ecc3a5 diff --git a/src/oscillator.cpp b/src/oscillator.cpp deleted file mode 100644 index bc87bf4..0000000 --- a/src/oscillator.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "oscillator.h" - -Oscillator::Oscillator() { - mode = MODE_SAW; - phase = 0; - phaseStep = (440 * 2 * M_PI) / 44100; - value = 0; - driftAmount = 0.005; - driftValue = 0; -} \ No newline at end of file diff --git a/src/synth/dsp/adsr.cpp b/src/synth/dsp/adsr.cpp new file mode 100644 index 0000000..4a779af --- /dev/null +++ b/src/synth/dsp/adsr.cpp @@ -0,0 +1,26 @@ +#include "synth/dsp/adsr.h" + +const ADSR::Envelope ADSR::DEFAULT_ENVELOPE = { + .attackStep = 1, + .decayStep = 1, + .sustain = 1, + .releaseStep = 1 +}; + +ADSR::ADSR() { + this->envelope = &DEFAULT_ENVELOPE; + this->state = IDLE; + this->gain = 0; +} + +void ADSR::setEnvelope(const Envelope* envelope) { + this->envelope = envelope; +} + +void ADSR::noteOn() { + state = ATTACK; +} + +void ADSR::noteOff() { + state = RELEASE; +} diff --git a/src/synth/synth.cpp b/src/synth/synth.cpp new file mode 100644 index 0000000..923e6c6 --- /dev/null +++ b/src/synth/synth.cpp @@ -0,0 +1,13 @@ +#include "synth/synth.h" + +void Synth::noteOn(int note, int velocity) { + +} + +void Synth::noteOff(int note) { + +} + +void Synth::control(int code, int value) { + +} \ No newline at end of file diff --git a/src/main.cpp b/src/synthapp.cpp similarity index 65% rename from src/main.cpp rename to src/synthapp.cpp index 33317fe..8db7665 100644 --- a/src/main.cpp +++ b/src/synthapp.cpp @@ -2,29 +2,13 @@ #include #include -#include -#include #include -#include "oscillator.h" -#include "svf.h" +#include "synthapp.h" +#include "synthframe.h" using namespace std; -#define SAMPLE_RATE 48000 -#define NUM_OSCS 7 - -float noteToFreq(float note) { - return 440 * pow(2, (note - 69) / 12.0); -} - -typedef struct { - Oscillator oscs[NUM_OSCS]; - SVFx2 filter; - float time; - PmStream* pmStream; -} State; - // PortAudio callback for audio I/O static int paCallback( const void *inputBuffer, @@ -34,75 +18,55 @@ static int paCallback( PaStreamCallbackFlags statusFlags, void *userData ) { - State *state = (State*) userData; + Synth *state = (Synth*) userData; float *out = (float*)outputBuffer; for(int i = 0; i < framesPerBuffer; i++) { float value = 0; - for(int j = 0; j < NUM_OSCS; ++j) { - value += state->oscs[j].tick(); - } - value /= NUM_OSCS; - value = state->filter.tick(value); - value = 0.25 * clamp(value, -1, 1); + *out++ = value; *out++ = value; - state->time += 0.5 / SAMPLE_RATE; - } + return 0; } // PortTimer callback for MIDI I/O static void ptCallback(PtTimestamp timestamp, void* userData) { - State* state = (State*) userData; + SynthApp* app = (SynthApp*) userData; PmEvent events[8]; - int numEvents = Pm_Read(state->pmStream, events, 8); + int numEvents = Pm_Read(app->pmStream, events, 8); for(int i = 0; i < numEvents; ++i) { PmMessage msg = events[i].message; - int cmd = (msg >> 4) & 0xF; - int chan = msg & 0xF; + int type = (msg >> 4) & 0xF; + int ch = msg & 0xF; - if(cmd == 0x9) { - int vel = (msg >> 16) & 0x7F; - if(vel > 0) { - int note = (msg >> 8) & 0x7F; - float centreFreq = noteToFreq(note); - float spreadCoeff = 0.04; - for(int osc = 0, pos = -3; pos <= 3; ++osc, ++pos) { - float freq = noteToFreq(note + spreadCoeff * pos); - state->oscs[osc].phaseStep = (freq * 2 * M_PI) / SAMPLE_RATE; - } - state->time = 0; - } + if(type == 0x8 || (type == 0x9 && Pm_MessageData2(msg) == 0)) { // Note Off + + } else if(type == 0x9) { // Note On + int note = Pm_MessageData1(msg); + int vel = Pm_MessageData2(msg); - } else if(cmd == 0xB) { - int ctl = (msg >> 8) & 0x7F; + } else if(type == 0xB) { // Controller Message + int cc = Pm_MessageData1(msg); + int val = Pm_MessageData2(msg); - if(ctl == 0x01) { - int val = (msg >> 16) & 0x7F; - state->filter.setFrequency(val / 256.0); + if(cc == 1 || cc == 74) { // Sound Controller 5: Brightness - /* - float flutterSpread = 0.001 * val / 127.0; - for(int osc = 0; i < NUM_OSCS; ++i) { - state->oscs[i].flutterSpread = flutterSpread; - } - */ } } cout << "MIDI:"; for(int j = 0; j < 24; j += 8) { cout << " " << hex << ((msg >> j) & 0xFF); - } cout << endl; } } void handlePMError(PmError err) { + Pt_Stop(); Pa_Terminate(); Pm_Terminate(); cerr << "PortMidi error" << endl; @@ -112,6 +76,7 @@ void handlePMError(PmError err) { } void handlePAError(PaError err) { + Pt_Stop(); Pa_Terminate(); Pm_Terminate(); cerr << "PortAudio error" << endl; @@ -120,16 +85,7 @@ void handlePAError(PaError err) { exit(err); } -int main(void) { - static State state; - for(int i = 0; i < 7; ++i) { - state.oscs[i].phaseStep = (440 * 2 * M_PI) / SAMPLE_RATE; - state.oscs[i].phase = PIx2 * (float) rand() / RAND_MAX; - } - state.filter.setMode(SVFx2::MODE_LP); - state.filter.setQ(1); - state.time = 0; - +bool SynthApp::OnInit() { // Initialise PortMIDI and PortAudio libraries PmError pmErr = Pm_Initialize(); if(pmErr != pmNoError) { @@ -189,9 +145,9 @@ int main(void) { } // Start MIDI timer and open MIDI input - Pt_Start(1, ptCallback, &state); + Pt_Start(1, ptCallback, this); PmError pmError = Pm_OpenInput( - &state.pmStream, + &pmStream, pmDeviceIndex, NULL, 8, @@ -202,22 +158,21 @@ int main(void) { } // Configure/start audio output stream - PaStream *paStream; PaStreamParameters paStreamParams; paStreamParams.device = paDeviceIndex; paStreamParams.channelCount = 2; paStreamParams.sampleFormat = paFloat32; - paStreamParams.suggestedLatency = 0; + paStreamParams.suggestedLatency = 512.0 / SAMPLE_RATE; paStreamParams.hostApiSpecificStreamInfo = NULL; paErr = Pa_OpenStream( &paStream, - NULL, /* no input channels */ - &paStreamParams, /* stereo output */ + NULL, // no input channels + &paStreamParams, // stereo output SAMPLE_RATE, - 0, /* frames per buffer */ + 0, // frames per buffer paNoFlag, paCallback, - &state); + &synth); if(paErr != paNoError) { handlePAError(paErr); } @@ -229,11 +184,15 @@ int main(void) { const PaStreamInfo *paStreamInfo = Pa_GetStreamInfo(paStream); cout << "Latency: " << round(1000 * paStreamInfo->outputLatency) << " ms" << endl; - // Wait for Return key - cout << "Running - press Enter to exit" << endl; - getchar(); + wxFrame *frame = new SynthFrame(); + frame->Show(true); + return true; +} + +int SynthApp::OnExit() { + Pt_Stop(); - paErr = Pa_StopStream(paStream); + PaError paErr = Pa_StopStream(paStream); if(paErr != paNoError) { handlePAError(paErr); } @@ -241,12 +200,14 @@ int main(void) { if(paErr != paNoError) { handlePAError(paErr); } - pmErr = Pm_Close(state.pmStream); + PmError pmErr = Pm_Close(pmStream); if(pmErr != pmNoError) { handlePMError(pmErr); } Pa_Terminate(); cout << "Terminating" << endl; - + return 0; -} \ No newline at end of file +} + +wxIMPLEMENT_APP(SynthApp); \ No newline at end of file diff --git a/src/synthframe.cpp b/src/synthframe.cpp new file mode 100644 index 0000000..0d0c06b --- /dev/null +++ b/src/synthframe.cpp @@ -0,0 +1,121 @@ +#include + +#include "synthframe.h" +#include "synthapp.h" + +wxDECLARE_APP(SynthApp); + +float ccToA(int x) { + return 5.0 * exp(9.2 * (x / 127.0) - 9.2); +} + +float ccToDR(int x) { + return 5.0 * exp(7.0 * (x / 127.0) - 7.0); +} + +SynthFrame::SynthFrame() : wxFrame(NULL, wxID_ANY, "Hello World") { + wxMenu *menuFile = new wxMenu; + menuFile->Append(wxID_EXIT); + + wxMenu *menuHelp = new wxMenu; + menuHelp->Append(wxID_ABOUT); + + wxMenuBar *menuBar = new wxMenuBar; + menuBar->Append(menuFile, "&File"); + menuBar->Append(menuHelp, "&Help"); + + SetMenuBar(menuBar); + + CreateStatusBar(); + SetStatusText("Welcome to wxWidgets!"); + + wxPanel* panel = new wxPanel(this); + + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + panel->SetSizer(sizer); + + 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->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)); + sizer->Add(new wxSlider(panel, FLT_RELEASE_SLIDER, 63, 0, 127, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE)); + + Bind(wxEVT_MENU, &SynthFrame::OnAbout, this, wxID_ABOUT); + Bind(wxEVT_MENU, &SynthFrame::OnExit, this, wxID_EXIT); +} + +void SynthFrame::OnExit(wxCommandEvent& event) { + Close(true); +} + +void SynthFrame::OnAbout(wxCommandEvent& event) { + wxMessageBox("This is a wxWidgets Hello World example", + "About Hello World", wxOK | wxICON_INFORMATION); +} + +void SynthFrame::OnAmpAttackScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float seconds = ccToA(event.GetPosition()); + //app.state.adsrAmp.attackStep = (1.0 / 48000.0) / seconds; +} + +void SynthFrame::OnAmpDecayScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float seconds = ccToDR(event.GetPosition()); + //app.state.adsrAmp.decayStep = (1.0 / 48000.0) / seconds; +} + +void SynthFrame::OnAmpSustainScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float gain = event.GetPosition() / 127.0; + std::cout << "Gain: " << gain << std::endl; + //app.state.adsrAmp.sustain = gain; +} + +void SynthFrame::OnAmpReleaseScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float seconds = ccToDR(event.GetPosition()); + //app.state.adsrAmp.releaseStep = (1.0 / 48000.0) / seconds; +} + +void SynthFrame::OnFltAttackScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float seconds = ccToA(event.GetPosition()); + //app.state.adsrFlt.attackStep = (1.0 / 48000.0) / seconds; +} + +void SynthFrame::OnFltDecayScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float seconds = ccToDR(event.GetPosition()); + //app.state.adsrFlt.decayStep = (1.0 / 48000.0) / seconds; +} + +void SynthFrame::OnFltSustainScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float gain = event.GetPosition() / 127.0; + //app.state.adsrFlt.sustain = gain; +} + +void SynthFrame::OnFltReleaseScroll(wxScrollEvent& event) { + SynthApp& app = wxGetApp(); + float seconds = ccToDR(event.GetPosition()); + //app.state.adsrFlt.releaseStep = (1.0 / 48000.0) / seconds; +} + +wxBEGIN_EVENT_TABLE(SynthFrame, wxFrame) + +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) + +wxEND_EVENT_TABLE() \ No newline at end of file