diff --git a/src/ParameterStore.h b/src/ParameterStore.h index 0fca5ff..d3db1f9 100644 --- a/src/ParameterStore.h +++ b/src/ParameterStore.h @@ -7,21 +7,23 @@ enum class ParamId : uint16_t { Osc1Frequency, - Osc1Gain, + Osc1WaveSelector1, + Osc1WaveSelector2, + Osc1Volume, Osc1VolumeEnvA, Osc1VolumeEnvD, Osc1VolumeEnvS, Osc1VolumeEnvR, + FilterCutoff, FilterCutoffEnvA, FilterCutoffEnvD, FilterCutoffEnvS, FilterCutoffEnvR, + FilterResonance, FilterResonanceEnvA, FilterResonanceEnvD, FilterResonanceEnvS, FilterResonanceEnvR, - Osc1WaveSelector1, - Osc1WaveSelector2, // ... and so on // this list could be like 200 long if I really wanted to Count // for sizing @@ -61,21 +63,23 @@ struct ParamDefault { // later TODO: and then when I have full on profile saving there will be a default profile to load from constexpr std::array(ParamId::Count)> PARAM_DEFS {{ { 100.0f, 20.0f, 600.0f}, // Osc1Freq - { 0.8f, 0.0f, 1.0f}, // Osc1Gain + { 0.0f, 0.0f, 0.0f}, // Osc1WaveSelector1 + { 1.0f, 0.0f, 0.0f}, // Osc1WaveSelector2 + { 1.0f, 0.0f, 2.0f}, // Osc1Volume { 0.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA { 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD { 0.7f, 0.0f, 1.0f}, // Osc1VolumeEnvS { 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvR + { 1.0f, 0.0f, 8.0f}, // FilterCutoff { 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvA { 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvD - { 1000.f, 0.0f, 40000.f}, // FilterCutoffEnvS + { 2.0f, 0.0f, 1.0f}, // FilterCutoffEnvS { 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvR + { 1.414f, 0.0f, 8.0f}, // FilterResonance { 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvA { 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvD - { 0.707f, 0.0f, 2.0f}, // FilterResonanceEnvS + { 0.5f, 0.0f, 1.0f}, // FilterResonanceEnvS { 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvR - { 0.0f, 0.0f, 0.0f}, // Osc1WaveSelector1 - { 1.0f, 0.0f, 0.0f}, // Osc1WaveSelector2 }}; constexpr size_t PARAM_COUNT = static_cast(ParamId::Count); diff --git a/src/synth/Filter.cpp b/src/synth/Filter.cpp index 9abd1e3..7e27c5e 100644 --- a/src/synth/Filter.cpp +++ b/src/synth/Filter.cpp @@ -15,7 +15,7 @@ void Filter::setSampleRate(float sampleRate) { void Filter::setParams(Type type, float frequency, float q) { type_ = type; - frequency_ = frequency; + frequency_ = std::min(frequency, sampleRate_ / 2.0f * 0.999f); q_ = q; calculateCoefficients(); } @@ -23,11 +23,11 @@ void Filter::setParams(Type type, float frequency, float q) { float Filter::biquadProcess(float in) { // calculate filtered sample - float out = a0_ * in + z1_; + float out = b0_ * in + z1_; // update states - z1_ = a1_ * in - b1_ * out + z2_; - z2_ = a2_ * in - b2_ * out; + z1_ = b1_ * in - a1_ * out + z2_; + z2_ = b2_ * in - a2_ * out; return out; } @@ -73,11 +73,11 @@ void Filter::calculateCoefficients() { } // Normalize - a0_ = b0 / a0; - a1_ = b1 / a0; - a2_ = b2 / a0; - b1_ = a1 / a0; - b2_ = a2 / a0; + b0_ = b0 / a0; + b1_ = b1 / a0; + b2_ = b2 / a0; + a1_ = a1 / a0; + a2_ = a2 / a0; } diff --git a/src/synth/Filter.h b/src/synth/Filter.h index 239c6be..5f66b77 100644 --- a/src/synth/Filter.h +++ b/src/synth/Filter.h @@ -40,7 +40,7 @@ private: float q_ = 0.707f; // biquad filter structure - float a0_, a1_, a2_, b1_, b2_; + float a1_, a2_, b0_, b1_, b2_; float z1_, z2_; }; \ No newline at end of file diff --git a/src/synth/Synth.cpp b/src/synth/Synth.cpp index 6f1cd5a..8baf613 100644 --- a/src/synth/Synth.cpp +++ b/src/synth/Synth.cpp @@ -87,8 +87,9 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) { gainEnvelope_.set(getParam(ParamId::Osc1VolumeEnvA), getParam(ParamId::Osc1VolumeEnvD), getParam(ParamId::Osc1VolumeEnvS), getParam(ParamId::Osc1VolumeEnvR)); cutoffEnvelope_.set(getParam(ParamId::FilterCutoffEnvA), getParam(ParamId::FilterCutoffEnvD), getParam(ParamId::FilterCutoffEnvS), getParam(ParamId::FilterCutoffEnvR)); resonanceEnvelope_.set(getParam(ParamId::FilterResonanceEnvA), getParam(ParamId::FilterResonanceEnvD), getParam(ParamId::FilterResonanceEnvS), getParam(ParamId::FilterResonanceEnvR)); - float gain = gainEnvelope_.process(); - filter_.setParams(Filter::Type::BiquadLowpass, cutoffEnvelope_.process(), resonanceEnvelope_.process()); + float gainEnv = gainEnvelope_.process(); + float cutoffEnv = cutoffEnvelope_.process(); + float resonanceEnv = resonanceEnvelope_.process(); // TODO: envelope is shared between all notes so this sequence involves a note change but only one envelope attack: // NOTE_A_ON > NOTE_B_ON > NOTE_A_OFF and note B starts playing part-way through note A's envelope @@ -106,6 +107,8 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) { float pitchOffset = 0.5f; float phaseInc = pitchOffset * 2.0f * M_PI * frequency_ / static_cast(sampleRate); + float gain = gainEnv * getParam(ParamId::Osc1Volume); + // sample generation // TODO: wavetables // TODO: wavetables should be scaled by their RMS for equal loudness (prelim standard = 0.707) @@ -132,6 +135,8 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) { } // filter sample + float cutoffFreq = cutoffEnv * pow(2.0f, getParam(ParamId::FilterCutoff)) * frequency_; + filter_.setParams(Filter::Type::BiquadLowpass, cutoffFreq, resonanceEnv * getParam(ParamId::FilterResonance)); sampleOut = filter_.biquadProcess(sampleOut); // write to buffer diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index 0ee8678..9b52f39 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -47,6 +47,16 @@ MainWindow::MainWindow(QWidget *parent) : audio_->parameters()->set(ParamId::Osc1WaveSelector2, index); }); + connect(ui_->sliderOsc1Volume, &SmartSlider::valueChanged, this, [this](float v) { + audio_->parameters()->set(ParamId::Osc1Volume, v); + }); + connect(ui_->sliderFilterCutoff, &SmartSlider::valueChanged, this, [this](float v) { + audio_->parameters()->set(ParamId::FilterCutoff, v); + }); + connect(ui_->sliderFilterResonance, &SmartSlider::valueChanged, this, [this](float v) { + audio_->parameters()->set(ParamId::FilterResonance, v); + }); + // synth business audio_->start(); @@ -77,4 +87,11 @@ void MainWindow::onResetClicked() { ui_->comboOsc1WaveSelector1->setCurrentIndex(static_cast(PARAM_DEFS[static_cast(ParamId::Osc1WaveSelector1)].def)); ui_->comboOsc1WaveSelector2->setCurrentIndex(static_cast(PARAM_DEFS[static_cast(ParamId::Osc1WaveSelector2)].def)); + // misc sliders + ui_->sliderOsc1Volume->setRange(PARAM_DEFS[static_cast(ParamId::Osc1Volume)].min, PARAM_DEFS[static_cast(ParamId::Osc1Volume)].max); + ui_->sliderFilterCutoff->setRange(PARAM_DEFS[static_cast(ParamId::FilterCutoff)].min, PARAM_DEFS[static_cast(ParamId::FilterCutoff)].max); + ui_->sliderFilterResonance->setRange(PARAM_DEFS[static_cast(ParamId::FilterResonance)].min, PARAM_DEFS[static_cast(ParamId::FilterResonance)].max); + ui_->sliderOsc1Volume->setValue(PARAM_DEFS[static_cast(ParamId::Osc1Volume)].def); + ui_->sliderFilterCutoff->setValue(PARAM_DEFS[static_cast(ParamId::FilterCutoff)].def); + ui_->sliderFilterResonance->setValue(PARAM_DEFS[static_cast(ParamId::FilterResonance)].def); } diff --git a/src/ui/MainWindow.ui b/src/ui/MainWindow.ui index 287cd30..7eaa169 100644 --- a/src/ui/MainWindow.ui +++ b/src/ui/MainWindow.ui @@ -7,7 +7,7 @@ 0 0 940 - 600 + 658 @@ -39,7 +39,7 @@ 20 - 270 + 360 300 300 @@ -68,7 +68,7 @@ 320 - 270 + 360 300 300 @@ -84,7 +84,7 @@ 620 - 270 + 360 300 300 @@ -100,7 +100,7 @@ 110 - 240 + 330 101 16 @@ -121,7 +121,7 @@ 420 - 240 + 330 101 16 @@ -142,7 +142,7 @@ 710 - 240 + 330 121 16 @@ -261,6 +261,108 @@ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + 770 + 30 + 65 + 280 + + + + true + + + + + + 700 + 30 + 65 + 280 + + + + true + + + + + + 840 + 30 + 65 + 280 + + + + true + + + + + + 690 + 10 + 91 + 16 + + + + + 8 + + + + Volume + + + Qt::AlignmentFlag::AlignCenter + + + + + + 760 + 10 + 81 + 16 + + + + + 8 + + + + Cutoff + + + Qt::AlignmentFlag::AlignCenter + + + + + + 830 + 10 + 81 + 16 + + + + + 8 + + + + Resonance + + + Qt::AlignmentFlag::AlignCenter + + @@ -270,6 +372,12 @@
EnvelopeGenerator/EnvelopeGenerator.h
1 + + SmartSlider + QWidget +
SmartSlider/SmartSlider.h
+ 1 +
Scope QWidget diff --git a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp index 5df505c..7d6577d 100644 --- a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp +++ b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp @@ -4,6 +4,8 @@ #include +// TODO: package the rogue sliders into the envelopeGenerators with a "base" column (its what the "peak" slider in the esp synth was supposed to be) + EnvelopeGenerator::EnvelopeGenerator(QWidget* parent) : QWidget(parent), ui_(new Ui::EnvelopeGenerator) { ui_->setupUi(this); diff --git a/src/ui/widgets/Scope/Scope.cpp b/src/ui/widgets/Scope/Scope.cpp index 3e2cff3..1b7e3f7 100644 --- a/src/ui/widgets/Scope/Scope.cpp +++ b/src/ui/widgets/Scope/Scope.cpp @@ -46,7 +46,7 @@ void Scope::paintEvent(QPaintEvent*) { // got caught playing around QPen pen; - pen.setWidthF(2.0f); + pen.setWidthF(3.0f); QColor green(50, 255, 70); pen.setColor(green); p.setPen(pen); @@ -54,9 +54,9 @@ void Scope::paintEvent(QPaintEvent*) { for (int32_t i = 1; i < samples_.size(); i++) { p.drawLine( (i) * width() / samples_.size(), - midY - samples_[i - 1] * scaleY, + midY + samples_[i - 1] * scaleY, i * width() / samples_.size(), - midY - samples_[i] * scaleY + midY + samples_[i] * scaleY ); } -} \ No newline at end of file +}