summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO2
-rw-r--r--source/saw/main.c290
2 files changed, 270 insertions, 22 deletions
diff --git a/TODO b/TODO
index 401279d..0231158 100644
--- a/TODO
+++ b/TODO
@@ -7,7 +7,6 @@ To-Do list
- Sound: Simple tonal synth
- Sound: Kick, snare, cymbal
- UI: Catalog
-- State load and store
- Data: WAV export
- Sound: Buffering
- Build: WebAssembly
@@ -51,3 +50,4 @@ Done
- UI: Piano roll panning
- UI: Track composing
- UI: Instrument settings
+- State load and store
diff --git a/source/saw/main.c b/source/saw/main.c
index b5f46be..e505e4f 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -6,6 +6,7 @@
#include "../kit/string_ref.h"
#include "../kit/mersenne_twister_64.h"
#include "../kit/secure_random.h"
+#include "../kit/file.h"
#ifdef __EMSCRIPTEN__
# include <GLES3/gl3.h>
@@ -76,19 +77,22 @@ typedef struct {
typedef struct {
i8 enabled;
i64 track;
- i32 last_index;
i8 pitch_turned_off[PITCH_COUNT];
i64 rate;
saw_roll_note_t notes[SHEET_SIZE];
- i64 frame;
i64 size;
- i8 grid_input;
- i32 grid_note;
- i32 grid_time;
- i8 offset_x_input;
- i8 offset_y_input;
i64 offset_x;
i64 offset_y;
+
+ // dynamic properties
+ //
+ i32 last_index;
+ i64 frame;
+ i8 grid_input;
+ i32 grid_note;
+ i32 grid_time;
+ i8 offset_x_input;
+ i8 offset_y_input;
} saw_roll_t;
typedef struct {
@@ -107,12 +111,18 @@ typedef struct {
f64 stereo_width;
f64 volume;
saw_envelope_t envelope;
- i32 value_input;
- f64 value_buffer;
+
+ // 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;
@@ -124,8 +134,9 @@ typedef struct {
static struct NVGcontext *saw_nvg;
static ma_device saw_ma;
-static i32 saw_font = -1;
-
+static char saw_project_file_buf[4096];
+static str_t saw_project_file;
+static i32 saw_font = -1;
static mt64_state_t saw_mt64;
static i32 saw_mouse_x = 0;
@@ -470,19 +481,20 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
saw_rolls[n] = (saw_roll_t) {
.enabled = 1,
.track = track,
- .last_index = -1,
.pitch_turned_off = { 0 },
.rate = 6,
.notes = { 0 },
- .frame = frame,
.size = 6 / grid_rate,
- .grid_input = 0,
- .grid_note = 0,
- .grid_time = 0,
- .offset_x_input = 0,
- .offset_y_input = 0,
.offset_x = 0,
.offset_y = ROLL_DEFAULT_OFFSET_Y,
+
+ .last_index = -1,
+ .frame = frame,
+ .grid_input = 0,
+ .grid_note = 0,
+ .grid_time = 0,
+ .offset_x_input = 0,
+ .offset_y_input = 0,
};
saw_current_roll = n;
@@ -738,9 +750,10 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
nvgText(saw_nvg, x0 + border * 2,
frame_height - y0 - height + next_y - border * 2,
"Instrument", 0);
- nvgText(saw_nvg, x0 + column_width + border * 2,
- frame_height - y0 - height + next_y - border * 2,
- buf_instr[track->instrument], 0);
+ if (track->instrument >= 0 && track->instrument <= INSTRUMENT_SINE)
+ 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];
@@ -1222,6 +1235,146 @@ static void saw_init(void) {
},
.value_input = TRACK_INPUT_NONE,
};
+
+ // Determine the project file name
+ //
+
+ if (saw_project_file.size == 0) {
+ char abc[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY"
+ "Z0123456789";
+
+ 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 {
+ for (i32 index = 0;; ++index) {
+ memcpy(saw_project_file_buf, cache.values, cache.size);
+ saw_project_file_buf[cache.size] = PATH_DELIM_C;
+
+ for (i32 i = 0; i < 16; i++)
+ saw_project_file_buf[cache.size + 1 + i] =
+ abc[mt64_generate(&saw_mt64) % (sizeof abc - 1)];
+
+ sprintf(saw_project_file_buf + cache.size + 17, "%d", index);
+
+ saw_project_file.size = strlen(saw_project_file_buf);
+ saw_project_file.values = saw_project_file_buf;
+
+ if (path_type(saw_project_file) == PATH_NONE)
+ break;
+ }
+
+ printf("Project file: %s\n", saw_project_file_buf);
+ }
+ }
+
+ // Load the project from a file
+ //
+
+ if (path_type(saw_project_file) == PATH_FILE) {
+ printf("Open project: %s\n", BS(saw_project_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);
+ 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);
+ 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);
+ 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_(" size %lld", 1, &roll->size);
+ 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) {
@@ -1281,6 +1434,99 @@ static void saw_cleanup(void) {
#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, "size %lld\n", roll->size);
+ 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) {
@@ -1472,6 +1718,8 @@ sapp_desc sokol_main(i32 argc, char **argv) {
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]);