[feature]: ADSR #15

Merged
e1lama merged 13 commits from feature/adsr into master 2023-09-06 08:29:46 +00:00
20 changed files with 444 additions and 140 deletions

16
.vscode/launch.json vendored Normal file
View File

@@ -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}"
}
]
}

18
.vscode/tasks.json vendored
View File

@@ -3,24 +3,12 @@
{ {
"type": "cppbuild", "type": "cppbuild",
"label": "C/C++: clang сборка активного файла", "label": "C/C++: clang сборка активного файла",
"command": "/usr/bin/clang", "command": "sh",
"args": [ "args": [
"-fcolor-diagnostics", "${workspaceFolder}/build.sh"
"-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}"
], ],
"options": { "options": {
"cwd": "${fileDirname}" "cwd": "${workspaceFolder}"
}, },
"problemMatcher": [ "problemMatcher": [
"$gcc" "$gcc"

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
CC="${CXX:-c++}" CC="${CXX:-c++}"
LL="-lm -lraylib" 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 $CC $FLAGS $(find ./src -type f -iregex ".*\.cpp") $LL -o ./bin/main

BIN
docs/ADSR States.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

View File

@@ -26,6 +26,7 @@ release_samples = int(release_time * sample_rate)
# Attack phase # Attack phase
envelope[:attack_samples] = np.linspace(0, 1, num=attack_samples) envelope[:attack_samples] = np.linspace(0, 1, num=attack_samples)
# 1/n * count;
# Decay phase # Decay phase
decay_slope = (1 - sustain_level) / decay_samples decay_slope = (1 - sustain_level) / decay_samples

BIN
docs/Attack Formula.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

37
inc/ADSR.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include "Effect.h"
#include "Ramp.h"
#include <cstddef>
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<float>& samples) override;
void Reset();
void SetParameters(float attack, float decay, float sustain, float release);
};

View File

@@ -6,23 +6,21 @@
#include <vector> #include <vector>
struct Adder { struct Adder {
static std::vector<float>& static void SumOscillators(const std::vector<Oscillator*>& oscillators,
SumOscillators(const std::vector<Oscillator*>& oscillators, std::vector<float>& signal) {
float duration) { size_t sample_count =
size_t sample_count = (size_t)(duration * SAMPLE_RATE); STREAM_BUFFER_SIZE; //(size_t)(1.f/FPS * SAMPLE_RATE);
std::vector<float>* output = new std::vector<float>(); // std::vector<float>* output = new std::vector<float>();
output->reserve(sample_count); // output->reserve(sample_count);
for (size_t i = 0; i < sample_count; i++) { for (size_t i = 0; i < sample_count; i++) {
float sample = 0.0f; float sample = 0.0f;
for (Oscillator* osc : oscillators) { for (Oscillator* osc : oscillators) {
sample += osc->GenerateSample(duration); sample += osc->GenerateSample(1.f);
} }
output->push_back(sample); signal[i] = sample;
} }
return (*output);
} }
}; };

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "Note.h" #include "Note.h"
#include "Renderer.h" #include "Renderer.h"
#include "RingBuffer.h"
#include "Synth.h" #include "Synth.h"
#include "SynthGuiState.h" #include "SynthGuiState.h"
#include "raylib.h" #include "raylib.h"
@@ -10,18 +9,15 @@ class Application {
private: private:
Synth m_synth; Synth m_synth;
SynthGuiState m_synth_gui_state; SynthGuiState m_synth_gui_state;
RingBuffer<float>* m_ring_buffer;
AudioStream m_synth_stream; AudioStream m_synth_stream;
int m_sound_played_count; int m_sound_played_count;
float* m_temp_buffer;
Note* m_current_note; Note* m_current_note;
Renderer m_renderer; Renderer m_renderer;
std::size_t detect_note_pressed(Note* note); bool detect_note_pressed(Note* note);
void init_synth(); void init_synth();
void init_audio(); void init_audio();
void update_on_note_input(); void update_on_note_input();
void play_buffered_audio(); void play_buffered_audio();
void fill_audio_buffer();
public: public:
Application(/* args */); Application(/* args */);

13
inc/Effect.h Normal file
View File

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

16
inc/Ramp.h Normal file
View File

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

View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "ADSR.h"
#include "Synth.h" #include "Synth.h"
#include "SynthGuiState.h" #include "SynthGuiState.h"
#include "raylib.h" #include "raylib.h"
@@ -9,7 +10,7 @@ class Renderer {
void draw_main_panel(const Rectangle& panel_bounds); void draw_main_panel(const Rectangle& panel_bounds);
void draw_add_oscillator_button(Synth& synth, SynthGuiState& synth_gui, void draw_add_oscillator_button(Synth& synth, SynthGuiState& synth_gui,
Rectangle panel_bounds); Rectangle panel_bounds);
void draw_oscillators_panels( float draw_oscillators_panels(
const std::vector<Oscillator*>& oscillators, const std::vector<Oscillator*>& oscillators,
const std::vector<OscillatorGuiState*>& gui_oscillators, const std::vector<OscillatorGuiState*>& gui_oscillators,
const Rectangle& panel_bounds); const Rectangle& panel_bounds);
@@ -18,6 +19,8 @@ class Renderer {
const std::vector<OscillatorGuiState*>& guiOscillators); const std::vector<OscillatorGuiState*>& guiOscillators);
void draw_ui(Synth& synth, SynthGuiState& synth_gui); void draw_ui(Synth& synth, SynthGuiState& synth_gui);
void draw_signal(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: public:
Renderer(/* args */); Renderer(/* args */);

View File

@@ -1,12 +1,13 @@
#pragma once #pragma once
#define SAMPLE_RATE 48000.f #define SAMPLE_RATE 44100.f
#define BPM 120.f #define BPM 120.f
#define BEAT_DURATION 60.f / BPM #define BEAT_DURATION 60.f / BPM
#define PITCH_STANDARD 440.f #define PITCH_STANDARD 440.f
#define VOLUME 0.5f #define VOLUME 0.5f
#define ATTACK_MS 100.f #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_PI 3.1415926535f
#define SYNTH_VOLUME 0.5f #define SYNTH_VOLUME 0.5f

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include "ADSR.h"
#include "Adder.h" #include "Adder.h"
#include "Effect.h"
#include "Note.h" #include "Note.h"
#include "Oscillator.h" #include "Oscillator.h"
#include "Settings.h" #include "Settings.h"
@@ -8,18 +10,28 @@
class Synth { class Synth {
private: private:
bool is_note_triggered;
std::vector<Oscillator*> m_oscillators; std::vector<Oscillator*> m_oscillators;
Adder m_adder; std::vector<Effect*> m_effects;
// OscillatorUI* ui_oscillators; // OscillatorUI* ui_oscillators;
// Note m_current_note; // Note m_current_note;
std::vector<float> m_out_signal; std::vector<float> m_out_signal;
std::vector<float>& 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: public:
Synth(/* args */); Synth(/* args */);
~Synth(); ~Synth();
void ProduceNoteSound(Note input); void TriggerNote(Note input);
void ProduceSound();
void StopSound();
void AddOscillator(); void AddOscillator();
void AddEffect(Effect* fx);
const std::vector<float>& GetOutSignal() { return m_out_signal; } const std::vector<float>& GetOutSignal() { return m_out_signal; }
const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; } const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; }
const bool& GetIsNoteTriggered() { return is_note_triggered; }
ADSR* GetADSR() { return (ADSR*)m_effects[0]; }
}; };

View File

@@ -11,6 +11,14 @@ struct OscillatorGuiState {
Rectangle shape_dropdown_rect; Rectangle shape_dropdown_rect;
}; };
struct ADSRGuiState {
float attack;
float decay;
float sustain;
float release;
};
struct SynthGuiState { struct SynthGuiState {
std::vector<OscillatorGuiState*> oscillators; std::vector<OscillatorGuiState*> oscillators;
ADSRGuiState adsr;
}; };

97
src/ADSR.cpp Normal file
View File

@@ -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<float>& 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;
}

