diff options
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | source/saw/main.c | 837 |
2 files changed, 513 insertions, 326 deletions
@@ -30,7 +30,6 @@ To-Do list - Color theme customization - Matrix view - Graph view - - Touchscreen support - Custom font and localization - Data @@ -66,6 +65,7 @@ Done - Piano roll panning - Track composing - Instrument settings + - Touchscreen support - Data - State load and store diff --git a/source/saw/main.c b/source/saw/main.c index 865b0ab..bdbb5ad 100644 --- a/source/saw/main.c +++ b/source/saw/main.c @@ -1,3 +1,9 @@ +// ================================================================ +// +// Headers +// +// ================================================================ + #include "../kit/math.h" #include "../kit/time.h" #include "../kit/string_ref.h" @@ -24,14 +30,31 @@ #include <stdio.h> #include <stdlib.h> +// Fonts data #include "fonts.inl.h" +// ================================================================ +// +// Definitions +// +// ================================================================ + +// Program version +// + #define VERSION_MAJOR 0 #define VERSION_MINOR 0 #define VERSION_BABY 2 #define VERSION_DEV 1 +// Constants +// + enum { + // TODO + // Use 28224000 for time rate, divisible by common sample rates + // like 192000, 44100 etc. + CHANNEL_COUNT = 2, SAMPLE_RATE = 44100, @@ -42,8 +65,8 @@ enum { UNISON_COUNT = 100, SHEET_SIZE = 200, - ROLL_DEFAULT_RATE = 8, - ROLL_DEFAULT_OFFSET_Y = 710, + ROLL_DEFAULT_RATE = 8, + ROLL_DEFAULT_UI_OFFSET_Y = 710, INSTRUMENT_SINE = 0, INSTRUMENT_SAW_UP, @@ -65,8 +88,16 @@ enum { TRACK_INPUT_ATTACK, TRACK_INPUT_DECAY, TRACK_INPUT_RELEASE, + + EDIT_MODE_HAND = 0, + EDIT_MODE_ERASE, + EDIT_MODE_PAN, + EDIT_MODE_CLONE, }; +// Data types +// + typedef struct { b8 enabled; i64 time; @@ -93,8 +124,8 @@ typedef struct { i64 time; i64 duration; i64 loop_duration; - i64 offset_x; - i64 offset_y; + i64 ui_offset_x; + i64 ui_offset_y; // dynamic properties // @@ -102,8 +133,8 @@ typedef struct { b8 grid_input; i32 grid_note; i32 grid_time; - b8 offset_x_input; - b8 offset_y_input; + b8 ui_offset_x_input; + b8 ui_offset_y_input; b8 loop_input; } saw_roll_t; @@ -138,34 +169,46 @@ typedef struct { b8 grid_input; i32 grid_roll; i32 grid_cell; - b8 offset_input; - i64 offset_x; - i64 offset_y; + b8 ui_offset_input; + i64 ui_offset_x; + i64 ui_offset_y; b8 duplicate_input; } saw_compose_t; static struct NVGcontext *saw_nvg; -static ma_device saw_ma; +static ma_device saw_audio_device; -static char saw_project_file_buf[4096]; -static str_t saw_project_file; -static i32 saw_font_text = -1; -static i32 saw_font_icons = -1; -static mt64_state_t saw_mt64; +// Input events +// -static i32 saw_mouse_x = 0; -static i32 saw_mouse_y = 0; -static b8 saw_lbutton_click = 0; -static b8 saw_lbutton_down = 0; -static b8 saw_rbutton_click = 0; -static b8 saw_rbutton_down = 0; -static b8 saw_mbutton_click = 0; -static b8 saw_mbutton_down = 0; +static i64 saw_mouse_x = 0; +static i64 saw_mouse_y = 0; +static i64 saw_mouse_dx = 0; +static i64 saw_mouse_dy = 0; +static b8 saw_lbutton_click = 0; +static b8 saw_lbutton_down = 0; +static b8 saw_rbutton_click = 0; +static b8 saw_rbutton_down = 0; +static b8 saw_mbutton_click = 0; +static b8 saw_mbutton_down = 0; +static b8 saw_shift_on = 0; +static b8 saw_ctrl_on = 0; +static b8 saw_key_pressed[512] = { 0 }; + +// Global state +// static b8 saw_playback_on = 0; static i64 saw_playback_frame = 0; static i64 saw_current_track = 0; static i64 saw_current_roll = 0; +static i64 saw_edit_mode = EDIT_MODE_HAND; + +static char saw_project_file_buf[4096]; +static str_t saw_project_file; +static i32 saw_font_text = -1; +static i32 saw_font_icons = -1; +static mt64_state_t saw_mt64; // RNG static saw_voice_t saw_voices[VOICE_COUNT] = { 0 }; static saw_roll_t saw_rolls[ROLL_COUNT]; @@ -176,15 +219,15 @@ static saw_compose_t saw_compose = { .grid_input = 0, .grid_roll = 0, .grid_cell = 0, - .offset_input = 0, - .offset_x = 0, - .offset_y = 0, + .ui_offset_input = 0, + .ui_offset_x = 0, + .ui_offset_y = 0, .duplicate_input = 0, }; // ================================================================ // -// Audio +// Sound // // ================================================================ @@ -316,9 +359,8 @@ static f64 saw_oscillator(i32 type, f64 frequency, f64 phase, static void saw_audio(ma_device *device, void *void_out_, void const *void_in_, ma_uint32 frame_count) { - // FIXME + // TODO // Render to audio buffer separately. - // saw_playback(frame_count); @@ -378,10 +420,21 @@ static void saw_audio(ma_device *device, void *void_out_, // // ================================================================ +static void saw_reset_ui_offset(void) { + if (saw_current_roll != -1) { + saw_rolls[saw_current_roll].ui_offset_x = 0; + saw_rolls[saw_current_roll].ui_offset_y = + ROLL_DEFAULT_UI_OFFSET_Y; + } + + saw_compose.ui_offset_x = 0; + saw_compose.ui_offset_y = 0; +} + static void saw_ui_header(i64 x0, u64 y0, u64 width, i64 height) { i64 frame_height = sapp_height(); - i64 border = 12; + i64 border = 14; nvgFontSize(saw_nvg, height - border * 2); nvgFontFaceId(saw_nvg, saw_font_icons); @@ -398,46 +451,81 @@ static void saw_ui_header(i64 x0, u64 y0, u64 width, i64 height) { char panning[] = "\uf047"; char clone[] = "\uf24d"; - u8 color[] = { 220, 220, 220, 255 }; - -#define ICON_(i_) \ - do { \ - b8 has_cursor = saw_mouse_x >= x && saw_mouse_x < x + height && \ - saw_mouse_y >= frame_height - y0 - height && \ - saw_mouse_y < frame_height - y0; \ - if (has_cursor) \ - nvgFillColor(saw_nvg, nvgRGBA(255, 255, 220, 255)); \ - nvgText(saw_nvg, x, h, (i_), (i_) + (sizeof(i_) - 1)); \ - if (has_cursor) \ - nvgFillColor(saw_nvg, \ - nvgRGBA(color[0], color[1], color[2], color[3])); \ - x += height; \ + u8 color_active[] = { 220, 220, 220, 255 }; + u8 color_hover[] = { 255, 255, 220, 255 }; + u8 color_selected[] = { 255, 255, 255, 255 }; + + b8 has_cursor = 0; + +#define ICON_(i_, selected_) \ + do { \ + has_cursor = saw_mouse_x >= x && saw_mouse_x < x + height && \ + saw_mouse_y >= frame_height - y0 - height && \ + saw_mouse_y < frame_height - y0; \ + if (has_cursor) \ + nvgFillColor(saw_nvg, \ + nvgRGBA(color_hover[0], color_hover[1], \ + color_hover[2], color_hover[3])); \ + else if (selected_) \ + nvgFillColor(saw_nvg, \ + nvgRGBA(color_selected[0], color_selected[1], \ + color_selected[2], color_selected[3])); \ + else \ + nvgFillColor(saw_nvg, \ + nvgRGBA(color_active[0], color_active[1], \ + color_active[2], color_active[3])); \ + nvgText(saw_nvg, x, h, (i_), (i_) + (sizeof(i_) - 1)); \ + x += height; \ } while (0) - nvgFillColor(saw_nvg, - nvgRGBA(color[0], color[1], color[2], color[3])); + // Global actions + // - ICON_(backward_fast); + ICON_(backward_fast, 0); + if (has_cursor && saw_lbutton_click) + saw_playback_frame = 0; if (saw_playback_on) - ICON_(stop); + ICON_(stop, 0); else - ICON_(play); + ICON_(play, 0); + if (has_cursor && saw_lbutton_click) + saw_playback_on = !saw_playback_on; + + ICON_(anchor, 0); + if (has_cursor && saw_lbutton_click) + saw_reset_ui_offset(); + + // Editing mode + // - ICON_(anchor); - ICON_(hand_pointer); + x += height / 2; - color[0] = 220; - color[1] = 220; - color[2] = 220; - color[3] = 160; + color_active[0] = 220; + color_active[1] = 180; + color_active[2] = 80; + color_active[3] = 160; - nvgFillColor(saw_nvg, - nvgRGBA(color[0], color[1], color[2], color[3])); + color_selected[0] = 220; + color_selected[1] = 180; + color_selected[2] = 80; + color_selected[3] = 255; - ICON_(eraser); - ICON_(panning); - ICON_(clone); + ICON_(hand_pointer, saw_edit_mode == EDIT_MODE_HAND); + if (has_cursor && saw_lbutton_click) + saw_edit_mode = EDIT_MODE_HAND; + + ICON_(eraser, saw_edit_mode == EDIT_MODE_ERASE); + if (has_cursor && saw_lbutton_click) + saw_edit_mode = EDIT_MODE_ERASE; + + ICON_(panning, saw_edit_mode == EDIT_MODE_PAN); + if (has_cursor && saw_lbutton_click) + saw_edit_mode = EDIT_MODE_PAN; + + ICON_(clone, saw_edit_mode == EDIT_MODE_CLONE); + if (has_cursor && saw_lbutton_click) + saw_edit_mode = EDIT_MODE_CLONE; #undef ICON_ } @@ -475,11 +563,11 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { i64 top = frame_height - y0 - height + track_height; i64 bottom = frame_height - y0; - i64 dx = x0 + saw_compose.offset_x; + i64 dx = x0 + saw_compose.ui_offset_x; i64 l = dx + (roll->time * grid_scale) / SAMPLE_RATE; i64 r = l + (roll->duration * grid_scale) / SAMPLE_RATE; i64 u = frame_height - y0 - height + track_height + - saw_compose.offset_y + roll->track * track_height; + saw_compose.ui_offset_y + roll->track * track_height; i64 d = u + track_height; i64 s = grid_scale / grid_rate; @@ -521,15 +609,16 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { nvgFill(saw_nvg); if (has_cursor) { - if (saw_rbutton_down) { + if (saw_rbutton_down || + (saw_edit_mode == EDIT_MODE_ERASE && saw_lbutton_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) { + if (saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_click) { if (saw_current_roll == saw_compose.rolls[i]) { - i64 cell = ((saw_mouse_x - saw_compose.offset_x) * + i64 cell = ((saw_mouse_x - saw_compose.ui_offset_x) * grid_rate) / grid_scale; i64 c0 = (roll->time * grid_rate) / SAMPLE_RATE; @@ -558,24 +647,26 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { // Placing new sheet // - if (!hover_any && saw_lbutton_down && - saw_mouse_x >= x0 + saw_compose.offset_x && + if (!hover_any && saw_edit_mode == EDIT_MODE_HAND && + saw_lbutton_down && + saw_mouse_x >= x0 + saw_compose.ui_offset_x && 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_playback_frame = ((saw_mouse_x - saw_compose.ui_offset_x) * SAMPLE_RATE) / grid_scale; - else if (saw_lbutton_click && + else if (saw_edit_mode == EDIT_MODE_HAND && 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) / + i64 track = (saw_mouse_y - saw_compose.ui_offset_y - + frame_height + y0 + height) / track_height - 1; - i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) / + i64 cell = ((saw_mouse_x - saw_compose.ui_offset_x) * + grid_rate) / grid_scale; i64 frame = (cell * SAMPLE_RATE) / grid_rate; @@ -587,10 +678,10 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { break; } - i64 x = x0 + saw_compose.offset_x + + i64 x = x0 + saw_compose.ui_offset_x + (frame * grid_scale) / SAMPLE_RATE; i64 y = frame_height - y0 - height + track_height + - saw_compose.offset_y + track * track_height; + saw_compose.ui_offset_y + track * track_height; if (track < 0 || track >= TRACK_COUNT || x < x0 || x >= x0 + width || y < frame_height - y0 - height || @@ -616,16 +707,16 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { .time = frame, .duration = (ROLL_DEFAULT_RATE * SAMPLE_RATE) / grid_rate, .loop_duration = 0, - .offset_x = 0, - .offset_y = ROLL_DEFAULT_OFFSET_Y, - - .last_index = -1, - .grid_input = 0, - .grid_note = 0, - .grid_time = 0, - .offset_x_input = 0, - .offset_y_input = 0, - .loop_input = 0, + .ui_offset_x = 0, + .ui_offset_y = ROLL_DEFAULT_UI_OFFSET_Y, + + .last_index = -1, + .grid_input = 0, + .grid_note = 0, + .grid_time = 0, + .ui_offset_x_input = 0, + .ui_offset_y_input = 0, + .loop_input = 0, }; saw_current_roll = n; @@ -644,11 +735,12 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { if (saw_current_roll == -1) break; - i64 track = (saw_mouse_y - saw_compose.offset_y - frame_height + - y0 + height) / + i64 track = (saw_mouse_y - saw_compose.ui_offset_y - + frame_height + y0 + height) / track_height - 1; - i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) / + i64 cell = ((saw_mouse_x - saw_compose.ui_offset_x) * + grid_rate) / grid_scale; if (cell < 0 || track < 0 || track >= TRACK_COUNT) @@ -700,22 +792,25 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { // Panning input // - if (saw_mbutton_click) { + if (saw_mbutton_click || + (saw_edit_mode == EDIT_MODE_PAN && saw_lbutton_click)) { if (saw_mouse_x >= x0 && saw_mouse_y >= frame_height - y0 - height + track_height && saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0) - saw_compose.offset_input = 1; + saw_compose.ui_offset_input = 1; } - if (!saw_mbutton_down) - saw_compose.offset_input = 0; + if (!(saw_mbutton_down || + (saw_edit_mode == EDIT_MODE_PAN && saw_lbutton_down))) + saw_compose.ui_offset_input = 0; // Track stretching input // if (saw_compose.grid_input) { - if (saw_lbutton_down) { - i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) / + if (saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_down) { + i64 cell = ((saw_mouse_x - saw_compose.ui_offset_x) * + grid_rate) / grid_scale; saw_roll_t *p = saw_rolls + saw_compose.grid_roll; @@ -768,7 +863,7 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { // Playback indicator // - i64 x = x0 + saw_compose.offset_x - border * 2 + + i64 x = x0 + saw_compose.ui_offset_x - border * 2 + (saw_playback_frame * grid_scale + SAMPLE_RATE / 2) / SAMPLE_RATE; i64 w = border * 4; @@ -784,17 +879,17 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { // if (!hover_any && !saw_compose.grid_input && - saw_mouse_x >= x0 + saw_compose.offset_x) { - i64 track = (saw_mouse_y - saw_compose.offset_y - frame_height + - y0 + height) / + saw_mouse_x >= x0 + saw_compose.ui_offset_x) { + i64 track = (saw_mouse_y - saw_compose.ui_offset_y - + frame_height + y0 + height) / track_height - 1; - i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) / + i64 cell = ((saw_mouse_x - saw_compose.ui_offset_x) * grid_rate) / grid_scale; - i64 x = x0 + saw_compose.offset_x + + i64 x = x0 + saw_compose.ui_offset_x + (cell * grid_scale) / grid_rate; i64 y = frame_height - y0 - height + track_height + - saw_compose.offset_y + track * track_height; + saw_compose.ui_offset_y + track * track_height; i64 w = grid_scale / grid_rate; if (track >= 0 && track < TRACK_COUNT && x >= x0 && @@ -812,11 +907,11 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) { // Cursor indicator // - if (saw_mouse_x >= x0 + saw_compose.offset_x && + if (saw_mouse_x >= x0 + saw_compose.ui_offset_x && saw_mouse_x < x0 + width && saw_mouse_y >= frame_height - y0 - height && saw_mouse_y < frame_height - y0) { - i64 dx = x0 + saw_compose.offset_x; + i64 dx = x0 + saw_compose.ui_offset_x; i64 s = grid_scale / grid_rate; i64 x = dx + ((saw_mouse_x - dx + s / 2) / s) * s; i64 w = border * 4; @@ -854,9 +949,9 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0, for (i64 input_index = TRACK_INPUT_INSTRUMENT; input_index <= TRACK_INPUT_RELEASE; ++input_index) { - // FIXME + // TODO // Implement Unison and Spread. - // + if (input_index == TRACK_INPUT_UNISON || input_index == TRACK_INPUT_SPREAD) continue; @@ -956,11 +1051,11 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0, title.values, title.values + title.size); next_y = header_offset + text_height; - char buf_instr[][100] = { "Sine", "Saw up", "Saw down", - "Square up", "Square down", "Kick" }; + char buf_instr[][100] = { "Sine", "Saw up", "Saw down", + "Sqr up", "Sqr down", "Kick" }; nvgText(saw_nvg, x0 + border * 2, - frame_height - y0 - height + next_y - border * 2, - "Instrument", 0); + frame_height - y0 - height + next_y - border * 2, "Instr.", + 0); if (track->instrument >= 0 && track->instrument < INSTRUMENT_COUNT) nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, @@ -1099,25 +1194,45 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, char repeat[] = "\uf363"; - nvgFontSize(saw_nvg, header_height - 4); + i64 border = 5; + nvgFontSize(saw_nvg, header_height - border * 2); nvgFontFaceId(saw_nvg, saw_font_icons); - nvgFillColor(saw_nvg, nvgRGBA(180, 80, 40, 200)); - nvgText(saw_nvg, x - header_height * 2 + 2, y + header_height - 2, - repeat, repeat + (sizeof repeat - 1)); - - i64 w0 = (roll->loop_duration * roll->rate * sheet_scale + + if (roll->loop_duration == 0) + nvgFillColor(saw_nvg, nvgRGBA(80, 80, 80, 160)); + else + nvgFillColor(saw_nvg, nvgRGBA(180, 80, 40, 200)); + nvgText(saw_nvg, x - header_height * 2 + border, + y + header_height - border, repeat, + repeat + (sizeof repeat - 1)); + + i64 rw = (roll->loop_duration * roll->rate * sheet_scale + SAMPLE_RATE / 2) / SAMPLE_RATE; + i64 rx = roll->ui_offset_x; - nvgBeginPath(saw_nvg); - nvgRect(saw_nvg, x, y, w0, h); - nvgRect(saw_nvg, x, y + h * 4, w0, h); - nvgFillColor(saw_nvg, nvgRGBA(180, 80, 40, 160)); - nvgFill(saw_nvg); + if (rx < 0) { + rw += rx; + rx = 0; + } + + if (rw > w - rx) + rw = w - rx; + + if (rw > 0) { + nvgBeginPath(saw_nvg); + nvgRect(saw_nvg, x + rx, y, rw, h); + nvgRect(saw_nvg, x + rx, y + h * 4, rw, h); + nvgFillColor(saw_nvg, nvgRGBA(180, 80, 40, 160)); + nvgFill(saw_nvg); + } nvgBeginPath(saw_nvg); - nvgRect(saw_nvg, x + w0, y, w - w0, h); - nvgRect(saw_nvg, x + w0, y + h * 4, w - w0, h); + if (rx > 0) { + nvgRect(saw_nvg, x, y, rx, h); + nvgRect(saw_nvg, x, y + h * 4, rx, h); + } + nvgRect(saw_nvg, x + rx + rw, y, w - rx - rw, h); + nvgRect(saw_nvg, x + rx + rw, y + h * 4, w - rx - rw, h); nvgFillColor(saw_nvg, nvgRGBA(80, 80, 80, 160)); nvgFill(saw_nvg); @@ -1128,7 +1243,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, if (roll->loop_input && saw_lbutton_down) { i64 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset - - roll->offset_x + sheet_scale / 2) / + roll->ui_offset_x + sheet_scale / 2) / sheet_scale; if (t <= 0) @@ -1150,7 +1265,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, for (i64 pitch = 0; pitch < PITCH_COUNT; pitch++) { i64 x = x0 + border; i64 y = frame_height - y0 - (pitch + 1) * pianokey_height + - border + roll->offset_y; + border + roll->ui_offset_y; i64 w = pianokey_width - border * 2; i64 h = pianokey_height - border * 2; @@ -1178,12 +1293,14 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, hover_any = 1; if (!roll->pitch_turned_off[pitch] && - (saw_lbutton_click || - (saw_lbutton_down && roll->last_index != pitch))) + ((saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_click) || + (saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_down && + roll->last_index != pitch))) saw_play_voice(saw_tracks + roll->track, roll, pitch, SAMPLE_RATE / roll->rate); - if (saw_rbutton_click) + if (saw_rbutton_click || + (saw_edit_mode == EDIT_MODE_ERASE && saw_lbutton_click)) roll->pitch_turned_off[pitch] = !roll->pitch_turned_off[pitch]; @@ -1197,20 +1314,23 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, // Panning input // - if (saw_mbutton_click) { + if (saw_mbutton_click || (saw_edit_mode == EDIT_MODE_PAN && + saw_lbutton_click && !roll->loop_input)) { if (saw_mouse_x >= x0 + pianokey_width + sheet_offset && saw_mouse_y >= frame_height - y0 - height + text_height && saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0) - roll->offset_x_input = 1; + roll->ui_offset_x_input = 1; if (saw_mouse_x >= x0 && saw_mouse_y >= frame_height - y0 - height + text_height && saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0) - roll->offset_y_input = 1; + roll->ui_offset_y_input = 1; } - if (!saw_mbutton_down) { - roll->offset_x_input = 0; - roll->offset_y_input = 0; + if (!(saw_mbutton_down || + (saw_edit_mode == EDIT_MODE_PAN && saw_lbutton_down && + !roll->loop_input))) { + roll->ui_offset_x_input = 0; + roll->ui_offset_y_input = 0; } // Draw music sheet @@ -1218,7 +1338,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, for (i64 pitch = 0; pitch < PITCH_COUNT; pitch++) { i64 y = frame_height - y0 - (pitch + 1) * pianokey_height + - roll->offset_y; + roll->ui_offset_y; if (y > frame_height - y0 - pianokey_height) continue; @@ -1230,7 +1350,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, for (i64 t = 0; t < (roll->duration * roll->rate) / SAMPLE_RATE; t++) { i64 x = x0 + pianokey_width + sheet_offset + t * sheet_scale + - roll->offset_x; + roll->ui_offset_x; if (x >= x0 + width - sheet_scale - border) break; @@ -1284,7 +1404,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, // if (!roll->grid_input && !turned_off && has_cursor && - saw_lbutton_click) + saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_click) for (i64 n = 0; n < SHEET_SIZE; n++) if (!roll->notes[n].enabled) { roll->notes[n] = (saw_roll_note_t) { @@ -1312,14 +1432,14 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, continue; i64 y = frame_height - y0 - (note->pitch + 1) * pianokey_height + - roll->offset_y; + roll->ui_offset_y; if (y + pianokey_height > frame_height - y0) continue; if (y < frame_height - y0 - height + text_height + header_height) continue; - i64 x = x0 + pianokey_width + sheet_offset + roll->offset_x + + i64 x = x0 + pianokey_width + sheet_offset + roll->ui_offset_x + (note->time * roll->rate * sheet_scale + SAMPLE_RATE / 2) / SAMPLE_RATE; @@ -1377,7 +1497,9 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, // Note input // - if (has_cursor && saw_rbutton_down) + if (has_cursor && + (saw_rbutton_down || + (saw_edit_mode == EDIT_MODE_ERASE && saw_lbutton_down))) note->enabled = 0; } @@ -1385,9 +1507,9 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, // if (roll->grid_input) { - if (saw_lbutton_down) { + if (saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_down) { i64 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset - - roll->offset_x) / + roll->ui_offset_x) / sheet_scale; saw_roll_note_t *p = roll->notes + roll->grid_note; @@ -1442,7 +1564,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, frame -= ((frame - roll->time) / roll->loop_duration) * roll->loop_duration; - i64 x = x0 + pianokey_width + sheet_offset + roll->offset_x - + i64 x = x0 + pianokey_width + sheet_offset + roll->ui_offset_x - border * 2 + ((frame - roll->time) * roll->rate * sheet_scale + SAMPLE_RATE / 2) / @@ -1465,7 +1587,7 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, saw_mouse_x < x0 + width && saw_mouse_y >= frame_height - y0 - height && saw_mouse_y < frame_height - y0) { - i64 dx = x0 + pianokey_width + sheet_offset + roll->offset_x; + i64 dx = x0 + pianokey_width + sheet_offset + roll->ui_offset_x; i64 x = dx + ((saw_mouse_x - dx + sheet_scale / 2) / sheet_scale) * sheet_scale; @@ -1516,12 +1638,13 @@ static void saw_init(void) { config.dataCallback = saw_audio; config.pUserData = NULL; - if (ma_device_init(NULL, &config, &saw_ma) != MA_SUCCESS) { + if (ma_device_init(NULL, &config, &saw_audio_device) != + MA_SUCCESS) { printf("ma_device_init failed.\n"); return; } - ma_device_start(&saw_ma); + ma_device_start(&saw_audio_device); // Load fonts // @@ -1554,15 +1677,15 @@ static void saw_init(void) { .time = 0, .duration = (48 * SAMPLE_RATE) / ROLL_DEFAULT_RATE, .loop_duration = 0, - .offset_x = 0, - .offset_y = ROLL_DEFAULT_OFFSET_Y, - - .grid_input = 0, - .grid_note = 0, - .grid_time = 0, - .offset_x_input = 0, - .offset_y_input = 0, - .loop_input = 0, + .ui_offset_x = 0, + .ui_offset_y = ROLL_DEFAULT_UI_OFFSET_Y, + + .grid_input = 0, + .grid_note = 0, + .grid_time = 0, + .ui_offset_x_input = 0, + .ui_offset_y_input = 0, + .loop_input = 0, }; } @@ -1692,7 +1815,8 @@ static void saw_init(void) { SCAN_(" time %lld", 1, &roll->time); SCAN_(" duration %lld", 1, &roll->duration); SCAN_(" loop_duration %lld", 1, &roll->loop_duration); - SCAN_(" offset %lld %lld", 2, &roll->offset_x, &roll->offset_y); + SCAN_(" ui_offset %lld %lld", 2, &roll->ui_offset_x, + &roll->ui_offset_y); } i32 total_tracks; @@ -1734,8 +1858,8 @@ static void saw_init(void) { } static void saw_frame(void) { - // FIXME - // - Adjust sleep depending on how much compute we need + // TODO + // Adjust sleep depending on how much compute we need // Sleep to prevent high CPU load thrd_sleep(&(struct timespec) { .tv_nsec = 10000000 }, NULL); @@ -1744,7 +1868,7 @@ static void saw_frame(void) { i64 frame_height = sapp_height(); glViewport(0, 0, frame_width, frame_height); - glClearColor(.15f, .12f, .10f, 1.f); + glClearColor(.11f, .09f, .08f, 1.f); #ifdef SOKOL_GLCORE33 glClearDepth(1.); #else @@ -1753,10 +1877,148 @@ static void saw_frame(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - nvgBeginFrame(saw_nvg, frame_width, frame_height, sapp_dpi_scale()); + // Process input + // + { + if (saw_key_pressed[SAPP_KEYCODE_SPACE]) + saw_playback_on = !saw_playback_on; + if (saw_key_pressed[SAPP_KEYCODE_ENTER]) + saw_playback_frame = 0; + if (saw_key_pressed[SAPP_KEYCODE_ESCAPE]) + saw_reset_ui_offset(); + if (saw_key_pressed[SAPP_KEYCODE_D] || + (saw_edit_mode == EDIT_MODE_CLONE && saw_lbutton_click)) + saw_compose.duplicate_input = 1; + + // Panning control + // + for (i64 i = 0; i < ROLL_COUNT; i++) { + if (saw_rolls[i].ui_offset_x_input) + saw_rolls[i].ui_offset_x += saw_mouse_dx; + if (saw_rolls[i].ui_offset_y_input) + saw_rolls[i].ui_offset_y += saw_mouse_dy; + } + if (saw_compose.ui_offset_input) { + saw_compose.ui_offset_x += saw_mouse_dx; + saw_compose.ui_offset_y += saw_mouse_dy; + } + + if (saw_current_track != -1 && + saw_tracks[saw_current_track].value_input != + TRACK_INPUT_NONE) { + // Value input + // + + saw_track_t *track = saw_tracks + saw_current_track; + + track->value_buffer -= saw_shift_on ? saw_mouse_dy * 300 + : saw_ctrl_on ? saw_mouse_dy + : saw_mouse_dy * 20; + + // TODO + // Unify value input logic. + + // Change input value buffer for selected value. + // + switch (track->value_input) { + case TRACK_INPUT_INSTRUMENT: + track->instrument = (i64) (track->value_buffer * .002 + + 0.5); + if (track->instrument < 0) + track->instrument = 0; + if (track->instrument >= INSTRUMENT_COUNT) + track->instrument = INSTRUMENT_COUNT - 1; + break; + + case TRACK_INPUT_WARP: + track->warp = track->value_buffer * .0001; + if (track->warp < -1.) + track->warp = -1.; + if (track->warp > 1.) + track->warp = 1.; + break; + + case TRACK_INPUT_PHASE: + track->value_buffer = (f64) ((i64) track->value_buffer % + 10000); + while (track->value_buffer < 0) + track->value_buffer += 10000; + track->phase = track->value_buffer * .0001; + break; + + case TRACK_INPUT_UNISON: + track->unison = (i64) (track->value_buffer * .01 + 0.5); + if (track->unison < 1) + track->unison = 1; + if (track->unison > UNISON_COUNT) + track->unison = UNISON_COUNT; + break; + + case TRACK_INPUT_SPREAD: + track->spread = track->value_buffer * .0001; + if (track->spread < 0.) + track->spread = 0.; + if (track->spread > 1.) + track->spread = 1.; + break; + + case TRACK_INPUT_STEREO_WIDTH: + track->stereo_width = track->value_buffer * .0001; + if (track->stereo_width < 0.) + track->stereo_width = 0.; + if (track->stereo_width > 2.) + track->stereo_width = 2.; + break; + + case TRACK_INPUT_VOLUME: + track->volume = track->value_buffer * .0001; + if (track->volume < 0.) + track->volume = 0.; + if (track->volume > 2.) + track->volume = 2.; + break; + + case TRACK_INPUT_SUSTAIN: + track->envelope.sustain = track->value_buffer * .0001; + if (track->envelope.sustain < 0.) + track->envelope.sustain = 0.; + if (track->envelope.sustain > 1.) + track->envelope.sustain = 1.; + break; + + case TRACK_INPUT_ATTACK: + track->envelope.attack = track->value_buffer * .00001; + if (track->envelope.attack < 0.) + track->envelope.attack = 0.; + if (track->envelope.attack > 60.) + track->envelope.attack = 60.; + break; + + case TRACK_INPUT_DECAY: + track->envelope.decay = track->value_buffer * .00001; + if (track->envelope.decay < 0.) + track->envelope.decay = 0.; + if (track->envelope.decay > 60.) + track->envelope.decay = 60.; + break; + + case TRACK_INPUT_RELEASE: + track->envelope.release = track->value_buffer * .00001; + if (track->envelope.release < 0.) + track->envelope.release = 0.; + if (track->envelope.release > 60.) + track->envelope.release = 60.; + break; + + default:; + } + } + } // Render UI // + + nvgBeginFrame(saw_nvg, frame_width, frame_height, sapp_dpi_scale()); { // Adjust UI layout // @@ -1820,19 +2082,27 @@ static void saw_frame(void) { ); } } - nvgEndFrame(saw_nvg); // Cleanup input state. // + { + saw_mouse_dx = 0; + saw_mouse_dy = 0; + + saw_lbutton_click = 0; + saw_rbutton_click = 0; + saw_mbutton_click = 0; + + saw_shift_on = 0; + saw_ctrl_on = 0; - saw_lbutton_click = 0; - saw_rbutton_click = 0; - saw_mbutton_click = 0; + memset(saw_key_pressed, 0, sizeof saw_key_pressed); + } } static void saw_cleanup(void) { - ma_device_uninit(&saw_ma); + ma_device_uninit(&saw_audio_device); #ifdef SOKOL_GLCORE33 nvgDeleteGL3(saw_nvg); @@ -1899,8 +2169,8 @@ static void saw_cleanup(void) { fprintf(f, "time %lld\n", roll->time); fprintf(f, "duration %lld\n", roll->duration); fprintf(f, "loop_duration %lld\n", roll->loop_duration); - fprintf(f, "offset %lld %lld\n\n", roll->offset_x, - roll->offset_y); + fprintf(f, "ui_offset %lld %lld\n\n", + roll->ui_offset_x, roll->ui_offset_y); } // Save tracks @@ -1937,144 +2207,23 @@ static void saw_cleanup(void) { } static void saw_event(sapp_event const *event) { - // FIXME - // Do input processing in frame update procedure. - // - switch (event->type) { 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++) { - // Panning control - // - if (saw_rolls[i].offset_x_input) - saw_rolls[i].offset_x += event->mouse_dx; - if (saw_rolls[i].offset_y_input) - saw_rolls[i].offset_y += event->mouse_dy; - } - - if (saw_compose.offset_input) { - // Panning control - // - saw_compose.offset_x += event->mouse_dx; - saw_compose.offset_y += event->mouse_dy; - } - - if (saw_current_track != -1) { - // Value control - // - - saw_track_t *track = saw_tracks + saw_current_track; - - track->value_buffer -= (event->modifiers & - SAPP_MODIFIER_SHIFT) - ? event->mouse_dy * 300 - : (event->modifiers & - SAPP_MODIFIER_CTRL) - ? event->mouse_dy - : event->mouse_dy * 20; - - // Change input value buffer for selected value. - // - switch (track->value_input) { - case TRACK_INPUT_INSTRUMENT: - track->instrument = (i64) (track->value_buffer * .002 + - 0.5); - if (track->instrument < 0) - track->instrument = 0; - if (track->instrument >= INSTRUMENT_COUNT) - track->instrument = INSTRUMENT_COUNT - 1; - break; - - case TRACK_INPUT_WARP: - track->warp = track->value_buffer * .0001; - if (track->warp < -1.) - track->warp = -1.; - if (track->warp > 1.) - track->warp = 1.; - break; - - case TRACK_INPUT_PHASE: - track->value_buffer = (f64) ((i64) track->value_buffer % - 10000); - while (track->value_buffer < 0) - track->value_buffer += 10000; - track->phase = track->value_buffer * .0001; - break; - - case TRACK_INPUT_UNISON: - track->unison = (i64) (track->value_buffer * .01 + 0.5); - if (track->unison < 1) - track->unison = 1; - if (track->unison > UNISON_COUNT) - track->unison = UNISON_COUNT; - break; - - case TRACK_INPUT_SPREAD: - track->spread = track->value_buffer * .0001; - if (track->spread < 0.) - track->spread = 0.; - if (track->spread > 1.) - track->spread = 1.; - break; - - case TRACK_INPUT_STEREO_WIDTH: - track->stereo_width = track->value_buffer * .0001; - if (track->stereo_width < 0.) - track->stereo_width = 0.; - if (track->stereo_width > 2.) - track->stereo_width = 2.; - break; + saw_shift_on = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0; + saw_ctrl_on = (event->modifiers & SAPP_MODIFIER_CTRL) != 0; - case TRACK_INPUT_VOLUME: - track->volume = track->value_buffer * .0001; - if (track->volume < 0.) - track->volume = 0.; - if (track->volume > 2.) - track->volume = 2.; - break; - - case TRACK_INPUT_SUSTAIN: - track->envelope.sustain = track->value_buffer * .0001; - if (track->envelope.sustain < 0.) - track->envelope.sustain = 0.; - if (track->envelope.sustain > 1.) - track->envelope.sustain = 1.; - break; - - case TRACK_INPUT_ATTACK: - track->envelope.attack = track->value_buffer * .00001; - if (track->envelope.attack < 0.) - track->envelope.attack = 0.; - if (track->envelope.attack > 60.) - track->envelope.attack = 60.; - break; - - case TRACK_INPUT_DECAY: - track->envelope.decay = track->value_buffer * .00001; - if (track->envelope.decay < 0.) - track->envelope.decay = 0.; - if (track->envelope.decay > 60.) - track->envelope.decay = 60.; - break; + saw_mouse_dx += (i64) floor(event->mouse_dx + .5); + saw_mouse_dy += (i64) floor(event->mouse_dy + .5); - case TRACK_INPUT_RELEASE: - track->envelope.release = track->value_buffer * .00001; - if (track->envelope.release < 0.) - track->envelope.release = 0.; - if (track->envelope.release > 60.) - track->envelope.release = 60.; - break; - - default:; - } - } + saw_mouse_x = (i64) floor(event->mouse_x + .5); + saw_mouse_y = (i64) floor(event->mouse_y + .5); break; case SAPP_EVENTTYPE_MOUSE_DOWN: + saw_shift_on = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0; + saw_ctrl_on = (event->modifiers & SAPP_MODIFIER_CTRL) != 0; + switch (event->mouse_button) { case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 1; @@ -2090,59 +2239,81 @@ static void saw_event(sapp_event const *event) { break; default:; } + break; case SAPP_EVENTTYPE_MOUSE_UP: + saw_shift_on = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0; + saw_ctrl_on = (event->modifiers & SAPP_MODIFIER_CTRL) != 0; + 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; case SAPP_EVENTTYPE_MOUSE_LEAVE: + saw_shift_on = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0; + saw_ctrl_on = (event->modifiers & SAPP_MODIFIER_CTRL) != 0; + saw_lbutton_down = 0; saw_rbutton_down = 0; saw_mbutton_down = 0; + break; case SAPP_EVENTTYPE_KEY_DOWN: - if (!event->key_repeat) { - switch (event->key_code) { - case SAPP_KEYCODE_SPACE: - // Playback control - saw_playback_on = !saw_playback_on; - break; - - case SAPP_KEYCODE_ENTER: - // Reset playback time - saw_playback_frame = 0; - break; + saw_shift_on = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0; + saw_ctrl_on = (event->modifiers & SAPP_MODIFIER_CTRL) != 0; - case SAPP_KEYCODE_ESCAPE: - // Reset panning - // + if (!event->key_repeat && event->key_code >= 0 && + event->key_code < + sizeof saw_key_pressed / sizeof *saw_key_pressed) + saw_key_pressed[event->key_code] = 1; - 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; - saw_compose.offset_x = 0; - saw_compose.offset_y = 0; + // Touch events. + // We treat touch as left mouse button and cursor. + // - break; + case SAPP_EVENTTYPE_TOUCHES_BEGAN: + if (event->num_touches >= 1) { + saw_mouse_x = (i64) floor(event->touches[0].pos_x + .5); + saw_mouse_y = (i64) floor(event->touches[0].pos_y + .5); + saw_lbutton_down = 1; + saw_lbutton_click = 1; + } + break; - case SAPP_KEYCODE_D: - // Duplicate selected track - saw_compose.duplicate_input = 1; - break; + case SAPP_EVENTTYPE_TOUCHES_MOVED: + if (event->num_touches >= 1) { + i64 x = (i64) floor(event->touches[0].pos_x + .5); + i64 y = (i64) floor(event->touches[0].pos_y + .5); + saw_mouse_dx += x - saw_mouse_x; + saw_mouse_dy += y - saw_mouse_y; + saw_mouse_x = x; + saw_mouse_y = y; + } + break; - default:; - } + case SAPP_EVENTTYPE_TOUCHES_ENDED: + if (event->num_touches >= 1) { + i64 x = (i64) floor(event->touches[0].pos_x + .5); + i64 y = (i64) floor(event->touches[0].pos_y + .5); + saw_mouse_dx += x - saw_mouse_x; + saw_mouse_dy += y - saw_mouse_y; + saw_mouse_x = x; + saw_mouse_y = y; } + saw_lbutton_down = 0; + break; + + case SAPP_EVENTTYPE_TOUCHES_CANCELLED: + saw_lbutton_down = 0; break; default:; @@ -2165,11 +2336,24 @@ static void log_(char const *tag, u32 log_level, u32 log_item_id, sapp_desc sokol_main(i32 argc, char **argv) { b8 print_version = 0; + b8 print_help = 0; for (i32 i = 0; i < argc; i++) if (strcmp(argv[i], "--version") == 0) print_version = 1; - else if (i > 0 && saw_project_file.size == 0) + else if (strcmp(argv[i], "--help") == 0) + print_help = 1; + else if (argv[i][0] == '-' && argv[i][1] != '-' && + argv[i][1] != '\0') { + for (i32 k = 1; argv[i][k] != '\0'; k++) + if (argv[i][k] == 'V') + print_version = 1; + else if (argv[i][k] == 'H') + print_help = 1; + else + printf("Unknown command line argument: \"-%c\"\n", + argv[i][k]); + } else if (i > 0 && saw_project_file.size == 0) saw_project_file = kit_str(strlen(argv[i]), argv[i]); else if (i > 0) printf("Unknown command line argument: \"%s\"\n", argv[i]); @@ -2185,9 +2369,12 @@ sapp_desc sokol_main(i32 argc, char **argv) { " - Music sequencer standalone application.\n", VERSION_MAJOR, VERSION_MINOR, VERSION_BABY); + if (print_help) + printf("Usage:\n%s [PROJECT_FILE]\n", argv[0]); + fflush(stdout); - if (print_version) + if (print_version || print_help) exit(0); return (sapp_desc) { |