diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2023-09-26 04:27:53 +0200 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2023-09-26 04:27:53 +0200 |
commit | ea17fff774c5a2f69f5e89227e6d5bab01bee5af (patch) | |
tree | cdea77d5d33f6c30701c3ac46cd7a4c27043df59 | |
parent | 20921d5a47e2b0336a03cf881cdd857201177607 (diff) | |
download | saw-ea17fff774c5a2f69f5e89227e6d5bab01bee5af.zip |
piano roll grid editing
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | source/saw/main.c | 231 | ||||
-rw-r--r-- | source/saw/profiler.h | 2 |
4 files changed, 202 insertions, 38 deletions
@@ -4,10 +4,10 @@ Music sequencer and audio editor Tech - kit - https://guattari.tech/kit.git +- stb - https://github.com/nothings/stb.git - sokol_app - https://github.com/floooh/sokol.git - nanovg - https://github.com/memononen/nanovg.git - miniaudio - https://github.com/mackron/miniaudio.git -- portmidi - https://github.com/PortMidi/portmidi.git Algorithms @@ -8,6 +8,11 @@ To-Do list - Build: WebAssembly - Sound: Kick, snare, cymbal - UI: Track composing +- Biquad filter +- Fourier transform +- UI: Spectrum view +- UI: Wave view +- Improve X11 startup time Done diff --git a/source/saw/main.c b/source/saw/main.c index 298eb2a..e65b5b9 100644 --- a/source/saw/main.c +++ b/source/saw/main.c @@ -29,10 +29,23 @@ enum { SAW_CHANNEL_COUNT = 2, SAW_SAMPLE_RATE = 44100, - VOICE_COUNT = 16, - PIANOROLL_SIZE = 40 + VOICE_COUNT = 16, + ROLL_SIZE = 40, + SHEET_SIZE = 200 }; +typedef struct { + i8 enabled; + i64 time; + i64 duration; + i64 pitch; +} saw_sheet_note_t; + +typedef struct { + i64 rate; + saw_sheet_note_t notes[SHEET_SIZE]; +} saw_sheet_t; + static struct NVGcontext *saw_nvg; static ma_device saw_ma; @@ -41,13 +54,21 @@ static i32 saw_mouse_y = 0; 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_voice_on[VOICE_COUNT] = { 0 }; -static i32 saw_voice_pitch[VOICE_COUNT] = { 0 }; -static i64 saw_voice_time[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 i32 saw_pianoroll_last_index = -1; -static i8 saw_pianoroll_turned_off[PIANOROLL_SIZE] = { 0 }; +static i8 saw_grid_input = 0; +static i32 saw_grid_note = 0; +static i32 saw_grid_pitch = 0; +static i32 saw_grid_time = 0; #ifdef __GNUC__ # pragma GCC diagnostic push @@ -90,19 +111,28 @@ static void saw_audio(ma_device *device, void *void_out_, continue; f64 period = M_PI * 2.; - f64 frequency = pow(2., 7 + saw_voice_pitch[n] / 12.); + f64 frequency = pow(2., 7.3 + saw_voice_pitch[n] / 12.); + f64 amplitude = .2; + + // envelope + f64 attack = .007; + f64 decay = .2; + f64 sustain = .2; + f64 duration = saw_voice_duration[n]; + f64 release = .4; for (i64 i = 0; i < frame_count; i++) { - f64 t = (f64) saw_voice_time[n] / (f64) SAW_SAMPLE_RATE; - f64 amplitude = .4 * saw_envelope(t, .007, .2, .2, .6, .4); - f64 k = period * frequency; + f64 t = (f64) saw_voice_time[n] / (f64) SAW_SAMPLE_RATE; + f64 a = amplitude * saw_envelope(t, attack, decay, sustain, + duration, release); + f64 k = period * frequency; - out[i * 2] += (f32) (sin(k * t) * amplitude); - out[i * 2 + 1] += (f32) (sin(k * t) * amplitude); + out[i * 2] += (f32) (sin(k * t) * a); + out[i * 2 + 1] += (f32) (sin(k * t) * a); saw_voice_time[n]++; - if (t > 1.0) + if (t > duration + release) saw_voice_on[n] = 0; } } @@ -151,7 +181,7 @@ static void saw_frame(void) { nvgBeginFrame(saw_nvg, width, height, sapp_dpi_scale()); - // Draw piano roll + // Piano roll // i32 x0 = 20; @@ -159,15 +189,18 @@ static void saw_frame(void) { i32 pianokey_height = 40; i32 pianokey_width = 100; - i32 pianokey_border = 2; + i32 roll_border = 2; + + i32 sheet_offset = 40; + i32 sheet_scale = 20; i8 hover_any = 0; - for (i32 i = 0; i < PIANOROLL_SIZE; i++) { - i32 x = x0 + pianokey_border; - i32 y = height - y0 - (i + 1) * pianokey_height; - i32 w = pianokey_width - pianokey_border * 2; - i32 h = pianokey_height - pianokey_border * 2; + for (i32 pitch = 0; pitch < ROLL_SIZE; pitch++) { + i32 x = x0 + roll_border; + i32 y = height - y0 - (pitch + 1) * pianokey_height + roll_border; + i32 w = pianokey_width - roll_border * 2; + i32 h = pianokey_height - roll_border * 2; if (y > height - pianokey_height) continue; @@ -180,39 +213,164 @@ static void saw_frame(void) { i8 has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w && saw_mouse_y >= y && saw_mouse_y < y + h; - nvgFillColor(saw_nvg, saw_pianoroll_turned_off[i] + nvgFillColor(saw_nvg, saw_roll_turned_off[pitch] ? nvgRGBA(220, 220, 220, 160) : has_cursor ? nvgRGBA(200, 200, 255, 255) : nvgRGBA(220, 220, 220, 255)); nvgFill(saw_nvg); + // Piano roll input + // + if (has_cursor) { hover_any = 1; - if (!saw_pianoroll_turned_off[i] && + if (!saw_roll_turned_off[pitch] && (saw_lbutton_click || - (saw_lbutton_down && saw_pianoroll_last_index != i)) && + (saw_lbutton_down && saw_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_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] = i; - saw_voice_time[0] = 0; + saw_voice_on[0] = 1; + saw_voice_pitch[0] = pitch; + saw_voice_time[0] = 0; + saw_voice_duration[0] = .6; } if (saw_rbutton_click) - saw_pianoroll_turned_off[i] = !saw_pianoroll_turned_off[i]; + saw_roll_turned_off[pitch] = !saw_roll_turned_off[pitch]; - saw_pianoroll_last_index = i; + saw_roll_last_index = pitch; } } if (!hover_any) - saw_pianoroll_last_index = -1; + saw_roll_last_index = -1; + + // Draw music sheet + // + + for (i32 pitch = 0; pitch < ROLL_SIZE; pitch++) { + i32 y = height - y0 - (pitch + 1) * pianokey_height; + + if (y > height - pianokey_height) + continue; + if (y < 0) + break; + + i32 h = pianokey_height; + + for (i32 t = 0;; t++) { + i32 x = x0 + pianokey_width + sheet_offset + t * sheet_scale; + + if (x >= width - roll_border) + break; + + i32 note = -1; + + for (i32 n = 0; n < SHEET_SIZE; n++) { + saw_sheet_note_t *p = saw_roll_sheet.notes + n; + if (p->enabled && p->pitch == pitch && t >= p->time && + t < p->time + p->duration) { + note = n; + break; + } + } + + i8 has_cursor = 0; + + if (note == -1) { + // Draw empty cell + // + + i32 w = sheet_scale; + + nvgBeginPath(saw_nvg); + nvgRect(saw_nvg, x + roll_border, y + roll_border, + w - roll_border * 2, h - roll_border * 2); + + has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w && + saw_mouse_y >= y && saw_mouse_y < y + h; + + nvgFillColor(saw_nvg, saw_roll_turned_off[pitch] + ? nvgRGBA(180, 180, 180, 120) + : has_cursor + ? nvgRGBA(180, 180, 220, 160) + : nvgRGBA(180, 180, 180, 160)); + nvgFill(saw_nvg); + + // Empty cell input + // + + if (!saw_grid_input && !saw_roll_turned_off[pitch] && + has_cursor && saw_lbutton_click) { + for (i32 n = 0; n < SHEET_SIZE; n++) + if (!saw_roll_sheet.notes[n].enabled) { + saw_roll_sheet.notes[n] = (saw_sheet_note_t) { + .enabled = 1, .time = t, .duration = 1, .pitch = pitch + }; + saw_grid_input = 1; + saw_grid_note = n; + saw_grid_pitch = pitch; + saw_grid_time = t; + break; + } + } + } else { + i32 w = sheet_scale * saw_roll_sheet.notes[note].duration; + + has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w && + saw_mouse_y >= y && saw_mouse_y < y + h; + + if (t == saw_roll_sheet.notes[note].time) { + // Draw note + // + + 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) + : nvgRGBA(180, 180, 180, 255)); + nvgFill(saw_nvg); + + // Note input + // + + if (has_cursor && saw_rbutton_down) + saw_roll_sheet.notes[note].enabled = 0; + } + } + } + + // Note stretching + // + + 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; + } + } nvgEndFrame(saw_nvg); @@ -241,13 +399,15 @@ static void saw_event(sapp_event const *event) { break; case SAPP_EVENTTYPE_MOUSE_DOWN: - switch (event->mouse_button) { case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 1; saw_lbutton_click = 1; break; - case SAPP_MOUSEBUTTON_RIGHT: saw_rbutton_click = 1; break; + case SAPP_MOUSEBUTTON_RIGHT: + saw_rbutton_down = 1; + saw_rbutton_click = 1; + break; default:; } break; @@ -255,6 +415,7 @@ static void saw_event(sapp_event const *event) { case SAPP_EVENTTYPE_MOUSE_UP: switch (event->mouse_button) { case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 0; break; + case SAPP_MOUSEBUTTON_RIGHT: saw_rbutton_down = 0; break; default:; } break; diff --git a/source/saw/profiler.h b/source/saw/profiler.h index a4e0f9f..850b2e8 100644 --- a/source/saw/profiler.h +++ b/source/saw/profiler.h @@ -1,8 +1,6 @@ #ifndef SAW_PROFILER_H #define SAW_PROFILER_H -extern long long profiler_time_; - void profile_frame(char const *s); #endif |