[feature]: ADSR #15

Merged
e1lama merged 13 commits from feature/adsr into master 2023-09-06 08:29:46 +00:00
17 changed files with 354 additions and 114 deletions
Showing only changes of commit d883bbbf12 - Show all commits

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",
"-g",
"${file}",
"${fileDirname}/utils.c",
"${fileDirname}/ring_buffer.c",
"${fileDirname}/oscillator.c",
"${fileDirname}/parser.c",
"${fileDirname}/export.c",
"$(find ${fileDirname}/src -type f -iregex '.*\\.cpp')",
"-lm",
"-lraylib",
"-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
#include "Effect.h"
#include <cstddef>
#include "Ramp.h"
struct ADSRParameters {
float attack_time; // Attack time in seconds
@@ -9,23 +10,26 @@ struct ADSRParameters {
float release_time;
};
enum ADSRState { Attack, Decay, Sustain, Release };
enum ADSRState { Off, Attack, Decay, Sustain, Release };
class ADSR : public Effect {
private:
ADSRParameters m_parameters;
ADSRState m_state;
std::size_t m_counter;
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);
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 RetriggerState() override;
void OnSetNote() override;
void OnUnsetNote() override;
//void RetriggerState() override;
void Process(std::vector<float>& samples) override;
void Reset();
};

View File

@@ -6,6 +6,8 @@ class Effect {
public:
Effect(/* args */){};
~Effect(){};
virtual void RetriggerState(){};
virtual void OnSetNote(){};
virtual void OnUnsetNote(){};
//virtual void RetriggerState(){};
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:
bool is_note_triggered;
std::vector<Oscillator*> m_oscillators;
Adder m_adder;
std::vector<Effect*> m_effects;
// OscillatorUI* ui_oscillators;
// Note m_current_note;
std::vector<float> m_out_signal;
void zero_signal();
void get_note();
void trigger_note_on_effects();
void untrigger_note_on_effects();
void apply_effects();
public:

View File

@@ -6,58 +6,96 @@ ADSR::ADSR(/* args */) {
m_parameters.attack_time = 1.f;
m_parameters.decay_time = 0.3f;
m_parameters.sustain_level = 0.6f;
m_parameters.release_time = 1.0f;
m_counter = 0;
m_parameters.release_time = 0.8f;
m_ramp = new Ramp(0, SAMPLE_RATE);
}
ADSR::ADSR(ADSRParameters 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,
std::size_t release_samples) {
if (m_counter < attack_samples) {
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 Off:
m_state = Attack;
} else if (m_counter >= attack_samples &&
m_counter < attack_samples + decay_samples) {
m_state = Decay;
} else if (m_counter >= attack_samples + decay_samples) {
m_state = Sustain;
break;
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, std::size_t attack_samples,
std::size_t decay_samples,
std::size_t release_samples) {
set_state(attack_samples, decay_samples, release_samples);
if (m_state == Attack) {
(*sample) = (*sample) * ((float)(1.f / attack_samples) * m_counter);
} else if (m_state == Decay) {
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();
}
m_counter++;
// todo: release state on note off (in reset function?)
}
void ADSR::RetriggerState() {
m_counter = 0;
m_state = Attack;
void ADSR::OnSetNote() {
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) {
const std::size_t attack_samples =
(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);
write_log("ADSR State: %d\n", m_state);
for (std::size_t i = 0; i < samples.size(); i++) {
process_sample(&samples[i], attack_samples, decay_samples,
release_samples);
recheck_state();
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;
}
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
void Application::update_on_note_input() {
if (detect_note_pressed(m_current_note)) {
@@ -92,14 +98,15 @@ void Application::update_on_note_input() {
if (!m_synth.GetIsNoteTriggered()){
m_synth.TriggerNote((*m_current_note));
}
m_synth.ProduceSound();
//m_sound_played_count = 0;
write_log("Note played: %s\n", m_current_note->name.c_str());
}
else {
else if (is_note_up()) {
m_synth.StopSound();
}
// will produce 0 signal if ADSR is in off state
m_synth.ProduceSound();
}
// Play ring-buffered audio
@@ -109,7 +116,7 @@ void Application::play_buffered_audio() {
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)));
//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
#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");
@@ -105,7 +112,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,7 +123,7 @@ 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;
@@ -124,15 +131,15 @@ void Renderer::draw_oscillators_panels(
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;
// 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;
}
*/
}
}

View File

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