#include "../kit/math.h" #include "../kit/time.h" #include "../kit/string_ref.h" #include "../kit/mersenne_twister_64.h" #include "../kit/secure_random.h" #include "../kit/file.h" #if defined(__EMSCRIPTEN__) || defined(__linux__) # include # define SOKOL_GLES3 # define NANOVG_GLES3 1 #else # include # define SOKOL_GLCORE33 # define NANOVG_GL3 1 #endif #include "../sokol/sokol_app.h" #include "../miniaudio/miniaudio.h" #include "../nanovg/nanovg.h" #include "../nanovg/nanovg_gl.h" #include #include #include "fonts.inl.h" #define VERSION_MAJOR 0 #define VERSION_MINOR 0 #define VERSION_BABY 2 #define VERSION_DEV 1 enum { CHANNEL_COUNT = 2, SAMPLE_RATE = 44100, TRACK_COUNT = 16, ROLL_COUNT = 32, PITCH_COUNT = 80, VOICE_COUNT = 32, UNISON_COUNT = 100, SHEET_SIZE = 200, ROLL_DEFAULT_RATE = 8, ROLL_DEFAULT_OFFSET_Y = 710, INSTRUMENT_SINE = 0, INSTRUMENT_SAW_UP, INSTRUMENT_SAW_DOWN, INSTRUMENT_SQUARE_UP, INSTRUMENT_SQUARE_DOWN, INSTRUMENT_KICK, INSTRUMENT_COUNT, TRACK_INPUT_NONE = 0, TRACK_INPUT_INSTRUMENT, TRACK_INPUT_WARP, TRACK_INPUT_PHASE, TRACK_INPUT_UNISON, TRACK_INPUT_SPREAD, TRACK_INPUT_STEREO_WIDTH, TRACK_INPUT_VOLUME, TRACK_INPUT_SUSTAIN, TRACK_INPUT_ATTACK, TRACK_INPUT_DECAY, TRACK_INPUT_RELEASE, }; typedef struct { i8 enabled; i64 time; f64 duration; f64 frequency; f64 amplitude; f64 phase[2]; i64 track; } saw_voice_t; typedef struct { i8 enabled; i64 time; i64 duration; i64 pitch; } saw_roll_note_t; typedef struct { i8 enabled; i64 track; i8 pitch_turned_off[PITCH_COUNT]; i64 rate; saw_roll_note_t notes[SHEET_SIZE]; i64 time; i64 duration; i64 loop_duration; i64 offset_x; i64 offset_y; // dynamic properties // i32 last_index; i8 grid_input; i32 grid_note; i32 grid_time; i8 offset_x_input; i8 offset_y_input; i8 loop_input; } saw_roll_t; typedef struct { f64 sustain; f64 attack; f64 decay; f64 release; } saw_envelope_t; typedef struct { i32 instrument; f64 warp; f64 phase; i32 unison; f64 spread; f64 stereo_width; f64 volume; saw_envelope_t envelope; // dynamic properties // i32 value_input; f64 value_buffer; } saw_track_t; typedef struct { i64 rolls[ROLL_COUNT]; // dynamic properties // i8 grid_input; i32 grid_roll; i32 grid_cell; i8 offset_input; i64 offset_x; i64 offset_y; i8 duplicate_input; } saw_compose_t; static struct NVGcontext *saw_nvg; static ma_device saw_ma; 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; static i32 saw_mouse_x = 0; static i32 saw_mouse_y = 0; 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_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_track = 0; 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_track_t saw_tracks[TRACK_COUNT]; static saw_compose_t saw_compose = { .rolls = { -1 }, .grid_input = 0, .grid_roll = 0, .grid_cell = 0, .offset_input = 0, .offset_x = 0, .offset_y = 0, .duplicate_input = 0, }; #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-function" # pragma GCC diagnostic ignored "-Wunknown-pragmas" # pragma GCC push_options # pragma GCC optimize("O3") #endif static f64 saw_random(f64 min, f64 max) { if (max - min < .000001) return min; u64 x = mt64_generate(&saw_mt64); u64 range = (u64) ((max - min) * 10000 + 0.5); return min + (max - min) * ((1.0 / range) * (x % (range + 1))); } static f64 saw_envelope(f64 t, f64 attack, f64 decay, f64 sustain, f64 duration, f64 release) { f64 a = 1.; if (t < attack) a *= t / attack; else if (t < attack + decay) a *= 1. - (1. - sustain) * (t - attack) / decay; else 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 f64 saw_pitch_amplitude(i64 pitch) { return .2 / exp(0.02 * pitch); } static void saw_play_voice(saw_track_t *track, saw_roll_t *roll, i64 pitch, i64 duration) { if (saw_voices[VOICE_COUNT - 1].enabled) return; for (i32 n = VOICE_COUNT - 1; n > 0; --n) saw_voices[n] = saw_voices[n - 1]; f64 s = track->stereo_width / 8; saw_voices[0] = (saw_voice_t) { .enabled = 1, .time = 0, .duration = (f64) duration / (f64) SAMPLE_RATE, .frequency = saw_pitch_frequency(pitch), .amplitude = saw_pitch_amplitude(pitch) * track->volume, .phase = { saw_random(-s, s), saw_random(-s, s), }, .track = roll->track, }; } static void saw_playback(i32 frame_count) { if (!saw_playback_on) return; for (i32 k = 0; k < ROLL_COUNT; k++) { saw_roll_t *roll = saw_rolls + k; if (!roll->enabled || saw_playback_frame + frame_count <= roll->time || saw_playback_frame + frame_count > roll->time + roll->duration) continue; i64 play_frame = roll->loop_duration == 0 ? saw_playback_frame : saw_playback_frame - ((saw_playback_frame + frame_count - roll->time) / roll->loop_duration) * roll->loop_duration; for (i32 i = 0; i < SHEET_SIZE; i++) { saw_roll_note_t *note = roll->notes + i; if (!note->enabled) continue; i64 note_frame = roll->time + note->time; if (play_frame + frame_count <= note_frame || play_frame > note_frame) continue; saw_play_voice(saw_tracks + roll->track, roll, note->pitch, note->duration); } } saw_playback_frame += frame_count; } static f64 saw_oscillator(i32 type, f64 frequency, f64 phase, f64 warp, f64 t) { if (type == INSTRUMENT_KICK) { frequency /= 8.; f64 q = t * frequency + phase; frequency /= 8.; f64 f = frequency * (10. + q) / (1. + q); return sin(q * f * (M_PI * 2)); } t = t * frequency + phase; t = t - floor(t); switch (type) { case INSTRUMENT_SINE: return sin(t * (M_PI * 2)); case INSTRUMENT_SAW_UP: return -1. + t * 2.; case INSTRUMENT_SAW_DOWN: return 1. - t * 2.; case INSTRUMENT_SQUARE_UP: return t < .5 + warp / 2. ? -1. : 1.; case INSTRUMENT_SQUARE_DOWN: return t < .5 + warp / 2. ? 1. : -1.; default:; } return 0.; } static void saw_audio(ma_device *device, void *void_out_, void const *void_in_, ma_uint32 frame_count) { saw_playback(frame_count); f32 *out = (f32 *) void_out_; for (i64 i = 0; i < frame_count; i++) { out[i * 2] = 0.f; out[i * 2 + 1] = 0.f; } for (i32 n = 0; n < VOICE_COUNT; n++) { if (!saw_voices[n].enabled) continue; saw_track_t *track = saw_tracks + saw_voices[n].track; i32 wave_type = track->instrument; f64 warp = track->warp; f64 frequency = saw_voices[n].frequency; f64 amplitude = saw_voices[n].amplitude; f64 phase_l = track->phase + saw_voices[n].phase[0]; f64 phase_r = track->phase + saw_voices[n].phase[1]; f64 attack = track->envelope.attack; f64 decay = track->envelope.decay; f64 sustain = track->envelope.sustain; f64 duration = saw_voices[n].duration; f64 release = track->envelope.release; for (i64 i = 0; i < frame_count; i++) { f64 t = (f64) saw_voices[n].time / (f64) SAMPLE_RATE; f64 a = amplitude * saw_envelope(t, attack, decay, sustain, duration, release); out[i * 2] += (f32) (saw_oscillator(wave_type, frequency, phase_l, warp, t) * a); out[i * 2 + 1] += (f32) (saw_oscillator(wave_type, frequency, phase_r, warp, t) * a); saw_voices[n].time++; if (t > duration + release) saw_voices[n].enabled = 0; } } } #ifdef __GNUC__ # pragma GCC pop_options # pragma GCC diagnostic pop #endif 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 = 50; i64 grid_rate = 3; i64 border = 2; // Time bar // nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x0, frame_height - y0 - height + border, width, frame_height - y0 - height + track_height / 5 - border * 2); nvgRect(saw_nvg, x0, frame_height - y0 - height + border + (track_height * 4) / 5, width, frame_height - y0 - height + track_height / 5 - 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 top = frame_height - y0 - height + track_height; i64 bottom = frame_height - y0; i64 dx = x0 + saw_compose.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; i64 d = u + track_height; i64 s = grid_scale / grid_rate; if (l < x0) l = dx + ((x0 - dx + (s - 1)) / s) * s; if (r >= x0 + width) r = dx + ((x0 + width - dx) / s) * s; if (u < top) u = top; if (d > bottom) d = bottom; if (l >= r || u >= d) continue; i64 x = l; i64 w = r - l; i64 y = u; i64 h = d - u; i8 is_choosen = (saw_current_roll == i); i8 is_playing = saw_playback_on && saw_playback_frame >= roll->time && saw_playback_frame < roll->time + roll->duration; 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 + h); nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x + border, y + border, w - border * 2, h - border * 2); nvgFillColor(saw_nvg, is_choosen ? nvgRGBA(240, 230, 200, 255) : is_playing ? nvgRGBA(240, 210, 180, 240) : 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) { if (saw_current_roll == saw_compose.rolls[i]) { i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) / grid_scale; i64 c0 = (roll->time * grid_rate) / SAMPLE_RATE; i64 c1 = c0 + (roll->duration * grid_rate) / SAMPLE_RATE; saw_compose.grid_input = 1; saw_compose.grid_roll = saw_current_roll; if (cell - c0 > c1 - cell) { saw_compose.grid_cell = c0; roll->duration = ((cell - c0 + 1) * SAMPLE_RATE) / grid_rate; } else { saw_compose.grid_cell = c1 - 1; roll->duration = ((c1 - cell + 1) * SAMPLE_RATE) / grid_rate; } } else { saw_current_roll = saw_compose.rolls[i]; saw_current_track = roll->track; } } hover_any = 1; } } } // Placing new sheet // if (!hover_any && saw_lbutton_down && saw_mouse_x >= x0 + saw_compose.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) * 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 * 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) / SAMPLE_RATE; i64 y = frame_height - y0 - height + track_height + saw_compose.offset_y + track * track_height; if (track < 0 || track >= TRACK_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, .pitch_turned_off = { 0 }, .rate = ROLL_DEFAULT_RATE, .notes = { 0 }, .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, }; saw_current_roll = n; saw_current_track = track; } } } // Duplicate selected sheet // if (saw_compose.duplicate_input) { saw_compose.duplicate_input = 0; do { if (saw_current_roll == -1) break; 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; if (cell < 0 || track < 0 || track >= TRACK_COUNT) break; i64 frame = (cell * SAMPLE_RATE) / grid_rate; i64 n = -1; saw_roll_t *roll = saw_rolls + saw_current_roll; i8 collision = 0; for (i64 i = 0; i < ROLL_COUNT; i++) { saw_roll_t *p = saw_rolls + i; if (p->enabled && p->track == track && ((p->time >= frame && p->time < frame + roll->duration) || (frame >= p->time && frame < p->time + p->duration))) { collision = 1; break; } } if (collision) break; for (i64 i = 0; i < ROLL_COUNT; i++) if (!saw_rolls[i].enabled) { n = i; break; } if (n == -1) break; for (i64 i = 0; i < ROLL_COUNT; i++) if (saw_compose.rolls[i] == -1) { saw_compose.rolls[i] = n; break; } saw_rolls[n] = *roll; saw_rolls[n].track = track; saw_rolls[n].time = frame; } while (0); } // Panning input // if (saw_mbutton_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; } if (!saw_mbutton_down) saw_compose.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) / grid_scale; saw_roll_t *p = saw_rolls + saw_compose.grid_roll; if (cell >= 0) { if (saw_compose.grid_cell <= cell) { p->time = (saw_compose.grid_cell * SAMPLE_RATE) / grid_rate; p->duration = ((1 + cell - saw_compose.grid_cell) * SAMPLE_RATE) / grid_rate; } else { p->time = (cell * SAMPLE_RATE) / grid_rate; p->duration = ((1 + saw_compose.grid_cell - cell) * SAMPLE_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->time * grid_rate) / SAMPLE_RATE; i64 q_size = (q->duration * grid_rate) / SAMPLE_RATE; if (saw_compose.grid_cell < q_cell && cell >= q_cell) { cell = q_cell - 1; p->time = (saw_compose.grid_cell * SAMPLE_RATE) / grid_rate; p->duration = ((q_cell - saw_compose.grid_cell) * SAMPLE_RATE) / grid_rate; } if (saw_compose.grid_cell > q_cell && cell < q_cell + q_size) { cell = q_cell + q_size; p->time = ((q_cell + q_size) * SAMPLE_RATE) / grid_rate; p->duration = ((1 + saw_compose.grid_cell - q_cell - q_size) * SAMPLE_RATE) / grid_rate; } } if (p->duration <= 0) p->duration = SAMPLE_RATE / grid_rate; } else saw_compose.grid_input = 0; } // Playback indicator // i32 x = x0 + saw_compose.offset_x - border * 2 + (saw_playback_frame * grid_scale + SAMPLE_RATE / 2) / 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); } // Draw cursor // 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) / 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 < TRACK_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); } } // Cursor indicator // if (saw_mouse_x >= x0 + saw_compose.offset_x && saw_mouse_x < x0 + width && saw_mouse_y >= frame_height - y0 - height && saw_mouse_y < frame_height - y0) { i32 dx = x0 + saw_compose.offset_x; i32 s = grid_scale / grid_rate; i32 x = dx + ((saw_mouse_x - dx + s / 2) / s) * s; i32 w = border * 4; nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x - w / 2, frame_height - y0 - height, w, height); nvgFillColor(saw_nvg, nvgRGBA(80, 80, 240, 160)); nvgFill(saw_nvg); } } static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0, i64 width, i64 height, str_t title) { i64 frame_height = sapp_height(); i64 text_height = 35; i64 header_offset = 60; i64 border = 2; i64 column_width = 200; i64 next_y = header_offset; // Values input and highlight // if (!saw_lbutton_down && track->value_input != TRACK_INPUT_NONE) { track->value_input = TRACK_INPUT_NONE; track->value_buffer = 0; #ifndef __EMSCRIPTEN__ sapp_lock_mouse(0); #endif } for (i32 input_index = TRACK_INPUT_INSTRUMENT; input_index <= TRACK_INPUT_RELEASE; ++input_index) { // FIXME // Implement Unison and Spread. // if (input_index == TRACK_INPUT_UNISON || input_index == TRACK_INPUT_SPREAD) continue; if ((track->value_input == TRACK_INPUT_NONE || track->value_input == input_index) && saw_mouse_x >= x0 && saw_mouse_x < x0 + width && saw_mouse_y >= frame_height - y0 - height + next_y && saw_mouse_y < frame_height - y0 - height + next_y + text_height) { i64 x = x0 + column_width; i64 y = frame_height - y0 - height + next_y; i64 w = width - column_width; i64 h = text_height; if (w > 0) { nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x + border, y, w - border * 2, h); nvgFillColor(saw_nvg, nvgRGBA(200, 240, 200, 80)); nvgFill(saw_nvg); } if (saw_lbutton_click && track->value_input == TRACK_INPUT_NONE) { #ifndef __EMSCRIPTEN__ sapp_lock_mouse(1); #endif track->value_input = input_index; switch (input_index) { case TRACK_INPUT_INSTRUMENT: track->value_buffer = track->instrument * 500; break; case TRACK_INPUT_UNISON: track->value_buffer = track->unison * 100; break; case TRACK_INPUT_WARP: track->value_buffer = track->warp * 10000; break; case TRACK_INPUT_PHASE: track->value_buffer = track->phase * 10000; break; case TRACK_INPUT_SPREAD: track->value_buffer = track->spread * 10000; break; case TRACK_INPUT_STEREO_WIDTH: track->value_buffer = track->stereo_width * 10000; break; case TRACK_INPUT_VOLUME: track->value_buffer = track->volume * 10000; break; case TRACK_INPUT_SUSTAIN: track->value_buffer = track->envelope.sustain * 10000; break; case TRACK_INPUT_ATTACK: track->value_buffer = track->envelope.attack * 100000; break; case TRACK_INPUT_DECAY: track->value_buffer = track->envelope.decay * 100000; break; case TRACK_INPUT_RELEASE: track->value_buffer = track->envelope.release * 100000; break; default:; } } } if (input_index == TRACK_INPUT_VOLUME) next_y += header_offset; next_y += text_height; } // Draw text // nvgBeginPath(saw_nvg); nvgFontSize(saw_nvg, text_height); nvgFontFaceId(saw_nvg, saw_font_text); nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255)); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + text_height - border * 2, 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" }; nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Instrument", 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, buf_instr[track->instrument], 0); next_y += text_height; char buf[100]; sprintf(buf, "%.3f", (f32) track->warp); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Warp", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; sprintf(buf, "%.3f", (f32) track->phase); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Phase", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; /* sprintf(buf, "%d", (i32) track->unison); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Unison", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; sprintf(buf, "%.3f", (f32) track->spread); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Spread", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; */ sprintf(buf, "%.3f", (f32) track->stereo_width); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Stereo", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; sprintf(buf, "%.3f", (f32) track->volume); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Volume", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += header_offset; nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + next_y - border * 2, "Envelope", 0); next_y += text_height; sprintf(buf, "%.3f", (f32) track->envelope.sustain); nvgText(saw_nvg, x0 + column_width / 4 + border * 2, frame_height - y0 - height + next_y - border * 2, "Sustain", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; sprintf(buf, "%.1f ms", (f32) (track->envelope.attack * 1000)); nvgText(saw_nvg, x0 + column_width / 4 + border * 2, frame_height - y0 - height + next_y - border * 2, "Attack", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; sprintf(buf, "%.1f ms", (f32) (track->envelope.decay * 1000)); nvgText(saw_nvg, x0 + column_width / 4 + border * 2, frame_height - y0 - height + next_y - border * 2, "Decay", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; sprintf(buf, "%.1f ms", (f32) (track->envelope.release * 1000)); nvgText(saw_nvg, x0 + column_width / 4 + border * 2, frame_height - y0 - height + next_y - border * 2, "Release", 0); nvgText(saw_nvg, x0 + column_width + border * 2, frame_height - y0 - height + next_y - border * 2, buf, 0); next_y += text_height; nvgFill(saw_nvg); } static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width, i64 height, str_t title) { i32 frame_height = sapp_height(); i32 text_height = 35; i32 header_height = 35; i32 pianokey_height = 35; i32 pianokey_width = 100; i32 border = 2; i32 sheet_offset = 40; i32 sheet_scale = (40 * SAMPLE_RATE) / (10000 * roll->rate); // Title text // nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x0, frame_height - y0 - height, width, text_height); nvgFillColor(saw_nvg, nvgRGBA(80, 60, 50, 160)); nvgFill(saw_nvg); nvgBeginPath(saw_nvg); nvgFontSize(saw_nvg, text_height); nvgFontFaceId(saw_nvg, saw_font_text); nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255)); nvgText(saw_nvg, x0 + border * 2, frame_height - y0 - height + text_height - border * 2, title.values, title.values + title.size); nvgFill(saw_nvg); // Loop control // { i64 x = x0 + pianokey_width + sheet_offset; i64 y = frame_height - y0 - height + text_height; i64 w = width - pianokey_width - sheet_offset; i64 h = header_height / 5; nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x, y, w, h); nvgRect(saw_nvg, x, y + h * 4, w, h); nvgFillColor(saw_nvg, nvgRGBA(80, 60, 50, 160)); nvgFill(saw_nvg); if (saw_mouse_x >= x && saw_mouse_y >= y && saw_mouse_x < x + w && saw_mouse_y < y + header_height && !roll->loop_input && saw_lbutton_click) roll->loop_input = 1; if (roll->loop_input && saw_lbutton_down) { i32 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset - roll->offset_x + sheet_scale / 2) / sheet_scale; if (t <= 0) roll->loop_duration = 0; else roll->loop_duration = (t * SAMPLE_RATE + roll->rate / 2) / roll->rate; } } if (!saw_lbutton_down) roll->loop_input = 0; // Piano roll // i8 hover_any = 0; for (i32 pitch = 0; pitch < PITCH_COUNT; pitch++) { i32 x = x0 + border; i32 y = frame_height - y0 - (pitch + 1) * pianokey_height + border + roll->offset_y; i32 w = pianokey_width - border * 2; i32 h = pianokey_height - border * 2; if (y > frame_height - y0 - pianokey_height) continue; if (y < frame_height - y0 - height + text_height + header_height) break; nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x, y, w, h); i8 has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w && saw_mouse_y >= y && saw_mouse_y < y + h; nvgFillColor(saw_nvg, roll->pitch_turned_off[pitch] ? nvgRGBA(220, 220, 220, 160) : has_cursor ? nvgRGBA(200, 200, 255, 255) : nvgRGBA(220, 220, 220, 255)); nvgFill(saw_nvg); // Piano roll input // if (has_cursor) { hover_any = 1; if (!roll->pitch_turned_off[pitch] && (saw_lbutton_click || (saw_lbutton_down && roll->last_index != pitch))) saw_play_voice(saw_tracks + roll->track, roll, pitch, SAMPLE_RATE / roll->rate); if (saw_rbutton_click) roll->pitch_turned_off[pitch] = !roll->pitch_turned_off[pitch]; roll->last_index = pitch; } } if (!hover_any) roll->last_index = -1; // Panning input // if (saw_mbutton_click) { 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; 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; } if (!saw_mbutton_down) { roll->offset_x_input = 0; roll->offset_y_input = 0; } // Draw music sheet // for (i32 pitch = 0; pitch < PITCH_COUNT; pitch++) { i32 y = frame_height - y0 - (pitch + 1) * pianokey_height + roll->offset_y; if (y > frame_height - y0 - pianokey_height) continue; if (y < frame_height - y0 - height + text_height + header_height) break; i32 h = pianokey_height; for (i32 t = 0; t < (roll->duration * roll->rate) / SAMPLE_RATE; t++) { i32 x = x0 + pianokey_width + sheet_offset + t * sheet_scale + roll->offset_x; if (x >= x0 + width - sheet_scale - border) break; i32 note = -1; for (i32 n = 0; n < SHEET_SIZE; n++) { saw_roll_note_t *p = roll->notes + n; if (p->enabled && p->pitch == pitch && t >= (p->time * roll->rate + SAMPLE_RATE / 2) / SAMPLE_RATE && t < ((p->time + p->duration) * roll->rate + SAMPLE_RATE / 2) / SAMPLE_RATE) { note = n; break; } } if (note != -1) continue; // Draw empty cell // if (x < x0 + pianokey_width + sheet_offset) continue; i32 w = sheet_scale; nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x + border, y + border, w - border * 2, h - border * 2); i8 turned_off = roll->pitch_turned_off[pitch] || (roll->loop_duration > 0 && t >= (roll->loop_duration * roll->rate) / SAMPLE_RATE); 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, turned_off ? nvgRGBA(160, 150, 120, 100) : has_cursor ? nvgRGBA(180, 180, 220, 160) : nvgRGBA(170, 160, 140, 150)); nvgFill(saw_nvg); // Empty cell input // if (!roll->grid_input && !turned_off && has_cursor && saw_lbutton_click) for (i32 n = 0; n < SHEET_SIZE; n++) if (!roll->notes[n].enabled) { roll->notes[n] = (saw_roll_note_t) { .enabled = 1, .time = (t * SAMPLE_RATE) / roll->rate, .duration = SAMPLE_RATE / roll->rate, .pitch = pitch }; roll->grid_input = 1; roll->grid_note = n; roll->grid_time = t; saw_play_voice(saw_tracks + roll->track, roll, pitch, SAMPLE_RATE / roll->rate); break; } } } // Draw notes // for (i32 n = 0; n < SHEET_SIZE; n++) { saw_roll_note_t *note = roll->notes + n; if (!note->enabled) continue; i32 y = frame_height - y0 - (note->pitch + 1) * pianokey_height + roll->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 + (note->time * roll->rate * sheet_scale + SAMPLE_RATE / 2) / SAMPLE_RATE; i64 w = (note->duration * roll->rate * sheet_scale + SAMPLE_RATE / 2) / SAMPLE_RATE; i32 h = pianokey_height; i8 has_cursor = (roll->grid_input && roll->grid_note == n) || (saw_mouse_x >= x && saw_mouse_x < x + w && saw_mouse_y >= y && saw_mouse_y < y + h); if (x >= x0 + width) continue; if (x + w - sheet_scale < x0 + pianokey_width + sheet_offset) continue; // Draw note // i64 frame = saw_playback_frame; if (frame >= roll->time && frame < roll->time + roll->duration && roll->loop_duration > 0) frame -= ((frame - roll->time) / roll->loop_duration) * roll->loop_duration; i8 is_playing = saw_playback_on && frame >= roll->time + note->time && frame < roll->time + note->time + note->duration; 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 + border, y + border, w - border * 2, h - border * 2); nvgFillColor(saw_nvg, is_playing ? nvgRGBA(255, 230, 200, 255) : has_cursor ? nvgRGBA(190, 190, 230, 255) : nvgRGBA(180, 180, 180, 255)); nvgFill(saw_nvg); // Note input // if (has_cursor && saw_rbutton_down) note->enabled = 0; } // Note stretching input // if (roll->grid_input) { if (saw_lbutton_down) { i32 t = (saw_mouse_x - x0 - pianokey_width - sheet_offset - roll->offset_x) / sheet_scale; saw_roll_note_t *p = roll->notes + roll->grid_note; if (t >= 0) { if (roll->grid_time <= t) { p->time = (roll->grid_time * SAMPLE_RATE) / roll->rate; p->duration = ((1 + t - roll->grid_time) * SAMPLE_RATE) / roll->rate; } else { p->time = (t * SAMPLE_RATE) / roll->rate; p->duration = ((1 + roll->grid_time - t) * SAMPLE_RATE) / roll->rate; } } for (i32 n = 0; n < SHEET_SIZE; n++) { if (n == roll->grid_note) continue; saw_roll_note_t *q = roll->notes + n; if (!q->enabled || q->pitch != p->pitch) continue; if (q->time < (roll->grid_time * SAMPLE_RATE) / roll->rate && q->time + q->duration > p->time) { p->time = q->time + q->duration; p->duration = (roll->grid_time * SAMPLE_RATE) / roll->rate > p->time ? ((roll->grid_time + 1) * SAMPLE_RATE) / roll->rate - p->time : SAMPLE_RATE / roll->rate; } if (q->time > (roll->grid_time * SAMPLE_RATE) / roll->rate && q->time < p->time + p->duration) { p->time = (roll->grid_time * SAMPLE_RATE) / roll->rate; p->duration = q->time - (roll->grid_time * SAMPLE_RATE) / roll->rate; assert(p->duration > 0); } } } else roll->grid_input = 0; } // Playback indicator // { i64 frame = saw_playback_frame; if (frame >= roll->time && frame < roll->time + roll->duration && roll->loop_duration > 0) frame -= ((frame - roll->time) / roll->loop_duration) * roll->loop_duration; i32 x = x0 + pianokey_width + sheet_offset + roll->offset_x - border * 2 + ((frame - roll->time) * roll->rate * sheet_scale + SAMPLE_RATE / 2) / 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, height - text_height); nvgFillColor(saw_nvg, nvgRGBA(240, 240, 80, 180)); nvgFill(saw_nvg); } } // Cursor indicator // if (saw_mouse_x >= x0 + pianokey_width + sheet_offset && saw_mouse_x < x0 + width && saw_mouse_y >= frame_height - y0 - height && saw_mouse_y < frame_height - y0) { i32 dx = x0 + pianokey_width + sheet_offset + roll->offset_x; i32 x = dx + ((saw_mouse_x - dx + sheet_scale / 2) / sheet_scale) * sheet_scale; i32 w = border * 4; nvgBeginPath(saw_nvg); nvgRect(saw_nvg, x - w / 2, frame_height - y0 - height + text_height, w, height - text_height); nvgFillColor(saw_nvg, nvgRGBA(80, 80, 240, 160)); nvgFill(saw_nvg); } } static void saw_init(void) { // Init RNG // u64 rng_seed; secure_random(sizeof rng_seed, &rng_seed); mt64_init(&saw_mt64, rng_seed); mt64_rotate(&saw_mt64); // Init NanoVG // #ifdef SOKOL_GLCORE33 saw_nvg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); #else saw_nvg = nvgCreateGLES3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); #endif // Init miniaudio // ma_device_config config = ma_device_config_init( ma_device_type_playback); config.playback.format = ma_format_f32; config.playback.channels = CHANNEL_COUNT; config.sampleRate = SAMPLE_RATE; config.dataCallback = saw_audio; config.pUserData = NULL; if (ma_device_init(NULL, &config, &saw_ma) != MA_SUCCESS) { printf("ma_device_init failed.\n"); return; } ma_device_start(&saw_ma); // Load fonts // saw_font_text = nvgCreateFontMem(saw_nvg, "", saw_ttf_text, SAW_TTF_TEXT_SIZE, 0); if (saw_font_text == -1) printf("nvgCreateFontMem failed.\n"); saw_font_icons = nvgCreateFontMem(saw_nvg, "", saw_ttf_icons, SAW_TTF_ICONS_SIZE, 0); if (saw_font_icons == -1) printf("nvgCreateFontMem failed.\n"); // Init Saw state // 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, .pitch_turned_off = { 0 }, .rate = ROLL_DEFAULT_RATE, .notes = { 0 }, .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, }; } for (i32 i = 0; i < TRACK_COUNT; i++) saw_tracks[i] = (saw_track_t) { .instrument = INSTRUMENT_SINE, .warp = .0, .phase = .0, .unison = 1, .spread = .1, .stereo_width = .2, .volume = 1., .envelope = { .sustain = .15, .attack = .007, .decay = .3, .release = .4, }, .value_input = TRACK_INPUT_NONE, }; // Determine the project file name // if (saw_project_file.size == 0) { char arena_buf[10000]; kit_allocator_t arena = kit_alloc_buffer(sizeof arena_buf, arena_buf); // No need to deallocate memory with arena. str_builder_t cache = path_join(WRAP_STR(path_cache(&arena)), SZ("saw"), &arena); kit_status_t s = folder_create_recursive(WRAP_STR(cache)); if (s != KIT_OK) printf("Failed to create cache folder: %s\n (code %d)", BS(cache), (i32) s); else { memcpy(saw_project_file_buf, cache.values, cache.size); saw_project_file_buf[cache.size] = PATH_DELIM_C; memcpy(saw_project_file_buf + cache.size + 1, "last", 5); saw_project_file.size = strlen(saw_project_file_buf); saw_project_file.values = saw_project_file_buf; } } printf("Project file: %s\n", BS(saw_project_file)); // Load the project from a file // if (path_type(saw_project_file) == PATH_FILE) { FILE *f = fopen(BS(saw_project_file), "rb"); if (f == NULL) { printf("Failed to read file: %s\n", BS(saw_project_file)); return; } #define SCAN_(format_, num_, ...) \ do { \ if (fscanf(f, format_, __VA_ARGS__) != num_) { \ printf("Invalid syntax at \"%s\"\n", format_); \ fclose(f); \ return; \ } \ } while (0) i32 total_rolls; SCAN_(" compose_rolls %d", 1, &total_rolls); if (total_rolls < 0 || total_rolls > ROLL_COUNT) { printf("Invalid roll count: %d\n", total_rolls); fclose(f); return; } for (i64 i = 0; i < total_rolls; i++) SCAN_(" %lld", 1, saw_compose.rolls + i); SCAN_(" rolls %d", 1, &total_rolls); for (i64 i = 0; i < total_rolls; i++) { saw_roll_t *roll = saw_rolls + i; i32 enabled; SCAN_(" enabled %d", 1, &enabled); roll->enabled = enabled ? 1 : 0; SCAN_(" track %lld", 1, &roll->track); i32 pitch_count; SCAN_(" pitch_turned_off %d", 1, &pitch_count); if (pitch_count < 0 || pitch_count > PITCH_COUNT) { printf("Invalid pitch count: %d\n", pitch_count); fclose(f); return; } for (i64 pitch = 0; pitch < pitch_count; pitch++) { i32 flag; SCAN_(" %d", 1, &flag); roll->pitch_turned_off[pitch] = flag ? 1 : 0; } SCAN_(" rate %lld", 1, &roll->rate); i32 sheet_size; SCAN_(" notes %d", 1, &sheet_size); if (sheet_size < 0 || sheet_size > SHEET_SIZE) { printf("Invalid note count: %d\n", sheet_size); fclose(f); return; } for (i64 n = 0; n < sheet_size; n++) { i32 flag; SCAN_(" %d %lld %lld %lld", 4, &flag, &roll->notes[n].time, &roll->notes[n].duration, &roll->notes[n].pitch); roll->notes[n].enabled = flag ? 1 : 0; } 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); } i32 total_tracks; SCAN_(" tracks %d", 1, &total_tracks); for (i64 i = 0; i < total_tracks; i++) { saw_track_t *track = saw_tracks + i; i64 warp, phase, spread, stereo_width, volume, sustain, attack, decay, release; SCAN_(" instrument %d", 1, &track->instrument); SCAN_(" warp %lld", 1, &warp); SCAN_(" phase %lld", 1, &phase); SCAN_(" unison %d", 1, &track->unison); SCAN_(" spread %lld", 1, &spread); SCAN_(" stereo_width %lld", 1, &stereo_width); SCAN_(" volume %lld", 1, &volume); SCAN_(" sustain %lld", 1, &sustain); SCAN_(" attack %lld", 1, &attack); SCAN_(" decay %lld", 1, &decay); SCAN_(" release %lld", 1, &release); track->warp = warp * 0.0001 - 1.; track->phase = phase * 0.0001; track->spread = spread * 0.0001; track->stereo_width = stereo_width * 0.0001; track->volume = volume * 0.0001; track->envelope.sustain = sustain * 0.0001; track->envelope.attack = attack * 0.0001; track->envelope.decay = decay * 0.0001; track->envelope.release = release * 0.0001; } #undef SCAN_ fclose(f); } } static void saw_frame(void) { i32 frame_width = sapp_width(); i32 frame_height = sapp_height(); glViewport(0, 0, frame_width, frame_height); glClearColor(.15f, .12f, .10f, 1.f); #if defined(__EMSCRIPTEN__) || defined(__linux__) glClearDepthf(1.f); #else glClearDepth(1.); #endif glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgBeginFrame(saw_nvg, frame_width, frame_height, sapp_dpi_scale()); i32 track_width = frame_width / 5; if (track_width < 330) track_width = 330; if (track_width > frame_width / 2) track_width = frame_width / 2; saw_ui_compose(0, (frame_height * 3) / 5, frame_width - track_width, (frame_height * 2) / 5); if (saw_current_track != -1) { char buf[64]; sprintf(buf, "Track %lld", saw_current_track + 1); saw_ui_track(saw_tracks + saw_current_track, frame_width - track_width, 0, track_width, frame_height, kit_str(strlen(buf), buf)); } if (saw_current_roll != -1) { char buf[64]; sprintf(buf, "Sheet %lld", saw_current_roll + 1); saw_ui_roll(saw_rolls + saw_current_roll, 0, 0, frame_width - track_width, (frame_height * 3) / 5, kit_str(strlen(buf), buf)); } nvgEndFrame(saw_nvg); // Cleanup input state. // saw_lbutton_click = 0; saw_rbutton_click = 0; saw_mbutton_click = 0; } static void saw_cleanup(void) { ma_device_uninit(&saw_ma); #ifdef SOKOL_GLCORE33 nvgDeleteGL3(saw_nvg); #else nvgDeleteGLES3(saw_nvg); #endif // Save the project to a file // if (saw_project_file.size == 0) return; printf("Save project: %s\n", BS(saw_project_file)); FILE *f = fopen(BS(saw_project_file), "wb"); if (f == NULL) { printf("Failed to write file: %s\n", BS(saw_project_file)); return; } // Save the compose // fprintf(f, "compose_rolls %d", ROLL_COUNT); for (i64 i = 0; i < ROLL_COUNT; i++) fprintf(f, " %lld", saw_compose.rolls[i]); fprintf(f, "\n\n"); // Save rolls // i32 total_rolls = 0; for (i64 i = 0; i < ROLL_COUNT; i++) if (saw_rolls[i].enabled) total_rolls = i + 1; fprintf(f, "rolls %d\n\n", total_rolls); for (i64 i = 0; i < total_rolls; i++) { saw_roll_t *roll = saw_rolls + i; fprintf(f, "enabled %d\n", (i32) roll->enabled); fprintf(f, "track %lld\n", roll->track); fprintf(f, "pitch_turned_off %d\n ", PITCH_COUNT); for (i64 pitch = 0; pitch < PITCH_COUNT; pitch++) fprintf(f, " %d", (i32) roll->pitch_turned_off[pitch]); fprintf(f, "\n"); fprintf(f, "rate %lld\n", roll->rate); i32 total_notes = 0; for (i32 n = 0; n < SHEET_SIZE; n++) if (roll->notes[n].enabled) total_notes = n + 1; fprintf(f, "notes %d\n", total_notes); for (i32 n = 0; n < total_notes; n++) fprintf(f, " %d %4lld %4lld %4lld\n", (i32) roll->notes[n].enabled, roll->notes[n].time, roll->notes[n].duration, roll->notes[n].pitch); 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); } // Save tracks // fprintf(f, "tracks %d\n\n", TRACK_COUNT); for (i64 i = 0; i < TRACK_COUNT; i++) { saw_track_t *track = saw_tracks + i; fprintf(f, "instrument %d\n", track->instrument); fprintf(f, "warp %lld\n", (i64) ((1. + track->warp) * 10000 + .5)); fprintf(f, "phase %lld\n", (i64) (track->phase * 10000 + .5)); fprintf(f, "unison %d\n", track->unison); fprintf(f, "spread %lld\n", (i64) (track->spread * 10000 + .5)); fprintf(f, "stereo_width %lld\n", (i64) (track->stereo_width * 10000 + .5)); fprintf(f, "volume %lld\n", (i64) (track->volume * 10000 + .5)); fprintf(f, "sustain %lld\n", (i64) (track->envelope.sustain * 10000 + .5)); fprintf(f, "attack %lld\n", (i64) (track->envelope.attack * 10000 + .5)); fprintf(f, "decay %lld\n", (i64) (track->envelope.decay * 10000 + .5)); fprintf(f, "release %lld\n\n", (i64) (track->envelope.release * 10000 + .5)); } fclose(f); } static void saw_event(sapp_event const *event) { 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++) { 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) { saw_compose.offset_x += event->mouse_dx; saw_compose.offset_y += event->mouse_dy; } if (saw_current_track != -1) { 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; 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:; } } break; case SAPP_EVENTTYPE_MOUSE_DOWN: switch (event->mouse_button) { case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 1; saw_lbutton_click = 1; break; case SAPP_MOUSEBUTTON_RIGHT: saw_rbutton_down = 1; saw_rbutton_click = 1; break; case SAPP_MOUSEBUTTON_MIDDLE: saw_mbutton_down = 1; saw_mbutton_click = 1; break; default:; } break; case SAPP_EVENTTYPE_MOUSE_UP: 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_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: 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; } saw_compose.offset_x = 0; saw_compose.offset_y = 0; break; case SAPP_KEYCODE_D: saw_compose.duplicate_input = 1; break; default:; } } break; default:; } } char const *__lsan_default_suppressions() { // There is leaks in NVidia driver on Linux. return "leak:nvidia"; } static void log_(const char *tag, uint32_t log_level, uint32_t log_item_id, const char *message_or_null, uint32_t line_nr, const char *filename_or_null, void *user_data) { if (message_or_null != NULL) printf("%s", message_or_null); printf("\n"); fflush(stdout); } sapp_desc sokol_main(i32 argc, char **argv) { i8 print_version = 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) saw_project_file = kit_str(strlen(argv[i]), argv[i]); else if (i > 0) printf("Unknown command line argument: \"%s\"\n", argv[i]); if (print_version) { printf("Saw v%d.%d.%d" #if VERSION_DEV "_dev" #endif #ifndef NDEBUG " (Debug)" #endif " - Music sequencer standalone application.\n", VERSION_MAJOR, VERSION_MINOR, VERSION_BABY); exit(0); } fflush(stdout); return (sapp_desc) { .window_title = "Saw", .width = 1280, .height = 720, .init_cb = saw_init, .frame_cb = saw_frame, .cleanup_cb = saw_cleanup, .event_cb = saw_event, .gl_major_version = 3, .gl_minor_version = 2, .logger.func = log_, }; }