View File

@@ -4,8 +4,6 @@
#include <string> #include <string>
Application::Application(/* args */) { Application::Application(/* args */) {
m_ring_buffer = new RingBuffer<float>((std::size_t)STREAM_BUFFER_SIZE);
m_temp_buffer = new float[STREAM_BUFFER_SIZE];
init_synth(); init_synth();
init_audio(); init_audio();
} }
@@ -15,8 +13,6 @@ Application::~Application() {
UnloadAudioStream(m_synth_stream); UnloadAudioStream(m_synth_stream);
CloseAudioDevice(); CloseAudioDevice();
CloseWindow(); CloseWindow();
delete m_ring_buffer;
delete[] m_temp_buffer;
// todo: move to gui state class destructor (make it a class) // todo: move to gui state class destructor (make it a class)
for (int i = 0; i < m_synth_gui_state.oscillators.size(); i++) { for (int i = 0; i < m_synth_gui_state.oscillators.size(); i++) {
delete m_synth_gui_state.oscillators[i]; delete m_synth_gui_state.oscillators[i];
@@ -39,7 +35,7 @@ void Application::init_synth() {
// todo: move that variables to Synth declaration // todo: move that variables to Synth declaration
std::string* nameString = new std::string(std::string(new char[3])); std::string* nameString = new std::string(std::string(new char[3]));
m_current_note = new Note{.length = 1, .name = (*nameString)}; m_current_note = new Note{.length = 1, .name = (*nameString)};
m_current_note->name.assign("G4");
// todo: move somewhere in initialization // todo: move somewhere in initialization
std::vector<Oscillator*> oscillators = m_synth.GetOscillators(); std::vector<Oscillator*> oscillators = m_synth.GetOscillators();
m_synth_gui_state.oscillators.reserve(oscillators.size()); 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; std::size_t is_pressed = 0;
note->length = 8; note->length = 8;
if (IsKeyPressed(KEY_A)) { if (IsKeyDown(KEY_A)) {
note->name.assign("A4"); note->name.assign("A2");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyPressed(KEY_B)) { if (IsKeyDown(KEY_B)) {
note->name.assign("B4"); note->name.assign("B2");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyPressed(KEY_C)) { if (IsKeyDown(KEY_C)) {
note->name.assign("C4"); note->name.assign("C2");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyPressed(KEY_D)) { if (IsKeyDown(KEY_D)) {
note->name.assign("D4"); note->name.assign("D2");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyPressed(KEY_E)) { if (IsKeyDown(KEY_E)) {
note->name.assign("E4"); note->name.assign("E2");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyPressed(KEY_F)) { if (IsKeyDown(KEY_F)) {
note->name.assign("F4"); note->name.assign("F2");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyPressed(KEY_G)) { if (IsKeyDown(KEY_G)) {
note->name.assign("G4"); note->name.assign("G2");
is_pressed = 1; 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 // Update On Input
void Application::update_on_note_input() { void Application::update_on_note_input() {
if (detect_note_pressed(m_current_note)) { if (detect_note_pressed(m_current_note)) {
m_synth.ProduceNoteSound((*m_current_note));
m_sound_played_count = 0; if (!m_synth.GetIsNoteTriggered()) {
write_log("Note played: %s\n", m_current_note->name.c_str()); 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 // Play ring-buffered audio
void Application::play_buffered_audio() { void Application::play_buffered_audio() {
if (IsAudioStreamProcessed(m_synth_stream) && !m_ring_buffer->IsEmpty()) { if (IsAudioStreamProcessed(m_synth_stream)) {
std::size_t size_to_read = m_ring_buffer->GetSize(); // const float audio_frame_start_time = GetTime();
update_on_note_input();
write_log("Samples to play:%zu \n", size_to_read); UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(),
// todo: try to start reading directly from ring buffer, avoiding STREAM_BUFFER_SIZE);
// temp_buffer // const float audio_freme_duration = GetTime() -
m_ring_buffer->Read(m_temp_buffer, size_to_read); // audio_frame_start_time; write_log("Frame time: %.3f%% \n", 100.0f /
// can try the SetAudioStreamCallback // ((1.0f / audio_freme_duration) /
UpdateAudioStream(m_synth_stream, m_temp_buffer, size_to_read); // ((float)SAMPLE_RATE/STREAM_BUFFER_SIZE)));
// 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;
} }
} }
void Application::Run() { void Application::Run() {
// Main game loop // Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key while (!WindowShouldClose()) {
{
fill_audio_buffer();
play_buffered_audio(); play_buffered_audio();
update_on_note_input();
m_renderer.Draw(m_synth, m_synth_gui_state); m_renderer.Draw(m_synth, m_synth_gui_state);
} }
} }

27
src/Ramp.cpp Normal file
View File

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

View File

@@ -2,11 +2,17 @@
#define RAYGUI_IMPLEMENTATION #define RAYGUI_IMPLEMENTATION
#include "Logger.h" #include "Logger.h"
#include "Settings.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" #include "raygui.h"
#pragma clang diagnostic pop
Renderer::Renderer(/* args */) { Renderer::Renderer(/* args */) {
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2"); InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2");
SetTargetFPS(60); SetTargetFPS(FPS);
} }
Renderer::~Renderer() {} 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<Oscillator*>& oscillators, const std::vector<Oscillator*>& oscillators,
const std::vector<OscillatorGuiState*>& gui_oscillators, const std::vector<OscillatorGuiState*>& gui_oscillators,
const Rectangle& panel_bounds) { const Rectangle& panel_bounds) {
@@ -105,7 +111,7 @@ void Renderer::draw_oscillators_panels(
// Volume slider // Volume slider
float decibels = (20.f * log10f(osc->GetVolume())); float decibels = (20.f * log10f(osc->GetVolume()));
char amp_slider_label[32]; char amp_slider_label[32];
sprintf(amp_slider_label, "%.1f dB", decibels); snprintf(amp_slider_label, 7, "%.1f dB", decibels);
decibels = decibels =
GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f); GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f);
ui_osc->volume = powf(10.f, decibels * (1.f / 20.f)); ui_osc->volume = powf(10.f, decibels * (1.f / 20.f));
@@ -116,33 +122,32 @@ void Renderer::draw_oscillators_panels(
// Defer shape drop-down box. // Defer shape drop-down box.
ui_osc->shape_dropdown_rect = el_rect; ui_osc->shape_dropdown_rect = el_rect;
el_rect.y += el_rect.height + el_spacing; el_rect.y += el_rect.height + el_spacing;
/*
Rectangle delete_button_rect = el_rect; Rectangle delete_button_rect = el_rect;
delete_button_rect.x = osc_panel_x + 5; delete_button_rect.x = osc_panel_x + 5;
delete_button_rect.y -= el_rect.height + el_spacing; delete_button_rect.y -= el_rect.height + el_spacing;
delete_button_rect.width = 30; delete_button_rect.width = 30;
bool is_delete_button_pressed = GuiButton(delete_button_rect, "X"); bool is_delete_button_pressed = GuiButton(delete_button_rect, "X");
if (is_delete_button_pressed) if (is_delete_button_pressed) {
{ // memmove(
memmove( // synth->ui_oscillator + ui_osc_i,
synth->ui_oscillator + ui_osc_i, // synth->ui_oscillator + ui_osc_i + 1,
synth->ui_oscillator + ui_osc_i + 1, // (synth->ui_oscillator_count - ui_osc_i) *
(synth->ui_oscillator_count - ui_osc_i) * // sizeof(UiOscillator)
sizeof(UiOscillator) // );
); // synth->ui_oscillator_count -= 1;
synth->ui_oscillator_count -= 1;
} }
*/
} }
return panel_y_offset;
} }
void Renderer::draw_main_panel(const Rectangle& panel_bounds) { void Renderer::draw_main_panel(const Rectangle& panel_bounds) {
bool is_shape_dropdown_open = false;
int shape_index = 0;
GuiPanel(panel_bounds, ""); GuiPanel(panel_bounds, "");
} }
void Renderer::draw_add_oscillator_button(Synth& synth, SynthGuiState& synth_gui, void Renderer::draw_add_oscillator_button(Synth& synth,
SynthGuiState& synth_gui,
Rectangle panel_bounds) { Rectangle panel_bounds) {
//clang-format off //clang-format off
bool click_add_oscillator = bool click_add_oscillator =
@@ -174,6 +179,67 @@ void Renderer::draw_ui(Synth& synth, SynthGuiState& synth_gui) {
std::vector<Oscillator*> oscillators = synth.GetOscillators(); std::vector<Oscillator*> oscillators = synth.GetOscillators();
std::vector<OscillatorGuiState*> gui_oscillators = synth_gui.oscillators; std::vector<OscillatorGuiState*> gui_oscillators = synth_gui.oscillators;
float panel_y_offset =
draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds); draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds);
draw_oscillators_shape_inputs(oscillators, gui_oscillators); draw_oscillators_shape_inputs(oscillators, gui_oscillators);
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);
} }

View File

@@ -1,32 +1,83 @@
#include "Synth.h" #include "Synth.h"
#include "ADSR.h"
#include "KeyBoard.h" #include "KeyBoard.h"
#include "Logger.h"
#include "OscillatorType.h" #include "OscillatorType.h"
#include "Settings.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<float>& Synth::get_note(int semitone, float beats) { void Synth::zero_signal() {
float hz = KeyBoard::GetHzBySemitone(semitone); float sample = 0.0f;
float duration = beats * BEAT_DURATION; 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 // will change after oscillator starts to be more autonomous
for (Oscillator* osc : m_oscillators) { for (Oscillator* osc : m_oscillators) {
osc->SetFreq(hz); osc->SetFreq(hz);
} }
is_note_triggered = true;
// todo: add other pipeline steps (e.g ADSR, Filters, FX); trigger_note_on_effects();
return m_adder.SumOscillators(m_oscillators, duration);
} }
void Synth::ProduceNoteSound(Note input) { void Synth::ProduceSound() {
float length = 1.f / input.length; get_note();
int semitone_shift = KeyBoard::GetSemitoneShift(input.name); apply_effects();
m_out_signal = get_note(semitone_shift, length); }
// todo: rename to something like untrigger note
void Synth::StopSound() {
zero_signal();
is_note_triggered = false;
untrigger_note_on_effects();
} }
void Synth::AddOscillator() { void Synth::AddOscillator() {
m_oscillators.push_back( m_oscillators.push_back(
new Oscillator(OscillatorType::Sine, 440.f, VOLUME)); new Oscillator(OscillatorType::Sine, 440.f, VOLUME));
} }
void Synth::AddEffect(Effect* fx) { m_effects.push_back(fx); }