From 4f00e27741c73efb0e962d0f0a2de15003b5e1cd Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Wed, 27 Sep 2023 18:30:25 +0200 Subject: Tracks composing --- source/saw/main.c | 479 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 385 insertions(+), 94 deletions(-) (limited to 'source') 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:; } } -- cgit v1.2.3