wip: adsr with ramp

This commit is contained in:
2023-09-04 22:30:37 +04:00
parent 73aae9a490
commit d883bbbf12
13 changed files with 204 additions and 77 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}"
}
]
}

6
.vscode/tasks.json vendored
View File

@@ -9,11 +9,7 @@
"-fansi-escape-codes", "-fansi-escape-codes",
"-g", "-g",
"${file}", "${file}",
"${fileDirname}/utils.c", "$(find ${fileDirname}/src -type f -iregex '.*\\.cpp')",
"${fileDirname}/ring_buffer.c",
"${fileDirname}/oscillator.c",
"${fileDirname}/parser.c",
"${fileDirname}/export.c",
"-lm", "-lm",
"-lraylib", "-lraylib",
"-o", "-o",

BIN
docs/ADSR States.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

BIN
docs/Attack Formula.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "Effect.h" #include "Effect.h"
#include <cstddef> #include <cstddef>
#include "Ramp.h"
struct ADSRParameters { struct ADSRParameters {
float attack_time; // Attack time in seconds float attack_time; // Attack time in seconds
@@ -9,23 +10,26 @@ struct ADSRParameters {
float release_time; float release_time;
}; };
enum ADSRState { Attack, Decay, Sustain, Release }; enum ADSRState { Off, Attack, Decay, Sustain, Release };
class ADSR : public Effect { class ADSR : public Effect {
private: private:
ADSRParameters m_parameters; ADSRParameters m_parameters;
ADSRState m_state; ADSRState m_state;
std::size_t m_counter; Ramp *m_ramp;
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);
void process_sample(float* sample);
bool is_attack_elapsed();
bool is_decay_elapsed();
bool is_release_elapsed();
void recheck_state();
public: public:
ADSR(/* args */); ADSR(/* args */);
ADSR(ADSRParameters param); ADSR(ADSRParameters param);
~ADSR(); ~ADSR();
void RetriggerState() override; void OnSetNote() override;
void OnUnsetNote() override;
//void RetriggerState() override;
void Process(std::vector<float>& samples) override; void Process(std::vector<float>& samples) override;
void Reset(); void Reset();
}; };

View File

@@ -6,6 +6,8 @@ class Effect {
public: public:
Effect(/* args */){}; Effect(/* args */){};
~Effect(){}; ~Effect(){};
virtual void RetriggerState(){}; virtual void OnSetNote(){};
virtual void OnUnsetNote(){};
//virtual void RetriggerState(){};
virtual void Process(std::vector<float>& samples){}; virtual void Process(std::vector<float>& samples){};
}; };

17
inc/Ramp.h Normal file
View File

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

View File

