add filter (needs fixes)
This commit is contained in:
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) {
|
||||
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()) {
|
||||
heldNotes_.push_back(event.note);
|
||||
gainEnvelope_.noteOn();
|
||||
cutoffEnvelope_.noteOn();
|
||||
resonanceEnvelope_.noteOn();
|
||||
// TODO: envelopes in an array so we can loop over them
|
||||
}
|
||||
} else {
|
||||
// remove note from activeNotes list
|
||||
@@ -52,6 +60,8 @@ void Synth::handleNoteEvent(const NoteEvent& event) {
|
||||
void Synth::updateCurrentNote() {
|
||||
if(heldNotes_.empty()) {
|
||||
gainEnvelope_.noteOff(); // TODO: move somewhere else when polyphony
|
||||
cutoffEnvelope_.noteOff();
|
||||
resonanceEnvelope_.noteOff();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,7 +85,10 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
// process all envelopes
|
||||
// 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));
|
||||
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());
|
||||
// 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
|
||||
|
||||
@@ -85,9 +98,13 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
out[2*i+1] = 0.0f;
|
||||
scope_->push(0.0f);
|
||||
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
|
||||
// TODO: wavetables
|
||||
@@ -114,6 +131,9 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
break;
|
||||
}
|
||||
|
||||
// filter sample
|
||||
sampleOut = filter_.biquadProcess(sampleOut);
|
||||
|
||||
// write to buffer
|
||||
out[2*i] = sampleOut; // left
|
||||
out[2*i+1] = sampleOut; // right
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../NoteQueue.h"
|
||||
#include "Envelope.h"
|
||||
#include "ScopeBuffer.h"
|
||||
#include "Filter.h"
|
||||
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
@@ -30,7 +31,7 @@ public:
|
||||
void handleNoteEvent(const NoteEvent& event);
|
||||
|
||||
// setters
|
||||
void setSampleRate(uint32_t sampleRate) { sampleRate_ = sampleRate; }
|
||||
void setSampleRate(uint32_t sampleRate);
|
||||
void setScopeBuffer(ScopeBuffer* scope) { scope_ = scope; }
|
||||
|
||||
private:
|
||||
@@ -51,20 +52,25 @@ private:
|
||||
// smoothed params creates a buffer in case the thread controlling paramStore gets blocked
|
||||
std::array<SmoothedParam, PARAM_COUNT> params_;
|
||||
uint32_t sampleRate_;
|
||||
|
||||
// for the scope
|
||||
ScopeBuffer* scope_ = nullptr;
|
||||
|
||||
// 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
|
||||
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;
|
||||
|
||||
// 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
|
||||
std::vector<uint8_t> heldNotes_;
|
||||
|
||||
// envelopes !!
|
||||
Envelope gainEnvelope_;
|
||||
Envelope cutoffEnvelope_;
|
||||
Envelope resonanceEnvelope_;
|
||||
|
||||
// for the scope
|
||||
ScopeBuffer* scope_ = nullptr;
|
||||
// filters, just one for now
|
||||
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
|
||||
Reference in New Issue
Block a user