From 2b9d44cf89f981f28cbbc6c60105f6c876340ffb Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Tue, 26 Sep 2023 06:17:17 +0200 Subject: piano roll and playback control --- TODO | 4 +- source/saw/main.c | 138 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 114 insertions(+), 28 deletions(-) diff --git a/TODO b/TODO index 795ab11..2e354db 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,6 @@ To-Do list -- UI: Piano roll - UI: Volume control -- UI: Playback control - Sound: Buffering. - Sound: Simple tonal synth - Build: WebAssembly @@ -19,3 +17,5 @@ Done - Script for fetching dependencies. - nanovg and miniaudio setup. - Build: Faster recompilation +- UI: Piano roll +- UI: Playback control diff --git a/source/saw/main.c b/source/saw/main.c index e65b5b9..d974e4b 100644 --- a/source/saw/main.c +++ b/source/saw/main.c @@ -63,7 +63,9 @@ static f64 saw_voice_duration[VOICE_COUNT] = { 0 }; static i32 saw_roll_last_index = -1; static i8 saw_roll_turned_off[ROLL_SIZE] = { 0 }; -static saw_sheet_t saw_roll_sheet = { .rate = 4, .notes = { 0 } }; +static saw_sheet_t saw_roll_sheet = { .rate = 6, .notes = { 0 } }; +static i8 saw_roll_playing = 0; +static i64 saw_roll_frame = 0; static i8 saw_grid_input = 0; static i32 saw_grid_note = 0; @@ -97,8 +99,41 @@ static f64 saw_envelope(f64 t, f64 attack, f64 decay, f64 sustain, return 0.; } +static void saw_roll_playback(i32 frame_count) { + if (!saw_roll_playing) + return; + + for (i32 i = 0; i < SHEET_SIZE; i++) { + saw_sheet_note_t *p = saw_roll_sheet.notes + i; + if (!p->enabled) + continue; + i64 frame = (p->time * SAW_SAMPLE_RATE) / saw_roll_sheet.rate; + if (saw_roll_frame + frame_count <= frame || + saw_roll_frame > frame) + continue; + if (saw_voice_on[VOICE_COUNT - 1]) + continue; + + for (i32 n = VOICE_COUNT - 1; n > 0; --n) { + saw_voice_on[n] = saw_voice_on[n - 1]; + saw_voice_pitch[n] = saw_voice_pitch[n - 1]; + saw_voice_time[n] = saw_voice_time[n - 1]; + saw_voice_duration[n] = saw_voice_duration[n - 1]; + } + + saw_voice_on[0] = 1; + saw_voice_pitch[0] = p->pitch; + saw_voice_time[0] = 0; + saw_voice_duration[0] = ((f64) p->duration) / saw_roll_sheet.rate; + } + + saw_roll_frame += frame_count; +} + static void saw_audio(ma_device *device, void *void_out_, void const *void_in_, ma_uint32 frame_count) { + saw_roll_playback(frame_count); + f32 *out = (f32 *) void_out_; for (i64 i = 0; i < frame_count; i++) { @@ -111,13 +146,13 @@ static void saw_audio(ma_device *device, void *void_out_, continue; f64 period = M_PI * 2.; - f64 frequency = pow(2., 7.3 + saw_voice_pitch[n] / 12.); + f64 frequency = pow(2., 7.6 + saw_voice_pitch[n] / 12.); f64 amplitude = .2; // envelope f64 attack = .007; - f64 decay = .2; - f64 sustain = .2; + f64 decay = .3; + f64 sustain = .15; f64 duration = saw_voice_duration[n]; f64 release = .4; @@ -187,7 +222,7 @@ static void saw_frame(void) { i32 x0 = 20; i32 y0 = 20; - i32 pianokey_height = 40; + i32 pianokey_height = 35; i32 pianokey_width = 100; i32 roll_border = 2; @@ -298,10 +333,10 @@ static void saw_frame(void) { saw_mouse_y >= y && saw_mouse_y < y + h; nvgFillColor(saw_nvg, saw_roll_turned_off[pitch] - ? nvgRGBA(180, 180, 180, 120) + ? nvgRGBA(160, 150, 120, 100) : has_cursor ? nvgRGBA(180, 180, 220, 160) - : nvgRGBA(180, 180, 180, 160)); + : nvgRGBA(170, 160, 140, 150)); nvgFill(saw_nvg); // Empty cell input @@ -348,30 +383,69 @@ static void saw_frame(void) { } } } + } - // Note stretching - // + // Note stretching input + // - if (saw_grid_input) { - if (saw_lbutton_down) { - i32 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset) / - sheet_scale; - - if (t >= 0) { - saw_sheet_note_t *p = saw_roll_sheet.notes + saw_grid_note; - if (saw_grid_time <= t) { - p->time = saw_grid_time; - p->duration = 1 + t - saw_grid_time; - } else { - p->time = t; - p->duration = saw_grid_time - t; - } + if (saw_grid_input) { + if (saw_lbutton_down) { + i32 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset) / + sheet_scale; + + if (t >= 0) { + saw_sheet_note_t *p = saw_roll_sheet.notes + saw_grid_note; + if (saw_grid_time <= t) { + p->time = saw_grid_time; + p->duration = 1 + t - saw_grid_time; + } else { + p->time = t; + p->duration = saw_grid_time - t; } - } else - saw_grid_input = 0; - } + } + + saw_sheet_note_t *p = saw_roll_sheet.notes + saw_grid_note; + for (i32 n = 0; n < SHEET_SIZE; n++) { + if (n == saw_grid_note) + continue; + saw_sheet_note_t *q = saw_roll_sheet.notes + n; + if (!q->enabled || q->pitch != p->pitch) + continue; + if (q->time < saw_grid_time && + q->time + q->duration > p->time) { + p->time = q->time + q->duration; + p->duration = saw_grid_time > p->time + ? saw_grid_time - p->time + : 1; + } + if (q->time > saw_grid_time && + q->time < p->time + p->duration) { + p->time = saw_grid_time; + p->duration = q->time - saw_grid_time; + assert(p->duration > 0); + } + } + } else + saw_grid_input = 0; } + // Playback indicator + // + + f64 playback_time = ((f64) saw_roll_frame) / SAW_SAMPLE_RATE; + + i32 x = x0 + pianokey_width + sheet_offset - roll_border * 2 + + (i32) (playback_time * saw_roll_sheet.rate * sheet_scale + + .5); + i32 y = 0; + i32 w = roll_border * 4; + i32 h = height - y0; + + nvgBeginPath(saw_nvg); + nvgRect(saw_nvg, x, y, w, h); + nvgFillColor(saw_nvg, nvgRGBA(240, 240, 80, 220)); + nvgFill(saw_nvg); + nvgEndFrame(saw_nvg); // Cleanup input state. @@ -420,6 +494,18 @@ static void saw_event(sapp_event const *event) { } break; + case SAPP_EVENTTYPE_KEY_DOWN: + if (!event->key_repeat) { + switch (event->key_code) { + case SAPP_KEYCODE_SPACE: + saw_roll_playing = !saw_roll_playing; + break; + case SAPP_KEYCODE_ENTER: saw_roll_frame = 0; break; + default:; + } + } + break; + default:; } } -- cgit v1.2.3