@@ -11,12 +11,14 @@ class Synth {
private: private:
bool is_note_triggered; bool is_note_triggered;
std::vector<Oscillator*> m_oscillators; std::vector<Oscillator*> m_oscillators;
Adder m_adder;
std::vector<Effect*> m_effects; 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;
void zero_signal();
void get_note(); void get_note();
void trigger_note_on_effects();
void untrigger_note_on_effects();
void apply_effects(); void apply_effects();
public: public:

View File

@@ -6,58 +6,96 @@ ADSR::ADSR(/* args */) {
m_parameters.attack_time = 1.f; m_parameters.attack_time = 1.f;
m_parameters.decay_time = 0.3f; m_parameters.decay_time = 0.3f;
m_parameters.sustain_level = 0.6f; m_parameters.sustain_level = 0.6f;
m_parameters.release_time = 1.0f; m_parameters.release_time = 0.8f;
m_counter = 0; m_ramp = new Ramp(0, SAMPLE_RATE);
} }
ADSR::ADSR(ADSRParameters param) { ADSR::ADSR(ADSRParameters param) {
m_parameters = 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, bool ADSR::is_attack_elapsed() {
std::size_t release_samples) { return m_state == Attack && m_ramp->IsCompleted();
if (m_counter < attack_samples) { }
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; m_state = Attack;
} else if (m_counter >= attack_samples && break;
m_counter < attack_samples + decay_samples) { case Attack:
if (is_attack_elapsed()) {
m_state = Decay; m_state = Decay;
} else if (m_counter >= attack_samples + decay_samples) { m_ramp->RampTo(m_parameters.sustain_level, m_parameters.decay_time);
}
break;
case Decay:
if (is_decay_elapsed()) {
m_state = Sustain; m_state = Sustain;
} }
} break;
case Release:
void ADSR::process_sample(float* sample, std::size_t attack_samples, if (is_release_elapsed()) {
std::size_t decay_samples, m_state = Off;
std::size_t release_samples) { }
break;
set_state(attack_samples, decay_samples, release_samples); default:
if (m_state == Attack) { break;
(*sample) = (*sample) * ((float)(1.f / attack_samples) * m_counter);
} else if (m_state == Decay) {
} }
m_counter++;
// todo: release state on note off (in reset function?)
} }
void ADSR::RetriggerState() { void ADSR::process_sample(float* sample) {
m_counter = 0; 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() {
if (m_state == Off) {
m_state = Attack; 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) { void ADSR::Process(std::vector<float>& samples) {
const std::size_t attack_samples = write_log("ADSR State: %d\n", m_state);
(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);
for (std::size_t i = 0; i < samples.size(); i++) { for (std::size_t i = 0; i < samples.size(); i++) {
process_sample(&samples[i], attack_samples, decay_samples, recheck_state();
release_samples); process_sample(&samples[i]);
} }
write_log("Processed samples: %zu \n", m_counter);
} }

View File

@@ -85,6 +85,12 @@ bool Application::detect_note_pressed(Note* note) {
return is_pressed == 1; 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 // 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)) {
@@ -92,14 +98,15 @@ void Application::update_on_note_input() {
if (!m_synth.GetIsNoteTriggered()){ if (!m_synth.GetIsNoteTriggered()){
m_synth.TriggerNote((*m_current_note)); m_synth.TriggerNote((*m_current_note));
} }
m_synth.ProduceSound();
//m_sound_played_count = 0; //m_sound_played_count = 0;
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 { else if (is_note_up()) {
m_synth.StopSound(); m_synth.StopSound();
} }
// will produce 0 signal if ADSR is in off state
m_synth.ProduceSound();
} }
// Play ring-buffered audio // Play ring-buffered audio
@@ -109,7 +116,7 @@ void Application::play_buffered_audio() {
update_on_note_input(); update_on_note_input();
UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(), STREAM_BUFFER_SIZE); UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(), STREAM_BUFFER_SIZE);
const float audio_freme_duration = GetTime() - audio_frame_start_time; 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)));
} }
} }

29
src/Ramp.cpp Normal file
View File

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

View File

@@ -2,7 +2,14 @@
#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");
@@ -105,7 +112,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,7 +123,7 @@ 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;
@@ -124,15 +131,15 @@ void Renderer::draw_oscillators_panels(
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;
} }
*/
} }
} }

View File

@@ -7,10 +7,8 @@
Synth::Synth(/* args */) { Synth::Synth(/* args */) {
AddOscillator(); AddOscillator();
AddEffect(new ADSR()); AddEffect(new ADSR());
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { m_out_signal.reserve(STREAM_BUFFER_SIZE);
float sample = 0.0f; zero_signal();
m_out_signal.push_back(sample);
}
} }
Synth::~Synth() { Synth::~Synth() {
@@ -19,26 +17,37 @@ Synth::~Synth() {
m_out_signal.clear(); m_out_signal.clear();
} }
void Synth::get_note() { void Synth::zero_signal() {
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
float sample = 0.0f; float sample = 0.0f;
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
m_out_signal[i] = sample; 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); Adder::SumOscillators(m_oscillators, m_out_signal);
} }
void Synth::apply_effects() { void Synth::apply_effects() {
for (Effect* effect : m_effects) { for (Effect* effect : m_effects) {
// maybe not here
//effect->RetriggerState();
effect->Process(m_out_signal); 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) { void Synth::TriggerNote(Note input) {
float length = 1.f / input.length;
int semitone_shift = KeyBoard::GetSemitoneShift(input.name); int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
float hz = KeyBoard::GetHzBySemitone(semitone_shift); float hz = KeyBoard::GetHzBySemitone(semitone_shift);
@@ -47,6 +56,7 @@ void Synth::TriggerNote(Note input) {
osc->SetFreq(hz); osc->SetFreq(hz);
} }
is_note_triggered = true; is_note_triggered = true;
trigger_note_on_effects();
} }
void Synth::ProduceSound() { void Synth::ProduceSound() {
@@ -54,12 +64,11 @@ void Synth::ProduceSound() {
apply_effects(); apply_effects();
} }
// todo: rename to something like untrigger note
void Synth::StopSound() { void Synth::StopSound() {
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { zero_signal();
float sample = 0.0f;
m_out_signal[i] = sample;
}
is_note_triggered = false; is_note_triggered = false;
untrigger_note_on_effects();
} }
void Synth::AddOscillator() { void Synth::AddOscillator() {