[feat]: Oscillator fine-tune (#22)

closes #19

Reviewed-on: #22
This commit is contained in:
2023-09-17 02:26:44 +03:00
parent bb3ccc296a
commit 2b4e3cb573
16 changed files with 209 additions and 132 deletions

View File

@@ -12,33 +12,33 @@ ADSR::ADSR(/* args */) {
ADSR::~ADSR() { delete m_ramp; }
bool ADSR::is_attack_elapsed() {
bool ADSR::IsAttackElapsed() {
return m_state == sAttack && m_ramp->IsCompleted();
}
bool ADSR::is_decay_elapsed() {
bool ADSR::IsDecayElapsed() {
return m_state == sDecay && m_ramp->IsCompleted();
}
bool ADSR::is_release_elapsed() {
bool ADSR::IsReleaseElapsed() {
return m_state == sRelease && m_ramp->IsCompleted();
}
void ADSR::recheck_state() {
void ADSR::RecheckState() {
switch (m_state) {
case sAttack:
if (is_attack_elapsed()) {
if (IsAttackElapsed()) {
m_state = sDecay;
m_ramp->RampTo(m_sustain_level, m_decay_time);
}
break;
case sDecay:
if (is_decay_elapsed()) {
if (IsDecayElapsed()) {
m_state = sSustain;
}
break;
case sRelease:
if (is_release_elapsed()) {
if (IsReleaseElapsed()) {
m_state = sOff;
}
break;
@@ -47,7 +47,7 @@ void ADSR::recheck_state() {
}
}
void ADSR::process_sample(float* sample) {
void ADSR::ProcessSample(float* sample) {
if (m_state == sOff) {
(*sample) = 0;
} else if (m_state == sAttack) {
@@ -80,8 +80,8 @@ void ADSR::Release() {
void ADSR::Process(std::vector<float>& samples) {
for (std::size_t i = 0; i < samples.size(); i++) {
recheck_state();
process_sample(&samples[i]);
RecheckState();
ProcessSample(&samples[i]);
}
}

View File

@@ -43,7 +43,7 @@ void Application::InitSynth() {
assert(osc);
OscillatorGuiState* ui =
new OscillatorGuiState{.freq = osc->GetFreq(),
new OscillatorGuiState{.fine = osc->GetFine(),
.waveshape = osc->GetType(),
.volume = osc->GetVolume()};
m_synth_gui_state.oscillators.push_back(ui);
@@ -94,7 +94,7 @@ void Application::UpdateOnNoteInput() {
if (!m_synth.GetIsNoteTriggered()) {
m_synth.Trigger((*m_current_note));
}
write_log("Note played: %s\n", m_current_note->name.c_str());
//write_log("Note played: %s\n", m_current_note->name.c_str());
} else if (is_note_up() && m_synth.GetIsNoteTriggered()) {
m_synth.Release();
}

View File

@@ -1,11 +1,15 @@
#include "Oscillator.h"
#include "Settings.h"
#include "KeyBoard.h"
#include "Logger.h"
#define TWO_PI 2 * SYNTH_PI
Oscillator::Oscillator(OscillatorType osc, float freq, float volume) {
Oscillator::Oscillator(OscillatorType osc, float fine, float volume) {
assert(fine >= -2.f && fine <= 2.f);
assert(volume >= 0.f && volume <= 1.f);
SetType(osc);
m_freq = freq;
m_fine = fine;
m_volume = volume;
}
@@ -21,26 +25,27 @@ void Oscillator::SetType(OscillatorType osc) {
m_osc = osc;
switch (m_osc) {
case Sine:
m_osc_function = &Oscillator::sineosc;
m_dt_function = &Oscillator::calc_sine_phase_delta;
m_osc_function = &Oscillator::SineOsc;
m_dt_function = &Oscillator::CalcSinePhaseDelta;
break;
case Triangle:
m_osc_function = &Oscillator::triangleosc;
m_dt_function = &Oscillator::calc_saw_phase_delta;
m_osc_function = &Oscillator::TriangleOsc;
m_dt_function = &Oscillator::CalcSawPhaseDelta;
break;
case Square:
m_osc_function = &Oscillator::squareosc;
m_dt_function = &Oscillator::calc_sine_phase_delta;
m_osc_function = &Oscillator::SquareOsc;
m_dt_function = &Oscillator::CalcSinePhaseDelta;
break;
case Saw:
m_osc_function = &Oscillator::sawosc;
m_dt_function = &Oscillator::calc_saw_phase_delta;
m_osc_function = &Oscillator::SawOsc;
m_dt_function = &Oscillator::CalcSawPhaseDelta;
break;
}
}
void Oscillator::SetFreq(float freq) {
m_freq = freq;
void Oscillator::SetKey(float key) {
m_key = key;
float freq = KeyBoard::GetHzBySemitone(m_key + m_fine);
m_phase = 0;
m_phase_dt = (this->*m_dt_function)(freq);
}
@@ -49,44 +54,44 @@ float Oscillator::Process() {
return (this->*m_osc_function)() * m_volume;
}
void Oscillator::sine_osc_phase_incr() {
void Oscillator::SineOscPhaseIncr() {
m_phase += m_phase_dt;
if (m_phase >= TWO_PI)
m_phase -= TWO_PI;
}
void Oscillator::saw_osc_phase_incr() {
void Oscillator::SawOscPhaseIncr() {
m_phase += m_phase_dt;
if (m_phase >= 1.0f)
m_phase -= 1.0f;
}
float Oscillator::calc_saw_phase_delta(float freq) {
float Oscillator::CalcSawPhaseDelta(float freq) {
return freq / SAMPLE_RATE;
}
float Oscillator::calc_sine_phase_delta(float freq) {
float Oscillator::CalcSinePhaseDelta(float freq) {
return (TWO_PI * freq) / SAMPLE_RATE;
}
float Oscillator::sineosc() {
float Oscillator::SineOsc() {
float result = sinf(m_phase);
sine_osc_phase_incr();
SineOscPhaseIncr();
return result;
}
float Oscillator::sign(float v) { return (v > 0.0) ? 1.f : -1.f; }
float Oscillator::Sign(float v) { return (v > 0.0) ? 1.f : -1.f; }
float Oscillator::squareosc() { return sign(sineosc()); }
float Oscillator::SquareOsc() { return Sign(SineOsc()); }
float Oscillator::triangleosc() {
float Oscillator::TriangleOsc() {
float result = 1.f - fabsf(m_phase - 0.5f) * 4.f;
saw_osc_phase_incr();
SawOscPhaseIncr();
return result;
}
float Oscillator::sawosc() {
float Oscillator::SawOsc() {
float result = m_phase * 2.f - 1.f;
saw_osc_phase_incr();
SawOscPhaseIncr();
return result;
}

View File

@@ -89,7 +89,7 @@ float Renderer::draw_oscillators_panels(
// Draw Oscillator Panel
const int osc_panel_width = panel_bounds.width - 20;
const int osc_panel_height = has_shape_param ? 130 : 100;
const int osc_panel_height = has_shape_param ? 150 : 120;
const int osc_panel_x = panel_bounds.x + 10;
const int osc_panel_y = panel_bounds.y + 50 + panel_y_offset;
panel_y_offset += osc_panel_height + 5;
@@ -112,13 +112,23 @@ float Renderer::draw_oscillators_panels(
decibels =
GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f);
ui_osc->volume = powf(10.f, decibels * (1.f / 20.f));
osc->SetVolume(ui_osc->volume);
el_rect.y += el_rect.height + el_spacing;
// Fine slider
float fine = osc->GetFine();
char fine_slider_label[10];
snprintf(fine_slider_label, 9, "%.3f u", fine);
fine = GuiSlider(el_rect, fine_slider_label, "", fine, -2.f, 2.f);
ui_osc->fine = fine;
el_rect.y += el_rect.height + el_spacing;
// Defer shape drop-down box.
ui_osc->shape_dropdown_rect = el_rect;
el_rect.y += el_rect.height + el_spacing;
// Apply values to real
osc->SetVolume(ui_osc->volume);
osc->SetFine(ui_osc->fine);
}
return panel_y_offset;
@@ -191,7 +201,7 @@ void Renderer::draw_second_panel(Rectangle& bounds) {
}
float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
const Rectangle& panel_bounds) {
const Rectangle& panel_bounds) {
#define FILTER_TYPE_OPTIONS "LP;BP;HP"
Filter* filter = synth.GetFilter();
float panel_y_offset = 0;
@@ -221,20 +231,20 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
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;
// 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);
GuiDropdownBox(el_rect, FILTER_TYPE_OPTIONS, &shape_index,
gui_filter.is_dropdown_open);
if (is_dropdown_click) {
write_log("Dropdown clicked!\n");
@@ -247,7 +257,8 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
// 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());
filter->SetParameters(gui_filter.freq, filter->GetRes(),
filter->GetPeakGain());
return panel_y_offset;
}

View File

@@ -4,18 +4,19 @@
#include "OscillatorType.h"
#include "Settings.h"
#include "FilterFactory.h"
#include "LFO.h"
Synth::Synth(/* args */) {
m_lfo = new Oscillator(OscillatorType::Sine, 5.f, VOLUME);
add_oscillator();
add_oscillator();
m_lfo = new LFO();
AddOscillator();
AddOscillator();
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);
}
zero_signal();
ZeroSignal();
}
Synth::~Synth() {
@@ -24,54 +25,53 @@ Synth::~Synth() {
m_out_signal.clear();
}
void Synth::zero_signal() {
void Synth::ZeroSignal() {
float sample = 0.0f;
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
m_out_signal[i] = sample;
}
}
void Synth::get_note() {
zero_signal();
void Synth::GetNote() {
ZeroSignal();
Adder::SumOscillators(m_oscillators, m_out_signal);
}
void Synth::apply_effects() {
for (Effect* effect : m_effects) {
void Synth::ApplyEffects() {
for (IEffect* effect : m_effects) {
effect->Process(m_out_signal);
}
}
void Synth::trigger_note_on_effects() {
for (Effect* effect : m_effects) {
void Synth::TriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
effect->Trigger();
}
}
void Synth::untrigger_note_on_effects() {
for (Effect* effect : m_effects) {
void Synth::UntriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
effect->Release();
}
}
void Synth::add_oscillator() {
void Synth::AddOscillator() {
m_oscillators.push_back(
new Oscillator(OscillatorType::Sine, 440.f, VOLUME));
new Oscillator(OscillatorType::Sine, 0.0f, VOLUME));
}
void Synth::Trigger(Note input) {
int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
float hz = KeyBoard::GetHzBySemitone(semitone_shift);
for (Oscillator* osc : m_oscillators) {
osc->SetFreq(hz);
osc->SetKey(semitone_shift);
}
is_note_triggered = true;
trigger_note_on_effects();
TriggerNoteOnEffects();
}
// todo: fix this
void Synth::apply_filter_lfo() {
void Synth::ApplyFilterLfo() {
float dt = m_lfo->Process();
Filter* filter = (Filter*)m_effects[1];
float freq = filter->GetFreq();
@@ -82,18 +82,18 @@ void Synth::apply_filter_lfo() {
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();
//ApplyFilterLfo();
GetNote();
ApplyEffects();
}
void Synth::Release() {
zero_signal();
ZeroSignal();
is_note_triggered = false;
untrigger_note_on_effects();
UntriggerNoteOnEffects();
}
void Synth::AddEffect(Effect* fx) { m_effects.push_back(fx); }
void Synth::AddEffect(IEffect* fx) { m_effects.push_back(fx); }
void Synth::SetFilter(FilterType type) {
Filter* old_filter = this->GetFilter();