[feat]: Oscillator fine-tune #22

Merged
e1lama merged 6 commits from feature/osc-19 into master 2023-09-16 23:26:45 +00:00
16 changed files with 209 additions and 132 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
*.wav
*.dSYM
/lib
/build

37
CMakeLists.txt Normal file
View File

@@ -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

View File

@@ -1,9 +1,9 @@
#pragma once
#include "Effect.h"
#include "IEffect.h"
#include "Ramp.h"
#include <cstddef>
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 */);

View File

@@ -1,12 +0,0 @@
#pragma once
#include <vector>
class Effect {
private:
/* data */
public:
Effect(/* args */){};
~Effect(){};
virtual void Trigger(){};
virtual void Release(){};
virtual void Process(std::vector<float>& samples){};
};

View File

@@ -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<float>& samples) override;
void Process(std::vector<float>& samples) override final;
void SetParameters(float freq, float res, float q);
float GetFreq() { return m_freq; }
float GetRes() { return m_q; }

10
inc/IEffect.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <vector>
class IEffect {
private:
/* data */
public:
virtual void Trigger() = 0;
virtual void Release() = 0;
virtual void Process(std::vector<float>& samples) = 0;
};

View File

@@ -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() {}

20
inc/LFO.h Normal file
View File

@@ -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()
{
}

View File

@@ -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();
};

View File

@@ -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<Oscillator*> m_oscillators;
std::vector<Effect*> m_effects;
std::vector<IEffect*> m_effects;
std::vector<float> 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<float>& GetOutSignal() { return m_out_signal; }
const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; }
const bool& GetIsNoteTriggered() { return is_note_triggered; }

View File

@@ -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;

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;
@@ -233,8 +243,8 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
// 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();