summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2024-03-01 00:01:51 +0100
committerMitya Selivanov <automainint@guattari.tech>2024-03-01 00:01:51 +0100
commit014c4b4e5a3187a3426247991bcdda1d665ec3a8 (patch)
treee2f9f01ebf83fdcce1fc97d8ed91c0a62488a8fe
parent52cbc1ff83eb3776100c13632157b61f6783471c (diff)
downloadsaw-014c4b4e5a3187a3426247991bcdda1d665ec3a8.zip
More flexible project file format
-rw-r--r--TODO2
-rw-r--r--source/saw/main.c1098
2 files changed, 718 insertions, 382 deletions
diff --git a/TODO b/TODO
index f069fe0..e4cca9b 100644
--- a/TODO
+++ b/TODO
@@ -28,6 +28,7 @@ To-Do list
- Matrix view
- Graph view
- Custom font and localization
+ - File browser
- Data
- WAV export
@@ -40,6 +41,7 @@ To-Do list
- MIDI import
- Drag & drop sheet files
- Drag & drop project files
+ - Automatic serialization
Bugs
diff --git a/source/saw/main.c b/source/saw/main.c
index 6cf0632..8d26d27 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -2,6 +2,12 @@
//
// Saw main code
//
+// TODO
+// - Use 28224000 for time rate, divisible by common sample rates
+// like 192000, 44100 etc.
+// - Dynamic buffer size.
+// - Logging routines.
+//
// ================================================================
// ================================================================
@@ -16,7 +22,9 @@
#include "../kit/mersenne_twister_64.h"
#include "../kit/secure_random.h"
#include "../kit/file.h"
+#include "../kit/input_buffer.h"
#include "../kit/threads.h"
+#include "../kit/status.h"
#if defined(__EMSCRIPTEN__)
# include <GLES3/gl3.h>
@@ -61,12 +69,11 @@
#define EQUAL_TEMPERAMENT_FACTOR 1.05946309436 // 2^(1/12)
#define GLOBAL_VOLUME 2.0
-enum {
- // TODO
- // - Use 28224000 for time rate, divisible by common sample rates
- // like 192000, 44100 etc.
- // - Dynamic buffer size.
+#define SPACES SZ(" \t\n\r")
+#define DELIM SZ(" \t\n\r}")
+#define NUMS SZ(" \t\n\r+-0123456789.")
+enum {
CHANNEL_COUNT = 2,
SAMPLE_RATE = 44100,
BUFFER_SIZE = 1024 * 16,
@@ -219,6 +226,11 @@ typedef struct {
f32 hover[4];
} saw_ui_color_t;
+typedef struct {
+ i8 type;
+ str_t data;
+} saw_project_token_t;
+
// ================================================================
//
// Global state
@@ -244,10 +256,10 @@ static ma_device saw_audio_device;
// Input events
//
-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 f64 saw_mouse_x = 0;
+static f64 saw_mouse_y = 0;
+static f64 saw_mouse_dx = 0;
+static f64 saw_mouse_dy = 0;
static b8 saw_lbutton_click = 0;
static b8 saw_lbutton_down = 0;
static b8 saw_rbutton_click = 0;
@@ -340,7 +352,6 @@ static f64 saw_ui_input_buffer = 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")
@@ -854,6 +865,666 @@ static void saw_sampler_cleanup(saw_sampler_t *sampler) {
// ================================================================
//
+// Data
+//
+// TODO
+// - Implement parsing routines in the kit library.
+// - Floating point number format without data loss.
+//
+// ================================================================
+
+static ib_token_t saw_parse_sign(ib_token_t tok, i64 *sign) {
+ assert(sign != NULL);
+ if (sign == NULL)
+ return (ib_token_t) {
+ .status = KIT_ERROR_INVALID_ARGUMENT,
+ };
+
+ ib_token_t next = ib_any(tok, SPACES);
+ ib_token_t plus = ib_exact(next, SZ("+"));
+ ib_token_t minus = ib_exact(next, SZ("-"));
+
+ *sign = 1;
+
+ if (plus.status == KIT_OK)
+ return ib_any(plus, SPACES);
+
+ if (minus.status == KIT_OK) {
+ *sign = -1;
+ return ib_any(minus, SPACES);
+ }
+
+ return next;
+}
+
+static ib_token_t saw_parse_int(ib_token_t tok, i64 *x) {
+ assert(x != NULL);
+ if (x == NULL)
+ return (ib_token_t) {
+ .status = KIT_ERROR_INVALID_ARGUMENT,
+ };
+
+ i64 sign;
+
+ tok = saw_parse_sign(tok, &sign);
+ tok = ib_any(tok, SZ("0123456789"));
+
+ if (tok.status != KIT_OK)
+ return tok;
+
+ if (tok.size == 0) {
+ tok.status = KIT_PARSING_FAILED;
+ return tok;
+ }
+
+ str_t s = ib_str(tok);
+
+ *x = 0;
+
+ for (i64 i = 0; i < s.size; ++i)
+ *x = *x * 10 + (i64) (s.values[i] - '0');
+
+ *x *= sign;
+ return tok;
+}
+
+static ib_token_t saw_parse_float(ib_token_t tok, f64 *x) {
+ assert(x != NULL);
+ if (x == NULL)
+ return (ib_token_t) {
+ .status = KIT_ERROR_INVALID_ARGUMENT,
+ };
+
+ i64 sign;
+
+ tok = saw_parse_sign(tok, &sign);
+ tok = ib_any(tok, SZ("0123456789."));
+
+ if (tok.status != KIT_OK)
+ return tok;
+
+ if (tok.size == 0) {
+ tok.status = KIT_PARSING_FAILED;
+ return tok;
+ }
+
+ str_t s = ib_str(tok);
+
+ i64 n = 0;
+ i64 dot = 0;
+
+ for (i64 i = 0; i < s.size; ++i) {
+ if (s.values[i] == '.')
+ dot = i;
+ else
+ n = n * 10 + (i64) (s.values[i] - '0');
+ }
+
+ *x = (f64) n;
+
+ if (dot < s.size - 1)
+ *x /= pow(10.0, s.size - dot - 1);
+
+ *x *= sign;
+ return tok;
+}
+
+static ib_token_t saw_parse_roll(ib_token_t tok, saw_roll_t *roll) {
+ assert(roll != NULL);
+ if (roll == NULL)
+ return (ib_token_t) {
+ .status = KIT_ERROR_INVALID_ARGUMENT,
+ };
+
+ // Init defaults
+ //
+ {
+ memset(roll, 0, sizeof *roll);
+
+ saw_tuning_equal_temperament(roll->tuning);
+ roll->tuning_tag = TUNING_EQUAL_TEMPERAMENT;
+ roll->mark_pitch = REFERENCE_PITCH_INDEX;
+ roll->rate = ROLL_DEFAULT_RATE;
+ }
+
+ tok = ib_any(tok, SPACES);
+ tok = ib_exact(tok, SZ("roll"));
+ tok = ib_any(tok, SPACES);
+ tok = ib_exact(tok, SZ("{"));
+
+ while (tok.status == KIT_OK) {
+ tok = ib_any(tok, SPACES);
+
+ // Finish parsing if we reached closing brace
+ //
+
+ ib_token_t close = ib_exact(tok, SZ("}"));
+
+ if (close.status == KIT_OK) {
+ tok = close;
+ break;
+ }
+
+ // Parse property name
+ //
+
+ tok = ib_any(tok, SPACES);
+ tok = ib_none(tok, DELIM);
+
+ if (tok.status != KIT_OK)
+ break;
+
+ if (tok.size == 0) {
+ tok.status = KIT_PARSING_FAILED;
+ break;
+ }
+
+ ib_token_t name = tok;
+
+ // Parse property values
+ //
+
+ if (AR_EQUAL(ib_str(name), SZ("pitch_turned_off")))
+ for (i64 pitch = 0; tok.status == KIT_OK; ++pitch) {
+ i64 x;
+ ib_token_t next = saw_parse_int(tok, &x);
+
+ if (next.status != KIT_OK)
+ break;
+
+ if (pitch >= PITCH_COUNT) {
+ printf("Too many roll pitch_turned_off values\n");
+ tok = ib_any(next, NUMS);
+ break;
+ }
+
+ roll->pitch_turned_off[pitch] = x ? 1 : 0;
+ tok = next;
+ }
+ else if (AR_EQUAL(ib_str(name), SZ("tuning")))
+ for (i64 pitch = 0; tok.status == KIT_OK; ++pitch) {
+ f64 x;
+ ib_token_t next = saw_parse_float(tok, &x);
+
+ if (next.status != KIT_OK)
+ break;
+
+ if (pitch >= PITCH_COUNT) {
+ printf("Too many roll tuning values\n");
+ tok = ib_any(next, NUMS);
+ break;
+ }
+
+ roll->tuning[pitch] = x;
+ tok = next;
+ }
+ else if (AR_EQUAL(ib_str(name), SZ("notes")))
+ for (i64 note = 0; tok.status == KIT_OK; ++note) {
+ i64 note_enabled, note_time, note_duration, note_pitch;
+
+ ib_token_t next = saw_parse_int(tok, &note_enabled);
+ next = saw_parse_int(next, &note_time);
+ next = saw_parse_int(next, &note_duration);
+ next = saw_parse_int(next, &note_pitch);
+
+ if (next.status != KIT_OK)
+ break;
+
+ if (note >= SHEET_SIZE) {
+ printf("Too many roll notes values\n");
+ tok = ib_any(next, NUMS);
+ break;
+ }
+
+ roll->notes[note] = (saw_roll_note_t) {
+ .enabled = note_enabled ? 1 : 0,
+ .time = note_time,
+ .duration = note_duration,
+ .pitch = note_pitch,
+ };
+
+ tok = next;
+ }
+ else if (AR_EQUAL(ib_str(name), SZ("ui_offset"))) {
+ i64 x, y;
+ ib_token_t next = saw_parse_int(tok, &x);
+ next = saw_parse_int(next, &y);
+
+ if (next.status == KIT_OK) {
+ tok = next;
+ roll->ui_offset_x = x;
+ roll->ui_offset_y = y;
+ } else {
+ printf("Ignored unknown roll property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ } else {
+ i64 x;
+ ib_token_t next = saw_parse_int(tok, &x);
+
+ if (next.status == KIT_OK) {
+ tok = next;
+
+ if (AR_EQUAL(ib_str(name), SZ("enabled")))
+ roll->enabled = x ? 1 : 0;
+ else if (AR_EQUAL(ib_str(name), SZ("track")))
+ roll->track = x;
+ else if (AR_EQUAL(ib_str(name), SZ("tuning_tag")))
+ roll->tuning_tag = x;
+ else if (AR_EQUAL(ib_str(name), SZ("mark_pitch")))
+ roll->mark_pitch = x;
+ else if (AR_EQUAL(ib_str(name), SZ("rate")))
+ roll->rate = x;
+ else if (AR_EQUAL(ib_str(name), SZ("time")))
+ roll->time = x;
+ else if (AR_EQUAL(ib_str(name), SZ("duration")))
+ roll->duration = x;
+ else if (AR_EQUAL(ib_str(name), SZ("loop_duration")))
+ roll->loop_duration = x;
+ else {
+ printf("Ignored unknown roll property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ } else {
+ printf("Ignored unknown roll property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ }
+ }
+
+ return tok;
+}
+
+static ib_token_t saw_parse_track(ib_token_t tok,
+ saw_track_t *track) {
+ assert(track != NULL);
+ if (track == NULL)
+ return (ib_token_t) {
+ .status = KIT_ERROR_INVALID_ARGUMENT,
+ };
+
+ // Init defaults
+ //
+ {
+ memset(track, 0, sizeof *track);
+ track->instrument = INSTRUMENT_OSCILLATOR;
+ }
+
+ tok = ib_any(tok, SPACES);
+ tok = ib_exact(tok, SZ("track"));
+ tok = ib_any(tok, SPACES);
+ tok = ib_exact(tok, SZ("{"));
+
+ while (tok.status == KIT_OK) {
+ tok = ib_any(tok, SPACES);
+
+ // Finish parsing if we reached closing brace
+ //
+
+ ib_token_t close = ib_exact(tok, SZ("}"));
+
+ if (close.status == KIT_OK) {
+ tok = close;
+ break;
+ }
+
+ // Parse property name
+ //
+
+ tok = ib_any(tok, SPACES);
+ tok = ib_none(tok, DELIM);
+
+ if (tok.status != KIT_OK)
+ break;
+
+ if (tok.size == 0) {
+ tok.status = KIT_PARSING_FAILED;
+ break;
+ }
+
+ ib_token_t name = tok;
+
+ // Parse property values
+ //
+
+ if (AR_EQUAL(ib_str(name), SZ("data")))
+ // Array values
+ //
+
+ for (i64 k = 0; tok.status == KIT_OK; ++k) {
+ assert(track->instrument == INSTRUMENT_SAMPLER);
+ if (track->instrument != INSTRUMENT_SAMPLER) {
+ printf("Ignore unexpected track data property\n");
+ tok = ib_any(tok, NUMS);
+ break;
+ }
+
+ f64 x;
+ ib_token_t next = saw_parse_float(tok, &x);
+
+ if (next.status != KIT_OK)
+ break;
+
+ DA_RESIZE(track->sampler.data, k + 1);
+
+ assert(track->sampler.data.size == k + 1);
+ if (track->sampler.data.size != k + 1) {
+ printf("Bad alloc\n");
+ fflush(stdout);
+ tok = ib_any(tok, NUMS);
+ break;
+ }
+
+ track->sampler.data.values[k] = x;
+ tok = next;
+ }
+ if (AR_EQUAL(ib_str(name), SZ("instrument")) ||
+ AR_EQUAL(ib_str(name), SZ("wave"))) {
+ // Integer values
+ //
+
+ i64 x;
+ ib_token_t next = saw_parse_int(tok, &x);
+
+ if (next.status == KIT_OK) {
+ tok = next;
+
+ if (AR_EQUAL(ib_str(name), SZ("instrument"))) {
+ if (track->instrument == INSTRUMENT_SAMPLER)
+ DA_DESTROY(track->sampler.data);
+ if (x == INSTRUMENT_SAMPLER)
+ DA_INIT(track->sampler.data, 0, NULL);
+ track->instrument = (i32) x;
+ } else if (AR_EQUAL(ib_str(name), SZ("wave")) &&
+ track->instrument == INSTRUMENT_OSCILLATOR)
+ track->oscillator.wave = (i32) x;
+ else {
+ printf("Ignored unknown track property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ } else {
+ printf("Ignored unknown track property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ } else {
+ // Float values
+ //
+
+ f64 x;
+ ib_token_t next = saw_parse_float(tok, &x);
+
+ if (next.status == KIT_OK) {
+ tok = next;
+
+ switch (track->instrument) {
+ case INSTRUMENT_OSCILLATOR:
+ if (AR_EQUAL(ib_str(name), SZ("warp")))
+ track->oscillator.warp = x;
+ else if (AR_EQUAL(ib_str(name), SZ("phase")))
+ track->oscillator.phase = x;
+ else if (AR_EQUAL(ib_str(name), SZ("stereo_width")))
+ track->oscillator.stereo_width = x;
+ else if (AR_EQUAL(ib_str(name), SZ("volume")))
+ track->oscillator.volume = x;
+ else if (AR_EQUAL(ib_str(name), SZ("sustain")))
+ track->oscillator.envelope.sustain = x;
+ else if (AR_EQUAL(ib_str(name), SZ("attack")))
+ track->oscillator.envelope.attack = x;
+ else if (AR_EQUAL(ib_str(name), SZ("decay")))
+ track->oscillator.envelope.decay = x;
+ else if (AR_EQUAL(ib_str(name), SZ("release")))
+ track->oscillator.envelope.release = x;
+ else {
+ printf("Ignored unknown track property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ break;
+
+ case INSTRUMENT_SAMPLER:
+ if (AR_EQUAL(ib_str(name), SZ("begin")))
+ track->sampler.begin = x;
+ else if (AR_EQUAL(ib_str(name), SZ("end")))
+ track->sampler.end = x;
+ else if (AR_EQUAL(ib_str(name), SZ("crossfade")))
+ track->sampler.crossfade = x;
+ else if (AR_EQUAL(ib_str(name), SZ("base_frequency")))
+ track->sampler.base_frequency = x;
+ else if (AR_EQUAL(ib_str(name), SZ("volume")))
+ track->sampler.volume = x;
+ else if (AR_EQUAL(ib_str(name), SZ("sustain")))
+ track->sampler.envelope.sustain = x;
+ else if (AR_EQUAL(ib_str(name), SZ("attack")))
+ track->sampler.envelope.attack = x;
+ else if (AR_EQUAL(ib_str(name), SZ("decay")))
+ track->sampler.envelope.decay = x;
+ else if (AR_EQUAL(ib_str(name), SZ("release")))
+ track->sampler.envelope.release = x;
+ else {
+ printf("Ignored unknown track property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ break;
+
+ default:
+ printf("Ignored unknown track property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ } else {
+ printf("Ignored unknown track property `%s`\n",
+ BS(ib_str(name)));
+ tok = ib_any(tok, NUMS);
+ }
+ }
+ }
+
+ return tok;
+}
+
+static void saw_project_parse_file(str_t file_name) {
+ FILE *f = fopen(BS(file_name), "rb");
+
+ if (f == NULL) {
+ printf("fopen failed.\n");
+ fflush(stdout);
+ return;
+ }
+
+ is_handle_t in = is_wrap_file(f, NULL);
+ input_buffer_t buf = ib_wrap(in, NULL);
+
+ ib_token_t last = ib_token(&buf);
+
+ memset(saw_rolls, 0, sizeof saw_rolls);
+ memset(saw_tracks, 0, sizeof saw_tracks);
+
+ saw_current_roll = -1;
+ saw_current_track = 0;
+
+ i64 roll_index = 0;
+ i64 track_index = 0;
+
+ for (;;) {
+ ib_token_t tok;
+
+ // Parse roll
+ //
+ {
+ saw_roll_t roll;
+ tok = saw_parse_roll(last, &roll);
+
+ if (tok.status == KIT_OK) {
+ if (roll_index < ROLL_COUNT)
+ saw_rolls[roll_index++] = roll;
+ else {
+ printf("Too many rolls.\n");
+ fflush(stdout);
+ break;
+ }
+
+ last = tok;
+ continue;
+ }
+ }
+
+ // Parse track
+ //
+ {
+ saw_track_t track;
+ tok = saw_parse_track(last, &track);
+
+ if (tok.status == KIT_OK) {
+ if (track_index < TRACK_COUNT)
+ saw_tracks[track_index++] = track;
+ else {
+ printf("Too many tracks.\n");
+ fflush(stdout);
+ break;
+ }
+
+ last = tok;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ ib_destroy(&buf);
+ is_destroy(in);
+
+ for (i64 i = 0; i < ROLL_COUNT; ++i)
+ if (saw_rolls[i].enabled) {
+ saw_current_roll = i;
+ break;
+ }
+}
+
+static void saw_project_print_to_file(str_t file_name) {
+ printf("Save project: %s\n", BS(saw_project_file));
+ fflush(stdout);
+
+ FILE *f = fopen(BS(saw_project_file), "wb");
+
+ if (f == NULL) {
+ printf("Failed to write file: %s\n", BS(saw_project_file));
+ fflush(stdout);
+ return;
+ }
+
+ // Save rolls
+ //
+
+ i32 total_rolls = 0;
+
+ for (i64 i = 0; i < ROLL_COUNT; i++)
+ if (saw_rolls[i].enabled)
+ total_rolls = i + 1;
+
+ for (i64 i = 0; i < total_rolls; i++) {
+ saw_roll_t *roll = saw_rolls + i;
+
+ fprintf(f, "roll {\n");
+
+ fprintf(f, " enabled %d\n", (i32) roll->enabled);
+
+ if (roll->enabled) {
+ fprintf(f, " track %lld\n", roll->track);
+ fprintf(f, " pitch_turned_off ");
+ for (i64 pitch = 0; pitch < PITCH_COUNT; ++pitch)
+ fprintf(f, " %d", (i32) roll->pitch_turned_off[pitch]);
+ fprintf(f, "\n");
+ fprintf(f, " tuning ");
+ for (i64 pitch = 0; pitch < PITCH_COUNT; ++pitch)
+ fprintf(f, " %f", roll->tuning[pitch]);
+ fprintf(f, "\n");
+ fprintf(f, " tuning_tag %lld\n", roll->tuning_tag);
+ fprintf(f, " mark_pitch %lld\n", roll->mark_pitch);
+ 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\n");
+ for (i32 n = 0; n < total_notes; n++)
+ fprintf(f, " %d %-6lld %-6lld %-6lld\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, " ui_offset %lld %lld\n\n",
+ roll->ui_offset_x, roll->ui_offset_y);
+ }
+
+ fprintf(f, "}\n");
+ }
+
+ // Save tracks
+ //
+
+ for (i64 i = 0; i < TRACK_COUNT; i++) {
+ saw_track_t *track = saw_tracks + i;
+
+ fprintf(f, "track {\n");
+
+ fprintf(f, " instrument %d\n", track->instrument);
+
+ switch (track->instrument) {
+ case INSTRUMENT_OSCILLATOR: {
+ saw_oscillator_t *osc = &track->oscillator;
+
+ fprintf(f, " wave %d\n", osc->wave);
+ fprintf(f, " warp %f\n", osc->warp);
+ fprintf(f, " phase %f\n", osc->phase);
+ fprintf(f, " stereo_width %f\n", osc->stereo_width);
+ fprintf(f, " volume %f\n", osc->volume);
+ fprintf(f, " sustain %f\n", osc->envelope.sustain);
+ fprintf(f, " attack %f\n", osc->envelope.attack);
+ fprintf(f, " decay %f\n", osc->envelope.decay);
+ fprintf(f, " release %f\n\n", osc->envelope.release);
+ } break;
+
+ case INSTRUMENT_SAMPLER: {
+ saw_sampler_t *sam = &track->sampler;
+
+ fprintf(f, " data ");
+ for (i64 i = 0; i < sam->data.size; i++)
+ fprintf(f, " %f", sam->data.values[i]);
+ fprintf(f, "\n");
+
+ fprintf(f, " begin %f\n", sam->begin);
+ fprintf(f, " end %f\n", sam->end);
+ fprintf(f, " crossfade %f\n", sam->crossfade);
+ fprintf(f, " base_frequency %f\n", sam->base_frequency);
+ fprintf(f, " volume %f\n", sam->volume);
+ fprintf(f, " sustain %f\n", sam->envelope.sustain);
+ fprintf(f, " attack %f\n", sam->envelope.attack);
+ fprintf(f, " decay %f\n", sam->envelope.decay);
+ fprintf(f, " release %f\n\n", sam->envelope.release);
+ } break;
+
+ default:;
+ }
+
+ fprintf(f, "}\n");
+ }
+
+ fclose(f);
+}
+
+// ================================================================
+//
// UI
//
// TODO
@@ -1430,14 +2101,6 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
.loop_duration = 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_tuning_equal_temperament(saw_rolls[n].tuning);
@@ -2551,6 +3214,9 @@ static void saw_init(void) {
// Init Saw state
//
+ // FIXME
+ // Add init routines
+
static f64 tuning[PITCH_COUNT];
saw_tuning_equal_temperament(tuning);
@@ -2559,7 +3225,6 @@ static void saw_init(void) {
saw_rolls[i] = (saw_roll_t) {
.enabled = (i == 0),
.track = 0,
- .last_index = -1,
.pitch_turned_off = { 0 },
.tuning = { 0 },
.tuning_tag = TUNING_EQUAL_TEMPERAMENT,
@@ -2571,13 +3236,6 @@ static void saw_init(void) {
.loop_duration = 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,
};
memcpy(saw_rolls[i].tuning, tuning, sizeof tuning);
@@ -2620,9 +3278,21 @@ static void saw_init(void) {
BS(cache), (i32) s);
fflush(stdout);
} else {
+ c8 project_name[] = "quick.saw";
+
+ assert(cache.size + 1 + sizeof project_name <
+ sizeof saw_project_file_buf);
+ if (cache.size + 1 + sizeof project_name >=
+ sizeof saw_project_file_buf) {
+ printf("File name too big\n");
+ fflush(stdout);
+ return;
+ }
+
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);
+ memcpy(saw_project_file_buf + cache.size + 1, project_name,
+ sizeof project_name);
saw_project_file.size = strlen(saw_project_file_buf);
saw_project_file.values = saw_project_file_buf;
@@ -2635,222 +3305,7 @@ static void saw_init(void) {
// Load the project from a file
//
- // TODO
- // - More flexible project file format.
-
- saw_current_roll = -1;
-
- 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));
- fflush(stdout);
- return;
- }
-
-#define SCAN_(format_, num_, ...) \
- do { \
- if (fscanf(f, format_, __VA_ARGS__) != num_) { \
- printf("Invalid syntax at \"%s\"\n", format_); \
- fflush(stdout); \
- fclose(f); \
- return; \
- } \
- } while (0)
-
- i32 total_rolls;
-
- 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;
-
- if (enabled && saw_current_roll == -1)
- saw_current_roll = i;
-
- 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);
- fflush(stdout);
- 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_(" tuning %d", 1, &pitch_count);
-
- if (pitch_count < 0 || pitch_count > PITCH_COUNT) {
- printf("Invalid tuning count: %d\n", pitch_count);
- fflush(stdout);
- fclose(f);
- return;
- }
-
- for (i64 pitch = 0; pitch < pitch_count; ++pitch) {
- i64 frequency;
- SCAN_(" %lld", 1, &frequency);
- roll->tuning[pitch] = .0001 * frequency;
- }
-
- SCAN_(" tuning_tag %lld", 1, &roll->tuning_tag);
- SCAN_(" mark_pitch %lld", 1, &roll->mark_pitch);
-
- 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);
- fflush(stdout);
- 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_(" ui_offset %lld %lld", 2, &roll->ui_offset_x,
- &roll->ui_offset_y);
- }
-
- for (i64 i = total_rolls; i < ROLL_COUNT; ++i)
- saw_rolls[i].enabled = false;
-
- i32 total_tracks;
- SCAN_(" tracks %d", 1, &total_tracks);
-
- for (i64 i = 0; i < total_tracks; i++) {
- saw_track_t *track = saw_tracks + i;
-
- SCAN_(" instrument %d", 1, &track->instrument);
-
- switch (track->instrument) {
- case INSTRUMENT_OSCILLATOR: {
- saw_oscillator_t *osc = &track->oscillator;
-
- i64 warp, phase, stereo_width, volume, sustain, attack,
- decay, release;
-
- SCAN_(" wave %d", 1, &osc->wave);
- SCAN_(" warp %lld", 1, &warp);
- SCAN_(" phase %lld", 1, &phase);
-
- 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);
-
- osc->warp = warp * 0.0001 - 1.;
- osc->phase = phase * 0.0001;
- osc->stereo_width = stereo_width * 0.0001;
- osc->volume = volume * 0.0001;
- osc->envelope.sustain = sustain * 0.0001;
- osc->envelope.attack = attack * 0.0001;
- osc->envelope.decay = decay * 0.0001;
- osc->envelope.release = release * 0.0001;
- } break;
-
- case INSTRUMENT_SAMPLER: {
- saw_sampler_t *sam = &track->sampler;
-
- DA_INIT(sam->data, 0, NULL);
- DA_INIT(sam->outline, SAMPLER_OUTLINE_SIZE, NULL);
-
- if (track->sampler.outline.size != SAMPLER_OUTLINE_SIZE) {
- printf("Bad alloc\n");
- fflush(stdout);
- track->sampler.outline.size = 0;
- }
-
- i64 data_size;
-
- SCAN_(" data %lld", 1, &data_size);
-
- if (data_size > 0) {
- DA_RESIZE(sam->data, data_size * CHANNEL_COUNT);
-
- assert(sam->data.size == data_size * CHANNEL_COUNT);
-
- if (sam->data.size == data_size * CHANNEL_COUNT) {
- for (i64 l, r, i = 0; i < data_size; i++) {
- SCAN_(" %lld %lld", 2, &l, &r);
- sam->data.values[i * 2] = (f32) (.0001 * l);
- sam->data.values[i * 2 + 1] = (f32) (.0001 * r);
- }
- } else {
- printf("Bad alloc\n");
- fflush(stdout);
-
- for (i64 l, r, i = 0; i < data_size; i++)
- SCAN_(" %lld %lld", 2, &l, &r);
- }
-
- if (sam->outline.size == SAMPLER_OUTLINE_SIZE)
- for (i64 i = 0; i < SAMPLER_OUTLINE_SIZE; i++) {
- f32 *v = sam->data.values;
- i64 k = i * data_size / SAMPLER_OUTLINE_SIZE;
- sam->outline.values[i] = fabs(v[k] + v[k + 1]) * .5;
- }
- }
-
- i64 begin, end, crossfade, bfreq, volume, sustain, attack,
- decay, release;
-
- SCAN_(" begin %lld", 1, &begin);
- SCAN_(" end %lld", 1, &end);
- SCAN_(" crossfade %lld", 1, &crossfade);
- SCAN_(" base_frequency %lld", 1, &bfreq);
- 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);
-
- sam->begin = begin * .0001;
- sam->end = end * .0001;
- sam->crossfade = crossfade * .0001;
- sam->base_frequency = bfreq * .0001;
- sam->volume = volume * .0001;
- sam->envelope.sustain = sustain * .0001;
- sam->envelope.attack = attack * .0001;
- sam->envelope.decay = decay * .0001;
- sam->envelope.release = release * .0001;
- } break;
-
- default:
- memset(track, 0, sizeof *track);
- track->instrument = INSTRUMENT_NONE;
- }
- }
-
-#undef SCAN_
-
- fclose(f);
- }
+ saw_project_parse_file(saw_project_file);
}
static void saw_frame(void) {
@@ -3011,135 +3466,8 @@ static void saw_cleanup(void) {
// Save the project to a file
//
- if (saw_project_file.size == 0)
- return;
-
- printf("Save project: %s\n", BS(saw_project_file));
- fflush(stdout);
-
- FILE *f = fopen(BS(saw_project_file), "wb");
-
- if (f == NULL) {
- printf("Failed to write file: %s\n", BS(saw_project_file));
- fflush(stdout);
- return;
- }
-
- // 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, "tuning %d\n ", PITCH_COUNT);
- for (i64 pitch = 0; pitch < PITCH_COUNT; ++pitch)
- fprintf(f, " %lld",
- (i64) floor(roll->tuning[pitch] * 10000 + .5));
- fprintf(f, "\n");
- fprintf(f, "tuning_tag %lld\n", roll->tuning_tag);
- fprintf(f, "mark_pitch %lld\n", roll->mark_pitch);
- 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, "ui_offset %lld %lld\n\n", roll->ui_offset_x,
- roll->ui_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);
-
- switch (track->instrument) {
- case INSTRUMENT_OSCILLATOR: {
- saw_oscillator_t *osc = &track->oscillator;
-
- fprintf(f, "wave %d\n", osc->wave);
- fprintf(f, "warp %lld\n",
- (i64) floor((1. + osc->warp) * 10000 + .5));
- fprintf(f, "phase %lld\n",
- (i64) floor(osc->phase * 10000 + .5));
- fprintf(f, "stereo_width %lld\n",
- (i64) floor(osc->stereo_width * 10000 + .5));
- fprintf(f, "volume %lld\n",
- (i64) floor(osc->volume * 10000 + .5));
- fprintf(f, "sustain %lld\n",
- (i64) floor(osc->envelope.sustain * 10000 + .5));
- fprintf(f, "attack %lld\n",
- (i64) floor(osc->envelope.attack * 10000 + .5));
- fprintf(f, "decay %lld\n",
- (i64) floor(osc->envelope.decay * 10000 + .5));
- fprintf(f, "release %lld\n\n",
- (i64) floor(osc->envelope.release * 10000 + .5));
- } break;
-
- case INSTRUMENT_SAMPLER: {
- saw_sampler_t *sam = &track->sampler;
-
- fprintf(f, "data %lld ",
- (i64) sam->data.size / CHANNEL_COUNT);
- for (i64 i = 0; i < sam->data.size; i++)
- fprintf(f, " %lld",
- (i64) floor(sam->data.values[i] * 10000 + .5));
- fprintf(f, "\n");
-
- fprintf(f, "begin %lld\n",
- (i64) floor(sam->begin * 10000 + .5));
- fprintf(f, "end %lld\n",
- (i64) floor(sam->end * 10000 + .5));
- fprintf(f, "crossfade %lld\n",
- (i64) floor(sam->crossfade * 10000 + .5));
- fprintf(f, "base_frequency %lld\n",
- (i64) floor(sam->base_frequency * 10000 + .5));
- fprintf(f, "volume %lld\n",
- (i64) floor(sam->volume * 10000 + .5));
- fprintf(f, "sustain %lld\n",
- (i64) floor(sam->envelope.sustain * 10000 + .5));
- fprintf(f, "attack %lld\n",
- (i64) floor(sam->envelope.attack * 10000 + .5));
- fprintf(f, "decay %lld\n",
- (i64) floor(sam->envelope.decay * 10000 + .5));
- fprintf(f, "release %lld\n\n",
- (i64) floor(sam->envelope.release * 10000 + .5));
- } break;
-
- default:;
- }
- }
-
- fclose(f);
+ if (saw_project_file.size != 0)
+ saw_project_print_to_file(saw_project_file);
// Cleanup samplers
//
@@ -3189,10 +3517,10 @@ static void saw_event(sapp_event const *event) {
saw_mouse_on = 1;
saw_shift_on = (event->modifiers & SAPP_MODIFIER_SHIFT) != 0;
saw_ctrl_on = (event->modifiers & SAPP_MODIFIER_CTRL) != 0;
- saw_mouse_dx += (i64) floor(event->mouse_dx + .5);
- saw_mouse_dy += (i64) floor(event->mouse_dy + .5);
- saw_mouse_x = (i64) floor(event->mouse_x + .5);
- saw_mouse_y = (i64) floor(event->mouse_y + .5);
+ saw_mouse_dx += event->mouse_dx;
+ saw_mouse_dy += event->mouse_dy;
+ saw_mouse_x = event->mouse_x;
+ saw_mouse_y = event->mouse_y;
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
@@ -3256,8 +3584,8 @@ static void saw_event(sapp_event const *event) {
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_mouse_x = event->touches[0].pos_x;
+ saw_mouse_y = event->touches[0].pos_y;
saw_lbutton_down = 1;
saw_lbutton_click = 1;
saw_mouse_on = 1;
@@ -3266,8 +3594,8 @@ static void saw_event(sapp_event const *event) {
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);
+ i64 x = event->touches[0].pos_x;
+ i64 y = event->touches[0].pos_y;
saw_mouse_dx += x - saw_mouse_x;
saw_mouse_dy += y - saw_mouse_y;
saw_mouse_x = x;
@@ -3277,8 +3605,8 @@ static void saw_event(sapp_event const *event) {
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);
+ i64 x = event->touches[0].pos_x;
+ i64 y = event->touches[0].pos_y;
saw_mouse_dx += x - saw_mouse_x;
saw_mouse_dy += y - saw_mouse_y;
saw_mouse_x = x;
@@ -3294,6 +3622,12 @@ static void saw_event(sapp_event const *event) {
break;
case SAPP_EVENTTYPE_FILES_DROPPED: {
+ // Get the drop cursor position
+ //
+
+ saw_mouse_x = event->mouse_x;
+ saw_mouse_y = event->mouse_y;
+
// Get the dropped file name
//