Skip to content

Commit 1265127

Browse files
committed
misc
1 parent 6d9b68a commit 1265127

File tree

10 files changed

+446
-35
lines changed

10 files changed

+446
-35
lines changed

src/app.c

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
#include "app.h"
22
#include <stdio.h>
33
#include <string.h>
4+
#include <stdlib.h>
45
#include "oscilloscope.h"
56
#include "gui.h"
67
#include "arpeggiator.h"
78

9+
static App *g_app_for_toggle = NULL;
10+
static void toggle_chord_progression(void);
11+
static void generate_random_chord_progression(App *app);
12+
static void play_chord(App *app, int root_note, float velocity);
13+
static void stop_active_notes(App *app);
14+
815
int app_init(App *app) {
916

1017
memset(app, 0, sizeof(App));
@@ -15,6 +22,19 @@ int app_init(App *app) {
1522
app->frame_count = 0;
1623
app->fps = 0.0f;
1724

25+
// Initialize chord progression
26+
app->chord_progression_enabled = 0;
27+
app->current_chord_index = 0;
28+
app->chord_progression_timer = 0.0f;
29+
app->chord_duration = 2.0f;
30+
app->chord_progression_length = 0;
31+
app->rhythm_pattern_length = 0;
32+
app->current_rhythm_step = 0;
33+
app->rhythm_step_timer = 0.0f;
34+
app->active_chord_note_count = 0;
35+
app->humanize_velocity_amount = 0.15f;
36+
app->humanize_timing_amount = 0.03f;
37+
app->bpm = 120.0f;
1838

1939
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO) < 0) {
2040
return 0;
@@ -66,6 +86,8 @@ int app_init(App *app) {
6686
}
6787

6888
gui_init(app->window, app->gl_context);
89+
g_app_for_toggle = app;
90+
gui_set_humanize_vars(&app->chord_progression_enabled, &app->humanize_velocity_amount, &app->humanize_timing_amount, &app->bpm, toggle_chord_progression, &app->synth);
6991

7092
oscilloscope_init();
7193

@@ -149,6 +171,103 @@ static void handle_keyboard_note(App *app, SDL_Keycode key, int is_down) {
149171
}
150172
}
151173

