diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2023-09-25 22:42:32 +0200 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2023-09-25 22:42:32 +0200 |
commit | 234110ad800da983608cad7eaa79d54e9416503c (patch) | |
tree | 880863db1cf793cddeb39a8ed415c7765e86e70f | |
parent | d4ae51a005db08108b2a6f1c4136d1698851e69d (diff) | |
download | saw-234110ad800da983608cad7eaa79d54e9416503c.zip |
pianoroll: in progress
-rw-r--r-- | TODO | 9 | ||||
-rw-r--r-- | source/saw/main.c | 180 |
2 files changed, 168 insertions, 21 deletions
@@ -1,10 +1,13 @@ To-Do list -- Sheet editor UI -- Playback control +- UI: Piano roll +- UI: Volume control +- UI: Playback control - Simple tonal synth +- Build: WebAssembly - Drum synth: kick, snare, cymbal -- Track composing UI +- UI: Track composing +- Build: Faster recompilation Done diff --git a/source/saw/main.c b/source/saw/main.c index d32188f..b362ed7 100644 --- a/source/saw/main.c +++ b/source/saw/main.c @@ -31,30 +31,91 @@ enum { SAW_CHANNEL_COUNT = 2, SAW_SAMPLE_RATE = 44100, + + VOICE_COUNT = 4, + PIANOROLL_SIZE = 40 }; static struct NVGcontext *saw_nvg; static ma_device saw_ma; -static void saw_audio(ma_device *device, void *void_out_, - void const *void_in_, ma_uint32 frame_count) { - static i64 t = 0; +static i32 saw_mouse_x = 0; +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_voice_on[VOICE_COUNT] = { 0 }; +static i32 saw_voice_pitch[VOICE_COUNT] = { 0 }; +static i64 saw_voice_time[VOICE_COUNT] = { 0 }; + +static i32 saw_pianoroll_last_index = -1; +static i8 saw_pianoroll_turned_off[PIANOROLL_SIZE] = { 0 }; - f64 period = M_PI * 2.; - f64 amplitude = .2; - f64 frequency = 240.; +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunknown-pragmas" +# pragma GCC push_options +# pragma GCC optimize("O3") +#endif + +static f64 saw_envelope(f64 t, f64 attack, f64 decay, f64 sustain, + f64 duration, f64 release) { + // FIXME + // Apply low-pass filter for the envelope curve to prevent + // clicking. + // + if (t < attack) + return t / attack; + else if (t < attack + decay) + return 1. - (1. - sustain) * (t - attack) / decay; + else if (t < duration) + return sustain; + else if (t < duration + release) + return sustain * (duration + release - t) / release; + else + return 0.; +} + +static void saw_audio(ma_device *device, void *void_out_, + void const *void_in_, ma_uint32 frame_count) { f32 *out = (f32 *) void_out_; for (i64 i = 0; i < frame_count; i++) { - f64 k = (period * frequency) / SAW_SAMPLE_RATE; - out[i * 2] = (f32) (sin(k * t) * amplitude); - out[i * 2 + 1] = (f32) (sin(k * t) * amplitude); + out[i * 2] = 0.f; + out[i * 2 + 1] = 0.f; + } + + for (i32 n = 0; n < VOICE_COUNT; n++) { + if (!saw_voice_on[n]) + continue; + + f64 period = M_PI * 2.; + f64 frequency = pow(2., 6.5 + saw_voice_pitch[n] / 12.); + + 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; - t++; + out[i * 2] += (f32) (sin(k * t) * amplitude); + out[i * 2 + 1] += (f32) (sin(k * t) * amplitude); + + saw_voice_time[n]++; + + if (t > 1.0) + saw_voice_on[n] = 0; + } } } +#ifdef __GNUC__ +# pragma GCC pop_options +# pragma GCC diagnostic pop +#endif + static void saw_init(void) { #ifdef SOKOL_GLCORE33 saw_nvg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); @@ -80,23 +141,78 @@ static void saw_init(void) { } static void saw_frame(void) { - int width = sapp_width(); - int height = sapp_height(); + i32 width = sapp_width(); + i32 height = sapp_height(); glViewport(0, 0, width, height); - glClearColor(.3f, .2f, .4f, 1.f); + glClearColor(.23f, .19f, .16f, 1.f); glClearDepthf(1.f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgBeginFrame(saw_nvg, width, height, sapp_dpi_scale()); - nvgBeginPath(saw_nvg); - nvgRect(saw_nvg, 100, 100, 300, 200); - nvgFillColor(saw_nvg, nvgRGBA(255, 192, 0, 255)); - nvgFill(saw_nvg); + // Draw piano roll + // + + i32 x0 = 20; + i32 y0 = 20; + + i32 pianokey_height = 40; + i32 pianokey_width = 100; + i32 pianokey_border = 2; + + 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; + + if (y > height - pianokey_height) + continue; + if (y < 0) + break; + + nvgBeginPath(saw_nvg); + nvgRect(saw_nvg, x, y, w, h); + + 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] + ? nvgRGBA(220, 220, 220, 160) + : has_cursor ? nvgRGBA(200, 200, 255, 255) + : nvgRGBA(220, 220, 220, 255)); + nvgFill(saw_nvg); + + if (has_cursor) { + if (!saw_pianoroll_turned_off[i] && + (saw_lbutton_click || + (saw_lbutton_down && saw_pianoroll_last_index != i))) { + 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[0] = 1; + saw_voice_pitch[0] = i; + saw_voice_time[0] = 0; + saw_pianoroll_last_index = i; + } + + if (saw_rbutton_click) + saw_pianoroll_turned_off[i] = !saw_pianoroll_turned_off[i]; + } + } nvgEndFrame(saw_nvg); + + // Cleanup input state. + // + + saw_lbutton_click = 0; + saw_rbutton_click = 0; } static void saw_cleanup(void) { @@ -109,7 +225,35 @@ static void saw_cleanup(void) { #endif } -static void saw_event(sapp_event const *event) { } +static void saw_event(sapp_event const *event) { + switch (event->type) { + case SAPP_EVENTTYPE_MOUSE_MOVE: + saw_mouse_x = event->mouse_x; + saw_mouse_y = event->mouse_y; + 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; + default:; + } + break; + + case SAPP_EVENTTYPE_MOUSE_UP: + switch (event->mouse_button) { + case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 0; break; + default:; + } + break; + + default:; + } +} char const *__lsan_default_suppressions() { // There is leaks in NVidia driver on Linux. |