diff --git a/base.h b/base.h index 446b627..36dd4ab 100644 --- a/base.h +++ b/base.h @@ -108,6 +108,7 @@ static bool orca_is_valid_glyph(Glyph c) { case ';': case '=': case '?': + case '$': return true; } return false; diff --git a/cli_main.c b/cli_main.c index 419f3fb..af6271f 100644 --- a/cli_main.c +++ b/cli_main.c @@ -1,4 +1,4 @@ -#include "base.h" +#include "state.h" #include "field.h" #include "gbuffer.h" #include "sim.h" @@ -85,11 +85,19 @@ int main(int argc, char **argv) { Oevent_list oevent_list; oevent_list_init(&oevent_list); Usz max_ticks = (Usz)ticks; + + State state; + state.tick_num = 0; + state.bpm = (Usz) 120; + state.oosc_dev = NULL; + state.is_playing = true; + for (Usz i = 0; i < max_ticks; ++i) { + state.tick_num = i; mbuffer_clear(mbuf_r.buffer, field.height, field.width); oevent_list_clear(&oevent_list); - orca_run(field.buffer, mbuf_r.buffer, field.height, field.width, i, - &oevent_list, 0); + orca_run(field.buffer, mbuf_r.buffer, field.height, field.width, + &oevent_list, 0, &state); } mbuf_reusable_deinit(&mbuf_r); oevent_list_deinit(&oevent_list); diff --git a/commander.c b/commander.c new file mode 100644 index 0000000..5b81242 --- /dev/null +++ b/commander.c @@ -0,0 +1,48 @@ +#include "commander.h" +#include +#include + +void parse_command(Glyph *command, State *state) { + const Glyph end_line[2] = "."; + Glyph *token; + token = strtok(command, end_line); + while (token != NULL) { + // Parse simple tokens + if (strcmp(token, "play") == 0) { + state->is_playing = true; + } else if (strcmp(token, "stop") == 0) { + state->is_playing = false; + } else if (strcmp(token, "run") == 0) { + state->tick_num++; + } else { + const Glyph arguments_separator[2] = ":"; + token = strtok(command, arguments_separator); + while(token != NULL) { + // TODO handle errors: https://stackoverflow.com/questions/15229411/input-validation-of-an-integer-using-atoi + if (strcmp(token, "bpm") == 0) { + token = strtok(NULL, end_line); + if (token == NULL) return; + Glyph *end_ptr; + state->bpm = (Usz) strtoul(token, &end_ptr, 10); + } else if (strcmp(token, "frame") == 0) { + token = strtok(NULL, end_line); + if (token == NULL) return; + Glyph *end_ptr; + state->tick_num = (Usz) strtoul(token, &end_ptr, 10); + } else if (strcmp(token, "rewind") == 0) { + token = strtok(NULL, end_line); + if (token == NULL) return; + Glyph *end_ptr; + state->tick_num -= (Usz) strtoul(token, &end_ptr, 10); + } else if (strcmp(token, "skip") == 0) { + token = strtok(NULL, end_line); + if (token == NULL) return; + Glyph *end_ptr; + state->tick_num += (Usz) strtoul(token, &end_ptr, 10); + } + token = strtok(NULL, arguments_separator); + } + } + token = strtok(NULL, end_line); + } +} \ No newline at end of file diff --git a/commander.h b/commander.h new file mode 100644 index 0000000..21b6962 --- /dev/null +++ b/commander.h @@ -0,0 +1,5 @@ +#pragma once +#include "base.h" +#include "state.h" + +void parse_command(Glyph *command, State *state); \ No newline at end of file diff --git a/sim.c b/sim.c index 01797d1..428d78f 100644 --- a/sim.c +++ b/sim.c @@ -1,4 +1,5 @@ #include "sim.h" +#include "commander.h" #include "gbuffer.h" //////// Utilities @@ -98,7 +99,7 @@ static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, OPER_FUNCTION_ATTRIBS oper_behavior_##_oper_name( \ Glyph *const restrict gbuffer, Mark *const restrict mbuffer, \ Usz const height, Usz const width, Usz const y, Usz const x, \ - Usz Tick_number, Oper_extra_params *const extra_params, \ + State *state, Oper_extra_params *const extra_params, \ Mark const cell_flags, Glyph const This_oper_char) { \ (void)gbuffer; \ (void)mbuffer; \ @@ -106,7 +107,7 @@ static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, (void)width; \ (void)y; \ (void)x; \ - (void)Tick_number; \ + (void)state; \ (void)extra_params; \ (void)cell_flags; \ (void)This_oper_char; @@ -155,7 +156,8 @@ static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, _(':', midi) \ _(';', udp) \ _('=', osc) \ - _('?', midipb) + _('?', midipb) \ + _('$', commander) #define ALPHA_OPERATORS(_) \ _('A', add) \ @@ -397,6 +399,33 @@ BEGIN_OPERATOR(midipb) oe->lsb = (U8)(index_of(lsb_g) * 127 / 35); END_OPERATOR +BEGIN_OPERATOR(commander) + Usz n = width - x - 1; + if (n > 16) + n = 16; + Glyph const *restrict gline = gbuffer + y * width + x + 1; + Mark *restrict mline = mbuffer + y * width + x + 1; + Glyph cpy[Oevent_udp_string_count]; + Usz i; + for (i = 0; i < n; ++i) { + Glyph g = gline[i]; + if (g == '.') + break; + cpy[i] = g; + mline[i] |= Mark_flag_lock; + } + n = i; + + STOP_IF_NOT_BANGED; + + // Handle empty cases + // TODO revise if necessary + if (PEEK(0, 1) == '.') return; + + parse_command(cpy, state); + +END_OPERATOR + BEGIN_OPERATOR(add) LOWERCASE_REQUIRES_BANG; PORT(0, -1, IN | PARAM); @@ -432,7 +461,7 @@ BEGIN_OPERATOR(clock) rate = 1; if (mod_num == 0) mod_num = 8; - Glyph g = glyph_of(Tick_number / rate % mod_num); + Glyph g = glyph_of(state->tick_num / rate % mod_num); POKE(1, 0, g); END_OPERATOR @@ -447,7 +476,7 @@ BEGIN_OPERATOR(delay) rate = 1; if (mod_num == 0) mod_num = 8; - Glyph g = Tick_number % (rate * mod_num) == 0 ? '*' : '.'; + Glyph g = state->tick_num % (rate * mod_num) == 0 ? '*' : '.'; POKE(1, 0, g); END_OPERATOR @@ -633,7 +662,7 @@ BEGIN_OPERATOR(random) } // Initial input params for the hash Usz key = (extra_params->random_seed + y * width + x) ^ - (Tick_number << UINT32_C(16)); + (state->tick_num << UINT32_C(16)); // 32-bit shift_mult hash to evenly distribute bits key = (key ^ UINT32_C(61)) ^ (key >> UINT32_C(16)); key = key + (key << UINT32_C(3)); @@ -676,7 +705,7 @@ BEGIN_OPERATOR(uclid) Usz max = index_of(PEEK(0, 1)); if (max == 0) max = 8; - Usz bucket = (steps * (Tick_number + max - 1)) % max + steps; + Usz bucket = (steps * (state->tick_num + max - 1)) % max + steps; Glyph g = (bucket >= max) ? '*' : '.'; POKE(1, 0, g); END_OPERATOR @@ -743,7 +772,7 @@ END_OPERATOR //////// Run simulation void orca_run(Glyph *restrict gbuf, Mark *restrict mbuf, Usz height, Usz width, - Usz tick_number, Oevent_list *oevent_list, Usz random_seed) { + Oevent_list *oevent_list, Usz random_seed, State *state) { Glyph vars_slots[Glyphs_index_count]; memset(vars_slots, '.', sizeof(vars_slots)); Oper_extra_params extras; @@ -764,15 +793,15 @@ void orca_run(Glyph *restrict gbuf, Mark *restrict mbuf, Usz height, Usz width, switch (glyph_char) { #define UNIQUE_CASE(_oper_char, _oper_name) \ case _oper_char: \ - oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, tick_number, \ - &extras, cell_flags, glyph_char); \ + oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, state, \ + &extras, cell_flags, glyph_char); \ break; #define ALPHA_CASE(_upper_oper_char, _oper_name) \ case _upper_oper_char: \ case (char)(_upper_oper_char | 1 << 5): \ - oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, tick_number, \ - &extras, cell_flags, glyph_char); \ + oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, state, \ + &extras, cell_flags, glyph_char); \ break; UNIQUE_OPERATORS(UNIQUE_CASE) ALPHA_OPERATORS(ALPHA_CASE) diff --git a/sim.h b/sim.h index 1c10283..4b51d50 100644 --- a/sim.h +++ b/sim.h @@ -1,7 +1,6 @@ #pragma once -#include "base.h" +#include "state.h" #include "vmio.h" void orca_run(Glyph *restrict gbuffer, Mark *restrict mbuffer, Usz height, - Usz width, Usz tick_number, Oevent_list *oevent_list, - Usz random_seed); + Usz width, Oevent_list *oevent_list, Usz random_seed, State *state); diff --git a/state.h b/state.h new file mode 100644 index 0000000..1e9addf --- /dev/null +++ b/state.h @@ -0,0 +1,11 @@ +#pragma once +#include "base.h" +#include "osc_out.h" + +typedef struct { + Usz tick_num; + Usz bpm; + bool is_playing : 1; + Oosc_dev *oosc_dev; + +} State; diff --git a/sysmisc.c b/sysmisc.c index b91eaad..0ca6547 100644 --- a/sysmisc.c +++ b/sysmisc.c @@ -122,7 +122,7 @@ Conf_read_result conf_read_line(FILE *file, char *buf, Usz bufsize, if (a0 == len) goto ignore; char c = s[a0]; - if (c == ';' || c == '#') // comment line, ignore + if (c == ';' || c == '#' || c == '$') // comment line, ignore goto ignore; if (c == '=') // '=' before any other char, bad goto ignore; diff --git a/tool b/tool index e32b94d..53ccf33 100755 --- a/tool +++ b/tool @@ -335,7 +335,7 @@ build_target() { out_exe=cli ;; orca|tui) - add source_files osc_out.c term_util.c sysmisc.c thirdparty/oso.c tui_main.c + add source_files commander.c osc_out.c term_util.c sysmisc.c thirdparty/oso.c tui_main.c add cc_flags -D_XOPEN_SOURCE_EXTENDED=1 # thirdparty headers (like sokol_time.h) should get -isystem for their # include dir so that any warnings they generate with our warning flags diff --git a/tui_main.c b/tui_main.c index 39310aa..57269c8 100644 --- a/tui_main.c +++ b/tui_main.c @@ -1,7 +1,6 @@ -#include "base.h" #include "field.h" #include "gbuffer.h" -#include "osc_out.h" +#include "state.h" #include "oso.h" #include "sim.h" #include "sysmisc.h" @@ -89,6 +88,7 @@ static Glyph_class glyph_class_of(Glyph glyph) { case '=': case '%': case '?': + case '$': return Glyph_class_lowercase; case '*': return Glyph_class_bang; @@ -880,14 +880,12 @@ typedef struct { Oevent_list scratch_oevent_list; Susnote_list susnote_list; Ged_cursor ged_cursor; - Usz tick_num; Usz ruler_spacing_y, ruler_spacing_x; Ged_input_mode input_mode; - Usz bpm; + State state; U64 clock; double accum_secs; double time_to_next_note_off; - Oosc_dev *oosc_dev; Midi_mode midi_mode; Usz activity_counter; Usz random_seed; @@ -899,7 +897,6 @@ typedef struct { U8 midi_bclock_sixths; // 0..5, holds 6th of the quarter note step bool needs_remarking : 1; bool is_draw_dirty : 1; - bool is_playing : 1; bool midi_bclock : 1; bool draw_event_list : 1; bool is_mouse_down : 1; @@ -908,6 +905,13 @@ typedef struct { } Ged; static void ged_init(Ged *a, Usz undo_limit, Usz init_bpm, Usz init_seed) { + State state; + state.tick_num = 0; + state.bpm = init_bpm; + state.oosc_dev = NULL; + state.is_playing = false; + a->state = state; + field_init(&a->field); field_init(&a->scratch_field); field_init(&a->clipboard_field); @@ -917,14 +921,11 @@ static void ged_init(Ged *a, Usz undo_limit, Usz init_bpm, Usz init_seed) { oevent_list_init(&a->scratch_oevent_list); susnote_list_init(&a->susnote_list); ged_cursor_init(&a->ged_cursor); - a->tick_num = 0; a->ruler_spacing_y = a->ruler_spacing_x = 8; a->input_mode = Ged_input_mode_normal; - a->bpm = init_bpm; a->clock = 0; a->accum_secs = 0.0; a->time_to_next_note_off = 1.0; - a->oosc_dev = NULL; midi_mode_init_null(&a->midi_mode); a->activity_counter = 0; a->random_seed = init_seed; @@ -936,7 +937,6 @@ static void ged_init(Ged *a, Usz undo_limit, Usz init_bpm, Usz init_seed) { a->midi_bclock_sixths = 0; a->needs_remarking = true; a->is_draw_dirty = false; - a->is_playing = false; a->midi_bclock = false; a->draw_event_list = false; a->is_mouse_down = false; @@ -953,8 +953,8 @@ static void ged_deinit(Ged *a) { oevent_list_deinit(&a->oevent_list); oevent_list_deinit(&a->scratch_oevent_list); susnote_list_deinit(&a->susnote_list); - if (a->oosc_dev) - oosc_dev_destroy(a->oosc_dev); + if (a->state.oosc_dev) + oosc_dev_destroy(a->state.oosc_dev); midi_mode_deinit(&a->midi_mode); } @@ -1058,7 +1058,7 @@ staticni void apply_time_to_sustained_notes(Oosc_dev *oosc_dev, staticni void ged_stop_all_sustained_notes(Ged *a) { Susnote_list *sl = &a->susnote_list; - send_midi_note_offs(a->oosc_dev, &a->midi_mode, sl->buffer, + send_midi_note_offs(a->state.oosc_dev, &a->midi_mode, sl->buffer, sl->buffer + sl->count); susnote_list_clear(sl); a->time_to_next_note_off = 1.0; @@ -1215,21 +1215,21 @@ staticni void send_output_events(Oosc_dev *oosc_dev, Midi_mode *midi_mode, } staticni void ged_clear_osc_udp(Ged *a) { - if (a->oosc_dev) { + if (a->state.oosc_dev) { if (a->midi_mode.any.type == Midi_mode_type_osc_bidule) { ged_stop_all_sustained_notes(a); } - oosc_dev_destroy(a->oosc_dev); - a->oosc_dev = NULL; + oosc_dev_destroy(a->state.oosc_dev); + a->state.oosc_dev = NULL; } } -static bool ged_is_using_osc_udp(Ged *a) { return (bool)a->oosc_dev; } +static bool ged_is_using_osc_udp(Ged *a) { return (bool)a->state.oosc_dev; } static bool ged_set_osc_udp(Ged *a, char const *dest_addr, char const *dest_port) { ged_clear_osc_udp(a); if (dest_port) { Oosc_udp_create_error err = - oosc_dev_create_udp(&a->oosc_dev, dest_addr, dest_port); + oosc_dev_create_udp(&a->state.oosc_dev, dest_addr, dest_port); if (err) { return false; } @@ -1240,9 +1240,9 @@ static bool ged_set_osc_udp(Ged *a, char const *dest_addr, static ORCA_FORCEINLINE double ms_to_sec(double ms) { return ms / 1000.0; } static double ged_secs_to_deadline(Ged const *a) { - if (!a->is_playing) + if (!a->state.is_playing) return 1.0; - double secs_span = 60.0 / (double)a->bpm / 4.0; + double secs_span = 60.0 / (double)a->state.bpm / 4.0; // If MIDI beat clock output is enabled, we need to send an event every 24 // parts per quarter note. Since we've already divided quarter notes into 4 // for ORCA's timing semantics, divide it by a further 6. This same logic is @@ -1259,20 +1259,20 @@ static double ged_secs_to_deadline(Ged const *a) { } staticni void clear_and_run_vm(Glyph *restrict gbuf, Mark *restrict mbuf, - Usz height, Usz width, Usz tick_number, - Oevent_list *oevent_list, Usz random_seed) { + Usz height, Usz width, Oevent_list *oevent_list, + Usz random_seed, State *state) { mbuffer_clear(mbuf, height, width); oevent_list_clear(oevent_list); - orca_run(gbuf, mbuf, height, width, tick_number, oevent_list, random_seed); + orca_run(gbuf, mbuf, height, width, oevent_list, random_seed, state); } staticni void ged_do_stuff(Ged *a) { - if (!a->is_playing) + if (!a->state.is_playing) return; - double secs_span = 60.0 / (double)a->bpm / 4.0; + double secs_span = 60.0 / (double)a->state.bpm / 4.0; if (a->midi_bclock) // see also ged_secs_to_deadline() secs_span /= 6.0; - Oosc_dev *oosc_dev = a->oosc_dev; + Oosc_dev *oosc_dev = a->state.oosc_dev; Midi_mode *midi_mode = &a->midi_mode; bool crossed_deadline = false; #if TIME_DEBUG @@ -1322,15 +1322,15 @@ staticni void ged_do_stuff(Ged *a) { apply_time_to_sustained_notes(oosc_dev, midi_mode, secs_span, &a->susnote_list, &a->time_to_next_note_off); clear_and_run_vm(a->field.buffer, a->mbuf_r.buffer, a->field.height, - a->field.width, a->tick_num, &a->oevent_list, - a->random_seed); - ++a->tick_num; + a->field.width, &a->oevent_list, a->random_seed, + &a->state); + ++a->state.tick_num; a->needs_remarking = true; a->is_draw_dirty = true; Usz count = a->oevent_list.count; if (count > 0) { - send_output_events(oosc_dev, midi_mode, a->bpm, &a->susnote_list, + send_output_events(oosc_dev, midi_mode, a->state.bpm, &a->susnote_list, a->oevent_list.buffer, count); a->activity_counter += count; } @@ -1421,14 +1421,14 @@ staticni void ged_draw(Ged *a, WINDOW *win, char const *filename, // mark buffer that it produces, then roll back the glyph buffer to where it // was before. This should produce results similar to having specialized UI // code that looks at each glyph and figures out the ports, etc. - if (a->needs_remarking && !a->is_playing) { + if (a->needs_remarking && !a->state.is_playing) { field_resize_raw_if_necessary(&a->scratch_field, a->field.height, a->field.width); field_copy(&a->field, &a->scratch_field); mbuf_reusable_ensure_size(&a->mbuf_r, a->field.height, a->field.width); clear_and_run_vm(a->scratch_field.buffer, a->mbuf_r.buffer, a->field.height, - a->field.width, a->tick_num, &a->scratch_oevent_list, - a->random_seed); + a->field.width, &a->scratch_oevent_list, a->random_seed, + &a->state); a->needs_remarking = false; } int win_w = a->win_w; @@ -1440,13 +1440,13 @@ staticni void ged_draw(Ged *a, WINDOW *win, char const *filename, a->field.height, a->field.width, a->grid_scroll_y, a->grid_scroll_x, a->ged_cursor.y, a->ged_cursor.x, a->ged_cursor.h, a->ged_cursor.w, a->input_mode, - a->is_playing); + a->state.is_playing); if (a->is_hud_visible) { filename = filename ? filename : "unnamed"; int hud_x = win_w > 50 + a->softmargin_x * 2 ? a->softmargin_x : 0; draw_hud(win, a->grid_h, hud_x, Hud_height, win_w, filename, a->field.height, a->field.width, a->ruler_spacing_y, - a->ruler_spacing_x, a->tick_num, a->bpm, &a->ged_cursor, + a->ruler_spacing_x, a->state.tick_num, a->state.bpm, &a->ged_cursor, a->input_mode, a->activity_counter); } if (a->draw_event_list) @@ -1455,19 +1455,19 @@ staticni void ged_draw(Ged *a, WINDOW *win, char const *filename, } staticni void ged_send_osc_bpm(Ged *a, I32 bpm) { - send_num_message(a->oosc_dev, "/orca/bpm", bpm); + send_num_message(a->state.oosc_dev, "/orca/bpm", bpm); } staticni void ged_adjust_bpm(Ged *a, Isz delta_bpm) { - Isz new_bpm = (Isz)a->bpm; + Isz new_bpm = (Isz)a->state.bpm; if (delta_bpm < 0 || new_bpm < INT_MAX - delta_bpm) new_bpm += delta_bpm; else new_bpm = INT_MAX; if (new_bpm < 1) new_bpm = 1; - if ((Usz)new_bpm != a->bpm) { - a->bpm = (Usz)new_bpm; + if ((Usz)new_bpm != a->state.bpm) { + a->state.bpm = (Usz)new_bpm; a->is_draw_dirty = true; ged_send_osc_bpm(a, (I32)new_bpm); } @@ -1535,7 +1535,7 @@ staticni bool ged_slide_selection(Ged *a, int delta_y, int delta_x) { if (curs_y_0 == curs_y_1 && curs_x_0 == curs_x_1 && curs_h_0 == curs_h_1 && curs_w_0 == curs_w_1) return false; - undo_history_push(&a->undo_hist, &a->field, a->tick_num); + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); Usz field_h = a->field.height; Usz field_w = a->field.width; gbuffer_copy_subrect(a->field.buffer, a->field.buffer, field_h, field_w, @@ -1739,7 +1739,7 @@ staticni void ged_adjust_rulers_relative(Ged *a, Isz delta_y, Isz delta_x) { staticni void ged_resize_grid_relative(Ged *a, Isz delta_y, Isz delta_x) { ged_resize_grid_snap_ruler(&a->field, &a->mbuf_r, a->ruler_spacing_y, - a->ruler_spacing_x, delta_y, delta_x, a->tick_num, + a->ruler_spacing_x, delta_y, delta_x, a->state.tick_num, &a->scratch_field, &a->undo_hist, &a->ged_cursor); a->needs_remarking = true; // could check if we actually resized a->is_draw_dirty = true; @@ -1748,7 +1748,7 @@ staticni void ged_resize_grid_relative(Ged *a, Isz delta_y, Isz delta_x) { } staticni void ged_write_character(Ged *a, char c) { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); gbuffer_poke(a->field.buffer, a->field.height, a->field.width, a->ged_cursor.y, a->ged_cursor.x, c); // Indicate we want the next simulation step to be run predictavely, @@ -1798,7 +1798,7 @@ staticni void ged_input_character(Ged *a, char c) { if (a->ged_cursor.h <= 1 && a->ged_cursor.w <= 1) { ged_write_character(a, c); } else { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); ged_fill_selection_with_char(a, c); a->needs_remarking = true; a->is_draw_dirty = true; @@ -1822,27 +1822,27 @@ typedef enum { } Ged_input_cmd; staticni void ged_set_playing(Ged *a, bool playing) { - if (playing == a->is_playing) + if (playing == a->state.is_playing) return; if (playing) { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - a->is_playing = true; + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); + a->state.is_playing = true; a->clock = stm_now(); a->midi_bclock_sixths = 0; // dumb'n'dirty, get us close to the next step time, but not quite - a->accum_secs = 60.0 / (double)a->bpm / 4.0; + a->accum_secs = 60.0 / (double)a->state.bpm / 4.0; if (a->midi_bclock) { - send_midi_byte(a->oosc_dev, &a->midi_mode, 0xFA); // "start" + send_midi_byte(a->state.oosc_dev, &a->midi_mode, 0xFA); // "start" a->accum_secs /= 6.0; } a->accum_secs -= 0.0001; - send_control_message(a->oosc_dev, "/orca/started"); + send_control_message(a->state.oosc_dev, "/orca/started"); } else { ged_stop_all_sustained_notes(a); - a->is_playing = false; - send_control_message(a->oosc_dev, "/orca/stopped"); + a->state.is_playing = false; + send_control_message(a->state.oosc_dev, "/orca/stopped"); if (a->midi_bclock) - send_midi_byte(a->oosc_dev, &a->midi_mode, 0xFC); // "stop" + send_midi_byte(a->state.oosc_dev, &a->midi_mode, 0xFC); // "stop" } a->is_draw_dirty = true; } @@ -1852,10 +1852,10 @@ staticni void ged_input_cmd(Ged *a, Ged_input_cmd ev) { case Ged_input_cmd_undo: if (undo_history_count(&a->undo_hist) == 0) break; - if (a->is_playing) - undo_history_apply(&a->undo_hist, &a->field, &a->tick_num); + if (a->state.is_playing) + undo_history_apply(&a->undo_hist, &a->field, &a->state.tick_num); else - undo_history_pop(&a->undo_hist, &a->field, &a->tick_num); + undo_history_pop(&a->undo_hist, &a->field, &a->state.tick_num); ged_cursor_confine(&a->ged_cursor, a->field.height, a->field.width); ged_update_internal_geometry(a); ged_make_cursor_visible(a); @@ -1881,17 +1881,17 @@ staticni void ged_input_cmd(Ged *a, Ged_input_cmd ev) { a->is_draw_dirty = true; break; case Ged_input_cmd_step_forward: - undo_history_push(&a->undo_hist, &a->field, a->tick_num); + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); clear_and_run_vm(a->field.buffer, a->mbuf_r.buffer, a->field.height, - a->field.width, a->tick_num, &a->oevent_list, - a->random_seed); - ++a->tick_num; + a->field.width, &a->oevent_list, a->random_seed, + &a->state); + ++a->state.tick_num; a->activity_counter += a->oevent_list.count; a->needs_remarking = true; a->is_draw_dirty = true; break; case Ged_input_cmd_toggle_play_pause: - ged_set_playing(a, !a->is_playing); + ged_set_playing(a, !a->state.is_playing); break; case Ged_input_cmd_toggle_show_event_list: a->draw_event_list = !a->draw_event_list; @@ -1899,7 +1899,7 @@ staticni void ged_input_cmd(Ged *a, Ged_input_cmd ev) { break; case Ged_input_cmd_cut: if (ged_copy_selection_to_clipbard(a)) { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); ged_fill_selection_with_char(a, '.'); a->needs_remarking = true; a->is_draw_dirty = true; @@ -1926,7 +1926,7 @@ staticni void ged_input_cmd(Ged *a, Ged_input_cmd ev) { cpy_w = field_w - curs_x; if (cpy_h == 0 || cpy_w == 0) break; - undo_history_push(&a->undo_hist, &a->field, a->tick_num); + undo_history_push(&a->undo_hist, &a->field, a->state.tick_num); gbuffer_copy_subrect(cb_field->buffer, a->field.buffer, cbfield_h, cbfield_w, field_h, field_w, 0, 0, curs_y, curs_x, cpy_h, cpy_w); @@ -2178,7 +2178,7 @@ static void push_controls_msg(void) { {"Arrow Keys", "Move Cursor"}, {"Ctrl+D or F1", "Open Main Menu"}, {"0-9, A-Z, a-z,", "Insert Character"}, - {"! : % / = # *", NULL}, + {"! : % / = # * $", NULL}, {"Spacebar", "Play/Pause"}, {"Ctrl+Z or Ctrl+U", "Undo"}, {"Ctrl+X", "Cut"}, @@ -2188,6 +2188,7 @@ static void push_controls_msg(void) { {"Ctrl+F", "Frame Step Forward"}, {"Ctrl+R", "Reset Frame Number"}, {"Ctrl+I or Insert", "Append/Overwrite Mode"}, + {"Ctrl+G", "Show Operators"}, // {"/", "Key Trigger Mode"}, {"' (quote)", "Rectangle Selection Mode"}, {"Shift+Arrow Keys", "Adjust Rectangle Selection"}, @@ -2266,11 +2267,11 @@ static void push_opers_guide_msg(void) { {'Z', "lerp", "Transitions operand to target."}, {'*', "bang", "Bangs neighboring operands."}, {'#', "comment", "Halts line."}, - // {'*', "self", "Sends ORCA command."}, + {'$', "self", "Sends ORCA command."}, {':', "midi", "Sends MIDI note."}, {'!', "cc", "Sends MIDI control change."}, {'?', "pb", "Sends MIDI pitch bend."}, - // {'%', "mono", "Sends MIDI monophonic note."}, + {'%', "mono", "Sends MIDI monophonic note."}, {'=', "osc", "Sends OSC message."}, {';', "udp", "Sends UDP message."}, }; @@ -2921,7 +2922,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { push_save_as_form(osoc(t->file_name)); break; case Main_menu_set_tempo: - push_set_tempo_form(t->ged.bpm); + push_set_tempo_form(t->ged.state.bpm); break; case Main_menu_set_grid_dims: push_set_grid_dims_form(t->ged.field.height, t->ged.field.width); @@ -2951,7 +2952,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { } if (did_get_ok_size) { ged_resize_grid(&t->ged.field, &t->ged.mbuf_r, new_field_h, - new_field_w, t->ged.tick_num, &t->ged.scratch_field, + new_field_w, t->ged.state.tick_num, &t->ged.scratch_field, &t->ged.undo_hist, &t->ged.ged_cursor); ged_update_internal_geometry(&t->ged); t->ged.needs_remarking = true; @@ -2972,7 +2973,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { if (tui_suggest_nice_grid_size(t, t->ged.win_h, t->ged.win_w, &new_field_h, &new_field_w)) { undo_history_push(&t->ged.undo_hist, &t->ged.field, - t->ged.tick_num); + t->ged.state.tick_num); field_resize_raw(&t->ged.field, new_field_h, new_field_w); memset(t->ged.field.buffer, '.', new_field_h * new_field_w * sizeof(Glyph)); @@ -3010,9 +3011,9 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { case Playback_menu_midi_bclock: { bool new_enabled = !t->ged.midi_bclock; t->ged.midi_bclock = new_enabled; - if (t->ged.is_playing) { + if (t->ged.state.is_playing) { int msgbyte = new_enabled ? 0xFA /* start */ : 0xFC /* stop */; - send_midi_byte(t->ged.oosc_dev, &t->ged.midi_mode, msgbyte); + send_midi_byte(t->ged.state.oosc_dev, &t->ged.midi_mode, msgbyte); // TODO timing judder will be experienced here, because the // deadline calculation conditions will have been changed by // toggling the midi_bclock flag. We would have to transfer the @@ -3098,7 +3099,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { if (!temp_name) break; bool added_hist = undo_history_push(&t->ged.undo_hist, &t->ged.field, - t->ged.tick_num); + t->ged.state.tick_num); Field_load_error fle = field_load_file(osoc(temp_name), &t->ged.field); if (fle == Field_load_error_ok) { @@ -3116,7 +3117,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { } else { if (added_hist) undo_history_pop(&t->ged.undo_hist, &t->ged.field, - &t->ged.tick_num); + &t->ged.state.tick_num); qmsg_printf_push("Error Loading File", "%s:\n%s", osoc(temp_name), field_load_error_string(fle)); } @@ -3140,7 +3141,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { break; int newbpm = atoi(osoc(tmpstr)); if (newbpm > 0) { - t->ged.bpm = (Usz)newbpm; + t->ged.state.bpm = (Usz)newbpm; qnav_stack_pop(); } osofree(tmpstr); @@ -3185,7 +3186,7 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { if (t->ged.field.height != (Usz)newheight || t->ged.field.width != (Usz)newwidth) { ged_resize_grid(&t->ged.field, &t->ged.mbuf_r, (Usz)newheight, - (Usz)newwidth, t->ged.tick_num, + (Usz)newwidth, t->ged.state.tick_num, &t->ged.scratch_field, &t->ged.undo_hist, &t->ged.ged_cursor); ged_update_internal_geometry(&t->ged); @@ -3446,7 +3447,7 @@ int main(int argc, char **argv) { mbuf_reusable_ensure_size(&t.ged.mbuf_r, t.ged.field.height, t.ged.field.width); ged_make_cursor_visible(&t.ged); - ged_send_osc_bpm(&t.ged, (I32)t.ged.bpm); // Send initial BPM + ged_send_osc_bpm(&t.ged, (I32)t.ged.state.bpm); // Send initial BPM ged_set_playing(&t.ged, true); // Auto-play // Enter main loop. Process events as they arrive. event_loop:; @@ -3632,7 +3633,7 @@ event_loop:; ged_input_cmd(&t.ged, Ged_input_cmd_undo); break; case CTRL_PLUS('r'): - t.ged.tick_num = 0; + t.ged.state.tick_num = 0; t.ged.needs_remarking = true; t.ged.is_draw_dirty = true; break; @@ -3698,14 +3699,14 @@ event_loop:; case CTRL_PLUS('v'): if (t.use_gui_cboard) { bool added_hist = - undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.tick_num); + undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.state.tick_num); Usz pasted_h, pasted_w; Cboard_error cberr = cboard_paste( t.ged.field.buffer, t.ged.field.height, t.ged.field.width, t.ged.ged_cursor.y, t.ged.ged_cursor.x, &pasted_h, &pasted_w); if (cberr) { if (added_hist) - undo_history_pop(&t.ged.undo_hist, &t.ged.field, &t.ged.tick_num); + undo_history_pop(&t.ged.undo_hist, &t.ged.field, &t.ged.state.tick_num); t.use_gui_cboard = false; ged_input_cmd(&t.ged, Ged_input_cmd_paste); } else { @@ -3738,7 +3739,7 @@ event_loop:; // handle. Such as bracketed paste. if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_begin) { is_in_brackpaste = true; - undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.tick_num); + undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.state.tick_num); brackpaste_y = t.ged.ged_cursor.y; brackpaste_x = t.ged.ged_cursor.x; brackpaste_starting_x = brackpaste_x;