summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2023-09-26 06:17:17 +0200
committerMitya Selivanov <automainint@guattari.tech>2023-09-26 06:17:17 +0200
commit2b9d44cf89f981f28cbbc6c60105f6c876340ffb (patch)
tree8a3f6c34aab63bb450fe99995115a8a57bdbd7a9
parentea17fff774c5a2f69f5e89227e6d5bab01bee5af (diff)
downloadsaw-2b9d44cf89f981f28cbbc6c60105f6c876340ffb.zip
piano roll and playback control
-rw-r--r--TODO4
-rw-r--r--source/saw/main.c138
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:;
}
}