diff --git a/.gitignore b/.gitignore index 533963c..1aa0b53 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /Debug/ *.wav *.dSYM -/lib \ No newline at end of file +/lib +/build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cc31e79 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.5) +project(SeeSynth) +#set(CMAKE_C_STANDARD 99) + +# Adding Raylib +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) +set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples +set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games + +FetchContent_Declare( + raylib + GIT_REPOSITORY "https://github.com/raysan5/raylib.git" + GIT_TAG "4.5.0" + GIT_PROGRESS TRUE +) + +FetchContent_MakeAvailable(raylib) + +# Adding our source files +file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp") # Define PROJECT_SOURCES as a list of all source files +set(PROJECT_INCLUDE "${CMAKE_CURRENT_LIST_DIR}/inc/") # Define PROJECT_INCLUDE to be the path to the include directory of the project + + +# Declaring our executable +add_executable(${PROJECT_NAME}) +set_target_properties( + ${PROJECT_NAME} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON) +target_sources(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCES}) +target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_INCLUDE}) +target_link_libraries(${PROJECT_NAME} PRIVATE raylib) + +# Setting ASSETS_PATH +target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/") # Set the asset path macro to the absolute path on the dev machine +#target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="./assets") # Set the asset path macro in release mode to a relative path that assumes the assets folder is in the same directory as the game executable diff --git a/inc/ADSR.h b/inc/ADSR.h index 71c3acf..624c922 100644 --- a/inc/ADSR.h +++ b/inc/ADSR.h @@ -1,9 +1,9 @@ #pragma once -#include "Effect.h" +#include "IEffect.h" #include "Ramp.h" #include -class ADSR : public Effect { +class ADSR : public IEffect { enum ADSRState { sOff, sAttack, sDecay, sSustain, sRelease }; private: @@ -14,11 +14,11 @@ class ADSR : public Effect { 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(); + void ProcessSample(float* sample); + bool IsAttackElapsed(); + bool IsDecayElapsed(); + bool IsReleaseElapsed(); + void RecheckState(); public: ADSR(/* args */); diff --git a/inc/Effect.h b/inc/Effect.h deleted file mode 100644 index 6e9f918..0000000 --- a/inc/Effect.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include -class Effect { - private: - /* data */ - public: - Effect(/* args */){}; - ~Effect(){}; - virtual void Trigger(){}; - virtual void Release(){}; - virtual void Process(std::vector& samples){}; -}; diff --git a/inc/Filter.h b/inc/Filter.h index 7b5f567..c4d8ba4 100644 --- a/inc/Filter.h +++ b/inc/Filter.h @@ -1,5 +1,5 @@ #pragma once -#include "Effect.h" +#include "IEffect.h" enum FilterType { LowPass, @@ -7,7 +7,7 @@ enum FilterType { HighPass }; -class Filter : public Effect { +class Filter : public IEffect { protected: float m_freq; // cutoff frequency float m_q; // filter quantity (resonance) @@ -23,10 +23,10 @@ class Filter : public Effect { public: Filter(/* args */); virtual ~Filter(); - void Trigger() override; - void Release() override; + void Trigger() override final; + void Release() override final; float Process(float in); - void Process(std::vector& samples) override; + void Process(std::vector& samples) override final; void SetParameters(float freq, float res, float q); float GetFreq() { return m_freq; } float GetRes() { return m_q; } diff --git a/inc/IEffect.h b/inc/IEffect.h new file mode 100644 index 0000000..9a8bc68 --- /dev/null +++ b/inc/IEffect.h @@ -0,0 +1,10 @@ +#pragma once +#include +class IEffect { + private: + /* data */ + public: + virtual void Trigger() = 0; + virtual void Release() = 0; + virtual void Process(std::vector& samples) = 0; +}; diff --git a/inc/KeyBoard.h b/inc/KeyBoard.h index b6b3d7a..634b353 100644 --- a/inc/KeyBoard.h +++ b/inc/KeyBoard.h @@ -54,10 +54,9 @@ class KeyBoard { } public: - KeyBoard(/* args */); - ~KeyBoard(); - static float GetHzBySemitone(int semitone) { + static float GetHzBySemitone(float semitone) { + //440 * Math.Pow(2, (note - 69) / 12.0) would it be better? return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone); } @@ -71,7 +70,3 @@ class KeyBoard { return result; } }; - -KeyBoard::KeyBoard(/* args */) {} - -KeyBoard::~KeyBoard() {} diff --git a/inc/LFO.h b/inc/LFO.h new file mode 100644 index 0000000..d5676fe --- /dev/null +++ b/inc/LFO.h @@ -0,0 +1,20 @@ +#pragma once +#include "Oscillator.h" + +class LFO: public Oscillator +{ +private: + /* data */ +public: + LFO(/* args */); + ~LFO(); + void SetFreq(float freq) { m_phase_dt = (this->*m_dt_function)(freq); } +}; + +LFO::LFO(/* args */): Oscillator(Sine, 0.f, 0.5f) +{ +} + +LFO::~LFO() +{ +} diff --git a/inc/Oscillator.h b/inc/Oscillator.h index d655075..01691b9 100644 --- a/inc/Oscillator.h +++ b/inc/Oscillator.h @@ -5,31 +5,41 @@ class Oscillator { private: OscillatorType m_osc; - float m_freq; + float m_fine; + float m_key; float m_volume; float m_phase; - float m_phase_dt; float (Oscillator::*m_osc_function)(void); + void SineOscPhaseIncr(); + void SawOscPhaseIncr(); + float CalcSawPhaseDelta(float freq); + float CalcSinePhaseDelta(float freq); + float SawOsc(); + float TriangleOsc(); + float SquareOsc(); + float Sign(float v); + float SineOsc(); + + protected: + float m_phase_dt; float (Oscillator::*m_dt_function)(float freq); - void sine_osc_phase_incr(); - void saw_osc_phase_incr(); - float calc_saw_phase_delta(float freq); - float calc_sine_phase_delta(float freq); - float sawosc(); - float triangleosc(); - float squareosc(); - float sign(float v); - float sineosc(); public: - Oscillator(OscillatorType osc, float freq, float volume); + Oscillator(OscillatorType osc, float fine, float volume); ~Oscillator(); OscillatorType GetType() { return m_osc; } void SetType(OscillatorType osc); float GetVolume() { return m_volume; } void SetVolume(float volume) { m_volume = volume; } - float GetFreq() { return m_freq; } - void SetFreq(float freq); + float GetKey() { return m_key; } + void SetKey(float key); + float GetFine() { return m_fine; } + void SetFine(float fine) { + if (fine != m_fine) { + assert(fine >= -2.f && fine <= 2.f); + m_fine = fine; + } + } void Reset(); float Process(); }; diff --git a/inc/Synth.h b/inc/Synth.h index c9bc8d8..d6acede 100644 --- a/inc/Synth.h +++ b/inc/Synth.h @@ -3,7 +3,7 @@ #include "ADSR.h" #include "Filter.h" #include "Adder.h" -#include "Effect.h" +#include "IEffect.h" #include "Note.h" #include "Oscillator.h" #include "Settings.h" @@ -13,23 +13,23 @@ class Synth { private: bool is_note_triggered; std::vector m_oscillators; - std::vector m_effects; + std::vector m_effects; std::vector m_out_signal; Oscillator* m_lfo; - void zero_signal(); - void get_note(); - void trigger_note_on_effects(); - void untrigger_note_on_effects(); - void apply_effects(); - void add_oscillator(); - void apply_filter_lfo(); + void ZeroSignal(); + void GetNote(); + void TriggerNoteOnEffects(); + void UntriggerNoteOnEffects(); + void ApplyEffects(); + void AddOscillator(); + void ApplyFilterLfo(); public: Synth(/* args */); ~Synth(); void Trigger(Note input); void Process(); void Release(); - void AddEffect(Effect* fx); + void AddEffect(IEffect* fx); const std::vector& GetOutSignal() { return m_out_signal; } const std::vector& GetOscillators() { return m_oscillators; } const bool& GetIsNoteTriggered() { return is_note_triggered; } diff --git a/inc/SynthGuiState.h b/inc/SynthGuiState.h index 6d404ed..03e2ba2 100644 --- a/inc/SynthGuiState.h +++ b/inc/SynthGuiState.h @@ -6,7 +6,7 @@ struct OscillatorGuiState { float volume; - float freq; // todo: remove or change to pitch shift + float fine; OscillatorType waveshape; bool is_dropdown_open; Rectangle shape_dropdown_rect; diff --git a/src/ADSR.cpp b/src/ADSR.cpp index 9f043a9..7a4c435 100644 --- a/src/ADSR.cpp +++ b/src/ADSR.cpp @@ -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& samples) { for (std::size_t i = 0; i < samples.size(); i++) { - recheck_state(); - process_sample(&samples[i]); + RecheckState(); + ProcessSample(&samples[i]); } } diff --git a/src/Application.cpp b/src/Application.cpp index 53a91ca..7be8bc9 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -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(); } diff --git a/src/Oscillator.cpp b/src/Oscillator.cpp index 6abe4b7..f73bd99 100644 --- a/src/Oscillator.cpp +++ b/src/Oscillator.cpp @@ -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; } \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 5032745..4a9a626 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -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; } diff --git a/src/Synth.cpp b/src/Synth.cpp index 59b1efa..089dcdc 100644 --- a/src/Synth.cpp +++ b/src/Synth.cpp @@ -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();