fixed filter issues

This commit is contained in:
2025-12-28 00:21:44 -06:00
parent b36d68ae99
commit bfcdd189f7
8 changed files with 167 additions and 31 deletions

View File

@@ -7,21 +7,23 @@
enum class ParamId : uint16_t { enum class ParamId : uint16_t {
Osc1Frequency, Osc1Frequency,
Osc1Gain, Osc1WaveSelector1,
Osc1WaveSelector2,
Osc1Volume,
Osc1VolumeEnvA, Osc1VolumeEnvA,
Osc1VolumeEnvD, Osc1VolumeEnvD,
Osc1VolumeEnvS, Osc1VolumeEnvS,
Osc1VolumeEnvR, Osc1VolumeEnvR,
FilterCutoff,
FilterCutoffEnvA, FilterCutoffEnvA,
FilterCutoffEnvD, FilterCutoffEnvD,
FilterCutoffEnvS, FilterCutoffEnvS,
FilterCutoffEnvR, FilterCutoffEnvR,
FilterResonance,
FilterResonanceEnvA, FilterResonanceEnvA,
FilterResonanceEnvD, FilterResonanceEnvD,
FilterResonanceEnvS, FilterResonanceEnvS,
FilterResonanceEnvR, FilterResonanceEnvR,
Osc1WaveSelector1,
Osc1WaveSelector2,
// ... and so on // ... and so on
// this list could be like 200 long if I really wanted to // this list could be like 200 long if I really wanted to
Count // for sizing 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 // later TODO: and then when I have full on profile saving there will be a default profile to load from
constexpr std::array<ParamDefault, static_cast<size_t>(ParamId::Count)> PARAM_DEFS {{ constexpr std::array<ParamDefault, static_cast<size_t>(ParamId::Count)> PARAM_DEFS {{
{ 100.0f, 20.0f, 600.0f}, // Osc1Freq { 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.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD { 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD
{ 0.7f, 0.0f, 1.0f}, // Osc1VolumeEnvS { 0.7f, 0.0f, 1.0f}, // Osc1VolumeEnvS
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvR { 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}, // FilterCutoffEnvA
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvD { 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 { 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}, // FilterResonanceEnvA
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvD { 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.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<size_t>(ParamId::Count); constexpr size_t PARAM_COUNT = static_cast<size_t>(ParamId::Count);

View File

@@ -15,7 +15,7 @@ void Filter::setSampleRate(float sampleRate) {
void Filter::setParams(Type type, float frequency, float q) { void Filter::setParams(Type type, float frequency, float q) {
type_ = type; type_ = type;
frequency_ = frequency; frequency_ = std::min(frequency, sampleRate_ / 2.0f * 0.999f);
q_ = q; q_ = q;
calculateCoefficients(); calculateCoefficients();
} }
@@ -23,11 +23,11 @@ void Filter::setParams(Type type, float frequency, float q) {
float Filter::biquadProcess(float in) { float Filter::biquadProcess(float in) {
// calculate filtered sample // calculate filtered sample
float out = a0_ * in + z1_; float out = b0_ * in + z1_;
// update states // update states
z1_ = a1_ * in - b1_ * out + z2_; z1_ = b1_ * in - a1_ * out + z2_;
z2_ = a2_ * in - b2_ * out; z2_ = b2_ * in - a2_ * out;
return out; return out;
} }
@@ -73,11 +73,11 @@ void Filter::calculateCoefficients() {
} }
// Normalize // Normalize
a0_ = b0 / a0; b0_ = b0 / a0;
a1_ = b1 / a0; b1_ = b1 / a0;
a2_ = b2 / a0; b2_ = b2 / a0;
b1_ = a1 / a0; a1_ = a1 / a0;
b2_ = a2 / a0; a2_ = a2 / a0;
} }

View File

@@ -40,7 +40,7 @@ private:
float q_ = 0.707f; float q_ = 0.707f;
// biquad filter structure // biquad filter structure
float a0_, a1_, a2_, b1_, b2_; float a1_, a2_, b0_, b1_, b2_;
float z1_, z2_; float z1_, z2_;
}; };

View File

@@ -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)); 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)); 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)); resonanceEnvelope_.set(getParam(ParamId::FilterResonanceEnvA), getParam(ParamId::FilterResonanceEnvD), getParam(ParamId::FilterResonanceEnvS), getParam(ParamId::FilterResonanceEnvR));
float gain = gainEnvelope_.process(); float gainEnv = gainEnvelope_.process();
filter_.setParams(Filter::Type::BiquadLowpass, cutoffEnvelope_.process(), resonanceEnvelope_.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: // 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 // 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 pitchOffset = 0.5f;
float phaseInc = pitchOffset * 2.0f * M_PI * frequency_ / static_cast<float>(sampleRate); float phaseInc = pitchOffset * 2.0f * M_PI * frequency_ / static_cast<float>(sampleRate);
float gain = gainEnv * getParam(ParamId::Osc1Volume);
// sample generation // sample generation
// TODO: wavetables // TODO: wavetables
// TODO: wavetables should be scaled by their RMS for equal loudness (prelim standard = 0.707) // 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 // 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); sampleOut = filter_.biquadProcess(sampleOut);
// write to buffer // write to buffer

View File

@@ -47,6 +47,16 @@ MainWindow::MainWindow(QWidget *parent) :
audio_->parameters()->set(ParamId::Osc1WaveSelector2, index); 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 // synth business
audio_->start(); audio_->start();
@@ -77,4 +87,11 @@ void MainWindow::onResetClicked() {
ui_->comboOsc1WaveSelector1->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector1)].def)); ui_->comboOsc1WaveSelector1->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector1)].def));
ui_->comboOsc1WaveSelector2->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector2)].def)); ui_->comboOsc1WaveSelector2->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector2)].def));
// misc sliders
ui_->sliderOsc1Volume->setRange(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Volume)].min, PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Volume)].max);
ui_->sliderFilterCutoff->setRange(PARAM_DEFS[static_cast<size_t>(ParamId::FilterCutoff)].min, PARAM_DEFS[static_cast<size_t>(ParamId::FilterCutoff)].max);
ui_->sliderFilterResonance->setRange(PARAM_DEFS[static_cast<size_t>(ParamId::FilterResonance)].min, PARAM_DEFS[static_cast<size_t>(ParamId::FilterResonance)].max);
ui_->sliderOsc1Volume->setValue(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Volume)].def);
ui_->sliderFilterCutoff->setValue(PARAM_DEFS[static_cast<size_t>(ParamId::FilterCutoff)].def);
ui_->sliderFilterResonance->setValue(PARAM_DEFS[static_cast<size_t>(ParamId::FilterResonance)].def);
} }

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>940</width> <width>940</width>
<height>600</height> <height>658</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -39,7 +39,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>20</x> <x>20</x>
<y>270</y> <y>360</y>
<width>300</width> <width>300</width>
<height>300</height> <height>300</height>
</rect> </rect>
@@ -68,7 +68,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>320</x> <x>320</x>
<y>270</y> <y>360</y>
<width>300</width> <width>300</width>
<height>300</height> <height>300</height>
</rect> </rect>
@@ -84,7 +84,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>620</x> <x>620</x>
<y>270</y> <y>360</y>
<width>300</width> <width>300</width>
<height>300</height> <height>300</height>
</rect> </rect>
@@ -100,7 +100,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>110</x> <x>110</x>
<y>240</y> <y>330</y>
<width>101</width> <width>101</width>
<height>16</height> <height>16</height>
</rect> </rect>
@@ -121,7 +121,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>420</x> <x>420</x>
<y>240</y> <y>330</y>
<width>101</width> <width>101</width>
<height>16</height> <height>16</height>
</rect> </rect>
@@ -142,7 +142,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>710</x> <x>710</x>
<y>240</y> <y>330</y>
<width>121</width> <width>121</width>
<height>16</height> <height>16</height>
</rect> </rect>
@@ -261,6 +261,108 @@
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
<widget class="SmartSlider" name="sliderFilterCutoff" native="true">
<property name="geometry">
<rect>
<x>770</x>
<y>30</y>
<width>65</width>
<height>280</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
<widget class="SmartSlider" name="sliderOsc1Volume" native="true">
<property name="geometry">
<rect>
<x>700</x>
<y>30</y>
<width>65</width>
<height>280</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
<widget class="SmartSlider" name="sliderFilterResonance" native="true">
<property name="geometry">
<rect>
<x>840</x>
<y>30</y>
<width>65</width>
<height>280</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>690</x>
<y>10</y>
<width>91</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Volume</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_7">
<property name="geometry">
<rect>
<x>760</x>
<y>10</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Cutoff</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_8">
<property name="geometry">
<rect>
<x>830</x>
<y>10</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Resonance</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</widget> </widget>
</widget> </widget>
<customwidgets> <customwidgets>
@@ -270,6 +372,12 @@
<header>EnvelopeGenerator/EnvelopeGenerator.h</header> <header>EnvelopeGenerator/EnvelopeGenerator.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>SmartSlider</class>
<extends>QWidget</extends>
<header>SmartSlider/SmartSlider.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>Scope</class> <class>Scope</class>
<extends>QWidget</extends> <extends>QWidget</extends>

