From f686896de7a0cd08a316cc3a9c3fd52d5b0075a3 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Wed, 27 Sep 2023 14:36:55 +0200 Subject: Piano roll panning --- TODO | 2 + source/saw/main.c | 186 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 128 insertions(+), 60 deletions(-) diff --git a/TODO b/TODO index 6ae9e3c..c23e262 100644 --- a/TODO +++ b/TODO @@ -22,6 +22,7 @@ To-Do list - UI: Panning and scaling - Sound: Track looping - Implement Undo and Redo +- Hot loading Done @@ -31,3 +32,4 @@ Done - UI: Piano roll - UI: Playback control - UI: Text rendering +- UI: Piano roll panning diff --git a/source/saw/main.c b/source/saw/main.c index 3f34e51..9b4fbde 100644 --- a/source/saw/main.c +++ b/source/saw/main.c @@ -33,6 +33,13 @@ enum { ROLL_COUNT = 4, }; +typedef struct { + i8 enabled; + i64 time; + f64 duration; + f64 frequency; +} saw_voice_t; + typedef struct { i8 enabled; i64 time; @@ -53,6 +60,9 @@ typedef struct { i32 grid_note; i32 grid_pitch; i32 grid_time; + i8 offset_input; + i64 offset_x; + i64 offset_y; } saw_roll_t; static struct NVGcontext *saw_nvg; @@ -66,16 +76,15 @@ static i8 saw_lbutton_click = 0; static i8 saw_lbutton_down = 0; static i8 saw_rbutton_click = 0; static i8 saw_rbutton_down = 0; - -static i8 saw_voice_on[VOICE_COUNT] = { 0 }; -static i32 saw_voice_pitch[VOICE_COUNT] = { 0 }; -static i64 saw_voice_time[VOICE_COUNT] = { 0 }; -static f64 saw_voice_duration[VOICE_COUNT] = { 0 }; +static i8 saw_mbutton_click = 0; +static i8 saw_mbutton_down = 0; static i8 saw_playback_on = 0; static i64 saw_playback_frame = 0; +static i64 saw_current_roll = 0; -static saw_roll_t saw_rolls[ROLL_COUNT]; +static saw_voice_t saw_voices[VOICE_COUNT] = { 0 }; +static saw_roll_t saw_rolls[ROLL_COUNT]; #ifdef __GNUC__ # pragma GCC diagnostic push @@ -119,20 +128,18 @@ static void saw_playback(i32 frame_count) { if (saw_playback_frame + frame_count <= frame || saw_playback_frame > frame) continue; - if (saw_voice_on[VOICE_COUNT - 1]) + if (saw_voices[VOICE_COUNT - 1].enabled) 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]; - } + for (i32 n = VOICE_COUNT - 1; n > 0; --n) + saw_voices[n] = saw_voices[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) / roll->sheet.rate; + saw_voices[0] = (saw_voice_t) { + .enabled = 1, + .time = 0, + .duration = ((f64) p->duration) / roll->sheet.rate, + .frequency = pow(2., 7.6 + p->pitch / 12.) + }; } } @@ -151,22 +158,22 @@ static void saw_audio(ma_device *device, void *void_out_, } for (i32 n = 0; n < VOICE_COUNT; n++) { - if (!saw_voice_on[n]) + if (!saw_voices[n].enabled) continue; f64 period = M_PI * 2.; - f64 frequency = pow(2., 7.6 + saw_voice_pitch[n] / 12.); + f64 frequency = saw_voices[n].frequency; f64 amplitude = .2; // envelope f64 attack = .007; f64 decay = .3; f64 sustain = .15; - f64 duration = saw_voice_duration[n]; + f64 duration = saw_voices[n].duration; f64 release = .4; for (i64 i = 0; i < frame_count; i++) { - f64 t = (f64) saw_voice_time[n] / (f64) SAW_SAMPLE_RATE; + f64 t = (f64) saw_voices[n].time / (f64) SAW_SAMPLE_RATE; f64 a = amplitude * saw_envelope(t, attack, decay, sustain, duration, release); f64 k = period * frequency; @@ -174,10 +181,10 @@ static void saw_audio(ma_device *device, void *void_out_, out[i * 2] += (f32) (sin(k * t) * a); out[i * 2 + 1] += (f32) (sin(k * t) * a); - saw_voice_time[n]++; + saw_voices[n].time++; if (t > duration + release) - saw_voice_on[n] = 0; + saw_voices[n].enabled = 0; } } } @@ -225,7 +232,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, for (i32 pitch = 0; pitch < ROLL_SIZE; pitch++) { i32 x = x0 + roll_border; i32 y = frame_height - y0 - (pitch + 1) * pianokey_height + - roll_border; + roll_border + roll->offset_y; i32 w = pianokey_width - roll_border * 2; i32 h = pianokey_height - roll_border * 2; @@ -255,18 +262,15 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, if (!roll->turned_off[pitch] && (saw_lbutton_click || (saw_lbutton_down && roll->last_index != pitch)) && - !saw_voice_on[VOICE_COUNT - 1]) { - 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] = pitch; - saw_voice_time[0] = 0; - saw_voice_duration[0] = .6; + !saw_voices[VOICE_COUNT - 1].enabled) { + for (i32 n = VOICE_COUNT - 1; n > 0; --n) + saw_voices[n] = saw_voices[n - 1]; + + saw_voices[0] = (saw_voice_t) { .enabled = 1, + .time = 0, + .duration = .6, + .frequency = pow( + 2., 7.6 + pitch / 12.) }; } if (saw_rbutton_click) @@ -279,11 +283,19 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, if (!hover_any) roll->last_index = -1; + if (saw_mbutton_click && saw_mouse_x >= x0 && + saw_mouse_y >= frame_height - y0 - height && + saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0) + roll->offset_input = 1; + if (!saw_mbutton_down && roll->offset_input) + roll->offset_input = 0; + // Draw music sheet // for (i32 pitch = 0; pitch < ROLL_SIZE; pitch++) { - i32 y = frame_height - y0 - (pitch + 1) * pianokey_height; + i32 y = frame_height - y0 - (pitch + 1) * pianokey_height + + roll->offset_y; if (y > frame_height - y0) continue; @@ -293,9 +305,10 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, i32 h = pianokey_height; for (i32 t = 0;; t++) { - i32 x = x0 + pianokey_width + sheet_offset + t * sheet_scale; + i32 x = x0 + pianokey_width + sheet_offset + t * sheet_scale + + roll->offset_x; - if (x >= x0 + width - roll_border) + if (x >= x0 + width - sheet_scale - roll_border) break; i32 note = -1; @@ -315,6 +328,9 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, // Draw empty cell // + if (x < x0 + pianokey_width + sheet_offset) + continue; + i32 w = sheet_scale; nvgBeginPath(saw_nvg); @@ -354,16 +370,43 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w && saw_mouse_y >= y && saw_mouse_y < y + h; + if (x + w - sheet_scale < x0 + pianokey_width + sheet_offset) + continue; + if (t == roll->sheet.notes[note].time) { // Draw note // + i64 t0 = (t * SAW_SAMPLE_RATE) / roll->sheet.rate; + i64 t1 = ((t + roll->sheet.notes[note].duration) * + SAW_SAMPLE_RATE) / + roll->sheet.rate; + + i8 is_playing = saw_playback_on && + saw_playback_frame >= t0 && + saw_playback_frame < t1; + + i32 underflow = (x0 + pianokey_width + sheet_offset + + sheet_scale - 1 - x) / + sheet_scale; + i32 overflow = (x + w + sheet_scale + 1 - x0 - width) / + sheet_scale; + + if (underflow > 0) { + x += underflow * sheet_scale; + w -= underflow * sheet_scale; + } + + if (overflow > 0) + w -= overflow * sheet_scale; + nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x + roll_border, y + roll_border, w - roll_border * 2, h - roll_border * 2); - nvgFillColor(saw_nvg, has_cursor - ? nvgRGBA(180, 180, 220, 255) + nvgFillColor(saw_nvg, + is_playing ? nvgRGBA(255, 230, 200, 255) + : has_cursor ? nvgRGBA(180, 180, 220, 255) : nvgRGBA(180, 180, 180, 255)); nvgFill(saw_nvg); @@ -382,7 +425,8 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, if (roll->grid_input) { if (saw_lbutton_down) { - i32 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset) / + i32 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset - + roll->offset_x) / sheet_scale; if (t >= 0) { @@ -426,15 +470,19 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, f64 playback_time = ((f64) saw_playback_frame) / SAW_SAMPLE_RATE; - i32 x = x0 + pianokey_width + sheet_offset - roll_border * 2 + + i32 x = x0 + pianokey_width + sheet_offset + roll->offset_x - + roll_border * 2 + (i32) (playback_time * roll->sheet.rate * sheet_scale + .5); i32 w = roll_border * 4; - nvgBeginPath(saw_nvg); - nvgRect(saw_nvg, x, frame_height - y0 - height + text_height, w, - height - text_height); - nvgFillColor(saw_nvg, nvgRGBA(240, 240, 80, 180)); - nvgFill(saw_nvg); + if (x >= x0 + pianokey_width + sheet_offset - roll_border * 2 && + x < x0 + width) { + nvgBeginPath(saw_nvg); + nvgRect(saw_nvg, x, frame_height - y0 - height + text_height, w, + height - text_height); + nvgFillColor(saw_nvg, nvgRGBA(240, 240, 80, 180)); + nvgFill(saw_nvg); + } } static void saw_init(void) { @@ -469,14 +517,18 @@ static void saw_init(void) { printf("nvgCreateFontMem failed.\n"); for (i32 i = 0; i < ROLL_COUNT; i++) - saw_rolls[i] = (saw_roll_t) { .last_index = -1, - .turned_off = { 0 }, - .sheet = { .rate = 6, - .notes = { 0 } }, - .grid_input = 0, - .grid_note = 0, - .grid_pitch = 0, - .grid_time = 0 }; + saw_rolls[i] = (saw_roll_t) { + .last_index = -1, + .turned_off = { 0 }, + .sheet = { .rate = 6, .notes = { 0 } }, + .grid_input = 0, + .grid_note = 0, + .grid_pitch = 0, + .grid_time = 0, + .offset_input = 0, + .offset_x = 0, + .offset_y = 0, + }; } static void saw_frame(void) { @@ -491,10 +543,11 @@ static void saw_frame(void) { nvgBeginFrame(saw_nvg, frame_width, frame_height, sapp_dpi_scale()); - saw_ui_roll(saw_rolls, 0, 10, frame_width, frame_height / 2 - 20, - SZ("Track 1")); - saw_ui_roll(saw_rolls + 1, 0, frame_height / 2 + 10, frame_width, - frame_height / 2 - 20, SZ("Track 2")); + char buf[64]; + sprintf(buf, "Track %lld", saw_current_roll + 1); + + saw_ui_roll(saw_rolls + saw_current_roll, 0, 10, frame_width, + frame_height / 2 - 20, kit_str(strlen(buf), buf)); nvgEndFrame(saw_nvg); @@ -503,6 +556,7 @@ static void saw_frame(void) { saw_lbutton_click = 0; saw_rbutton_click = 0; + saw_mbutton_click = 0; } static void saw_cleanup(void) { @@ -520,6 +574,13 @@ static void saw_event(sapp_event const *event) { case SAPP_EVENTTYPE_MOUSE_MOVE: saw_mouse_x = event->mouse_x; saw_mouse_y = event->mouse_y; + + for (i64 i = 0; i < ROLL_COUNT; i++) + if (saw_rolls[i].offset_input) { + saw_rolls[i].offset_x += event->mouse_dx; + saw_rolls[i].offset_y += event->mouse_dy; + } + break; case SAPP_EVENTTYPE_MOUSE_DOWN: @@ -532,6 +593,10 @@ static void saw_event(sapp_event const *event) { saw_rbutton_down = 1; saw_rbutton_click = 1; break; + case SAPP_MOUSEBUTTON_MIDDLE: + saw_mbutton_down = 1; + saw_mbutton_click = 1; + break; default:; } break; @@ -540,6 +605,7 @@ static void saw_event(sapp_event const *event) { switch (event->mouse_button) { case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 0; break; case SAPP_MOUSEBUTTON_RIGHT: saw_rbutton_down = 0; break; + case SAPP_MOUSEBUTTON_MIDDLE: saw_mbutton_down = 0; break; default:; } break; -- cgit v1.2.3