parent
6574268466
commit
5f2086e829
20 changed files with 507 additions and 128 deletions
@ -0,0 +1,3 @@ |
||||
{ |
||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" |
||||
} |
@ -0,0 +1,76 @@ |
||||
#ifndef __ADSR_H__ |
||||
#define __ADSR_H__ |
||||
|
||||
#include <cmath> |
||||
|
||||
#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 |
@ -0,0 +1,26 @@ |
||||
#ifndef __UTIL_H__ |
||||
#define __UTIL_H__ |
||||
|
||||
#include <cmath> |
||||
|
||||
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 |
@ -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 |
@ -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 |
@ -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 |
@ -0,0 +1,25 @@ |
||||
#ifndef __SYNTHAPP_H__ |
||||
#define __SYNTHAPP_H__ |
||||
|
||||
#include <wx/wxprec.h> |
||||
|
||||
#ifndef WX_PRECOMP |
||||
#include <wx/wx.h> |
||||
#endif |
||||
|
||||
#include <portaudio.h> |
||||
#include <portmidi.h> |
||||
|
||||
#include "synth/synth.h" |
||||
|
||||
class SynthApp : public wxApp { |
||||
public: |
||||
PaStream* paStream; |
||||
PmStream* pmStream; |
||||
Synth synth; |
||||
|
||||
virtual bool OnInit(); |
||||
virtual int OnExit(); |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,53 @@ |
||||
#ifndef __SYNTHFRAME_H__ |
||||
#define __SYNTHFRAME_H__ |
||||
|
||||
#include <wx/wxprec.h> |
||||
|
||||
#ifndef WX_PRECOMP |
||||
#include <wx/wx.h> |
||||
#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 |
@ -0,0 +1 @@ |
||||
Subproject commit a242283456ffd0920a2384f6fee45d56a7ecc3a5 |
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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) { |
||||
|
||||
} |
@ -0,0 +1,121 @@ |
||||
#include <iostream> |
||||
|
||||
#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() |
Loading…
Reference in new issue