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..650be58 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,24 +3,12 @@ { "type": "cppbuild", "label": "C/C++: clang сборка активного файла", - "command": "/usr/bin/clang", + "command": "sh", "args": [ - "-fcolor-diagnostics", - "-fansi-escape-codes", - "-g", - "${file}", - "${fileDirname}/utils.c", - "${fileDirname}/ring_buffer.c", - "${fileDirname}/oscillator.c", - "${fileDirname}/parser.c", - "${fileDirname}/export.c", - "-lm", - "-lraylib", - "-o", - "${fileDirname}/bin/${fileBasenameNoExtension}" + "${workspaceFolder}/build.sh" ], "options": { - "cwd": "${fileDirname}" + "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" diff --git a/build.sh b/build.sh index caa2865..f9cbdfc 100644 --- a/build.sh +++ b/build.sh @@ -1,5 +1,5 @@ #!/bin/bash CC="${CXX:-c++}" LL="-lm -lraylib" -FLAGS="-Wall -std=c++17 -I./inc/" +FLAGS="-Wall -std=c++17 -I./inc/ -g" $CC $FLAGS $(find ./src -type f -iregex ".*\.cpp") $LL -o ./bin/main 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/ADSR.md b/docs/ADSR.md index 211b7d2..be4c98f 100644 --- a/docs/ADSR.md +++ b/docs/ADSR.md @@ -26,6 +26,7 @@ release_samples = int(release_time * sample_rate) # Attack phase envelope[:attack_samples] = np.linspace(0, 1, num=attack_samples) +# 1/n * count; # Decay phase decay_slope = (1 - sustain_level) / decay_samples 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 new file mode 100644 index 0000000..53b8c20 --- /dev/null +++ b/inc/ADSR.h @@ -0,0 +1,37 @@ +#pragma once +#include "Effect.h" +#include "Ramp.h" +#include + +struct ADSRParameters { + float attack_time; // Attack time in seconds + float decay_time; // Decay time in seconds + float sustain_level; // Sustain level (0 to 1) + float release_time; +}; + +enum ADSRState { Off, Attack, Decay, Sustain, Release }; + +class ADSR : public Effect { + private: + ADSRParameters m_parameters; + ADSRState m_state; + 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 OnSetNote() override; + void OnUnsetNote() override; + // void RetriggerState() override; + void Process(std::vector& samples) override; + void Reset(); + void SetParameters(float attack, float decay, float sustain, float release); +}; diff --git a/inc/Adder.h b/inc/Adder.h index 23abd87..d3bd044 100644 --- a/inc/Adder.h +++ b/inc/Adder.h @@ -6,23 +6,21 @@ #include struct Adder { - static std::vector& - SumOscillators(const std::vector& oscillators, - float duration) { - size_t sample_count = (size_t)(duration * SAMPLE_RATE); + static void SumOscillators(const std::vector& oscillators, + std::vector& signal) { + size_t sample_count = + STREAM_BUFFER_SIZE; //(size_t)(1.f/FPS * SAMPLE_RATE); - std::vector* output = new std::vector(); - output->reserve(sample_count); + // std::vector* output = new std::vector(); + // output->reserve(sample_count); for (size_t i = 0; i < sample_count; i++) { float sample = 0.0f; for (Oscillator* osc : oscillators) { - sample += osc->GenerateSample(duration); + sample += osc->GenerateSample(1.f); } - output->push_back(sample); + signal[i] = sample; } - - return (*output); } }; diff --git a/inc/Application.h b/inc/Application.h index 7b649c3..a98ba7c 100644 --- a/inc/Application.h +++ b/inc/Application.h @@ -1,7 +1,6 @@ #pragma once #include "Note.h" #include "Renderer.h" -#include "RingBuffer.h" #include "Synth.h" #include "SynthGuiState.h" #include "raylib.h" @@ -10,18 +9,15 @@ class Application { private: Synth m_synth; SynthGuiState m_synth_gui_state; - RingBuffer* m_ring_buffer; AudioStream m_synth_stream; int m_sound_played_count; - float* m_temp_buffer; Note* m_current_note; Renderer m_renderer; - std::size_t detect_note_pressed(Note* note); + bool detect_note_pressed(Note* note); void init_synth(); void init_audio(); void update_on_note_input(); void play_buffered_audio(); - void fill_audio_buffer(); public: Application(/* args */); diff --git a/inc/Effect.h b/inc/Effect.h new file mode 100644 index 0000000..c35ce24 --- /dev/null +++ b/inc/Effect.h @@ -0,0 +1,13 @@ +#pragma once +#include +class Effect { + private: + /* data */ + public: + Effect(/* args */){}; + ~Effect(){}; + 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..9a98de5 --- /dev/null +++ b/inc/Ramp.h @@ -0,0 +1,16 @@ +#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/Renderer.h b/inc/Renderer.h index 8140114..169a788 100644 --- a/inc/Renderer.h +++ b/inc/Renderer.h @@ -1,4 +1,5 @@ #pragma once +#include "ADSR.h" #include "Synth.h" #include "SynthGuiState.h" #include "raylib.h" @@ -8,8 +9,8 @@ class Renderer { private: void draw_main_panel(const Rectangle& panel_bounds); void draw_add_oscillator_button(Synth& synth, SynthGuiState& synth_gui, - Rectangle panel_bounds); - void draw_oscillators_panels( + Rectangle panel_bounds); + float draw_oscillators_panels( const std::vector& oscillators, const std::vector& gui_oscillators, const Rectangle& panel_bounds); @@ -18,6 +19,8 @@ class Renderer { const std::vector& guiOscillators); void draw_ui(Synth& synth, SynthGuiState& synth_gui); void draw_signal(Synth& synth, SynthGuiState& synth_gui); + void draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr, + const Rectangle& panel_bounds, float panel_y_offset); public: Renderer(/* args */); diff --git a/inc/Settings.h b/inc/Settings.h index dbf08e5..4d8c0ad 100644 --- a/inc/Settings.h +++ b/inc/Settings.h @@ -1,12 +1,13 @@ #pragma once -#define SAMPLE_RATE 48000.f +#define SAMPLE_RATE 44100.f #define BPM 120.f -#define BEAT_DURATION 60.f/BPM +#define BEAT_DURATION 60.f / BPM #define PITCH_STANDARD 440.f #define VOLUME 0.5f #define ATTACK_MS 100.f -#define STREAM_BUFFER_SIZE 4096 +#define STREAM_BUFFER_SIZE 1024 +#define FPS 60 #define SYNTH_PI 3.1415926535f #define SYNTH_VOLUME 0.5f diff --git a/inc/Synth.h b/inc/Synth.h index f322421..b0f4eee 100644 --- a/inc/Synth.h +++ b/inc/Synth.h @@ -1,6 +1,8 @@ #pragma once +#include "ADSR.h" #include "Adder.h" +#include "Effect.h" #include "Note.h" #include "Oscillator.h" #include "Settings.h" @@ -8,18 +10,28 @@ 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; - std::vector& get_note(int semitone, float beats); + void zero_signal(); + void get_note(); + void trigger_note_on_effects(); + void untrigger_note_on_effects(); + void apply_effects(); public: Synth(/* args */); ~Synth(); - void ProduceNoteSound(Note input); + void TriggerNote(Note input); + void ProduceSound(); + void StopSound(); void AddOscillator(); + void AddEffect(Effect* fx); const std::vector& GetOutSignal() { return m_out_signal; } const std::vector& GetOscillators() { return m_oscillators; } + const bool& GetIsNoteTriggered() { return is_note_triggered; } + ADSR* GetADSR() { return (ADSR*)m_effects[0]; } }; \ No newline at end of file diff --git a/inc/SynthGuiState.h b/inc/SynthGuiState.h index 5eb02c3..f07a21d 100644 --- a/inc/SynthGuiState.h +++ b/inc/SynthGuiState.h @@ -11,6 +11,14 @@ struct OscillatorGuiState { Rectangle shape_dropdown_rect; }; +struct ADSRGuiState { + float attack; + float decay; + float sustain; + float release; +}; + struct SynthGuiState { std::vector oscillators; + ADSRGuiState adsr; }; \ No newline at end of file diff --git a/src/ADSR.cpp b/src/ADSR.cpp new file mode 100644 index 0000000..5d204fa --- /dev/null +++ b/src/ADSR.cpp @@ -0,0 +1,97 @@ +#include "ADSR.h" +#include "Logger.h" +#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_ramp = new Ramp(0, SAMPLE_RATE); +} + +ADSR::ADSR(ADSRParameters param) { m_parameters = param; } + +ADSR::~ADSR() { delete m_ramp; } + +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 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) { + 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(); + } +} + +void ADSR::OnSetNote() { + write_log("Set ADSR\n"); + 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) { + for (std::size_t i = 0; i < samples.size(); i++) { + recheck_state(); + process_sample(&samples[i]); + } +} + +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; +} \ No newline at end of file diff --git a/src/Application.cpp b/src/Application.cpp index 2349fbb..6ec69e0 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -4,8 +4,6 @@ #include Application::Application(/* args */) { - m_ring_buffer = new RingBuffer((std::size_t)STREAM_BUFFER_SIZE); - m_temp_buffer = new float[STREAM_BUFFER_SIZE]; init_synth(); init_audio(); } @@ -15,8 +13,6 @@ Application::~Application() { UnloadAudioStream(m_synth_stream); CloseAudioDevice(); CloseWindow(); - delete m_ring_buffer; - delete[] m_temp_buffer; // todo: move to gui state class destructor (make it a class) for (int i = 0; i < m_synth_gui_state.oscillators.size(); i++) { delete m_synth_gui_state.oscillators[i]; @@ -39,7 +35,7 @@ void Application::init_synth() { // todo: move that variables to Synth declaration 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"); // todo: move somewhere in initialization std::vector oscillators = m_synth.GetOscillators(); m_synth_gui_state.oscillators.reserve(oscillators.size()); @@ -55,103 +51,81 @@ void Application::init_synth() { } } -std::size_t Application::detect_note_pressed(Note* note) { +bool Application::detect_note_pressed(Note* note) { std::size_t is_pressed = 0; note->length = 8; - if (IsKeyPressed(KEY_A)) { - note->name.assign("A4"); + if (IsKeyDown(KEY_A)) { + note->name.assign("A2"); is_pressed = 1; } - if (IsKeyPressed(KEY_B)) { - note->name.assign("B4"); + if (IsKeyDown(KEY_B)) { + note->name.assign("B2"); is_pressed = 1; } - if (IsKeyPressed(KEY_C)) { - note->name.assign("C4"); + if (IsKeyDown(KEY_C)) { + note->name.assign("C2"); is_pressed = 1; } - if (IsKeyPressed(KEY_D)) { - note->name.assign("D4"); + if (IsKeyDown(KEY_D)) { + note->name.assign("D2"); is_pressed = 1; } - if (IsKeyPressed(KEY_E)) { - note->name.assign("E4"); + if (IsKeyDown(KEY_E)) { + note->name.assign("E2"); is_pressed = 1; } - if (IsKeyPressed(KEY_F)) { - note->name.assign("F4"); + if (IsKeyDown(KEY_F)) { + note->name.assign("F2"); is_pressed = 1; } - if (IsKeyPressed(KEY_G)) { - note->name.assign("G4"); + if (IsKeyDown(KEY_G)) { + note->name.assign("G2"); is_pressed = 1; } - return is_pressed; + return is_pressed == 1; +} + +bool is_note_up() { + return IsKeyUp(KEY_A) || IsKeyUp(KEY_B) || + IsKeyUp(KEY_C) || IsKeyUp(KEY_D) || + IsKeyUp(KEY_E) || IsKeyUp(KEY_F) || IsKeyUp(KEY_G); } // Update On Input void Application::update_on_note_input() { if (detect_note_pressed(m_current_note)) { - m_synth.ProduceNoteSound((*m_current_note)); - m_sound_played_count = 0; + + if (!m_synth.GetIsNoteTriggered()) { + m_synth.TriggerNote((*m_current_note)); + } + + // m_sound_played_count = 0; write_log("Note played: %s\n", m_current_note->name.c_str()); + } else if (is_note_up() && m_synth.GetIsNoteTriggered()) { + m_synth.StopSound(); } + // will produce 0 signal if ADSR is in off state + m_synth.ProduceSound(); } // Play ring-buffered audio void Application::play_buffered_audio() { - if (IsAudioStreamProcessed(m_synth_stream) && !m_ring_buffer->IsEmpty()) { - std::size_t size_to_read = m_ring_buffer->GetSize(); - - write_log("Samples to play:%zu \n", size_to_read); - // todo: try to start reading directly from ring buffer, avoiding - // temp_buffer - m_ring_buffer->Read(m_temp_buffer, size_to_read); - // can try the SetAudioStreamCallback - UpdateAudioStream(m_synth_stream, m_temp_buffer, size_to_read); - // can overwrite the ring buffer to avoid that - if (m_synth.GetOutSignal().size() == m_sound_played_count) { - m_ring_buffer->Reset(); - } - } -} - -// Fill ring buffer from current sound -void Application::fill_audio_buffer() { - if (!m_ring_buffer->IsFull() && - m_synth.GetOutSignal().size() != m_sound_played_count) { - write_log("[INFO] IsFull:%d Samples:%zu Played:%d\n", - m_ring_buffer->IsFull(), m_synth.GetOutSignal().size(), - m_sound_played_count); - - // how many samples need write - std::size_t size_to_fill = 0; - - if ((m_synth.GetOutSignal().size() - m_sound_played_count) > - m_ring_buffer->GetCapacity()) { - size_to_fill = m_ring_buffer->GetCapacity(); - } else { - size_to_fill = m_synth.GetOutSignal().size() - m_sound_played_count; - } - - write_log("[INFO] SizeToFill:%zu\n", size_to_fill); - for (size_t i = 0; i < size_to_fill; i++) { - m_temp_buffer[i] = m_synth.GetOutSignal()[i]; - } - - m_ring_buffer->Write(m_temp_buffer, size_to_fill); - m_sound_played_count += size_to_fill; + if (IsAudioStreamProcessed(m_synth_stream)) { + // const float audio_frame_start_time = GetTime(); + 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))); } } void Application::Run() { // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - fill_audio_buffer(); + while (!WindowShouldClose()) { play_buffered_audio(); - update_on_note_input(); - m_renderer.Draw(m_synth, m_synth_gui_state); } } diff --git a/src/Ramp.cpp b/src/Ramp.cpp new file mode 100644 index 0000000..d93fb9e --- /dev/null +++ b/src/Ramp.cpp @@ -0,0 +1,27 @@ +#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 val, float time) { + m_increment = (val - m_level) / (m_sample_rate * time); + m_counter = (int)(m_sample_rate * time); + write_log("Ramping from: %.1f to: %.1f by: %lf for: %d\n", m_level, val, + 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 32a8748..7fa528f 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -2,11 +2,17 @@ #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"); - SetTargetFPS(60); + SetTargetFPS(FPS); } Renderer::~Renderer() {} @@ -70,7 +76,7 @@ void Renderer::draw_oscillators_shape_inputs( } } -void Renderer::draw_oscillators_panels( +float Renderer::draw_oscillators_panels( const std::vector& oscillators, const std::vector& gui_oscillators, const Rectangle& panel_bounds) { @@ -105,7 +111,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,34 +122,33 @@ 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; delete_button_rect.width = 30; 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; + 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; } - */ } + + return panel_y_offset; } void Renderer::draw_main_panel(const Rectangle& panel_bounds) { - bool is_shape_dropdown_open = false; - int shape_index = 0; GuiPanel(panel_bounds, ""); } -void Renderer::draw_add_oscillator_button(Synth& synth, SynthGuiState& synth_gui, - Rectangle panel_bounds) { +void Renderer::draw_add_oscillator_button(Synth& synth, + SynthGuiState& synth_gui, + Rectangle panel_bounds) { //clang-format off bool click_add_oscillator = GuiButton((Rectangle){panel_bounds.x + 10, panel_bounds.y + 10, @@ -174,6 +179,67 @@ void Renderer::draw_ui(Synth& synth, SynthGuiState& synth_gui) { std::vector oscillators = synth.GetOscillators(); std::vector gui_oscillators = synth_gui.oscillators; - draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds); + float panel_y_offset = + draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds); draw_oscillators_shape_inputs(oscillators, gui_oscillators); -} \ No newline at end of file + + draw_adsr_panel(synth.GetADSR(), synth_gui.adsr, panel_bounds, + panel_y_offset); +} + +void Renderer::draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr, + const Rectangle& panel_bounds, + float panel_y_offset) { + // Draw ADSR Panel + const int osc_panel_width = panel_bounds.width - 20; + const int osc_panel_height = 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; + GuiPanel((Rectangle){(float)osc_panel_x, (float)osc_panel_y, + (float)osc_panel_width, (float)osc_panel_height}, + "ADSR"); + + 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}; + + // Attack slider + float attack = gui_adsr.attack; + char attack_slider_label[32]; + snprintf(attack_slider_label, 7, "%.1f s", attack); + attack = GuiSlider(el_rect, attack_slider_label, "", attack, 0.0f, 2.0f); + gui_adsr.attack = attack; + el_rect.y += el_rect.height + el_spacing; + + // Decay slider + float decay = gui_adsr.decay; + char decay_slider_label[32]; + snprintf(decay_slider_label, 7, "%.1f s", decay); + decay = GuiSlider(el_rect, decay_slider_label, "", decay, 0.0f, 1.0f); + gui_adsr.decay = decay; + el_rect.y += el_rect.height + el_spacing; + + // Sustain slider + float sustain = gui_adsr.sustain; + char sustain_slider_label[32]; + snprintf(sustain_slider_label, 7, "%.1f u", sustain); + sustain = GuiSlider(el_rect, sustain_slider_label, "", sustain, 0.0f, 1.0f); + gui_adsr.sustain = sustain; + el_rect.y += el_rect.height + el_spacing; + + // Release slider + float release = gui_adsr.release; + char release_slider_label[32]; + snprintf(release_slider_label, 7, "%.1f s", release); + release = GuiSlider(el_rect, release_slider_label, "", release, 0.0f, 5.0f); + gui_adsr.release = release; + el_rect.y += el_rect.height + el_spacing; + + // apply values to real one + adsr->SetParameters(gui_adsr.attack, gui_adsr.decay, gui_adsr.sustain, + gui_adsr.release); +} diff --git a/src/Synth.cpp b/src/Synth.cpp index b56d33b..30de536 100644 --- a/src/Synth.cpp +++ b/src/Synth.cpp @@ -1,32 +1,83 @@ #include "Synth.h" +#include "ADSR.h" #include "KeyBoard.h" +#include "Logger.h" #include "OscillatorType.h" #include "Settings.h" -Synth::Synth(/* args */) { AddOscillator(); } +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); + } + zero_signal(); +} -Synth::~Synth() {} +Synth::~Synth() { + m_oscillators.clear(); + m_effects.clear(); + m_out_signal.clear(); +} -std::vector& Synth::get_note(int semitone, float beats) { - float hz = KeyBoard::GetHzBySemitone(semitone); - float duration = beats * BEAT_DURATION; +void Synth::zero_signal() { + 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(); + Adder::SumOscillators(m_oscillators, m_out_signal); +} + +void Synth::apply_effects() { + for (Effect* effect : m_effects) { + 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) { + int semitone_shift = KeyBoard::GetSemitoneShift(input.name); + float hz = KeyBoard::GetHzBySemitone(semitone_shift); // will change after oscillator starts to be more autonomous for (Oscillator* osc : m_oscillators) { osc->SetFreq(hz); } - - // todo: add other pipeline steps (e.g ADSR, Filters, FX); - return m_adder.SumOscillators(m_oscillators, duration); + is_note_triggered = true; + trigger_note_on_effects(); } -void Synth::ProduceNoteSound(Note input) { - float length = 1.f / input.length; - int semitone_shift = KeyBoard::GetSemitoneShift(input.name); - m_out_signal = get_note(semitone_shift, length); +void Synth::ProduceSound() { + get_note(); + apply_effects(); +} + +// todo: rename to something like untrigger note +void Synth::StopSound() { + zero_signal(); + is_note_triggered = false; + untrigger_note_on_effects(); } void Synth::AddOscillator() { m_oscillators.push_back( new Oscillator(OscillatorType::Sine, 440.f, VOLUME)); } + +void Synth::AddEffect(Effect* fx) { m_effects.push_back(fx); }