From c4084d30609a7fe7edaa3d16c3f87985178c692d Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 15:18:13 -0400 Subject: [PATCH 01/10] biquad struct --- include/filter.h | 19 +++++++++++++ src/filter.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 include/filter.h create mode 100644 src/filter.c diff --git a/include/filter.h b/include/filter.h new file mode 100644 index 0000000..59fd437 --- /dev/null +++ b/include/filter.h @@ -0,0 +1,19 @@ +#pragma once + +typedef struct { + float b0, b1, b2; // Numerator coefficients + float a1, a2; // Denominator coefficients + float z1, z2; // State variables +} BiquadFilter; + +// Initialize the biquad filter +void Biquad_init(BiquadFilter *filter, float b0, float b1, float b2, float a1, float a2); + +// Process a single sample through the biquad filter +float Biquad_process(BiquadFilter *filter, float input); + +// Configure filter as a low-pass +void Biquad_design_lowpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q); + +// Configure filter as a high-pass +void Biquad_design_highpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q); \ No newline at end of file diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 0000000..f4ccf02 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,69 @@ +#include +#include "filter.h" + +// Initialize the biquad filter +void Biquad_init(BiquadFilter *filter, float b0, float b1, float b2, float a1, float a2) { + filter->b0 = b0; + filter->b1 = b1; + filter->b2 = b2; + filter->a1 = a1; + filter->a2 = a2; + filter->z1 = 0.0f; + filter->z2 = 0.0f; +} + +// Process a single sample through the biquad filter +float Biquad_process(BiquadFilter *filter, float input) { + float output = filter->b0 * input + filter->z1; + filter->z1 = filter->b1 * input + filter->z2 - filter->a1 * output; + filter->z2 = filter->b2 * input - filter->a2 * output; + return output; +} + +// Configure filter as a low-pass +void Biquad_design_lowpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q) { + float omega = 2.0f * M_PI * cutoff / sampleRate; + float sn = sinf(omega); + float cs = cosf(omega); + float alpha = sn / (2.0f * Q); + + float b0 = (1.0f - cs) / 2.0f; + float b1 = 1.0f - cs; + float b2 = (1.0f - cs) / 2.0f; + float a0 = 1.0f + alpha; + float a1 = -2.0f * cs; + float a2 = 1.0f - alpha; + + filter->b0 = b0 / a0; + filter->b1 = b1 / a0; + filter->b2 = b2 / a0; + filter->a1 = a1 / a0; + filter->a2 = a2 / a0; + // Reset state variables + filter->z1 = 0.0f; + filter->z2 = 0.0f; +} + +// Configure filter as a high-pass +void Biquad_design_highpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q) { + float omega = 2.0f * M_PI * cutoff / sampleRate; + float sn = sinf(omega); + float cs = cosf(omega); + float alpha = sn / (2.0f * Q); + + float b0 = (1.0f + cs) / 2.0f; + float b1 = -(1.0f + cs); + float b2 = (1.0f + cs) / 2.0f; + float a0 = 1.0f + alpha; + float a1 = -2.0f * cs; + float a2 = 1.0f - alpha; + + filter->b0 = b0 / a0; + filter->b1 = b1 / a0; + filter->b2 = b2 / a0; + filter->a1 = a1 / a0; + filter->a2 = a2 / a0; + // Reset state variables + filter->z1 = 0.0f; + filter->z2 = 0.0f; +} \ No newline at end of file From 639269ac66e5b56f4861febb7644819ed1cd4591 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 15:18:19 -0400 Subject: [PATCH 02/10] rough use of biquad in main --- src/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.c b/src/main.c index 9919218..669b55b 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ #include "config.h" #include "state.h" +#include "filter.h" #include #include #include @@ -14,6 +15,8 @@ static float previewBuffer[PREVIEW_SIZE] = {0}; static int previewIndex = 0; static pthread_mutex_t preview_mutex = PTHREAD_MUTEX_INITIALIZER; +BiquadFilter lowpass_filter; + static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { (void)frame_count_min; @@ -33,6 +36,7 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m float sample = 0.0f; pthread_mutex_lock(&state_mutex); sample = State_mix_sample(state); + sample = Biquad_process(&lowpass_filter, sample); pthread_mutex_unlock(&state_mutex); // Write sample to the preview buffer (using trylock to minimize blocking) if (pthread_mutex_trylock(&preview_mutex) == 0) { @@ -113,6 +117,8 @@ int main(void) { const double base_freq = 130.81; const double semitone_ratio = pow(2.0, 1.0 / 12.0); + Biquad_design_lowpass(&lowpass_filter, 44100 , 10000, 1); + // Initialize SoundIo. struct SoundIo *soundio = soundio_create(); if (!soundio) { From bf43057f9ab4ed8986cb90b76b5493176e38db29 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 15:35:06 -0400 Subject: [PATCH 03/10] lpf struct --- include/filter.h | 17 ++++++++++------- include/state.h | 2 ++ src/filter.c | 25 ++++++++++++++++++------- src/main.c | 7 +++---- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/include/filter.h b/include/filter.h index 59fd437..d37786c 100644 --- a/include/filter.h +++ b/include/filter.h @@ -6,14 +6,17 @@ typedef struct { float z1, z2; // State variables } BiquadFilter; -// Initialize the biquad filter void Biquad_init(BiquadFilter *filter, float b0, float b1, float b2, float a1, float a2); - -// Process a single sample through the biquad filter float Biquad_process(BiquadFilter *filter, float input); +void Biquad_design_lowpass(BiquadFilter *filter, float cutoff, float Q); +void Biquad_design_highpass(BiquadFilter *filter, float cutoff, float Q); -// Configure filter as a low-pass -void Biquad_design_lowpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q); +typedef struct { + BiquadFilter biquad; + float cutoff; + float q; +} LowpassFilter; -// Configure filter as a high-pass -void Biquad_design_highpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q); \ No newline at end of file +float Lowpass_process(LowpassFilter *filter, float input); +void Lowpass_set_cutoff(LowpassFilter *filter, float cutoff); +void Lowpass_set_q(LowpassFilter *filter, float Q); \ No newline at end of file diff --git a/include/state.h b/include/state.h index 0555a03..3af755a 100644 --- a/include/state.h +++ b/include/state.h @@ -1,6 +1,7 @@ #pragma once #include "osc.h" #include "wavetable.h" +#include "filter.h" // Fixed constants. extern const int NUM_OSCS; // oscillators per voice (e.g., 4) @@ -12,6 +13,7 @@ typedef struct { Wavetable *wts; // shared array of NUM_WAVETABLES wavetables float *wt_levels; // per-wavetable level multipliers; array of NUM_WAVETABLES floats int *active; // for each voice (size NUM_VOICES), 1 if active, 0 if not + BiquadFilter lowpass_filter; } State; State *State_create(void); diff --git a/src/filter.c b/src/filter.c index f4ccf02..bf27490 100644 --- a/src/filter.c +++ b/src/filter.c @@ -1,5 +1,6 @@ #include #include "filter.h" +#include "config.h" // Initialize the biquad filter void Biquad_init(BiquadFilter *filter, float b0, float b1, float b2, float a1, float a2) { @@ -12,7 +13,6 @@ void Biquad_init(BiquadFilter *filter, float b0, float b1, float b2, float a1, f filter->z2 = 0.0f; } -// Process a single sample through the biquad filter float Biquad_process(BiquadFilter *filter, float input) { float output = filter->b0 * input + filter->z1; filter->z1 = filter->b1 * input + filter->z2 - filter->a1 * output; @@ -20,9 +20,8 @@ float Biquad_process(BiquadFilter *filter, float input) { return output; } -// Configure filter as a low-pass -void Biquad_design_lowpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q) { - float omega = 2.0f * M_PI * cutoff / sampleRate; +void Biquad_design_lowpass(BiquadFilter *filter, float cutoff, float Q) { + float omega = 2.0f * M_PI * cutoff / SAMPLE_RATE; float sn = sinf(omega); float cs = cosf(omega); float alpha = sn / (2.0f * Q); @@ -44,9 +43,8 @@ void Biquad_design_lowpass(BiquadFilter *filter, float sampleRate, float cutoff, filter->z2 = 0.0f; } -// Configure filter as a high-pass -void Biquad_design_highpass(BiquadFilter *filter, float sampleRate, float cutoff, float Q) { - float omega = 2.0f * M_PI * cutoff / sampleRate; +void Biquad_design_highpass(BiquadFilter *filter, float cutoff, float Q) { + float omega = 2.0f * M_PI * cutoff / SAMPLE_RATE; float sn = sinf(omega); float cs = cosf(omega); float alpha = sn / (2.0f * Q); @@ -66,4 +64,17 @@ void Biquad_design_highpass(BiquadFilter *filter, float sampleRate, float cutoff // Reset state variables filter->z1 = 0.0f; filter->z2 = 0.0f; +} + +float Lowpass_process(LowpassFilter *filter, float input) { + return Biquad_process(&filter->biquad, input); +} + +void Lowpass_set_cutoff(LowpassFilter *filter, float cutoff) { + filter->cutoff = cutoff; + Biquad_design_lowpass(&filter->biquad, filter->cutoff, filter->q); +} + +void Lowpass_set_q(LowpassFilter *filter, float Q) { + } \ No newline at end of file diff --git a/src/main.c b/src/main.c index 669b55b..9b004ff 100644 --- a/src/main.c +++ b/src/main.c @@ -15,8 +15,6 @@ static float previewBuffer[PREVIEW_SIZE] = {0}; static int previewIndex = 0; static pthread_mutex_t preview_mutex = PTHREAD_MUTEX_INITIALIZER; -BiquadFilter lowpass_filter; - static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { (void)frame_count_min; @@ -36,7 +34,7 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m float sample = 0.0f; pthread_mutex_lock(&state_mutex); sample = State_mix_sample(state); - sample = Biquad_process(&lowpass_filter, sample); + sample = Biquad_process(&state->lowpass_filter, sample); pthread_mutex_unlock(&state_mutex); // Write sample to the preview buffer (using trylock to minimize blocking) if (pthread_mutex_trylock(&preview_mutex) == 0) { @@ -117,9 +115,10 @@ int main(void) { const double base_freq = 130.81; const double semitone_ratio = pow(2.0, 1.0 / 12.0); - Biquad_design_lowpass(&lowpass_filter, 44100 , 10000, 1); + Biquad_design_lowpass(&state->lowpass_filter, SAMPLE_RATE, 10000, 1); // Initialize SoundIo. + struct SoundIo *soundio = soundio_create(); if (!soundio) { fprintf(stderr, "Out of memory.\n"); From 6e9b616f44534e7e9611dd935b6d47983fa171ce Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 16:32:25 -0400 Subject: [PATCH 04/10] refactor lpf into state --- include/filter.h | 3 ++- include/state.h | 2 +- src/filter.c | 11 +++++++++-- src/main.c | 4 +--- src/state.c | 3 +++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/filter.h b/include/filter.h index d37786c..a668988 100644 --- a/include/filter.h +++ b/include/filter.h @@ -17,6 +17,7 @@ typedef struct { float q; } LowpassFilter; +void Lowpass_init(LowpassFilter *filter); float Lowpass_process(LowpassFilter *filter, float input); void Lowpass_set_cutoff(LowpassFilter *filter, float cutoff); -void Lowpass_set_q(LowpassFilter *filter, float Q); \ No newline at end of file +void Lowpass_set_q(LowpassFilter *filter, float q); \ No newline at end of file diff --git a/include/state.h b/include/state.h index 3af755a..7fb9d06 100644 --- a/include/state.h +++ b/include/state.h @@ -13,7 +13,7 @@ typedef struct { Wavetable *wts; // shared array of NUM_WAVETABLES wavetables float *wt_levels; // per-wavetable level multipliers; array of NUM_WAVETABLES floats int *active; // for each voice (size NUM_VOICES), 1 if active, 0 if not - BiquadFilter lowpass_filter; + LowpassFilter lpf; } State; State *State_create(void); diff --git a/src/filter.c b/src/filter.c index bf27490..8093a8c 100644 --- a/src/filter.c +++ b/src/filter.c @@ -66,6 +66,12 @@ void Biquad_design_highpass(BiquadFilter *filter, float cutoff, float Q) { filter->z2 = 0.0f; } +void Lowpass_init(LowpassFilter *filter) { + filter->cutoff = 20000; + filter->q = 1; + Biquad_design_lowpass(&filter->biquad, filter->cutoff, filter->q); +} + float Lowpass_process(LowpassFilter *filter, float input) { return Biquad_process(&filter->biquad, input); } @@ -75,6 +81,7 @@ void Lowpass_set_cutoff(LowpassFilter *filter, float cutoff) { Biquad_design_lowpass(&filter->biquad, filter->cutoff, filter->q); } -void Lowpass_set_q(LowpassFilter *filter, float Q) { - +void Lowpass_set_q(LowpassFilter *filter, float q) { + filter->q = q; + Biquad_design_lowpass(&filter->biquad, filter->cutoff, filter->q); } \ No newline at end of file diff --git a/src/main.c b/src/main.c index 9b004ff..7be7299 100644 --- a/src/main.c +++ b/src/main.c @@ -34,7 +34,7 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m float sample = 0.0f; pthread_mutex_lock(&state_mutex); sample = State_mix_sample(state); - sample = Biquad_process(&state->lowpass_filter, sample); + sample = Lowpass_process(&state->lpf, sample); pthread_mutex_unlock(&state_mutex); // Write sample to the preview buffer (using trylock to minimize blocking) if (pthread_mutex_trylock(&preview_mutex) == 0) { @@ -115,8 +115,6 @@ int main(void) { const double base_freq = 130.81; const double semitone_ratio = pow(2.0, 1.0 / 12.0); - Biquad_design_lowpass(&state->lowpass_filter, SAMPLE_RATE, 10000, 1); - // Initialize SoundIo. struct SoundIo *soundio = soundio_create(); diff --git a/src/state.c b/src/state.c index a885882..4e0a3f8 100644 --- a/src/state.c +++ b/src/state.c @@ -1,5 +1,6 @@ #include "state.h" #include "config.h" +#include "filter.h" #include #include @@ -32,6 +33,8 @@ State *State_create(void) { state->wts[WAVEFORM_SAW] = *Wavetable_create(WAVEFORM_SAW, TABLE_SIZE); state->wts[WAVEFORM_SQUARE] = *Wavetable_create(WAVEFORM_SQUARE, TABLE_SIZE); state->wts[WAVEFORM_TRIANGLE] = *Wavetable_create(WAVEFORM_TRIANGLE, TABLE_SIZE); + + Lowpass_init(&state->lpf); return state; } From a39d177d3badc26f40407f936f425842595d39ed Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 16:41:20 -0400 Subject: [PATCH 05/10] clamp functions --- include/config.h | 3 +++ src/config.c | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/config.c diff --git a/include/config.h b/include/config.h index 03cf54a..7e0d4a2 100644 --- a/include/config.h +++ b/include/config.h @@ -5,3 +5,6 @@ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif + +float clamp_SR(float freq); // SR = sample rate +float clamp_unit(float value); \ No newline at end of file diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..037c5c1 --- /dev/null +++ b/src/config.c @@ -0,0 +1,17 @@ +#include "config.h" + +float clamp_SR(float freq) { + if (freq < 0.0f) + return 0.0f; + if (freq > SAMPLE_RATE / 2.0f) + return SAMPLE_RATE / 2.0f; + return freq; +} + +float clamp_unit(float value) { + if (value < 0.0f) + return 0.0f; + if (value > 1.0f) + return 1.0f; + return value; +} \ No newline at end of file From 20624f5f6e02ffd0c0774a7073c50889f44ce77e Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 16:41:36 -0400 Subject: [PATCH 06/10] add cutoff and q controls --- src/main.c | 114 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/src/main.c b/src/main.c index 7be7299..54f1047 100644 --- a/src/main.c +++ b/src/main.c @@ -179,59 +179,77 @@ int main(void) { while (!WindowShouldClose()) { pthread_mutex_lock(&state_mutex); - // Process white keys. - for (int i = 0; i < NUM_WHITE_KEYS; i++) { - if (IsKeyDown(white_keys[i].key)) { - if (active_voice[i] == -1) { - double freq = base_freq * pow(semitone_ratio, white_keys[i].semitone_offset); - active_voice[i] = i; - State_set_note(state, active_voice[i], freq); - } - } else { - if (active_voice[i] != -1) { - State_clear_voice(state, active_voice[i]); - active_voice[i] = -1; + { + // Process white keys. + for (int i = 0; i < NUM_WHITE_KEYS; i++) { + if (IsKeyDown(white_keys[i].key)) { + if (active_voice[i] == -1) { + double freq = base_freq * pow(semitone_ratio, white_keys[i].semitone_offset); + active_voice[i] = i; + State_set_note(state, active_voice[i], freq); + } + } else { + if (active_voice[i] != -1) { + State_clear_voice(state, active_voice[i]); + active_voice[i] = -1; + } } } - } - // Process black keys. - for (int i = 0; i < NUM_BLACK_KEYS; i++) { - int voice_index = i + NUM_WHITE_KEYS; - if (IsKeyDown(black_keys[i].key)) { - if (active_voice[voice_index] == -1) { - double freq = base_freq * pow(semitone_ratio, black_keys[i].semitone_offset); - active_voice[voice_index] = voice_index; - State_set_note(state, active_voice[voice_index], freq); - } - } else { - if (active_voice[voice_index] != -1) { - State_clear_voice(state, active_voice[voice_index]); - active_voice[voice_index] = -1; + // Process black keys. + for (int i = 0; i < NUM_BLACK_KEYS; i++) { + int voice_index = i + NUM_WHITE_KEYS; + if (IsKeyDown(black_keys[i].key)) { + if (active_voice[voice_index] == -1) { + double freq = base_freq * pow(semitone_ratio, black_keys[i].semitone_offset); + active_voice[voice_index] = voice_index; + State_set_note(state, active_voice[voice_index], freq); + } + } else { + if (active_voice[voice_index] != -1) { + State_clear_voice(state, active_voice[voice_index]); + active_voice[voice_index] = -1; + } } } + // Process wavetable level adjustments. + if (IsKeyPressed(KEY_ONE)) + state->wt_levels[0] -= 0.1f; + if (IsKeyPressed(KEY_TWO)) + state->wt_levels[0] += 0.1f; + if (IsKeyPressed(KEY_THREE)) + state->wt_levels[1] -= 0.1f; + if (IsKeyPressed(KEY_FOUR)) + state->wt_levels[1] += 0.1f; + if (IsKeyPressed(KEY_FIVE)) + state->wt_levels[2] -= 0.1f; + if (IsKeyPressed(KEY_SIX)) + state->wt_levels[2] += 0.1f; + if (IsKeyPressed(KEY_SEVEN)) + state->wt_levels[3] -= 0.1f; + if (IsKeyPressed(KEY_EIGHT)) + state->wt_levels[3] += 0.1f; + if (IsKeyPressed(KEY_MINUS)) { + printf("-: %f\n", state->lpf.cutoff); + Lowpass_set_cutoff(&state->lpf, clamp_SR(state->lpf.cutoff * 0.9)); + } + if (IsKeyPressed(KEY_EQUAL)) { + printf("=: %f\n", state->lpf.cutoff); + Lowpass_set_cutoff(&state->lpf, clamp_SR(state->lpf.cutoff * 1.1)); + } + if (IsKeyPressed(KEY_LEFT_BRACKET)) { + printf("[: %f\n", state->lpf.q); + Lowpass_set_q(&state->lpf, clamp_unit(state->lpf.q + 0.1)); + } + if (IsKeyPressed(KEY_RIGHT_BRACKET)) { + printf("]: %f\n", state->lpf.q); + Lowpass_set_q(&state->lpf, clamp_unit(state->lpf.q - 0.1)); + } + // Clamp levels to [0.0, 1.0] + state->wt_levels[0] = fmaxf(0.0f, fminf(state->wt_levels[0], 1.0f)); + state->wt_levels[1] = fmaxf(0.0f, fminf(state->wt_levels[1], 1.0f)); + state->wt_levels[2] = fmaxf(0.0f, fminf(state->wt_levels[2], 1.0f)); + state->wt_levels[3] = fmaxf(0.0f, fminf(state->wt_levels[3], 1.0f)); } - // Process wavetable level adjustments. - if (IsKeyPressed(KEY_ONE)) - state->wt_levels[0] += 0.1f; - if (IsKeyPressed(KEY_TWO)) - state->wt_levels[0] -= 0.1f; - if (IsKeyPressed(KEY_THREE)) - state->wt_levels[1] += 0.1f; - if (IsKeyPressed(KEY_FOUR)) - state->wt_levels[1] -= 0.1f; - if (IsKeyPressed(KEY_FIVE)) - state->wt_levels[2] += 0.1f; - if (IsKeyPressed(KEY_SIX)) - state->wt_levels[2] -= 0.1f; - if (IsKeyPressed(KEY_SEVEN)) - state->wt_levels[3] += 0.1f; - if (IsKeyPressed(KEY_EIGHT)) - state->wt_levels[3] -= 0.1f; - // Clamp levels to [0.0, 1.0] - state->wt_levels[0] = fmaxf(0.0f, fminf(state->wt_levels[0], 1.0f)); - state->wt_levels[1] = fmaxf(0.0f, fminf(state->wt_levels[1], 1.0f)); - state->wt_levels[2] = fmaxf(0.0f, fminf(state->wt_levels[2], 1.0f)); - state->wt_levels[3] = fmaxf(0.0f, fminf(state->wt_levels[3], 1.0f)); pthread_mutex_unlock(&state_mutex); BeginDrawing(); From 44667243507aaeada74b3fc7023e166079940928 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 18:06:16 -0400 Subject: [PATCH 07/10] scale func --- include/config.h | 3 ++- src/config.c | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 7e0d4a2..0e25175 100644 --- a/include/config.h +++ b/include/config.h @@ -7,4 +7,5 @@ #endif float clamp_SR(float freq); // SR = sample rate -float clamp_unit(float value); \ No newline at end of file +float clamp_unit(float value); +float scale_unit(float value, float min, float max); \ No newline at end of file diff --git a/src/config.c b/src/config.c index 037c5c1..946fb3b 100644 --- a/src/config.c +++ b/src/config.c @@ -14,4 +14,8 @@ float clamp_unit(float value) { if (value > 1.0f) return 1.0f; return value; +} + +float scale_unit(float value, float min, float max) { + return value / (max - min); } \ No newline at end of file From 042a6e90534275101240e6f8943ac8de659f69e1 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 18:06:36 -0400 Subject: [PATCH 08/10] DrawSlider --- include/graphics.h | 5 +++++ src/graphics.c | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 include/graphics.h create mode 100644 src/graphics.c diff --git a/include/graphics.h b/include/graphics.h new file mode 100644 index 0000000..deb290f --- /dev/null +++ b/include/graphics.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +void DrawSlider(int x, int y, int width, int height, float level, const char *label); \ No newline at end of file diff --git a/src/graphics.c b/src/graphics.c new file mode 100644 index 0000000..f1fc66e --- /dev/null +++ b/src/graphics.c @@ -0,0 +1,22 @@ +#include "graphics.h" +#include + +void DrawSlider(int x, int y, int width, int height, float level, const char *label) { + // Clamp level between 0 and 1 + if (level < 0.0f) level = 0.0f; + if (level > 1.0f) level = 1.0f; + + // Draw slider outline + DrawRectangleLines(x, y, width, height, BLACK); + + // Compute filled height based on level + int fillHeight = (int)(level * height); + DrawRectangle(x, y + (height - fillHeight), width, fillHeight, GREEN); + + // Draw label underneath the slider and display the level value above the slider + DrawText(label, x, y + height, 20, DARKGRAY); + + char levelText[16]; + // sprintf(levelText, "%.1f", level); + // DrawText(levelText, x, y - 20, 20, DARKGRAY); +} From cf773e342989fbf1d35d5cb84051291d37f49748 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 18:06:44 -0400 Subject: [PATCH 09/10] use drawslider for everything + cutoff + q --- src/main.c | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/main.c b/src/main.c index 54f1047..de26bf0 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,7 @@ #include "config.h" #include "state.h" #include "filter.h" +#include "graphics.h" #include #include #include @@ -238,11 +239,11 @@ int main(void) { } if (IsKeyPressed(KEY_LEFT_BRACKET)) { printf("[: %f\n", state->lpf.q); - Lowpass_set_q(&state->lpf, clamp_unit(state->lpf.q + 0.1)); + Lowpass_set_q(&state->lpf, clamp_unit(state->lpf.q - 0.1)+ 0.01); } if (IsKeyPressed(KEY_RIGHT_BRACKET)) { printf("]: %f\n", state->lpf.q); - Lowpass_set_q(&state->lpf, clamp_unit(state->lpf.q - 0.1)); + Lowpass_set_q(&state->lpf, clamp_unit(state->lpf.q + 0.1) + 0.01); } // Clamp levels to [0.0, 1.0] state->wt_levels[0] = fmaxf(0.0f, fminf(state->wt_levels[0], 1.0f)); @@ -290,27 +291,18 @@ int main(void) { const int bar_spacing = 20; int bar_x = 10; int bar_y = GetScreenHeight() - bar_height - 20; - for (int i = 0; i < NUM_WAVETABLES; i++) { - DrawRectangleLines(bar_x, bar_y, bar_width, bar_height, BLACK); - int fill_height = (int)(state->wt_levels[i] * bar_height); - DrawRectangle(bar_x, bar_y + (bar_height - fill_height), bar_width, fill_height, GREEN); - // Draw waveform label. - char name_text[16]; - if (i == 0) - sprintf(name_text, "SIN"); - else if (i == 1) - sprintf(name_text, "SAW"); - else if (i == 2) - sprintf(name_text, "SQR"); - else if (i == 3) - sprintf(name_text, "TRI"); - DrawText(name_text, bar_x, bar_y + bar_height, 20, DARKGRAY); - // Draw level text. - char level_text[16]; - sprintf(level_text, "%.1f", state->wt_levels[i]); - DrawText(level_text, bar_x, bar_y - 20, 20, DARKGRAY); - bar_x += bar_width + bar_spacing; - } + + DrawSlider(bar_x, bar_y, bar_width, bar_height, state->wt_levels[0], "SIN"); + bar_x += bar_width + bar_spacing; + DrawSlider(bar_x, bar_y, bar_width, bar_height, state->wt_levels[1], "SAW"); + bar_x += bar_width + bar_spacing; + DrawSlider(bar_x, bar_y, bar_width, bar_height, state->wt_levels[2], "SQR"); + bar_x += bar_width + bar_spacing; + DrawSlider(bar_x, bar_y, bar_width, bar_height, state->wt_levels[3], "TRI"); + bar_x += bar_width + bar_spacing; + DrawSlider(bar_x, bar_y, bar_width, bar_height, scale_unit(state->lpf.cutoff, 20.0f, 20000.0f), "FREQ"); + bar_x += bar_width + bar_spacing; + DrawSlider(bar_x, bar_y, bar_width, bar_height, scale_unit(state->lpf.q, 0.0f, 1.0f), "Q"); EndDrawing(); } From bef07dc7dbc17e9de8e8049aedc82d684757cf04 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Fri, 4 Apr 2025 18:06:50 -0400 Subject: [PATCH 10/10] link test with raylib --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea861e6..7ab7cd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ target_include_directories(unit_tests PRIVATE ${PROJECT_SOURCE_DIR}/include) # target_compile_definitions(unit_tests PRIVATE PLATFORM_DESKTOP) # Link against the Criterion library and other system libraries -target_link_libraries(unit_tests criterion m pthread) +target_link_libraries(unit_tests raylib criterion m pthread) # Register the unit tests with CTest add_test(NAME unit_tests COMMAND unit_tests)