summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2023-09-27 14:36:55 +0200
committerMitya Selivanov <automainint@guattari.tech>2023-09-27 14:36:55 +0200
commitf686896de7a0cd08a316cc3a9c3fd52d5b0075a3 (patch)
tree2d3c6e17289f3bacf8b724695af267f90c8c014e
parent709c66db61bbcd1ca2b8f92a9517e4130622ce27 (diff)
downloadsaw-f686896de7a0cd08a316cc3a9c3fd52d5b0075a3.zip
Piano roll panning
-rw-r--r--TODO2
-rw-r--r--source/saw/main.c186
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
@@ -36,6 +36,13 @@ enum {
typedef struct {
i8 enabled;
i64 time;
+ f64 duration;
+ f64 frequency;
+} saw_voice_t;
+
+typedef struct {
+ i8 enabled;
+ i64 time;
i64 duration;
i64 pitch;
} saw_sheet_note_t;
@@ -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;