174+
static void toggle_chord_progression(void) {
175+
if (g_app_for_toggle) {
176+
g_app_for_toggle->chord_progression_enabled = !g_app_for_toggle->chord_progression_enabled;
177+
if (g_app_for_toggle->chord_progression_enabled) {
178+
generate_random_chord_progression(g_app_for_toggle);
179+
play_chord(g_app_for_toggle, g_app_for_toggle->chord_progression[0],
180+
g_app_for_toggle->rhythm_pattern[0]);
181+
} else {
182+
stop_active_notes(g_app_for_toggle);
183+
}
184+
}
185+
}
186+
187+
static void generate_random_chord_progression(App *app) {
188+
int scale[] = {0, 2, 4, 5, 7, 9, 11};
189+
int progression_patterns[][8] = {
190+
{0, 5, 3, 4, 2, 6, 1, 0},
191+
{0, 3, 5, 4, 2, 1, 6, 5},
192+
{0, 4, 5, 3, 1, 6, 2, 0},
193+
{0, 5, 3, 4, 0, 5, 3, 6},
194+
{0, 3, 4, 5, 1, 2, 6, 0},
195+
{0, 4, 3, 5, 6, 1, 2, 4},
196+
{0, 6, 3, 5, 4, 1, 2, 0},
197+
{0, 2, 5, 3, 6, 4, 1, 0},
198+
};
199+
200+
int pattern_idx = rand() % 8;
201+
int root = 48 + (rand() % 12);
202+
203+
app->chord_progression_length = 8;
204+
for (int i = 0; i < 8; i++) {
205+
app->chord_progression[i] = root + scale[progression_patterns[pattern_idx][i]];
206+
}
207+
208+
// Generate 128-step rhythm pattern by repeating 16-step patterns
209+
float base_patterns[][16] = {
210+
{1.0f, 0.0f, 0.5f, 0.0f, 0.75f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.75f, 0.0f, 0.5f, 0.0f},
211+
{1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.75f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.75f, 0.5f, 0.0f, 0.0f},
212+
{1.0f, 0.5f, 0.5f, 0.0f, 0.75f, 0.0f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.75f, 0.0f, 0.5f, 0.0f},
213+
{1.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.75f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.75f, 0.0f, 0.0f},
214+
{1.0f, 0.5f, 0.0f, 0.5f, 0.75f, 0.5f, 0.0f, 0.5f, 1.0f, 0.5f, 0.0f, 0.5f, 0.75f, 0.5f, 0.0f, 0.5f},
215+
};
216+
217+
int rhythm_idx = rand() % 5;
218+
app->rhythm_pattern_length = 128;
219+
220+
// Fill 128 steps by repeating the 16-step pattern 8 times
221+
for (int i = 0; i < 128; i++) {
222+
app->rhythm_pattern[i] = base_patterns[rhythm_idx][i % 16];
223+
}
224+
225+
app->current_chord_index = 0;
226+
app->chord_progression_timer = 0.0f;
227+
app->current_rhythm_step = 0;
228+
app->rhythm_step_timer = 0.0f;
229+
}
230+
231+
static float humanize_velocity(float velocity, float amount) {
232+
float random = ((float)rand() / RAND_MAX) * 2.0f - 1.0f;
233+
velocity += random * amount;
234+
if (velocity < 0.1f) velocity = 0.1f;
235+
if (velocity > 1.0f) velocity = 1.0f;
236+
return velocity;
237+
}
238+
239+
static void stop_active_notes(App *app) {
240+
for (int i = 0; i < app->active_chord_note_count; i++) {
241+
synth_note_off(&app->synth, app->active_chord_notes[i]);
242+
}
243+
app->active_chord_note_count = 0;
244+
}
245+
246+
static void play_chord(App *app, int root_note, float velocity) {
247+
if (velocity <= 0.0f) {
248+
return;
249+
}
250+
251+
velocity = humanize_velocity(velocity, app->humanize_velocity_amount);
252+
253+
int chord_notes[8];
254+
chord_notes[0] = root_note;
255+
chord_notes[1] = root_note + 3;
256+
chord_notes[2] = root_note + 4;
257+
chord_notes[3] = root_note + 7;
258+
chord_notes[4] = root_note + 10;
259+
chord_notes[5] = root_note + 12;
260+
chord_notes[6] = root_note + 15;
261+
chord_notes[7] = root_note + 16;
262+
263+
app->active_chord_note_count = 0;
264+
for (int i = 0; i < 8; i++) {
265+
float note_velocity = humanize_velocity(velocity, app->humanize_velocity_amount);
266+
synth_note_on(&app->synth, chord_notes[i], note_velocity);
267+
app->active_chord_notes[app->active_chord_note_count++] = chord_notes[i];
268+
}
269+
}
270+
152271
void app_poll_events(App *app) {
153272
SDL_Event e;
154273
int event_count = 0;
@@ -160,7 +279,13 @@ void app_poll_events(App *app) {
160279
}
161280
if (e.type == SDL_KEYDOWN) {
162281
if (e.key.keysym.sym == SDLK_F1) {
163-
app->show_help = !app->show_help;
282+
app->chord_progression_enabled = !app->chord_progression_enabled;
283+
if (app->chord_progression_enabled) {
284+
generate_random_chord_progression(app);
285+
play_chord(app, app->chord_progression[0], app->rhythm_pattern[0]);
286+
} else {
287+
stop_active_notes(app);
288+
}
164289
} else if (app->show_help) {
165290
app->show_help = 0;
166291
}
@@ -184,6 +309,37 @@ void app_poll_events(App *app) {
184309

185310
void app_update(App *app) {
186311

312+
// Update chord progression with rhythm
313+
if (app->chord_progression_enabled && app->chord_progression_length > 0 && app->rhythm_pattern_length > 0) {
314+
float delta_time = app->target_frame_time / 1000.0f;
315+
float seconds_per_beat = 60.0f / app->bpm;
316+
float step_duration = seconds_per_beat * 4.0f / app->rhythm_pattern_length;
317+
318+
float timing_variation = ((float)rand() / RAND_MAX - 0.5f) * 2.0f * app->humanize_timing_amount;
319+
step_duration += timing_variation;
320+
if (step_duration < 0.01f) step_duration = 0.01f;
321+
322+
app->rhythm_step_timer += delta_time;
323+
324+
if (app->rhythm_step_timer >= step_duration) {
325+
app->rhythm_step_timer -= step_duration;
326+
327+
stop_active_notes(app);
328+
329+
app->current_rhythm_step = (app->current_rhythm_step + 1) % app->rhythm_pattern_length;
330+
331+
if (app->current_rhythm_step == 0) {
332+
app->current_chord_index = (app->current_chord_index + 1) % app->chord_progression_length;
333+
}
334+
335+
float velocity = app->rhythm_pattern[app->current_rhythm_step];
336+
play_chord(app, app->chord_progression[app->current_chord_index], velocity);
337+
}
338+
}
339+
340+
// Sync FX BPM with chord progression BPM
341+
synth_set_bpm(&app->synth, app->bpm);
342+
187343
// Calculate FPS
188344
app->frame_count++;
189345
Uint32 current_time = SDL_GetTicks();
@@ -213,7 +369,10 @@ void app_update(App *app) {
213369
}
214370

215371
void app_render(App *app) {
216-
gui_draw(&app->synth, app->window, app->gl_context);
372+
gui_draw(&app->synth, app->window, app->gl_context,
373+
app->chord_progression_enabled, app->current_chord_index,
374+
app->chord_progression_length, app->current_rhythm_step,
375+
app->rhythm_pattern_length);
217376
SDL_GL_SwapWindow(app->window);
218377
}
219378

src/app.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ typedef struct {
1515
Uint32 frame_count;
1616
float fps;
1717
Uint32 target_frame_time; // 1000ms / 60fps = ~16.67ms
18+
19+
// Chord progression
20+
int chord_progression_enabled;
21+
int current_chord_index;
22+
float chord_progression_timer;
23+
float chord_duration;
24+
int chord_progression[8];
25+
int chord_progression_length;
26+
float rhythm_pattern[128];
27+
int rhythm_pattern_length;
28+
int current_rhythm_step;
29+
float rhythm_step_timer;
30+
int active_chord_notes[16];
31+
int active_chord_note_count;
32+
float humanize_velocity_amount;
33+
float humanize_timing_amount;
34+
float bpm;
1835
} App;
1936

2037
int app_init(App *app);

src/fx.c

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ void fx_init(FX *fx, int samplerate) {
6464
fx->flanger_rate = 0.25f;
6565
fx->flanger_feedback = 0.5f;
6666
fx->delay_time = 0.0f; // Set to 0 (disabled)
67-
fx->delay_feedback = 0.0f; // Set to 0 (disabled)
68-
fx->delay_mix = 0.25f;
67+
fx->delay_feedback = 0.3f; // Some feedback for richer delays
68+
fx->delay_mix = 0.4f; // Higher mix to hear the effect better
6969
fx->reverb_size = 0.0f; // Set to 0.0 (minimal/no reverb)
7070
fx->reverb_damping = 0.3f;
7171
fx->reverb_mix = 0.0f; // Set to 0.0 (100% dry - no reverb in output)
@@ -76,6 +76,24 @@ void fx_init(FX *fx, int samplerate) {
7676
fx->flanger_buffer = calloc(fx->flanger_bufsize * 2, sizeof(float));
7777
fx->flanger_pos = 0;
7878

79+
// Initialize multi-tap delay
80+
fx->bpm = 120.0f;
81+
fx->multitap_enabled = 0;
82+
fx->multitap_delay_pos = 0;
83+
fx->num_taps = 4;
84+
// Default tap delays in beats: quarter, dotted eighth, eighth, triplet
85+
float tap_beats[] = {1.0f, 0.75f, 0.5f, 0.333f};
86+
float tap_levels[] = {0.7f, 0.5f, 0.4f, 0.3f};
87+
for (int i = 0; i < MAX_DELAY_TAPS; i++) {
88+
if (i < 4) {
89+
fx->multitap_taps[i] = tap_beats[i];
90+
fx->multitap_levels[i] = tap_levels[i];
91+
} else {
92+
fx->multitap_taps[i] = 0.0f;
93+
fx->multitap_levels[i] = 0.0f;
94+
}
95+
}
96+
7997
// Initialize analog filter
8098
fx->filter_enabled = 0; // Disabled by default
8199
fx->filter_cutoff = 1000.0f; // 1kHz default
@@ -126,6 +144,22 @@ void fx_set_param(FX *fx, const char *param, float value) {
126144
fx->reverb_damping = value;
127145
else if (!strcmp(param, "reverb.mix"))
128146
fx->reverb_mix = value;
147+
// Multi-tap delay parameters
148+
else if (!strcmp(param, "multitap.enabled"))
149+
fx->multitap_enabled = (int)value;
150+
else if (!strcmp(param, "multitap.bpm"))
151+
fx->bpm = value;
152+
else if (!strncmp(param, "multitap.tap", 12)) {
153+
// Parse tap index: multitap.tap0, multitap.tap1, etc.
154+
int tap_idx = atoi(param + 12);
155+
if (tap_idx >= 0 && tap_idx < MAX_DELAY_TAPS) {
156+
if (!strncmp(param + 13, "_level", 6)) {
157+
fx->multitap_levels[tap_idx] = value;
158+
} else {
159+
fx->multitap_taps[tap_idx] = value;
160+
}
161+
}
162+
}
129163
// Analog filter parameters
130164
else if (!strcmp(param, "filter.enabled"))
131165
fx->filter_enabled = (int)value;
@@ -192,17 +226,59 @@ void fx_process(FX *fx, float *stereo, int frames) {
192226
}
193227
fx->flanger_pos = (fx->flanger_pos + 1) % fx->flanger_bufsize;
194228
}
195-
for (int n = 0; n < frames; ++n) {
196-
int delay_samp = (int)(fx->delay_time * fx->samplerate);
197-
int pos =
198-
(fx->delay_pos + fx->delay_bufsize - delay_samp) % fx->delay_bufsize;
199-
for (int ch = 0; ch < 2; ++ch) {
200-
float dry = stereo[n * 2 + ch];
201-
float wet = fx->delay_buffer[pos * 2 + ch];
202-
stereo[n * 2 + ch] = dry * (1.0f - fx->delay_mix) + wet * fx->delay_mix;
203-
fx->delay_buffer[fx->delay_pos * 2 + ch] = dry + wet * fx->delay_feedback;
229+
// Multi-tap delay synced to BPM (replaces standard delay when enabled)
230+
if (fx->multitap_enabled && fx->delay_mix > 0.0f) {
231+
float seconds_per_beat = 60.0f / fx->bpm;
232+
233+
for (int n = 0; n < frames; ++n) {
234+
float dryL = stereo[n * 2 + 0];
235+
float dryR = stereo[n * 2 + 1];
236+
float wetL = 0.0f, wetR = 0.0f;
237+
238+
// Sum all taps
239+
for (int tap = 0; tap < fx->num_taps; ++tap) {
240+
if (fx->multitap_levels[tap] > 0.0f && fx->multitap_taps[tap] > 0.0f) {
241+
float delay_seconds = fx->multitap_taps[tap] * seconds_per_beat;
242+
int delay_samp = (int)(delay_seconds * fx->samplerate);
243+
244+
// Ensure delay is within buffer bounds
245+
if (delay_samp > 0 && delay_samp < fx->delay_bufsize) {
246+
int pos = (fx->delay_pos + fx->delay_bufsize - delay_samp) % fx->delay_bufsize;
247+
248+
// Read delayed signal
249+
float tapL = fx->delay_buffer[pos * 2 + 0];
250+
float tapR = fx->delay_buffer[pos * 2 + 1];
251+
252+
// Add to wet mix with tap level
253+
wetL += tapL * fx->multitap_levels[tap];
254+
wetR += tapR * fx->multitap_levels[tap];
255+
}
256+
}
257+
}
258+
259+
// Mix wet and dry
260+
stereo[n * 2 + 0] = dryL * (1.0f - fx->delay_mix) + wetL * fx->delay_mix;
261+
stereo[n * 2 + 1] = dryR * (1.0f - fx->delay_mix) + wetR * fx->delay_mix;
262+
263+
// Write input to buffer (ALWAYS write dry signal to keep buffer filled)
264+
fx->delay_buffer[fx->delay_pos * 2 + 0] = dryL;
265+
fx->delay_buffer[fx->delay_pos * 2 + 1] = dryR;
266+
fx->delay_pos = (fx->delay_pos + 1) % fx->delay_bufsize;
267+
}
268+
} else {
269+
// Standard delay (original behavior)
270+
for (int n = 0; n < frames; ++n) {
271+
int delay_samp = (int)(fx->delay_time * fx->samplerate);
272+
int pos =
273+
(fx->delay_pos + fx->delay_bufsize - delay_samp) % fx->delay_bufsize;
274+
for (int ch = 0; ch < 2; ++ch) {
275+
float dry = stereo[n * 2 + ch];
276+
float wet = fx->delay_buffer[pos * 2 + ch];
277+
stereo[n * 2 + ch] = dry * (1.0f - fx->delay_mix) + wet * fx->delay_mix;
278+
fx->delay_buffer[fx->delay_pos * 2 + ch] = dry + wet * fx->delay_feedback;
279+
}
280+
fx->delay_pos = (fx->delay_pos + 1) % fx->delay_bufsize;
204281
}
205-
fx->delay_pos = (fx->delay_pos + 1) % fx->delay_bufsize;
206282
}
207283
for (int n = 0; n < frames; ++n) {
208284
float inL = stereo[n * 2 + 0];
@@ -223,6 +299,10 @@ void fx_process(FX *fx, float *stereo, int frames) {
223299
}
224300
}
225301

302+
void fx_set_bpm(FX *fx, float bpm) {
303+
fx->bpm = bpm;
304+
}
305+
226306
void fx_cleanup(FX *fx) {
227307
if (fx->delay_buffer) {
228308
free(fx->delay_buffer);

0 commit comments

Comments
 (0)