View File

@@ -4,6 +4,8 @@
#include <iostream> #include <iostream>
// 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) { EnvelopeGenerator::EnvelopeGenerator(QWidget* parent) : QWidget(parent), ui_(new Ui::EnvelopeGenerator) {
ui_->setupUi(this); ui_->setupUi(this);

View File

@@ -46,7 +46,7 @@ void Scope::paintEvent(QPaintEvent*) {
// got caught playing around // got caught playing around
QPen pen; QPen pen;
pen.setWidthF(2.0f); pen.setWidthF(3.0f);
QColor green(50, 255, 70); QColor green(50, 255, 70);
pen.setColor(green); pen.setColor(green);
p.setPen(pen); p.setPen(pen);
@@ -54,9 +54,9 @@ void Scope::paintEvent(QPaintEvent*) {
for (int32_t i = 1; i < samples_.size(); i++) { for (int32_t i = 1; i < samples_.size(); i++) {
p.drawLine( p.drawLine(
(i) * width() / samples_.size(), (i) * width() / samples_.size(),
midY - samples_[i - 1] * scaleY, midY + samples_[i - 1] * scaleY,
i * width() / samples_.size(), i * width() / samples_.size(),
midY - samples_[i] * scaleY midY + samples_[i] * scaleY
); );
} }
} }