add filter (needs fixes)
This commit is contained in:
@@ -59,6 +59,12 @@ qt_add_executable(metabolus
|
|||||||
src/synth/Synth.h
|
src/synth/Synth.h
|
||||||
src/synth/ScopeBuffer.cpp
|
src/synth/ScopeBuffer.cpp
|
||||||
src/synth/ScopeBuffer.h
|
src/synth/ScopeBuffer.h
|
||||||
|
src/synth/Filter.cpp
|
||||||
|
src/synth/Filter.h
|
||||||
|
src/synth/Oscillator.cpp
|
||||||
|
src/synth/Oscillator.h
|
||||||
|
src/synth/Voice.cpp
|
||||||
|
src/synth/Voice.h
|
||||||
resources/resources.qrc
|
resources/resources.qrc
|
||||||
src/ui/widgets/SmartSlider/SmartSlider.cpp
|
src/ui/widgets/SmartSlider/SmartSlider.cpp
|
||||||
src/ui/widgets/SmartSlider/SmartSlider.h
|
src/ui/widgets/SmartSlider/SmartSlider.h
|
||||||
|
|||||||
@@ -66,15 +66,15 @@ constexpr std::array<ParamDefault, static_cast<size_t>(ParamId::Count)> PARAM_DE
|
|||||||
{ 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
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvA
|
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvA
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvD
|
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvD
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvS
|
{ 1000.f, 0.0f, 40000.f}, // FilterCutoffEnvS
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvR
|
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvR
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvA
|
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvA
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvD
|
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvD
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvS
|
{ 0.707f, 0.0f, 2.0f}, // FilterResonanceEnvS
|
||||||
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvR
|
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvR
|
||||||
{ 1.0f, 0.0f, 0.0f}, // Osc1WaveSelector1
|
{ 0.0f, 0.0f, 0.0f}, // Osc1WaveSelector1
|
||||||
{ 1.0f, 0.0f, 0.0f}, // Osc1WaveSelector2
|
{ 1.0f, 0.0f, 0.0f}, // Osc1WaveSelector2
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
87
src/synth/Filter.cpp
Normal file
87
src/synth/Filter.cpp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
#include "Filter.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifndef M_PI // I hate my stupid chungus life
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Filter::setSampleRate(float sampleRate) {
|
||||||
|
sampleRate_ = sampleRate;
|
||||||
|
calculateCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::setParams(Type type, float frequency, float q) {
|
||||||
|
type_ = type;
|
||||||
|
frequency_ = frequency;
|
||||||
|
q_ = q;
|
||||||
|
calculateCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Filter::biquadProcess(float in) {
|
||||||
|
|
||||||
|
// calculate filtered sample
|
||||||
|
float out = a0_ * in + z1_;
|
||||||
|
|
||||||
|
// update states
|
||||||
|
z1_ = a1_ * in - b1_ * out + z2_;
|
||||||
|
z2_ = a2_ * in - b2_ * out;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::calculateCoefficients() {
|
||||||
|
|
||||||
|
if(q_ < 0.001f) q_ = 0.001f;
|
||||||
|
|
||||||
|
float omega = 2.0f * M_PI * frequency_ / sampleRate_;
|
||||||
|
float sinOmega = std::sin(omega);
|
||||||
|
float cosOmega = std::cos(omega);
|
||||||
|
float alpha = sinOmega / (2.0f * q_);
|
||||||
|
|
||||||
|
float b0, b1, b2, a0, a1, a2;
|
||||||
|
|
||||||
|
switch (type_) {
|
||||||
|
case Type::BiquadLowpass:
|
||||||
|
b0 = (1.0f - cosOmega) * 0.5f;
|
||||||
|
b1 = 1.0f - cosOmega;
|
||||||
|
b2 = (1.0f - cosOmega) * 0.5f;
|
||||||
|
a0 = 1.0f + alpha;
|
||||||
|
a1 = -2.0f * cosOmega;
|
||||||
|
a2 = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::BiquadHighpass:
|
||||||
|
b0 = (1.0f + cosOmega) * 0.5f;
|
||||||
|
b1 = -(1.0f + cosOmega);
|
||||||
|
b2 = (1.0f + cosOmega) * 0.5f;
|
||||||
|
a0 = 1.0f + alpha;
|
||||||
|
a1 = -2.0f * cosOmega;
|
||||||
|
a2 = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::BiquadBandpass:
|
||||||
|
b0 = sinOmega * 0.5f;
|
||||||
|
b1 = 0.0f;
|
||||||
|
b2 = -sinOmega * 0.5f;
|
||||||
|
a0 = 1.0f + alpha;
|
||||||
|
a1 = -2.0f * cosOmega;
|
||||||
|
a2 = 1.0f - alpha;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
a0_ = b0 / a0;
|
||||||
|
a1_ = b1 / a0;
|
||||||
|
a2_ = b2 / a0;
|
||||||
|
b1_ = a1 / a0;
|
||||||
|
b2_ = a2 / a0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::resetSate() {
|
||||||
|
z1_ = 0.0f;
|
||||||
|
z2_ = 0.0f;
|
||||||
|
}
|
||||||
46
src/synth/Filter.h
Normal file
46
src/synth/Filter.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class Filter {
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum class Type : uint16_t {
|
||||||
|
BiquadLowpass,
|
||||||
|
BiquadNotch,
|
||||||
|
BiquadBandpass,
|
||||||
|
BiquadHighpass
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter() = default;
|
||||||
|
~Filter() = default;
|
||||||
|
|
||||||
|
void setSampleRate(float sampleRate);
|
||||||
|
void setParams(Type type, float frequency, float q);
|
||||||
|
float biquadProcess(float in);
|
||||||
|
void resetSate();
|
||||||
|
|
||||||
|
// TODO: add more filter types here
|
||||||
|
// high pass
|
||||||
|
// band pass
|
||||||
|
// notch
|
||||||
|
// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
|
||||||
|
// instead of making different calculate functions, have an enum which specifies the filter type
|
||||||
|
// one public calculate which the enum is passed through and a private calculate for the specific types, switch statement to choose
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void calculateCoefficients();
|
||||||
|
|
||||||
|
Type type_ = Type::BiquadLowpass;
|
||||||
|
float sampleRate_ = 44100.0f;
|
||||||
|
|
||||||
|
float frequency_ = 6000.0f;
|
||||||
|
float q_ = 0.707f;
|
||||||
|
|
||||||
|
// biquad filter structure
|
||||||
|
float a0_, a1_, a2_, b1_, b2_;
|
||||||
|
float z1_, z2_;
|
||||||
|
|
||||||
|
};
|
||||||
4
src/synth/Oscillator.cpp
Normal file
4
src/synth/Oscillator.cpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
#include "Oscillator.h"
|
||||||
|
|
||||||
|
// placeholder
|
||||||
4
src/synth/Oscillator.h
Normal file
4
src/synth/Oscillator.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
// placeholder
|
||||||
@@ -23,6 +23,11 @@ void Synth::updateParams() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Synth::setSampleRate(uint32_t sampleRate) {
|
||||||
|
sampleRate_ = sampleRate;
|
||||||
|
filter_.setSampleRate(static_cast<float>(sampleRate));
|
||||||
|
}
|
||||||
|
|
||||||
inline float Synth::getParam(ParamId id) {
|
inline float Synth::getParam(ParamId id) {
|
||||||
return params_[static_cast<size_t>(id)].current;
|
return params_[static_cast<size_t>(id)].current;
|
||||||
}
|
}
|
||||||
@@ -37,6 +42,9 @@ void Synth::handleNoteEvent(const NoteEvent& event) {
|
|||||||
if (std::find(heldNotes_.begin(), heldNotes_.end(), event.note) == heldNotes_.end()) {
|
if (std::find(heldNotes_.begin(), heldNotes_.end(), event.note) == heldNotes_.end()) {
|
||||||
heldNotes_.push_back(event.note);
|
heldNotes_.push_back(event.note);
|
||||||
gainEnvelope_.noteOn();
|
gainEnvelope_.noteOn();
|
||||||
|
cutoffEnvelope_.noteOn();
|
||||||
|
resonanceEnvelope_.noteOn();
|
||||||
|
// TODO: envelopes in an array so we can loop over them
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// remove note from activeNotes list
|
// remove note from activeNotes list
|
||||||
@@ -52,6 +60,8 @@ void Synth::handleNoteEvent(const NoteEvent& event) {
|
|||||||
void Synth::updateCurrentNote() {
|
void Synth::updateCurrentNote() {
|
||||||
if(heldNotes_.empty()) {
|
if(heldNotes_.empty()) {
|
||||||
gainEnvelope_.noteOff(); // TODO: move somewhere else when polyphony
|
gainEnvelope_.noteOff(); // TODO: move somewhere else when polyphony
|
||||||
|
cutoffEnvelope_.noteOff();
|
||||||
|
resonanceEnvelope_.noteOff();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +85,10 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
|||||||
// process all envelopes
|
// process all envelopes
|
||||||
// should be easy enough if all the envelopes are in an array to loop over them
|
// should be easy enough if all the envelopes are in an array to loop over them
|
||||||
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));
|
||||||
|
resonanceEnvelope_.set(getParam(ParamId::FilterResonanceEnvA), getParam(ParamId::FilterResonanceEnvD), getParam(ParamId::FilterResonanceEnvS), getParam(ParamId::FilterResonanceEnvR));
|
||||||
float gain = gainEnvelope_.process();
|
float gain = gainEnvelope_.process();
|
||||||
|
filter_.setParams(Filter::Type::BiquadLowpass, cutoffEnvelope_.process(), 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
|
||||||
|
|
||||||
@@ -85,9 +98,13 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
|||||||
out[2*i+1] = 0.0f;
|
out[2*i+1] = 0.0f;
|
||||||
scope_->push(0.0f);
|
scope_->push(0.0f);
|
||||||
continue;
|
continue;
|
||||||
|
// TODO: should I have a write() function ?
|
||||||
|
// maybe we change the synth.process into just returning a single float and the write can be in audioEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
float phaseInc = 2.0f * M_PI * frequency_ / static_cast<float>(sampleRate);
|
// TODO: make pitchOffset variable for each oscillator (maybe three values like octave, semitone offset, and pitch offset in cents)
|
||||||
|
float pitchOffset = 0.5f;
|
||||||
|
float phaseInc = pitchOffset * 2.0f * M_PI * frequency_ / static_cast<float>(sampleRate);
|
||||||
|
|
||||||
// sample generation
|
// sample generation
|
||||||
// TODO: wavetables
|
// TODO: wavetables
|
||||||
@@ -114,6 +131,9 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter sample
|
||||||
|
sampleOut = filter_.biquadProcess(sampleOut);
|
||||||
|
|
||||||
// write to buffer
|
// write to buffer
|
||||||
out[2*i] = sampleOut; // left
|
out[2*i] = sampleOut; // left
|
||||||
out[2*i+1] = sampleOut; // right
|
out[2*i+1] = sampleOut; // right
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "../NoteQueue.h"
|
#include "../NoteQueue.h"
|
||||||
#include "Envelope.h"
|
#include "Envelope.h"
|
||||||
#include "ScopeBuffer.h"
|
#include "ScopeBuffer.h"
|
||||||
|
#include "Filter.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -30,7 +31,7 @@ public:
|
|||||||
void handleNoteEvent(const NoteEvent& event);
|
void handleNoteEvent(const NoteEvent& event);
|
||||||
|
|
||||||
// setters
|
// setters
|
||||||
void setSampleRate(uint32_t sampleRate) { sampleRate_ = sampleRate; }
|
void setSampleRate(uint32_t sampleRate);
|
||||||
void setScopeBuffer(ScopeBuffer* scope) { scope_ = scope; }
|
void setScopeBuffer(ScopeBuffer* scope) { scope_ = scope; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -52,19 +53,24 @@ private:
|
|||||||
std::array<SmoothedParam, PARAM_COUNT> params_;
|
std::array<SmoothedParam, PARAM_COUNT> params_;
|
||||||
uint32_t sampleRate_;
|
uint32_t sampleRate_;
|
||||||
|
|
||||||
// here's where the actual sound generation happens
|
// for the scope
|
||||||
// TODO: put this in an oscillator class
|
ScopeBuffer* scope_ = nullptr;
|
||||||
float frequency_ = 220.0f;
|
|
||||||
float phase_ = 0.0f;
|
|
||||||
|
|
||||||
// TODO: might make this a fixed array where index=midi-note and the value=velocity
|
// TODO: might make this a fixed array where index=midi-note and the value=velocity
|
||||||
// so non-zero elements are the ones currently being played
|
// so non-zero elements are the ones currently being played
|
||||||
std::vector<uint8_t> heldNotes_;
|
std::vector<uint8_t> heldNotes_;
|
||||||
|
|
||||||
|
// here's where the actual sound generation happens
|
||||||
|
// TODO: put this in an oscillator class
|
||||||
|
float frequency_ = 220.0f;
|
||||||
|
float phase_ = 0.0f;
|
||||||
|
|
||||||
// envelopes !!
|
// envelopes !!
|
||||||
Envelope gainEnvelope_;
|
Envelope gainEnvelope_;
|
||||||
|
Envelope cutoffEnvelope_;
|
||||||
|
Envelope resonanceEnvelope_;
|
||||||
|
|
||||||
// for the scope
|
// filters, just one for now
|
||||||
ScopeBuffer* scope_ = nullptr;
|
Filter filter_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
4
src/synth/Voice.cpp
Normal file
4
src/synth/Voice.cpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
#include "Voice.h"
|
||||||
|
|
||||||
|
// placeholder
|
||||||
4
src/synth/Voice.h
Normal file
4
src/synth/Voice.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
// placeholder
|
||||||
@@ -117,6 +117,9 @@
|
|||||||
<property name="buttonSymbols">
|
<property name="buttonSymbols">
|
||||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>80000.000000000000000</double>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="Line" name="line">
|
<widget class="Line" name="line">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
|
|||||||
Reference in New Issue
Block a user