diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ba7134e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/bin/main", + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f40e7f1..950d9e3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,11 +9,7 @@ "-fansi-escape-codes", "-g", "${file}", - "${fileDirname}/utils.c", - "${fileDirname}/ring_buffer.c", - "${fileDirname}/oscillator.c", - "${fileDirname}/parser.c", - "${fileDirname}/export.c", + "$(find ${fileDirname}/src -type f -iregex '.*\\.cpp')", "-lm", "-lraylib", "-o", diff --git a/docs/ADSR States.png b/docs/ADSR States.png new file mode 100644 index 0000000..fbf4cea Binary files /dev/null and b/docs/ADSR States.png differ diff --git a/docs/Attack Formula.png b/docs/Attack Formula.png new file mode 100644 index 0000000..805f15d Binary files /dev/null and b/docs/Attack Formula.png differ diff --git a/inc/ADSR.h b/inc/ADSR.h index 3af24d7..77a7588 100644 --- a/inc/ADSR.h +++ b/inc/ADSR.h @@ -1,6 +1,7 @@ #pragma once #include "Effect.h" #include +#include "Ramp.h" struct ADSRParameters { float attack_time; // Attack time in seconds @@ -9,23 +10,26 @@ struct ADSRParameters { float release_time; }; -enum ADSRState { Attack, Decay, Sustain, Release }; +enum ADSRState { Off, Attack, Decay, Sustain, Release }; class ADSR : public Effect { private: ADSRParameters m_parameters; ADSRState m_state; - std::size_t m_counter; - void set_state(std::size_t attack_samples, std::size_t decay_samples, - std::size_t release_samples); - void process_sample(float* sample, std::size_t attack_samples, - std::size_t decay_samples, std::size_t release_samples); + Ramp *m_ramp; + void process_sample(float* sample); + bool is_attack_elapsed(); + bool is_decay_elapsed(); + bool is_release_elapsed(); + void recheck_state(); public: ADSR(/* args */); ADSR(ADSRParameters param); ~ADSR(); - void RetriggerState() override; + void OnSetNote() override; + void OnUnsetNote() override; + //void RetriggerState() override; void Process(std::vector& samples) override; void Reset(); }; diff --git a/inc/Effect.h b/inc/Effect.h index 5065b60..726f033 100644 --- a/inc/Effect.h +++ b/inc/Effect.h @@ -6,6 +6,8 @@ class Effect { public: Effect(/* args */){}; ~Effect(){}; - virtual void RetriggerState(){}; + virtual void OnSetNote(){}; + virtual void OnUnsetNote(){}; + //virtual void RetriggerState(){}; virtual void Process(std::vector& samples){}; }; diff --git a/inc/Ramp.h b/inc/Ramp.h new file mode 100644 index 0000000..f9ace72 --- /dev/null +++ b/inc/Ramp.h @@ -0,0 +1,17 @@ +#pragma once + +class Ramp +{ +private: + float m_level; + float m_sample_rate; + float m_increment; + int m_counter; +public: + Ramp(float starting_level, float sample_rate); + ~Ramp(); + void RampTo(float value, float time); + float Process(); + bool IsCompleted(); +}; + diff --git a/inc/Synth.h b/inc/Synth.h index 679817d..e897460 100644 --- a/inc/Synth.h +++ b/inc/Synth.h @@ -11,12 +11,14 @@ class Synth { private: bool is_note_triggered; std::vector m_oscillators; - Adder m_adder; std::vector m_effects; // OscillatorUI* ui_oscillators; // Note m_current_note; std::vector m_out_signal; + void zero_signal(); void get_note(); + void trigger_note_on_effects(); + void untrigger_note_on_effects(); void apply_effects(); public: diff --git a/src/ADSR.cpp b/src/ADSR.cpp index f670c13..13119dc 100644 --- a/src/ADSR.cpp +++ b/src/ADSR.cpp @@ -6,58 +6,96 @@ ADSR::ADSR(/* args */) { m_parameters.attack_time = 1.f; m_parameters.decay_time = 0.3f; m_parameters.sustain_level = 0.6f; - m_parameters.release_time = 1.0f; - m_counter = 0; + m_parameters.release_time = 0.8f; + m_ramp = new Ramp(0, SAMPLE_RATE); } ADSR::ADSR(ADSRParameters param) { m_parameters = param; - m_counter = 0; } -ADSR::~ADSR() {} +ADSR::~ADSR() { + delete m_ramp; +} -void ADSR::set_state(std::size_t attack_samples, std::size_t decay_samples, - std::size_t release_samples) { - if (m_counter < attack_samples) { +bool ADSR::is_attack_elapsed() { + return m_state == Attack && m_ramp->IsCompleted(); +} + +bool ADSR::is_decay_elapsed() { + return m_state == Decay && m_ramp->IsCompleted(); +} + +bool ADSR::is_release_elapsed() { + return m_state == Release && m_ramp->IsCompleted(); +} + +void ADSR::recheck_state() { + switch (m_state) + { + case Off: m_state = Attack; - } else if (m_counter >= attack_samples && - m_counter < attack_samples + decay_samples) { - m_state = Decay; - } else if (m_counter >= attack_samples + decay_samples) { - m_state = Sustain; + break; + case Attack: + if (is_attack_elapsed()) { + m_state = Decay; + m_ramp->RampTo(m_parameters.sustain_level, m_parameters.decay_time); + } + break; + case Decay: + if (is_decay_elapsed()) { + m_state = Sustain; + } + break; + case Release: + if (is_release_elapsed()) { + m_state = Off; + } + break; + default: + break; } } -void ADSR::process_sample(float* sample, std::size_t attack_samples, - std::size_t decay_samples, - std::size_t release_samples) { - - set_state(attack_samples, decay_samples, release_samples); - if (m_state == Attack) { - (*sample) = (*sample) * ((float)(1.f / attack_samples) * m_counter); - } else if (m_state == Decay) { +void ADSR::process_sample(float* sample) { + if (m_state == Off) { + (*sample) = 0; + } + else if (m_state == Attack) { + (*sample) = (*sample) * m_ramp->Process(); + } + else if (m_state == Decay) { + (*sample) = (*sample) * m_ramp->Process(); + } + else if (m_state == Sustain) { + (*sample) = (*sample) * m_parameters.sustain_level; + } + else if (m_state == Release) { + (*sample) = (*sample) * m_ramp->Process(); } - m_counter++; - // todo: release state on note off (in reset function?) } -void ADSR::RetriggerState() { - m_counter = 0; - m_state = Attack; +void ADSR::OnSetNote() { + if (m_state == Off) { + m_state = Attack; + } + else if (m_state == Release) { + m_state = Attack; + }; + + m_ramp->RampTo(1, m_parameters.attack_time); +} + +void ADSR::OnUnsetNote() { + write_log("Unset ADSR\n"); + m_state = Release; + m_ramp->RampTo(0, m_parameters.release_time); } void ADSR::Process(std::vector& samples) { - const std::size_t attack_samples = - (std::size_t)(m_parameters.attack_time * SAMPLE_RATE); - const std::size_t decay_samples = - (std::size_t)(m_parameters.decay_time * SAMPLE_RATE); - const std::size_t release_samples = - (std::size_t)(m_parameters.release_time * SAMPLE_RATE); - write_log("Attack samples: %zu \n", attack_samples); + write_log("ADSR State: %d\n", m_state); for (std::size_t i = 0; i < samples.size(); i++) { - process_sample(&samples[i], attack_samples, decay_samples, - release_samples); + recheck_state(); + process_sample(&samples[i]); } - write_log("Processed samples: %zu \n", m_counter); } \ No newline at end of file diff --git a/src/Application.cpp b/src/Application.cpp index 03b6489..6647de4 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -85,6 +85,12 @@ bool Application::detect_note_pressed(Note* note) { return is_pressed == 1; } +bool is_note_up() { + return IsKeyReleased(KEY_A) || IsKeyReleased(KEY_B) || IsKeyReleased(KEY_C) + || IsKeyReleased(KEY_D) || IsKeyReleased(KEY_E) || IsKeyReleased(KEY_F) + || IsKeyReleased(KEY_G); +} + // Update On Input void Application::update_on_note_input() { if (detect_note_pressed(m_current_note)) { @@ -92,14 +98,15 @@ void Application::update_on_note_input() { if (!m_synth.GetIsNoteTriggered()){ m_synth.TriggerNote((*m_current_note)); } - m_synth.ProduceSound(); + //m_sound_played_count = 0; write_log("Note played: %s\n", m_current_note->name.c_str()); } - else { + else if (is_note_up()) { m_synth.StopSound(); } - + // will produce 0 signal if ADSR is in off state + m_synth.ProduceSound(); } // Play ring-buffered audio @@ -109,7 +116,7 @@ void Application::play_buffered_audio() { update_on_note_input(); UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(), STREAM_BUFFER_SIZE); const float audio_freme_duration = GetTime() - audio_frame_start_time; - write_log("Frame time: %.3f%% \n", 100.0f / ((1.0f / audio_freme_duration) / ((float)SAMPLE_RATE/STREAM_BUFFER_SIZE))); + //write_log("Frame time: %.3f%% \n", 100.0f / ((1.0f / audio_freme_duration) / ((float)SAMPLE_RATE/STREAM_BUFFER_SIZE))); } } diff --git a/src/Ramp.cpp b/src/Ramp.cpp new file mode 100644 index 0000000..1269742 --- /dev/null +++ b/src/Ramp.cpp @@ -0,0 +1,29 @@ +#include "Ramp.h" +#include "Logger.h" + +Ramp::Ramp(float starting_level, float sample_rate) { + m_level = starting_level; + m_sample_rate = sample_rate; +} + +Ramp::~Ramp() { +} + +void Ramp::RampTo(float value, float time) { + m_increment = (value - m_level) / (m_sample_rate * time); + m_counter = (int)(m_sample_rate * time); + write_log("Ramping from: %.1f to: %.1f by: %.1f for: %d\n", m_level, value, m_increment, m_counter); +} + +float Ramp::Process() { + if (m_counter > 0) { + m_counter--; + m_level += m_increment; + } + + return m_level; +} + +bool Ramp::IsCompleted() { + return m_counter == 0; +} \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index a73e016..341221b 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -2,7 +2,14 @@ #define RAYGUI_IMPLEMENTATION #include "Logger.h" #include "Settings.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-but-set-variable" +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" #include "raygui.h" +#pragma clang diagnostic pop + Renderer::Renderer(/* args */) { InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2"); @@ -105,7 +112,7 @@ void Renderer::draw_oscillators_panels( // Volume slider float decibels = (20.f * log10f(osc->GetVolume())); char amp_slider_label[32]; - sprintf(amp_slider_label, "%.1f dB", decibels); + snprintf(amp_slider_label, 7, "%.1f dB", decibels); decibels = GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f); ui_osc->volume = powf(10.f, decibels * (1.f / 20.f)); @@ -116,7 +123,7 @@ void Renderer::draw_oscillators_panels( // Defer shape drop-down box. ui_osc->shape_dropdown_rect = el_rect; el_rect.y += el_rect.height + el_spacing; - /* + Rectangle delete_button_rect = el_rect; delete_button_rect.x = osc_panel_x + 5; delete_button_rect.y -= el_rect.height + el_spacing; @@ -124,15 +131,15 @@ void Renderer::draw_oscillators_panels( bool is_delete_button_pressed = GuiButton(delete_button_rect, "X"); if (is_delete_button_pressed) { - memmove( - synth->ui_oscillator + ui_osc_i, - synth->ui_oscillator + ui_osc_i + 1, - (synth->ui_oscillator_count - ui_osc_i) * - sizeof(UiOscillator) - ); - synth->ui_oscillator_count -= 1; + // memmove( + // synth->ui_oscillator + ui_osc_i, + // synth->ui_oscillator + ui_osc_i + 1, + // (synth->ui_oscillator_count - ui_osc_i) * + // sizeof(UiOscillator) + // ); + // synth->ui_oscillator_count -= 1; } - */ + } } diff --git a/src/Synth.cpp b/src/Synth.cpp index 16f3780..d99456d 100644 --- a/src/Synth.cpp +++ b/src/Synth.cpp @@ -7,10 +7,8 @@ Synth::Synth(/* args */) { AddOscillator(); AddEffect(new ADSR()); - for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { - float sample = 0.0f; - m_out_signal.push_back(sample); - } + m_out_signal.reserve(STREAM_BUFFER_SIZE); + zero_signal(); } Synth::~Synth() { @@ -19,26 +17,37 @@ Synth::~Synth() { m_out_signal.clear(); } -void Synth::get_note() { +void Synth::zero_signal() { + float sample = 0.0f; for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { - float sample = 0.0f; m_out_signal[i] = sample; } +} - // todo: add other pipeline steps (e.g ADSR, Filters, FX); +void Synth::get_note() { + zero_signal(); Adder::SumOscillators(m_oscillators, m_out_signal); } void Synth::apply_effects() { for (Effect* effect : m_effects) { - // maybe not here - //effect->RetriggerState(); effect->Process(m_out_signal); } } +void Synth::trigger_note_on_effects() { + for (Effect* effect : m_effects) { + effect->OnSetNote(); + } +} + +void Synth::untrigger_note_on_effects() { + for (Effect* effect : m_effects) { + effect->OnUnsetNote(); + } +} + void Synth::TriggerNote(Note input) { - float length = 1.f / input.length; int semitone_shift = KeyBoard::GetSemitoneShift(input.name); float hz = KeyBoard::GetHzBySemitone(semitone_shift); @@ -47,6 +56,7 @@ void Synth::TriggerNote(Note input) { osc->SetFreq(hz); } is_note_triggered = true; + trigger_note_on_effects(); } void Synth::ProduceSound() { @@ -54,12 +64,11 @@ void Synth::ProduceSound() { apply_effects(); } +// todo: rename to something like untrigger note void Synth::StopSound() { - for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { - float sample = 0.0f; - m_out_signal[i] = sample; - } + zero_signal(); is_note_triggered = false; + untrigger_note_on_effects(); } void Synth::AddOscillator() {