[feat]: Filter (#18)

closes #16

Reviewed-on: #18
This commit is contained in:
2023-09-10 00:56:47 +03:00
parent 868a59da0e
commit bb3ccc296a
22 changed files with 372 additions and 48 deletions

View File

@@ -3,15 +3,13 @@
#include "Settings.h"
ADSR::ADSR(/* args */) {
m_parameters.attack_time = 1.f;
m_parameters.decay_time = 0.4f;
m_parameters.sustain_level = 0.6f;
m_parameters.release_time = 0.8f;
m_attack_time = 1.f;
m_decay_time = 0.4f;
m_sustain_level = 0.6f;
m_release_time = 0.8f;
m_ramp = new Ramp(0, SAMPLE_RATE);
}
ADSR::ADSR(ADSRParameters param) { m_parameters = param; }
ADSR::~ADSR() { delete m_ramp; }
bool ADSR::is_attack_elapsed() {
@@ -31,8 +29,7 @@ void ADSR::recheck_state() {
case sAttack:
if (is_attack_elapsed()) {
m_state = sDecay;
m_ramp->RampTo(m_parameters.sustain_level,
m_parameters.decay_time);
m_ramp->RampTo(m_sustain_level, m_decay_time);
}
break;
case sDecay:
@@ -58,7 +55,7 @@ void ADSR::process_sample(float* sample) {
} else if (m_state == sDecay) {
(*sample) = (*sample) * m_ramp->Process();
} else if (m_state == sSustain) {
(*sample) = (*sample) * m_parameters.sustain_level;
(*sample) = (*sample) * m_sustain_level;
} else if (m_state == sRelease) {
(*sample) = (*sample) * m_ramp->Process();
}
@@ -72,13 +69,13 @@ void ADSR::Trigger() {
m_state = sAttack;
};
m_ramp->RampTo(1, m_parameters.attack_time);
m_ramp->RampTo(1, m_attack_time);
}
void ADSR::Release() {
write_log("Unset ADSR\n");
m_state = sRelease;
m_ramp->RampTo(0, m_parameters.release_time);
m_ramp->RampTo(0, m_release_time);
}
void ADSR::Process(std::vector<float>& samples) {
@@ -90,8 +87,8 @@ void ADSR::Process(std::vector<float>& samples) {
void ADSR::SetParameters(float attack, float decay, float sustain,
float release) {
m_parameters.attack_time = attack;
m_parameters.decay_time = decay;
m_parameters.sustain_level = sustain;
m_parameters.release_time = release;
m_attack_time = attack;
m_decay_time = decay;
m_sustain_level = sustain;
m_release_time = release;
}

View File

@@ -4,8 +4,8 @@
#include <string>
Application::Application(/* args */) {
init_synth();
init_audio();
InitSynth();
InitAudio();
}
Application::~Application() {
@@ -19,7 +19,7 @@ Application::~Application() {
}
}
void Application::init_audio() {
void Application::InitAudio() {
m_sound_played_count = 0;
InitAudioDevice();
@@ -31,7 +31,7 @@ void Application::init_audio() {
PlayAudioStream(m_synth_stream);
}
void Application::init_synth() {
void Application::InitSynth() {
std::string* nameString = new std::string(std::string(new char[3]));
m_current_note = new Note{.length = 1, .name = (*nameString)};
m_current_note->name.assign("G4");
@@ -50,7 +50,7 @@ void Application::init_synth() {
}
}
bool Application::detect_note_pressed(Note* note) {
bool Application::DetectNotePressed(Note* note) {
std::size_t is_pressed = 0;
note->length = 8;
if (IsKeyDown(KEY_A)) {
@@ -89,8 +89,8 @@ bool is_note_up() {
IsKeyUp(KEY_D) || IsKeyUp(KEY_E) || IsKeyUp(KEY_F) || IsKeyUp(KEY_G);
}
void Application::update_on_note_input() {
if (detect_note_pressed(m_current_note)) {
void Application::UpdateOnNoteInput() {
if (DetectNotePressed(m_current_note)) {
if (!m_synth.GetIsNoteTriggered()) {
m_synth.Trigger((*m_current_note));
}
@@ -103,9 +103,9 @@ void Application::update_on_note_input() {
}
// Play ring-buffered audio
void Application::play_buffered_audio() {
void Application::PlayBufferedAudio() {
if (IsAudioStreamProcessed(m_synth_stream)) {
update_on_note_input();
UpdateOnNoteInput();
UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(),
STREAM_BUFFER_SIZE);
}
@@ -114,7 +114,7 @@ void Application::play_buffered_audio() {
void Application::Run() {
// Main game loop
while (!WindowShouldClose()) {
play_buffered_audio();
PlayBufferedAudio();
m_renderer.Draw(m_synth, m_synth_gui_state);
}
}

23
src/BandPassFilter.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "BandPassFilter.h"
BandPassFilter::BandPassFilter(/* args */) {}
BandPassFilter::BandPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
}
BandPassFilter::BandPassFilter(float freq, float res, float q) {}
BandPassFilter::~BandPassFilter() {}
void BandPassFilter::CalculateCoefficients() {
CalculateNormals();
m_norm = 1 / (1 + m_k / m_q + m_k * m_k);
m_a0 = m_k / m_q * m_norm;
m_a1 = 0;
m_a2 = -m_a0;
m_b1 = 2 * (m_k * m_k - 1) * m_norm;
m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm;
}

36
src/Filter.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "Filter.h"
#include "Settings.h"
Filter::Filter(/* args */) {}
Filter::~Filter() {}
void Filter::CalculateNormals() {
m_v = powf(10, fabs(m_order) / 20.0);
m_k = tanf(M_PI * m_freq);
}
void Filter::Trigger() {}
void Filter::Release() {}
float Filter::Process(float in) {
// may move to a compile-time dictionary calculation, if needed
CalculateCoefficients();
float out = in * m_a0 + m_z1;
m_z1 = in * m_a1 + m_z2 - m_b1 * out;
m_z2 = in * m_a2 - m_b2 * out;
return out;
}
void Filter::Process(std::vector<float>& samples) {
for (std::size_t i = 0; i < samples.size(); i++) {
samples[i] = Process(samples[i]);
}
}
void Filter::SetParameters(float freq, float res, float q) {
m_freq = freq / SAMPLE_RATE;
m_q = res;
m_order = q;
}

23
src/HighPassFilter.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "HighPassFilter.h"
HighPassFilter::HighPassFilter(/* args */) {}
HighPassFilter::HighPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
}
HighPassFilter::HighPassFilter(float freq, float res, float q) {}
HighPassFilter::~HighPassFilter() {}
void HighPassFilter::CalculateCoefficients() {
CalculateNormals();
m_norm = 1 / (1 + m_k / m_q + m_k * m_k);
m_a0 = 1 * m_norm;
m_a1 = -2 * m_a0;
m_a2 = m_a0;
m_b1 = 2 * (m_k * m_k - 1) * m_norm;
m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm;
}

33
src/LowPassFilter.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "LowPassFilter.h"
#include "Settings.h"
LowPassFilter::LowPassFilter() {
// todo: defaults
m_freq = 200.f / SAMPLE_RATE;
m_q = 1.0f;//0.707f;
m_order = 0;
}
LowPassFilter::LowPassFilter(float freq, float res, float q) {
m_freq = freq / SAMPLE_RATE;
m_q = res;
m_order = q;
}
LowPassFilter::LowPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
}
LowPassFilter::~LowPassFilter() {}
void LowPassFilter::CalculateCoefficients() {
CalculateNormals();
m_norm = 1 / (1 + m_k / m_q + m_k * m_k);
m_a0 = m_k * m_k * m_norm;
m_a1 = 2 * m_a0;
m_a2 = m_a0;
m_b1 = 2 * (m_k * m_k - 1) * m_norm;
m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm;
}

View File

@@ -45,7 +45,7 @@ void Oscillator::SetFreq(float freq) {
m_phase_dt = (this->*m_dt_function)(freq);
}
float Oscillator::GenerateSample(float duration) {
float Oscillator::Process() {
return (this->*m_osc_function)() * m_volume;
}

View File

@@ -185,6 +185,73 @@ void Renderer::draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr,
gui_adsr.release);
}
void Renderer::draw_second_panel(Rectangle& bounds) {
bounds.x += bounds.width;
GuiPanel(bounds, "");
}
float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
const Rectangle& panel_bounds) {
#define FILTER_TYPE_OPTIONS "LP;BP;HP"
Filter* filter = synth.GetFilter();
float panel_y_offset = 0;
// Draw Filter Panel
const int osc_panel_width = panel_bounds.width - 20;
const int osc_panel_height = 100;
const int osc_panel_x = panel_bounds.x + 10;
const int osc_panel_y = panel_bounds.y + 50;
panel_y_offset += osc_panel_height + 5;
GuiPanel((Rectangle){(float)osc_panel_x, (float)osc_panel_y,
(float)osc_panel_width, (float)osc_panel_height},
"Filter");
const float slider_padding = 50.f;
const float el_spacing = 5.f;
Rectangle el_rect = {.x = (float)osc_panel_x + slider_padding + 30,
.y = (float)osc_panel_y + 10,
.width = (float)osc_panel_width - (slider_padding * 2),
.height = 25.f};
// Frequency slider
float freq = log10f(gui_filter.freq);
char freq_slider_label[32];
snprintf(freq_slider_label, 10, "%.1f hz", powf(10.f, freq));
freq = GuiSlider(el_rect, freq_slider_label, "", freq, 0.f, 4.f);
gui_filter.freq = powf(10.f, freq);
el_rect.y += el_rect.height + el_spacing;
//todo: implement that when Res will be fixed
// Resonance slider
// float res = gui_filter.res;
// char res_slider_label[32];
// snprintf(res_slider_label, 7, "%.1f u", res);
// res = GuiSlider(el_rect, res_slider_label, "", res, 0.0f, 1.0f);
// gui_filter.res = res;
// el_rect.y += el_rect.height + el_spacing;
// Shape select
int shape_index = (int)(gui_filter.type);
bool is_dropdown_click =
GuiDropdownBox(el_rect, FILTER_TYPE_OPTIONS,
&shape_index, gui_filter.is_dropdown_open);
if (is_dropdown_click) {
write_log("Dropdown clicked!\n");
gui_filter.is_dropdown_open = !gui_filter.is_dropdown_open;
gui_filter.type = (FilterType)(shape_index);
// APPLY STATE TO REAL SYNTH
synth.SetFilter(gui_filter.type);
}
// apply values to real one
// todo: thrid (order) parameter
// todo: why resonance changing does not work?
filter->SetParameters(gui_filter.freq, filter->GetRes(), filter->GetPeakGain());
return panel_y_offset;
}
void Renderer::draw_ui(Synth& synth, SynthGuiState& synth_gui) {
Rectangle panel_bounds = {.x = 0,
.y = 0,
@@ -200,4 +267,7 @@ void Renderer::draw_ui(Synth& synth, SynthGuiState& synth_gui) {
draw_adsr_panel(synth.GetADSR(), synth_gui.adsr, panel_bounds,
panel_y_offset);
draw_oscillators_shape_inputs(oscillators, gui_oscillators);
draw_second_panel(panel_bounds);
DrawFilterPanel(synth, synth_gui.filter, panel_bounds);
}

View File

@@ -1,14 +1,16 @@
#include "Synth.h"
#include "ADSR.h"
#include "KeyBoard.h"
#include "Logger.h"
#include "OscillatorType.h"
#include "Settings.h"
#include "FilterFactory.h"
Synth::Synth(/* args */) {
m_lfo = new Oscillator(OscillatorType::Sine, 5.f, VOLUME);
add_oscillator();
add_oscillator();
AddEffect(new ADSR());
AddEffect(FilterFactory::GetDefaultFilter());
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
float sample = 0.0f;
m_out_signal.push_back(sample);
@@ -68,7 +70,19 @@ void Synth::Trigger(Note input) {
trigger_note_on_effects();
}
// todo: fix this
void Synth::apply_filter_lfo() {
float dt = m_lfo->Process();
Filter* filter = (Filter*)m_effects[1];
float freq = filter->GetFreq();
//todo: check formula
//filter->SetParameters(freq + dt * 0.2f, filter->GetRes(), filter->GetPeakGain());
}
void Synth::Process() {
//todo: on each sample.
//in order to do that, we need to move to per-sample processing
apply_filter_lfo();
get_note();
apply_effects();
}
@@ -80,3 +94,12 @@ void Synth::Release() {
}
void Synth::AddEffect(Effect* fx) { m_effects.push_back(fx); }
void Synth::SetFilter(FilterType type) {
Filter* old_filter = this->GetFilter();
if (!old_filter->IsSameFilterType(type)) {
Filter* new_filter = FilterFactory::CreateFilter(old_filter, type);
delete old_filter;
m_effects[1] = new_filter;
}
}