summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO3
-rw-r--r--source/saw/main.c479
2 files changed, 387 insertions, 95 deletions
diff --git a/TODO b/TODO
index c23e262..fbb744b 100644
--- a/TODO
+++ b/TODO
@@ -7,7 +7,6 @@ To-Do list
- Build: WebAssembly
- Sound: Kick, snare, cymbal
- Sound: EQ, delay, reverb, compressor, limiter
-- UI: Track composing
- UI: Effects stack
- Biquad filter
- Fourier transform
@@ -23,6 +22,7 @@ To-Do list
- Sound: Track looping
- Implement Undo and Redo
- Hot loading
+- UI: Playback controls
Done
@@ -33,3 +33,4 @@ Done
- UI: Playback control
- UI: Text rendering
- UI: Piano roll panning
+- UI: Track composing
diff --git a/source/saw/main.c b/source/saw/main.c
index 3dc5daa..0720960 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -28,9 +28,11 @@ enum {
SAW_SAMPLE_RATE = 44100,
VOICE_COUNT = 16,
- ROLL_SIZE = 40,
+ ROLL_SIZE = 60,
SHEET_SIZE = 200,
- ROLL_COUNT = 4,
+ ROLL_COUNT = 16,
+
+ ROLL_DEFAULT_OFFSET_Y = 710,
};
typedef struct {
@@ -45,25 +47,34 @@ typedef struct {
i64 time;
i64 duration;
i64 pitch;
-} saw_sheet_note_t;
+} saw_roll_note_t;
typedef struct {
- i64 rate;
- saw_sheet_note_t notes[SHEET_SIZE];
-} saw_sheet_t;
+ i8 enabled;
+ i64 track;
+ i32 last_index;
+ i8 turned_off[ROLL_SIZE];
+ i64 rate;
+ saw_roll_note_t notes[SHEET_SIZE];
+ i64 frame;
+ i64 size;
+ i8 grid_input;
+ i32 grid_note;
+ i32 grid_time;
+ i8 offset_input;
+ i64 offset_x;
+ i64 offset_y;
+} saw_roll_t;
typedef struct {
- i32 last_index;
- i8 turned_off[ROLL_SIZE];
- saw_sheet_t sheet;
- i8 grid_input;
- i32 grid_note;
- i32 grid_pitch;
- i32 grid_time;
- i8 offset_input;
- i64 offset_x;
- i64 offset_y;
-} saw_roll_t;
+ i64 rolls[ROLL_COUNT];
+ i8 offset_input;
+ i64 offset_x;
+ i64 offset_y;
+ i8 grid_input;
+ i32 grid_roll;
+ i32 grid_cell;
+} saw_compose_t;
static struct NVGcontext *saw_nvg;
static ma_device saw_ma;
@@ -86,6 +97,16 @@ static i64 saw_current_roll = 0;
static saw_voice_t saw_voices[VOICE_COUNT] = { 0 };
static saw_roll_t saw_rolls[ROLL_COUNT];
+static saw_compose_t saw_compose = {
+ .rolls = { -1 },
+ .offset_input = 0,
+ .offset_x = 0,
+ .offset_y = 0,
+ .grid_input = 0,
+ .grid_roll = 0,
+ .grid_cell = 0,
+};
+
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-function"
@@ -96,21 +117,22 @@ static saw_roll_t saw_rolls[ROLL_COUNT];
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.
- //
-
+ f64 a = 1.;
if (t < attack)
- return t / attack;
+ a *= 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;
+ a *= 1. - (1. - sustain) * (t - attack) / decay;
else
- return 0.;
+ a *= sustain;
+ if (t >= duration && t < duration + release)
+ a *= (duration + release - t) / release;
+ else if (t >= duration + release)
+ a = 0.;
+ return a;
+}
+
+static f64 saw_pitch_frequency(i64 pitch) {
+ return pow(2., 5.5 + pitch / 12.);
}
static void saw_playback(i32 frame_count) {
@@ -119,12 +141,18 @@ static void saw_playback(i32 frame_count) {
for (i32 k = 0; k < ROLL_COUNT; k++) {
saw_roll_t *roll = saw_rolls + k;
+ if (!roll->enabled ||
+ saw_playback_frame + frame_count <= roll->frame ||
+ saw_playback_frame >=
+ roll->frame + (roll->size * SAW_SAMPLE_RATE) / roll->rate)
+ continue;
for (i32 i = 0; i < SHEET_SIZE; i++) {
- saw_sheet_note_t *p = roll->sheet.notes + i;
+ saw_roll_note_t *p = roll->notes + i;
if (!p->enabled)
continue;
- i64 frame = (p->time * SAW_SAMPLE_RATE) / roll->sheet.rate;
+ i64 frame = roll->frame +
+ (p->time * SAW_SAMPLE_RATE) / roll->rate;
if (saw_playback_frame + frame_count <= frame ||
saw_playback_frame > frame)
continue;
@@ -137,8 +165,8 @@ static void saw_playback(i32 frame_count) {
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.)
+ .duration = ((f64) p->duration) / roll->rate,
+ .frequency = saw_pitch_frequency(p->pitch),
};
}
}
@@ -194,7 +222,251 @@ static void saw_audio(ma_device *device, void *void_out_,
# pragma GCC diagnostic pop
#endif
-static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { }
+static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
+ i64 frame_height = sapp_height();
+
+ i64 track_height = 60;
+ i64 grid_scale = 40;
+ i64 grid_rate = 2;
+ i64 border = 2;
+
+ // Time bar
+ //
+
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x0, frame_height - y0 - height + border, width,
+ frame_height - y0 - height + track_height / 2 - border * 2);
+ nvgFillColor(saw_nvg, nvgRGBA(180, 140, 120, 160));
+ nvgFill(saw_nvg);
+
+ // Tracks
+ //
+
+ i8 hover_any = 0;
+
+ for (i64 i = 0; i < ROLL_COUNT; i++) {
+ if (saw_compose.rolls[i] == -1)
+ continue;
+ saw_roll_t *roll = saw_rolls + saw_compose.rolls[i];
+
+ i64 x = x0 + saw_compose.offset_x +
+ (roll->frame * grid_scale) / SAW_SAMPLE_RATE;
+ i64 w = (roll->size * grid_scale) / roll->rate;
+ i64 y = frame_height - y0 - height + track_height +
+ saw_compose.offset_y + roll->track * track_height;
+
+ if (x < x0 || x + w >= x0 + width ||
+ y < frame_height - y0 - height ||
+ y + track_height >= frame_height - y0)
+ continue;
+
+ i8 is_choosen = (saw_current_roll == i);
+
+ i8 is_playing = saw_playback_on &&
+ saw_playback_frame >= roll->frame &&
+ saw_playback_frame <
+ roll->frame + (roll->size * SAW_SAMPLE_RATE) /
+ roll->rate;
+
+ i8 has_cursor = (saw_compose.grid_input &&
+ saw_compose.grid_roll == i) ||
+ (saw_mouse_x >= x && saw_mouse_x < x + w &&
+ saw_mouse_y >= y &&
+ saw_mouse_y < y + track_height);
+
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x + border, y + border, w - border * 2,
+ track_height - border * 2);
+ nvgFillColor(saw_nvg, is_choosen ? nvgRGBA(240, 230, 200, 255)
+ : is_playing ? nvgRGBA(255, 230, 200, 255)
+ : has_cursor ? nvgRGBA(210, 210, 255, 255)
+ : nvgRGBA(180, 180, 180, 220));
+ nvgFill(saw_nvg);
+
+ if (has_cursor) {
+ if (saw_rbutton_down) {
+ if (saw_current_roll == saw_compose.rolls[i])
+ saw_current_roll = -1;
+ saw_compose.rolls[i] = -1;
+ roll->enabled = 0;
+ } else {
+ if (saw_lbutton_click)
+ saw_current_roll = saw_compose.rolls[i];
+ hover_any = 1;
+ }
+ }
+ }
+
+ // Draw cursor
+ //
+
+ if (!hover_any && !saw_compose.grid_input) {
+ i64 track = (saw_mouse_y - saw_compose.offset_y - frame_height +
+ y0 + height) /
+ track_height -
+ 1;
+ i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) /
+ grid_scale;
+ i64 x = x0 + saw_compose.offset_x +
+ (cell * grid_scale) / grid_rate;
+ i64 y = frame_height - y0 - height + track_height +
+ saw_compose.offset_y + track * track_height;
+ i64 w = grid_scale / grid_rate;
+
+ if (track >= 0 && track < ROLL_COUNT && x >= x0 &&
+ x + w < x0 + width &&
+ y >= frame_height - y0 - height + track_height &&
+ y + track_height < frame_height - y0) {
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x + border, y + border, w - border * 2,
+ track_height - border * 2);
+ nvgFillColor(saw_nvg, nvgRGBA(180, 160, 140, 160));
+ nvgFill(saw_nvg);
+ }
+ }
+
+ // Placing new track
+ //
+
+ if (!hover_any && saw_lbutton_down && saw_mouse_x >= x0 &&
+ saw_mouse_x < x0 + width) {
+ if (!saw_compose.grid_input &&
+ saw_mouse_y >= frame_height - y0 - height &&
+ saw_mouse_y < frame_height - y0 - height + track_height)
+ saw_playback_frame = ((saw_mouse_x - saw_compose.offset_x) *
+ SAW_SAMPLE_RATE) /
+ grid_scale;
+ else if (saw_lbutton_click &&
+ saw_mouse_y >=
+ frame_height - y0 - height + track_height &&
+ saw_mouse_y < frame_height - y0) {
+ i64 track = (saw_mouse_y - saw_compose.offset_y - frame_height +
+ y0 + height) /
+ track_height -
+ 1;
+ i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) /
+ grid_scale;
+ i64 frame = (cell * SAW_SAMPLE_RATE) / grid_rate;
+
+ i64 n = -1;
+
+ for (i64 i = 0; i < ROLL_COUNT; i++)
+ if (!saw_rolls[i].enabled) {
+ n = i;
+ break;
+ }
+
+ i64 x = x0 + saw_compose.offset_x +
+ (frame * grid_scale) / SAW_SAMPLE_RATE;
+ i64 y = frame_height - y0 - height + track_height +
+ saw_compose.offset_y + track * track_height;
+
+ if (track < 0 || track >= ROLL_COUNT || x < x0 ||
+ x >= x0 + width || y < frame_height - y0 - height ||
+ y + track_height >= frame_height - y0)
+ n = -1;
+
+ if (n != -1) {
+ for (i64 i = 0; i < ROLL_COUNT; i++)
+ if (saw_compose.rolls[i] == -1) {
+ saw_compose.rolls[i] = n;
+ saw_compose.grid_input = 1;
+ saw_compose.grid_roll = n;
+ saw_compose.grid_cell = cell;
+ break;
+ }
+
+ saw_rolls[n] = (saw_roll_t) {
+ .enabled = 1,
+ .track = track,
+ .last_index = -1,
+ .turned_off = { 0 },
+ .rate = 6,
+ .notes = { 0 },
+ .frame = frame,
+ .size = 6 / grid_rate,
+ .grid_input = 0,
+ .grid_note = 0,
+ .grid_time = 0,
+ .offset_input = 0,
+ .offset_x = 0,
+ .offset_y = ROLL_DEFAULT_OFFSET_Y,
+ };
+
+ saw_current_roll = n;
+ }
+ }
+ }
+
+ // Track stretching input
+ //
+
+ if (saw_compose.grid_input) {
+ if (saw_lbutton_down) {
+ i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) /
+ grid_scale;
+ saw_roll_t *p = saw_rolls + saw_compose.grid_roll;
+
+ if (cell >= 0) {
+ if (saw_compose.grid_cell <= cell) {
+ p->frame = (saw_compose.grid_cell * SAW_SAMPLE_RATE) /
+ grid_rate;
+ p->size = ((1 + cell - saw_compose.grid_cell) * p->rate) /
+ grid_rate;
+ } else {
+ p->frame = (cell * SAW_SAMPLE_RATE) / grid_rate;
+ p->size = ((1 + saw_compose.grid_cell - cell) * p->rate) /
+ grid_rate;
+ }
+ }
+
+ for (i64 i = 0; i < ROLL_COUNT; i++) {
+ if (i == saw_compose.grid_roll)
+ continue;
+ saw_roll_t *q = saw_rolls + i;
+ if (!q->enabled || p->track != q->track)
+ continue;
+ i64 q_cell = (q->frame * grid_rate) / SAW_SAMPLE_RATE;
+ i64 q_size = (q->size * grid_rate) / q->rate;
+ if (saw_compose.grid_cell < q_cell && cell >= q_cell) {
+ cell = q_cell - 1;
+ p->frame = (saw_compose.grid_cell * SAW_SAMPLE_RATE) /
+ grid_rate;
+ p->size = ((q_cell - saw_compose.grid_cell) * p->rate) /
+ grid_rate;
+ }
+ if (saw_compose.grid_cell > q_cell &&
+ cell < q_cell + q_size) {
+ cell = q_cell + q_size;
+ p->frame = ((q_cell + q_size) * SAW_SAMPLE_RATE) /
+ grid_rate;
+ p->size = ((1 + saw_compose.grid_cell - q_cell - q_size) *
+ p->rate) /
+ grid_rate;
+ }
+ }
+
+ if (p->size <= 0)
+ p->size = 1;
+ } else
+ saw_compose.grid_input = 0;
+ }
+
+ // Playback indicator
+ //
+
+ i32 x = x0 + saw_compose.offset_x - border * 2 +
+ (saw_playback_frame * grid_scale + SAW_SAMPLE_RATE / 2) /
+ SAW_SAMPLE_RATE;
+ i32 w = border * 4;
+
+ if (x >= x0 - border * 2 && x < x0 + width) {
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x, frame_height - y0 - height, w, height);
+ nvgFillColor(saw_nvg, nvgRGBA(240, 240, 80, 180));
+ nvgFill(saw_nvg);
+ }
+}
static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
i64 height, str_t title) {
@@ -203,7 +475,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
i32 text_height = 35;
i32 pianokey_height = 35;
i32 pianokey_width = 100;
- i32 roll_border = 2;
+ i32 border = 2;
i32 sheet_offset = 40;
i32 sheet_scale = 20;
@@ -221,8 +493,8 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
nvgFontSize(saw_nvg, text_height);
nvgFontFaceId(saw_nvg, saw_font);
nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
- nvgText(saw_nvg, x0 + roll_border * 2,
- frame_height - y0 - height + text_height - roll_border * 2,
+ nvgText(saw_nvg, x0 + border * 2,
+ frame_height - y0 - height + text_height - border * 2,
title.values, title.values + title.size);
nvgFill(saw_nvg);
@@ -232,13 +504,13 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
i8 hover_any = 0;
for (i32 pitch = 0; pitch < ROLL_SIZE; pitch++) {
- i32 x = x0 + roll_border;
+ i32 x = x0 + border;
i32 y = frame_height - y0 - (pitch + 1) * pianokey_height +
- roll_border + roll->offset_y;
- i32 w = pianokey_width - roll_border * 2;
- i32 h = pianokey_height - roll_border * 2;
+ border + roll->offset_y;
+ i32 w = pianokey_width - border * 2;
+ i32 h = pianokey_height - border * 2;
- if (y > frame_height - y0)
+ if (y > frame_height - y0 - pianokey_height)
continue;
if (y < frame_height - y0 - height + text_height)
break;
@@ -268,11 +540,12 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
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.) };
+ saw_voices[0] = (saw_voice_t) {
+ .enabled = 1,
+ .time = 0,
+ .duration = .6,
+ .frequency = saw_pitch_frequency(pitch)
+ };
}
if (saw_rbutton_click)
@@ -299,24 +572,24 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
i32 y = frame_height - y0 - (pitch + 1) * pianokey_height +
roll->offset_y;
- if (y > frame_height - y0)
+ if (y + border > frame_height - y0 - pianokey_height)
continue;
- if (y < frame_height - y0 - height + text_height)
+ if (y + border < frame_height - y0 - height + text_height)
break;
i32 h = pianokey_height;
- for (i32 t = 0;; t++) {
+ for (i32 t = 0; t < roll->size; t++) {
i32 x = x0 + pianokey_width + sheet_offset + t * sheet_scale +
roll->offset_x;
- if (x >= x0 + width - sheet_scale - roll_border)
+ if (x >= x0 + width - sheet_scale - border)
break;
i32 note = -1;
for (i32 n = 0; n < SHEET_SIZE; n++) {
- saw_sheet_note_t *p = roll->sheet.notes + n;
+ saw_roll_note_t *p = roll->notes + n;
if (p->enabled && p->pitch == pitch && t >= p->time &&
t < p->time + p->duration) {
note = n;
@@ -324,8 +597,6 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
}
}
- i8 has_cursor = 0;
-
if (note == -1) {
// Draw empty cell
//
@@ -336,11 +607,12 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
i32 w = sheet_scale;
nvgBeginPath(saw_nvg);
- nvgRect(saw_nvg, x + roll_border, y + roll_border,
- w - roll_border * 2, h - roll_border * 2);
+ nvgRect(saw_nvg, x + border, y + border, w - border * 2,
+ h - border * 2);
- has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w &&
- saw_mouse_y >= y && saw_mouse_y < y + h;
+ i8 has_cursor = !roll->grid_input && saw_mouse_x >= x &&
+ saw_mouse_x < x + w && saw_mouse_y >= y &&
+ saw_mouse_y < y + h;
nvgFillColor(saw_nvg, roll->turned_off[pitch]
? nvgRGBA(160, 150, 120, 100)
@@ -355,38 +627,39 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
if (!roll->grid_input && !roll->turned_off[pitch] &&
has_cursor && saw_lbutton_click) {
for (i32 n = 0; n < SHEET_SIZE; n++)
- if (!roll->sheet.notes[n].enabled) {
- roll->sheet.notes[n] = (saw_sheet_note_t) {
+ if (!roll->notes[n].enabled) {
+ roll->notes[n] = (saw_roll_note_t) {
.enabled = 1, .time = t, .duration = 1, .pitch = pitch
};
roll->grid_input = 1;
roll->grid_note = n;
- roll->grid_pitch = pitch;
roll->grid_time = t;
break;
}
}
} else {
- i32 w = sheet_scale * roll->sheet.notes[note].duration;
+ i32 w = sheet_scale * roll->notes[note].duration;
- has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w &&
- saw_mouse_y >= y && saw_mouse_y < y + h;
+ i8 has_cursor = (roll->grid_input &&
+ roll->grid_note == note) ||
+ (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) {
+ if (t == roll->notes[note].time) {
// Draw note
//
- i64 t0 = (t * SAW_SAMPLE_RATE) / roll->sheet.rate;
- i64 t1 = ((t + roll->sheet.notes[note].duration) *
+ i64 t0 = (t * SAW_SAMPLE_RATE) / roll->rate;
+ i64 t1 = ((t + roll->notes[note].duration) *
SAW_SAMPLE_RATE) /
- roll->sheet.rate;
+ roll->rate;
i8 is_playing = saw_playback_on &&
- saw_playback_frame >= t0 &&
- saw_playback_frame < t1;
+ saw_playback_frame - roll->frame >= t0 &&
+ saw_playback_frame - roll->frame < t1;
i32 underflow = (x0 + pianokey_width + sheet_offset +
sheet_scale - 1 - x) /
@@ -403,12 +676,12 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
w -= overflow * sheet_scale;
nvgBeginPath(saw_nvg);
- nvgRect(saw_nvg, x + roll_border, y + roll_border,
- w - roll_border * 2, h - roll_border * 2);
+ nvgRect(saw_nvg, x + border, y + border, w - border * 2,
+ h - border * 2);
nvgFillColor(saw_nvg,
is_playing ? nvgRGBA(255, 230, 200, 255)
- : has_cursor ? nvgRGBA(180, 180, 220, 255)
+ : has_cursor ? nvgRGBA(190, 190, 230, 255)
: nvgRGBA(180, 180, 180, 255));
nvgFill(saw_nvg);
@@ -416,7 +689,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
//
if (has_cursor && saw_rbutton_down)
- roll->sheet.notes[note].enabled = 0;
+ roll->notes[note].enabled = 0;
}
}
}
@@ -431,22 +704,22 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
roll->offset_x) /
sheet_scale;
+ saw_roll_note_t *p = roll->notes + roll->grid_note;
+
if (t >= 0) {
- saw_sheet_note_t *p = roll->sheet.notes + roll->grid_note;
if (roll->grid_time <= t) {
p->time = roll->grid_time;
p->duration = 1 + t - roll->grid_time;
} else {
p->time = t;
- p->duration = roll->grid_time - t + 1;
+ p->duration = 1 + roll->grid_time - t;
}
}
- saw_sheet_note_t *p = roll->sheet.notes + roll->grid_note;
for (i32 n = 0; n < SHEET_SIZE; n++) {
if (n == roll->grid_note)
continue;
- saw_sheet_note_t *q = roll->sheet.notes + n;
+ saw_roll_note_t *q = roll->notes + n;
if (!q->enabled || q->pitch != p->pitch)
continue;
if (q->time < roll->grid_time &&
@@ -470,14 +743,15 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
// Playback indicator
//
- f64 playback_time = ((f64) saw_playback_frame) / SAW_SAMPLE_RATE;
-
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;
-
- if (x >= x0 + pianokey_width + sheet_offset - roll_border * 2 &&
+ border * 2 +
+ ((saw_playback_frame - roll->frame) * roll->rate *
+ sheet_scale +
+ SAW_SAMPLE_RATE / 2) /
+ SAW_SAMPLE_RATE;
+ i32 w = border * 4;
+
+ if (x >= x0 + pianokey_width + sheet_offset - border * 2 &&
x < x0 + width) {
nvgBeginPath(saw_nvg);
nvgRect(saw_nvg, x, frame_height - y0 - height + text_height, w,
@@ -518,19 +792,26 @@ static void saw_init(void) {
if (saw_font == -1)
printf("nvgCreateFontMem failed.\n");
- for (i32 i = 0; i < ROLL_COUNT; i++)
+ for (i32 i = 0; i < ROLL_COUNT; i++) {
+ saw_compose.rolls[i] = (i == 0 ? 0 : -1);
+
saw_rolls[i] = (saw_roll_t) {
+ .enabled = (i == 0),
+ .track = 0,
.last_index = -1,
.turned_off = { 0 },
- .sheet = { .rate = 6, .notes = { 0 } },
+ .rate = 6,
+ .notes = { 0 },
+ .frame = 0,
+ .size = 48,
.grid_input = 0,
.grid_note = 0,
- .grid_pitch = 0,
.grid_time = 0,
.offset_input = 0,
.offset_x = 0,
- .offset_y = 0,
+ .offset_y = ROLL_DEFAULT_OFFSET_Y,
};
+ }
}
static void saw_frame(void) {
@@ -545,13 +826,16 @@ static void saw_frame(void) {
nvgBeginFrame(saw_nvg, frame_width, frame_height, sapp_dpi_scale());
- saw_ui_compose(0, frame_height / 2, frame_width, frame_height / 2);
+ saw_ui_compose(0, (frame_height * 3) / 5, frame_width,
+ (frame_height * 2) / 5);
- char buf[64];
- sprintf(buf, "Track %lld", saw_current_roll + 1);
+ if (saw_current_roll != -1) {
+ char buf[64];
+ sprintf(buf, "Track %lld", saw_current_roll + 1);
- saw_ui_roll(saw_rolls + saw_current_roll, 0, 0, frame_width,
- frame_height / 2, kit_str(strlen(buf), buf));
+ saw_ui_roll(saw_rolls + saw_current_roll, 0, 0, frame_width,
+ (frame_height * 3) / 5, kit_str(strlen(buf), buf));
+ }
nvgEndFrame(saw_nvg);
@@ -621,6 +905,13 @@ static void saw_event(sapp_event const *event) {
saw_playback_on = !saw_playback_on;
break;
case SAPP_KEYCODE_ENTER: saw_playback_frame = 0; break;
+ case SAPP_KEYCODE_ESCAPE:
+ if (saw_current_roll != -1) {
+ saw_rolls[saw_current_roll].offset_x = 0;
+ saw_rolls[saw_current_roll].offset_y =
+ ROLL_DEFAULT_OFFSET_Y;
+ }
+ break;
default:;
}
}