@@ -1,5 +1,60 @@
# What's new?

## Version 2.1

Teletype version 2.1 introduces new operators that mature the syntax and capability of the Teletype, as well as several bug fixes and enhancement features.

### Major new features

#### Tracker Data Entry Improvements

Data entry in the tracker screen is now _buffered_, requiring an `ENTER` keystroke to commit changes, or `SHIFT-ENTER` to insert the value. All other navigation keystrokes will abandon data entry. The increment / decrement keystrokes (`]` and `[`), as well as the negate keystroke (`-`) function immediately if not in data entry mode, but modify the currently buffered value in edit mode (again, requiring a commit).

#### Turtle Operator

The Turtle operator allows 2-dimensional access to the patterns as portrayed out in Tracker mode. It uses new operators with the `@` prefix. You can `@MOVE X Y` the turtle relative to its current position, or set its direction in degrees with `@DIR` and its speed with `@SPEED` and then execute a `@STEP`.

To access the value that the turtle operator points to, use `@`, which can also set the value with an argument.

The turtle can be constrained on the tracker grid by setting its fence with `@FX1`, `@FY1`, `@FX2`, and `@FY2`, or by using the shortcut operator `@F x1 y1 x2 y2`. When the turtle reaches the fence, its behaviour is governed by its _fence mode_, where the turtle can simply stop (`@BUMP`), wrap around to the other edge (`@WRAP`), or bounce off the fence and change direction (`@BOUNCE`). Each of these can be set to `1` to enable that mode.

Finally, the turtle can be displayed on the tracker screen with `@SHOW 1`, where it will indicate the current cell by pointing to it from the right side with the `<` symbol.

#### Script Line "Commenting"

Individual lines in scripts can now be disabled from execution by highlighting the line and pressing `ALT-/`. Disabled lines will appear dim. This status will persist through save/load from flash, but will not carry over to scenes saved to USB drive.

### New Operators

`W [condition]:` is a new mod that operates as a while loop.
The `BREAK` operator stops executing the current script
`BPM [bpm]` returns the number of milliseconds per beat in a given BPM, great for setting `M`.
`LAST [script]` returns the number of milliseconds since `script` was last called.

### New Operator Behaviour

`SCRIPT` with no argument now returns the current script number.
`I` is now local to its corresponding `L` statement.
`IF/ELSE` is now local to its script.

### New keybindings

`CTRL-1` through `CTRL-8` toggle the mute status for scripts 1 to 8 respectively.
`CTRL-9` toggles the METRO script.
`SHIFT-ENTER` now inserts a line in Scene Write mode.

### Bug fixes

Temporal recursion now possible by fixing delay allocation issue, e.g.: DEL 250: SCRIPT SCRIPT
`KILL` now clears `TR` outputs and stops METRO.
`SCENE` will no longer execute from the INIT script on initial scene load.
`AVG` and `Q.AVG` now round up from offsets of 0.5 and greater.

### Breaking Changes

As `I` is now local to `L` loops, it is no longer usable across scripts or as a general-purpose variable.
As `IF/ELSE` is now local to a script, scenes that relied on IF in one script and ELSE in another will be functionally broken.

## Version 2.0

Teletype version 2.0 represents a large rewrite of the Teletype code base. There are many new language additions, some small breaking changes and a lot of under the hood enhancements.
@@ -73,12 +73,14 @@ CSRCS = \
../module/preset_w_mode.c \
../module/usb_disk_mode.c \
../src/command.c \
../src/every.c \
../src/helpers.c \
../src/match_token.c \
../src/scanner.c \
../src/state.c \
../src/table.c \
../src/teletype.c \
../src/turtle.c \
../src/ops/op.c \
../src/ops/ansible.c \
../src/ops/controlflow.c \
@@ -96,6 +98,7 @@ CSRCS = \
../src/ops/telex.c \
../src/ops/variables.c \
../src/ops/whitewhale.c \
../src/ops/turtle.c \
../libavr32/src/adc.c \
../libavr32/src/events.c \
../libavr32/src/euclidean/euclidean.c \
@@ -190,10 +193,10 @@ INC_PATH = \
common/utils

# Additional search paths for libraries.
LIB_PATH =
LIB_PATH =

# List of libraries to use during linking.
LIBS =
LIBS =

# Path relative to top level directory pointing to a linker script.
LINKER_SCRIPT = ../src/link_uc3b0512.lds
@@ -73,7 +73,7 @@ void process_edit_keys(uint8_t k, uint8_t m, bool is_held_key) {
if (script)
script--;
else
script = SCRIPT_COUNT - 1;
script = SCRIPT_COUNT - 2; // due to TEMP_SCRIPT
if (line_no > ss_get_script_len(&scene_state, script))
line_no = ss_get_script_len(&scene_state, script);
line_editor_set_command(
@@ -86,7 +86,7 @@ void process_edit_keys(uint8_t k, uint8_t m, bool is_held_key) {
status = E_OK;
error_msg[0] = 0;
script++;
if (script >= SCRIPT_COUNT) script = 0;
if (script >= SCRIPT_COUNT - 1) script = 0; // due to TEMP_SCRIPT
if (line_no > ss_get_script_len(&scene_state, script))
line_no = ss_get_script_len(&scene_state, script);
line_editor_set_command(
@@ -161,12 +161,20 @@ void process_edit_keys(uint8_t k, uint8_t m, bool is_held_key) {
dirty |= D_LIST;
dirty |= D_INPUT;
}
// alt-slash comment toggle current line
else if (match_alt(m, k, HID_SLASH)) {
ss_toggle_script_comment(&scene_state, script, line_no);
dirty |= D_LIST;
}
else { // pass the key though to the line editor
bool processed = line_editor_process_keys(&le, k, m, is_held_key);
if (processed) dirty |= D_INPUT;
}
}

void screen_mutes_updated() {
dirty |= D_INPUT;
}

bool screen_refresh_edit() {
bool screen_dirty = false;
@@ -179,6 +187,11 @@ bool screen_refresh_edit() {
prefix = 'I';

line_editor_draw(&le, prefix, &line[7]);
// maybe find a better way than stomping it?
if (ss_get_mute(&scene_state, script)) {
char shaded[2] = { prefix, '\0' };
font_string_region_clip(&line[7], shaded, 0, 0, 0x4, 0);
}
screen_dirty = true;
dirty &= ~D_INPUT;
}
@@ -209,12 +222,14 @@ bool screen_refresh_edit() {
if (dirty & D_LIST) {
for (int i = 0; i < 6; i++) {
uint8_t a = line_no == i;
uint8_t fg =
ss_get_script_comment(&scene_state, script, i) ? 0x7 : 0xf;
region_fill(&line[i], a);
if (ss_get_script_len(&scene_state, script) > i) {
char s[32];
print_command(ss_get_script_command(&scene_state, script, i),
s);
region_string(&line[i], s, 2, 0, 0xf, a, 0);
region_string(&line[i], s, 2, 0, fg, a, 0);
}
}

@@ -7,6 +7,7 @@
void set_edit_mode(void);
void set_edit_mode_script(uint8_t new_script);
void process_edit_keys(uint8_t key, uint8_t mod_key, bool is_held_key);
void screen_mutes_updated(void);
bool screen_refresh_edit(void);

#endif
@@ -15,18 +15,20 @@
////////////////////////////////////////////////////////////////////////////////
// Help text ///////////////////////////////////////////////////////////////////

#define HELP_PAGES 7
#define HELP_PAGES 8

#define HELP1_LENGTH 41
const char* help1[HELP1_LENGTH] = { "1/7 HELP",
#define HELP1_LENGTH 46
const char* help1[HELP1_LENGTH] = { "1/8 HELP",
"[ ] NAVIGATE HELP PAGES",
"UP/DOWN TO SCROLL",
" ",
"TAB|EDIT/LIVE/PATTERN",
"PRINT SCREEN|JUMP TO LIVE",
"PRT SC|JUMP TO LIVE",
"NUM LOCK|JUMP TO PATTERN",
"F1-F10|EXECUTE SCRIPT",
"ALT-F1-F10|EDIT SCRIPT",
"CTRL-F1-F8|MUTE SCRIPT",
"CTRL-F9|STOP/START METRO",
"ESC|SCENE",
"ALT-ESC|WRITE",
" ",
@@ -44,24 +46,27 @@ const char* help1[HELP1_LENGTH] = { "1/7 HELP",
"ENTER|ADD/OVERWRITE",
"SH-ENTER|INSERT",
"SH-BSP|CLEAR",
"ALT-SLASH|DISABLE LINE",
" ",
"// PATTERN",
"ARROWS|NAVIGATE",
"ALT-ARROWS|JUMP",
"0-9|NUMERIC ENTRY",
"-|FLIP SIGN",
"SPACE|TOGGLE 0/1",
"ENTER|COMMIT CHANGE",
"[ ]|NUDGE UP, DOWN",
"SH-ALT-V|INSERT PASTE",
"SH-BSP|DELETE",
"SH-ENTER|DUPE INSERT",
"SH-L|SET LENGTH",
"SH-S|SET START",
"SH-E|SET END",
"ALT-L,S,E|JUMP" };
"ALT-L,S,E|JUMP",
"SHIFT-2|SHOW/HIDE TURTLE" };

#define HELP2_LENGTH 13
const char* help2[HELP2_LENGTH] = { "2/7 VARIABLES",
const char* help2[HELP2_LENGTH] = { "2/8 VARIABLES",
" ",
"X, Y, Z|GENERAL PURPOSE",
"T|USE FOR TIME",
@@ -75,8 +80,8 @@ const char* help2[HELP2_LENGTH] = { "2/7 VARIABLES",
"Q.N|SET Q LENGTH",
"Q.AVG|AVERAGE OF ALL Q" };

#define HELP3_LENGTH 21
const char* help3[HELP3_LENGTH] = { "3/7 PARAMETERS",
#define HELP3_LENGTH 22
const char* help3[HELP3_LENGTH] = { "3/8 PARAMETERS",
" ",
"TR A-D|SET TR VALUE (0,1)",
"TR.TIME A-D|TR PULSE TIME",
@@ -95,22 +100,24 @@ const char* help3[HELP3_LENGTH] = { "3/7 PARAMETERS",
"TIME|TIMER COUNT (MS)",
"TIME.ACT|ENABLE TIMER (0/1)",
" ",
"SCRIPT A|RUN SCRIPT",
"SCENE|GET/SET SCENE #" };
"SCRIPT A|GET/RUN SCRIPT",
"SCENE|GET/SET SCENE #",
"LAST N|GET SCRIPT LAST RUN" };

#define HELP4_LENGTH 9
const char* help4[HELP4_LENGTH] = { "4/7 DATA AND TABLES",
#define HELP4_LENGTH 10
const char* help4[HELP4_LENGTH] = { "4/8 DATA AND TABLES",
" ",
"ALL PARAMS HAVE 16B RANGE",
"-32768 TO 32767",
" ",
"// LOOKUP TABLES",
"N 0-127|CONVERT TO 1V/8VE",
"V 0-10|VOLT LOOKUP",
"VV 0-1000|V WITH 2 DECIMALS" };
"VV 0-1000|V WITH 2 DECIMALS",
"BPM 2-MAX|MS PER BPM" };

#define HELP5_LENGTH 35
const char* help5[HELP5_LENGTH] = { "5/7 OPERATORS",
const char* help5[HELP5_LENGTH] = { "5/8 OPERATORS",
" ",
"RAND A|RANDOM 0 - A",
"RRAND A B|RANDOM A - B",
@@ -146,8 +153,8 @@ const char* help5[HELP5_LENGTH] = { "5/7 OPERATORS",
"TR.TOG X|FLIP STATE OF TR X",
"TR.PULSE X|PULSE TR X" };

#define HELP6_LENGTH 22
const char* help6[HELP6_LENGTH] = { "6/7 PRE :",
#define HELP6_LENGTH 31
const char* help6[HELP6_LENGTH] = { "6/8 PRE :",
" ",
"EACH PRE NEEDS A : FOLLOWED",
"BY A COMMAND TO OPERATE ON",
@@ -168,10 +175,21 @@ const char* help6[HELP6_LENGTH] = { "6/7 PRE :",
"ELSE: |AFTER FAILED IF",
" ",
"L A B: |ITERATE FROM A-B",
"NB: I IS UPDATED EACH TIME" };
"NB: I IS UPDATED EACH TIME",
" ",
"W X:|ITERATE WHILE X",
" ",
"EVERY X:|EXECUTE EACH X",
"SKIP X:|EXECUTE EACH BUT X",
"OTHER:|EXECUTE OTHERWISE",
"SYNC X|SYNC TO STEP X",
" ",
"BREAK|STOP EXECUTION"

};

#define HELP7_LENGTH 26
const char* help7[HELP7_LENGTH] = { "7/7 PATTERNS",
const char* help7[HELP7_LENGTH] = { "7/8 PATTERNS",
" ",
"// DIRECT ACCESS",
"P A|GET VAL AT INDEX A",
@@ -197,17 +215,35 @@ const char* help7[HELP7_LENGTH] = { "7/7 PATTERNS",
"P.HERE A|GET/SET VAL AT P.I",
"P.NEXT A|GET/SET NEXT POS",
"P.PREV A|GET/SET PREV POS" };
#define HELP8_LENGTH 17
const char* help8[HELP8_LENGTH] = { "8/8 TURTLE",
" ",
"// CRAWLS TRACKER DATA",
"@|GET/SET DATA",
"@X/@Y|GET/SET POSITION",
"@F X1 Y1 X2 Y2",
" |SET FENCE",
" OR @FX1/@FY1/@FX2/@FY2",
"@BUMP 1|STOP AT FENCE",
"@WRAP 1|WRAP AT FENCE",
"@BOUNCE 1|BOUNCE OFF FENCE",
"@MOVE X Y|MOVE RELATIVE",
"@DIR 0-360|GET/SET DIRECTION",
"@SPEED|GET/SET CENTICELLS",
"@STEP|MOVE AT SPEED/DIR",
"@SCRIPT N|GET/SET EDGE SCRIPT",
"@SHOW 1/0|DISPLAY < ON TRACKER" };


////////////////////////////////////////////////////////////////////////////////
// Help mode ///////////////////////////////////////////////////////////////////

const char** help_pages[HELP_PAGES] = { help1, help2, help3, help4,
help5, help6, help7 };
help5, help6, help7, help8 };
const uint8_t help_length[HELP_PAGES] = { HELP1_LENGTH, HELP2_LENGTH,
HELP3_LENGTH, HELP4_LENGTH,
HELP5_LENGTH, HELP6_LENGTH,
HELP7_LENGTH };
HELP7_LENGTH, HELP8_LENGTH };

static uint8_t page_no;
static uint8_t offset;
@@ -146,7 +146,14 @@ void process_live_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
memcpy(&history[0], &command, sizeof(command));

output = run_command(&scene_state, &command);
ss_clear_script(&scene_state, TEMP_SCRIPT);
ss_overwrite_script_command(&scene_state, TEMP_SCRIPT, 0, &command);
exec_state_t es;
es_init(&es);
es_push(&es);
es_variables(&es)->script_number = TEMP_SCRIPT;

output = run_script_with_exec_state(&scene_state, &es, TEMP_SCRIPT);
}

history_line = -1;
@@ -58,12 +58,14 @@
scene_state_t scene_state;
char scene_text[SCENE_TEXT_LINES][SCENE_TEXT_CHARS];
uint8_t preset_select;
region line[8] = {
{.w = 128, .h = 8, .x = 0, .y = 0 }, {.w = 128, .h = 8, .x = 0, .y = 8 },
{.w = 128, .h = 8, .x = 0, .y = 16 }, {.w = 128, .h = 8, .x = 0, .y = 24 },
{.w = 128, .h = 8, .x = 0, .y = 32 }, {.w = 128, .h = 8, .x = 0, .y = 40 },
{.w = 128, .h = 8, .x = 0, .y = 48 }, {.w = 128, .h = 8, .x = 0, .y = 56 }
};
region line[8] = { { .w = 128, .h = 8, .x = 0, .y = 0 },
{ .w = 128, .h = 8, .x = 0, .y = 8 },
{ .w = 128, .h = 8, .x = 0, .y = 16 },
{ .w = 128, .h = 8, .x = 0, .y = 24 },
{ .w = 128, .h = 8, .x = 0, .y = 32 },
{ .w = 128, .h = 8, .x = 0, .y = 40 },
{ .w = 128, .h = 8, .x = 0, .y = 48 },
{ .w = 128, .h = 8, .x = 0, .y = 56 } };


////////////////////////////////////////////////////////////////////////////////
@@ -90,13 +92,13 @@ static uint8_t front_timer;
static uint8_t mod_key = 0, hold_key, hold_key_count = 0;

// timers
static softTimer_t clockTimer = {.next = NULL, .prev = NULL };
static softTimer_t refreshTimer = {.next = NULL, .prev = NULL };
static softTimer_t keyTimer = {.next = NULL, .prev = NULL };
static softTimer_t cvTimer = {.next = NULL, .prev = NULL };
static softTimer_t adcTimer = {.next = NULL, .prev = NULL };
static softTimer_t hidTimer = {.next = NULL, .prev = NULL };
static softTimer_t metroTimer = {.next = NULL, .prev = NULL };
static softTimer_t clockTimer = { .next = NULL, .prev = NULL };
static softTimer_t refreshTimer = { .next = NULL, .prev = NULL };
static softTimer_t keyTimer = { .next = NULL, .prev = NULL };
static softTimer_t cvTimer = { .next = NULL, .prev = NULL };
static softTimer_t adcTimer = { .next = NULL, .prev = NULL };
static softTimer_t hidTimer = { .next = NULL, .prev = NULL };
static softTimer_t metroTimer = { .next = NULL, .prev = NULL };


////////////////////////////////////////////////////////////////////////////////
@@ -190,32 +192,32 @@ void cvTimer_callback(void* o) {
}

void clockTimer_callback(void* o) {
event_t e = {.type = kEventTimer, .data = 0 };
event_t e = { .type = kEventTimer, .data = 0 };
event_post(&e);
}

void refreshTimer_callback(void* o) {
event_t e = {.type = kEventScreenRefresh, .data = 0 };
event_t e = { .type = kEventScreenRefresh, .data = 0 };
event_post(&e);
}

void keyTimer_callback(void* o) {
event_t e = {.type = kEventKeyTimer, .data = 0 };
event_t e = { .type = kEventKeyTimer, .data = 0 };
event_post(&e);
}

void adcTimer_callback(void* o) {
event_t e = {.type = kEventPollADC, .data = 0 };
event_t e = { .type = kEventPollADC, .data = 0 };
event_post(&e);
}

void hidTimer_callback(void* o) {
event_t e = {.type = kEventHidTimer, .data = 0 };
event_t e = { .type = kEventHidTimer, .data = 0 };
event_post(&e);
}

void metroTimer_callback(void* o) {
event_t e = {.type = kEventAppCustom, .data = 0 };
event_t e = { .type = kEventAppCustom, .data = 0 };
event_post(&e);
}

@@ -536,6 +538,19 @@ bool process_global_keys(uint8_t k, uint8_t m, bool is_held_key) {
set_mode(M_EDIT);
return true;
}
// ctrl-<F1> through ctrl-<F8> mute triggers
// ctrl-<F9> toggle metro
else if (mod_only_ctrl(m) && k >= HID_F1 && k <= HID_F8) {
bool muted = ss_get_mute(&scene_state, (k - HID_F1));
ss_set_mute(&scene_state, (k - HID_F1), !muted);
screen_mutes_updated();
return true;
}
else if (mod_only_ctrl(m) && k == HID_F9) {
scene_state.variables.m_act = !scene_state.variables.m_act;
tele_metro_updated();
return true;
}
// <numpad-1> through <numpad-8>: run corresponding script
else if (no_mod(m) && k >= HID_KEYPAD_1 && k <= HID_KEYPAD_8) {
run_script(&scene_state, k - HID_KEYPAD_1);
@@ -580,7 +595,7 @@ void render_init(void) {
void tele_metro_updated() {
uint32_t metro_time = scene_state.variables.m;

bool m_act = scene_state.variables.m_act > 0;
bool m_act = scene_state.variables.m_act;
if (metro_time < METRO_MIN_UNSUPPORTED_MS) {
metro_time = METRO_MIN_UNSUPPORTED_MS;
}
@@ -658,7 +673,10 @@ void tele_scene(uint8_t i) {
}

void tele_kill() {
for (int i = 0; i < 4; i++) aout[i].step = 1;
for (int i = 0; i < 4; i++) {
aout[i].step = 1;
tele_tr(i, 0);
}
}


@@ -737,6 +755,7 @@ int main(void) {
delay_ms(50);

run_script(&scene_state, INIT_SCRIPT);
scene_state.initializing = false;

while (true) { check_events(); }
}
@@ -23,6 +23,9 @@ static uint8_t base; // base + offset determine what we are editting
static uint8_t offset;

static bool dirty;
static bool editing_number;
static int32_t edit_buffer;
static bool edit_negative;

// teletype_io.h
void tele_pattern_updated() {
@@ -31,11 +34,15 @@ void tele_pattern_updated() {

void set_pattern_mode() {
dirty = true;
editing_number = false;
edit_negative = false;
edit_buffer = 0;
}

void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
// <down>: move down
if (match_no_mod(m, k, HID_DOWN)) {
editing_number = false;
base++;
if (base == 8) {
base = 7;
@@ -45,6 +52,7 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// alt-<down>: move a page down
else if (match_alt(m, k, HID_DOWN)) {
editing_number = false;
if (offset < 48)
offset += 8;
else {
@@ -55,6 +63,7 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// <up>: move up
else if (match_no_mod(m, k, HID_UP)) {
editing_number = false;
if (base)
base--;
else if (offset)
@@ -63,6 +72,7 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// alt-<up>: move a page up
else if (match_alt(m, k, HID_UP)) {
editing_number = false;
if (offset > 8) { offset -= 8; }
else {
offset = 0;
@@ -72,50 +82,84 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// <left>: move left
else if (match_no_mod(m, k, HID_LEFT)) {
editing_number = false;
if (pattern > 0) pattern--;
dirty = true;
}
// alt-<left>: move to the very left
else if (match_alt(m, k, HID_LEFT)) {
editing_number = false;
base = 0;
offset = 0;
dirty = true;
}
// <right>: move right
else if (match_no_mod(m, k, HID_RIGHT)) {
editing_number = false;
if (pattern < 3) pattern++;
dirty = true;
}
// alt-<right>: move to the very right
else if (match_alt(m, k, HID_RIGHT)) {
editing_number = false;
base = 7;
offset = 56;
dirty = true;
}
// [: decrement by 1
else if (match_no_mod(m, k, HID_OPEN_BRACKET)) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, base + offset);
if (v > INT16_MIN) { // -32767
ss_set_pattern_val(&scene_state, pattern, base + offset, v - 1);
if (editing_number) {
if (edit_buffer == INT16_MIN)
edit_buffer = INT16_MAX;
else
edit_buffer -= 1;
dirty = true;
}
else {
int16_t v =
ss_get_pattern_val(&scene_state, pattern, base + offset);
if (v == INT16_MIN)
ss_set_pattern_val(&scene_state, pattern, base + offset,
INT16_MAX);
else
ss_set_pattern_val(&scene_state, pattern, base + offset, v - 1);
dirty = true;
}
}
// ]: increment by 1
else if (match_no_mod(m, k, HID_CLOSE_BRACKET)) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, base + offset);
if (v < INT16_MAX) { // 32766
ss_set_pattern_val(&scene_state, pattern, base + offset, v + 1);
if (editing_number) {
if (edit_buffer == INT16_MAX)
edit_buffer = INT16_MIN;
else
edit_buffer += 1;
dirty = true;
}
else {
int16_t v =
ss_get_pattern_val(&scene_state, pattern, base + offset);
if (v == INT16_MAX)
ss_set_pattern_val(&scene_state, pattern, base + offset,
INT16_MIN);
else
ss_set_pattern_val(&scene_state, pattern, base + offset, v + 1);
dirty = true;
}
}
// <backspace>: delete a digit
else if (match_no_mod(m, k, HID_BACKSPACE)) {
int16_t v =
ss_get_pattern_val(&scene_state, pattern, base + offset) / 10;
ss_set_pattern_val(&scene_state, pattern, base + offset, v);
if (editing_number)
edit_buffer /= 10;
else {
editing_number = true;
edit_buffer =
ss_get_pattern_val(&scene_state, pattern, base + offset) / 10;
}
dirty = true;
}
// shift-<backspace>: delete an entry, shift numbers up
else if (match_shift(m, k, HID_BACKSPACE)) {
editing_number = false;
for (size_t i = base + offset; i < 63; i++) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, i + 1);
ss_set_pattern_val(&scene_state, pattern, i, v);
@@ -125,22 +169,30 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
if (l > base + offset) ss_set_pattern_len(&scene_state, pattern, l - 1);
dirty = true;
}
// <enter>: move down (increase length only if on the entry immediately
// after the current length)
// <enter>: commit edit, extend pattern length
else if (match_no_mod(m, k, HID_ENTER)) {
// commit an edit if active
if (editing_number) {
ss_set_pattern_val(&scene_state, pattern, base + offset,
edit_buffer);
editing_number = false;
edit_negative = false;
}
uint16_t l = ss_get_pattern_len(&scene_state, pattern);
if (base + offset == l && l < 64)
ss_set_pattern_len(&scene_state, pattern, l + 1);
base++;
if (base == 8) {
base = 7;
if (offset < 56) { offset++; }
}
dirty = true;
}
// shift-<enter>: duplicate entry and shift downwards (increase length only
// if on the entry immediately after the current length)
else if (match_shift(m, k, HID_ENTER)) {
// commit an edit before duplication
if (editing_number) {
ss_set_pattern_val(&scene_state, pattern, base + offset,
edit_buffer);
editing_number = false;
edit_negative = false;
}
for (int i = 63; i > base + offset; i--) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, i - 1);
ss_set_pattern_val(&scene_state, pattern, i, v);
@@ -153,6 +205,7 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// alt-x: cut value (n.b. ctrl-x not supported)
else if (match_alt(m, k, HID_X)) {
editing_number = false;
copy_buffer = ss_get_pattern_val(&scene_state, pattern, base + offset);
for (int i = base + offset; i < 63; i++) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, i + 1);
@@ -167,15 +220,21 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// alt-c: copy value (n.b. ctrl-c not supported)
else if (match_alt(m, k, HID_C)) {
copy_buffer = ss_get_pattern_val(&scene_state, pattern, base + offset);
if (editing_number)
copy_buffer = edit_buffer;
else
copy_buffer =
ss_get_pattern_val(&scene_state, pattern, base + offset);
}
// alt-v: paste value (n.b. ctrl-v not supported)
else if (match_alt(m, k, HID_V)) {
editing_number = false;
ss_set_pattern_val(&scene_state, pattern, base + offset, copy_buffer);
dirty = true;
}
// shift-alt-v: insert value
else if (match_shift_alt(m, k, HID_V)) {
editing_number = false;
for (int i = 63; i > base + offset; i--) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, i - 1);
ss_set_pattern_val(&scene_state, pattern, i, v);
@@ -189,11 +248,13 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// shift-l: set length to current position
else if (match_shift(m, k, HID_L)) {
editing_number = false;
ss_set_pattern_len(&scene_state, pattern, base + offset + 1);
dirty = true;
}
// alt-l: go to current length entry
else if (match_alt(m, k, HID_L)) {
editing_number = false;
uint16_t l = ss_get_pattern_len(&scene_state, pattern);
if (l) {
offset = ((l - 1) >> 3) << 3;
@@ -212,11 +273,13 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// shift-s: set start to current position
else if (match_shift(m, k, HID_S)) {
editing_number = false;
ss_set_pattern_start(&scene_state, pattern, offset + base);
dirty = true;
}
// alt-s: go to start entry
else if (match_alt(m, k, HID_S)) {
editing_number = false;
int16_t start = ss_get_pattern_start(&scene_state, pattern);
if (start) {
offset = (start >> 3) << 3;
@@ -235,11 +298,13 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// shift-e: set end to current position
else if (match_shift(m, k, HID_E)) {
editing_number = false;
ss_set_pattern_end(&scene_state, pattern, offset + base);
dirty = true;
}
// alt-e: go to end entry
else if (match_alt(m, k, HID_E)) {
editing_number = false;
int16_t end = ss_get_pattern_end(&scene_state, pattern);
if (end) {
offset = (end >> 3) << 3;
@@ -259,33 +324,62 @@ void process_pattern_keys(uint8_t k, uint8_t m, bool is_held_key) {
// -: negate value
else if (match_no_mod(m, k, HID_UNDERSCORE)) {
int16_t v = ss_get_pattern_val(&scene_state, pattern, base + offset);
ss_set_pattern_val(&scene_state, pattern, base + offset, -v);
if (v == 0 && !editing_number) {
editing_number = true;
edit_buffer = 0;
}
if (editing_number) {
if (edit_buffer == 0)
edit_negative = !edit_negative;
else
edit_buffer *= -1;
}
else {
ss_set_pattern_val(&scene_state, pattern, base + offset, -v);
}
dirty = true;
}
// <space>: toggle non-zero to zero, and zero to 1
else if (match_no_mod(m, k, HID_SPACEBAR)) {
editing_number = false;
if (ss_get_pattern_val(&scene_state, pattern, base + offset))
ss_set_pattern_val(&scene_state, pattern, base + offset, 0);
else
ss_set_pattern_val(&scene_state, pattern, base + offset, 1);
dirty = true;
}
else if (match_shift(m, k, HID_2)) {
turtle_set_shown(&scene_state.turtle,
!turtle_get_shown(&scene_state.turtle));
dirty = true;
}
// 0-9: numeric entry
else if (no_mod(m) && k >= HID_1 && k <= HID_0) {
if (!editing_number) {
editing_number = true;
edit_buffer = 0;
}
uint8_t n = (k - HID_1 + 1) % 10; // convert HID numbers to decimal,
// taking care of HID_0
int16_t v = ss_get_pattern_val(&scene_state, pattern, base + offset);
if (v && v < 3276 && v > -3276) {
v = v * 10;
if (v > 0)
ss_set_pattern_val(&scene_state, pattern, base + offset, v + n);
else
ss_set_pattern_val(&scene_state, pattern, base + offset, v - n);
uint32_t old_buffer = edit_buffer;

edit_buffer *= 10;
if (edit_buffer == 0) { edit_buffer = n; }
else if (edit_buffer < 0) {
edit_buffer -= n;
if (edit_buffer < INT16_MIN) edit_buffer = old_buffer;
}
else {
edit_buffer += n;
if (edit_buffer > INT16_MAX) edit_buffer = old_buffer;
}
if (edit_negative && edit_buffer != 0) {
edit_negative = false;
edit_buffer *= -1;
}
else
ss_set_pattern_val(&scene_state, pattern, base + offset, n);
dirty = true;
}
if (!editing_number) edit_negative = false;
}

void process_pattern_knob(uint16_t knob, uint8_t m) {
@@ -332,9 +426,33 @@ bool screen_refresh_pattern() {
}
}

itoa(ss_get_pattern_val(&scene_state, pattern, base + offset), s, 10);
font_string_region_clip_right(&line[base], s, (pattern + 1) * 30 + 4, 0,
0xf, 0);
if (editing_number) {
font_string_region_clip_right(&line[base], " ",
(pattern + 1) * 30 + 4, 0, 0xf, 0);
if (edit_negative && edit_buffer == 0)
font_string_region_clip_right(&line[base], " -0",
(pattern + 1) * 30 + 4, 0, 0xf, 0);
else {
itoa(edit_buffer, s, 10);
font_string_region_clip_right(&line[base], s,
(pattern + 1) * 30 + 4, 0, 0xf, 0);
}
}
else {
itoa(ss_get_pattern_val(&scene_state, pattern, base + offset), s, 10);
font_string_region_clip_right(&line[base], s, (pattern + 1) * 30 + 4, 0,
0xf, 0);
}

if (scene_state.turtle.shown) {
int16_t y = turtle_get_y(&scene_state.turtle);
int16_t x = turtle_get_x(&scene_state.turtle);
if (y >= offset && y < offset + 8) {
font_string_region_clip_right(&line[y - offset], "<",
(x + 1) * 30 + 9, 0, 0xf, 0);
}
}


for (uint8_t y = 0; y < 64; y += 2) {
line[y >> 3].data[(y & 0x7) * 128 + 8] = 1;
@@ -99,7 +99,9 @@ void do_preset_read() {
flash_update_last_saved_scene(preset_select);
ss_set_scene(&scene_state, preset_select);

scene_state.initializing = true;
run_script(&scene_state, INIT_SCRIPT);
scene_state.initializing = false;

set_last_mode();
}
@@ -84,8 +84,8 @@ void process_preset_w_keys(uint8_t k, uint8_t m, bool is_held_key) {
}
// shift-<enter>: insert text
else if (match_shift(m, k, HID_ENTER)) {
for(uint8_t i = SCENE_TEXT_LINES -1; i > edit_line + edit_offset; i--)
strcpy(scene_text[i], scene_text[i-1]); // overwrites final line!
for (uint8_t i = SCENE_TEXT_LINES - 1; i > edit_line + edit_offset; i--)
strcpy(scene_text[i], scene_text[i - 1]); // overwrites final line!
strcpy(scene_text[edit_line + edit_offset], line_editor_get(&le));
dirty |= D_LIST;
}
@@ -2,11 +2,11 @@
CFLAGS=-std=c99 -g -Wall -fno-common -DSIM -I. -I../src -I../libavr32/src
DEPS =
OBJ = tt.o ../src/teletype.o ../src/command.o ../src/helpers.o \
../src/match_token.o ../src/scanner.o \
../src/state.o ../src/table.o \
../src/every.o ../src/match_token.o ../src/scanner.o \
../src/state.o ../src/table.o ../src/turtle.o \
../src/ops/op.o ../src/ops/ansible.c ../src/ops/controlflow.o \
../src/ops/delay.o ../src/ops/earthsea.o ../src/ops/hardware.o \
../src/ops/justfriends.o ../src/ops/meadowphysics.o \
../src/ops/justfriends.o ../src/ops/meadowphysics.o ../src/ops/turtle.o \
../src/ops/metronome.o ../src/ops/maths.o ../src/ops/orca.o \
../src/ops/patterns.o ../src/ops/queue.o ../src/ops/stack.o \
../src/ops/telex.o ../src/ops/variables.o ../src/ops/whitewhale.c \
@@ -3,7 +3,7 @@

#include <stdint.h>

#define COMMAND_MAX_LENGTH 12
#define COMMAND_MAX_LENGTH 16

typedef enum { NUMBER, OP, MOD, PRE_SEP, SUB_SEP } tele_word_t;

@@ -0,0 +1,24 @@
#include "every.h"

void every_tick(every_count_t *e) {
(e->count)++;
e->count %= e->mod;
}

void every_set_skip(every_count_t *e, bool skip) {
e->skip = skip;
}

void every_set_count(every_count_t *e, int16_t count) {
if (count < 0) count = 0;
e->count = count;
}

void every_set_mod(every_count_t *e, int16_t mod) {
if (mod < 0)
mod = -mod;
else if (mod == 0)
mod = 1; // lazy initialization
e->mod = mod;
e->count %= e->mod;
}
@@ -0,0 +1,19 @@
#ifndef _EVERY_H
#define _EVERY_H

#include <stdbool.h>
#include <stdint.h>

typedef struct {
int16_t count;
int16_t mod;
bool skip;
} every_count_t;


void every_tick(every_count_t*);
void every_set_skip(every_count_t*, bool);
void every_set_count(every_count_t*, int16_t);
void every_set_mod(every_count_t*, int16_t);

#endif
@@ -1,5 +1,5 @@
#include "match_token.h"

#
#include <ctype.h> // isdigit
#include <stdlib.h> // rand, strtol

@@ -37,9 +37,29 @@
"T" => { MATCH_OP(E_OP_T); };
"TIME" => { MATCH_OP(E_OP_TIME); };
"TIME.ACT" => { MATCH_OP(E_OP_TIME_ACT); };
"LAST" => { MATCH_OP(E_OP_LAST); };
"X" => { MATCH_OP(E_OP_X); };
"Y" => { MATCH_OP(E_OP_Y); };
"Z" => { MATCH_OP(E_OP_Z); };

# turtle
"@" => { MATCH_OP(E_OP_TURTLE); };
"@X" => { MATCH_OP(E_OP_TURTLE_X); };
"@Y" => { MATCH_OP(E_OP_TURTLE_Y); };
"@MOVE" => { MATCH_OP(E_OP_TURTLE_MOVE); };
"@F" => { MATCH_OP(E_OP_TURTLE_F); };
"@FX1" => { MATCH_OP(E_OP_TURTLE_FX1); };
"@FY1" => { MATCH_OP(E_OP_TURTLE_FY1); };
"@FX2" => { MATCH_OP(E_OP_TURTLE_FX2); };
"@FY2" => { MATCH_OP(E_OP_TURTLE_FY2); };
"@SPEED" => { MATCH_OP(E_OP_TURTLE_SPEED); };
"@DIR" => { MATCH_OP(E_OP_TURTLE_DIR); };
"@STEP" => { MATCH_OP(E_OP_TURTLE_STEP); };
"@BUMP" => { MATCH_OP(E_OP_TURTLE_BUMP); };
"@WRAP" => { MATCH_OP(E_OP_TURTLE_WRAP); };
"@BOUNCE" => { MATCH_OP(E_OP_TURTLE_BOUNCE); };
"@SCRIPT" => { MATCH_OP(E_OP_TURTLE_SCRIPT); };
"@SHOW" => { MATCH_OP(E_OP_TURTLE_SHOW); };

# metronome
"M" => { MATCH_OP(E_OP_M); };
@@ -133,6 +153,7 @@
"V" => { MATCH_OP(E_OP_V); };
"VV" => { MATCH_OP(E_OP_VV); };
"ER" => { MATCH_OP(E_OP_ER); };
"BPM" => { MATCH_OP(E_OP_BPM);; };
"XOR" => { MATCH_OP(E_OP_XOR); };
"+" => { MATCH_OP(E_OP_SYM_PLUS); };
"-" => { MATCH_OP(E_OP_SYM_DASH); };
@@ -161,6 +182,9 @@
"SCRIPT" => { MATCH_OP(E_OP_SCRIPT); };
"KILL" => { MATCH_OP(E_OP_KILL); };
"SCENE" => { MATCH_OP(E_OP_SCENE); };
"BREAK" => { MATCH_OP(E_OP_BREAK); };
"BRK" => { MATCH_OP(E_OP_BRK); };
"SYNC" => { MATCH_OP(E_OP_SYNC); };

# delay
"DEL.CLR" => { MATCH_OP(E_OP_DEL_CLR); };
@@ -393,6 +417,10 @@
"ELIF" => { MATCH_MOD(E_MOD_ELIF); };
"ELSE" => { MATCH_MOD(E_MOD_ELSE); };
"L" => { MATCH_MOD(E_MOD_L); };
"W" => { MATCH_MOD(E_MOD_W); };
"EVERY" => { MATCH_MOD(E_MOD_EVERY); };
"SKIP" => { MATCH_MOD(E_MOD_SKIP); };
"OTHER" => { MATCH_MOD(E_MOD_OTHER); };

# delay
"PROB" => { MATCH_MOD(E_MOD_PROB); };
@@ -20,27 +20,52 @@ static void mod_ELSE_func(scene_state_t *ss, exec_state_t *es,
const tele_command_t *post_command);
static void mod_L_func(scene_state_t *ss, exec_state_t *es, command_state_t *cs,
const tele_command_t *post_command);
static void mod_W_func(scene_state_t *ss, exec_state_t *es, command_state_t *cs,
const tele_command_t *post_command);
static void mod_EVERY_func(scene_state_t *ss, exec_state_t *es,
command_state_t *cs,
const tele_command_t *post_command);
static void mod_SKIP_func(scene_state_t *ss, exec_state_t *es,
command_state_t *cs,
const tele_command_t *post_command);
static void mod_OTHER_func(scene_state_t *ss, exec_state_t *es,
command_state_t *cs,
const tele_command_t *post_command);

static void op_SCENE_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_SCENE_set(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_SCRIPT_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_SCRIPT_set(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_KILL_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_BREAK_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_SYNC_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);


const tele_mod_t mod_PROB = MAKE_MOD(PROB, mod_PROB_func, 1);
const tele_mod_t mod_IF = MAKE_MOD(IF, mod_IF_func, 1);
const tele_mod_t mod_ELIF = MAKE_MOD(ELIF, mod_ELIF_func, 1);
const tele_mod_t mod_ELSE = MAKE_MOD(ELSE, mod_ELSE_func, 0);
const tele_mod_t mod_L = MAKE_MOD(L, mod_L_func, 2);
const tele_mod_t mod_W = MAKE_MOD(W, mod_W_func, 1);
const tele_mod_t mod_EVERY = MAKE_MOD(EVERY, mod_EVERY_func, 1);
const tele_mod_t mod_SKIP = MAKE_MOD(SKIP, mod_SKIP_func, 1);
const tele_mod_t mod_OTHER = MAKE_MOD(OTHER, mod_OTHER_func, 0);

const tele_op_t op_SCRIPT = MAKE_GET_OP(SCRIPT, op_SCRIPT_get, 1, false);
const tele_op_t op_SCRIPT =
MAKE_GET_SET_OP(SCRIPT, op_SCRIPT_get, op_SCRIPT_set, 0, true);
const tele_op_t op_KILL = MAKE_GET_OP(KILL, op_KILL_get, 0, false);
const tele_op_t op_SCENE =
MAKE_GET_SET_OP(SCENE, op_SCENE_get, op_SCENE_set, 0, true);
const tele_op_t op_BREAK = MAKE_GET_OP(BREAK, op_BREAK_get, 0, false);
const tele_op_t op_BRK = MAKE_ALIAS_OP(BRK, op_BREAK_get, NULL, 0, false);
const tele_op_t op_SYNC = MAKE_GET_OP(SYNC, op_SYNC_get, 1, false);


static void mod_PROB_func(scene_state_t *ss, exec_state_t *es,
@@ -56,9 +81,9 @@ static void mod_IF_func(scene_state_t *ss, exec_state_t *es,
const tele_command_t *post_command) {
int16_t a = cs_pop(cs);

es->if_else_condition = false;
es_variables(es)->if_else_condition = false;
if (a) {
es->if_else_condition = true;
es_variables(es)->if_else_condition = true;
process_command(ss, es, post_command);
}
}
@@ -68,9 +93,9 @@ static void mod_ELIF_func(scene_state_t *ss, exec_state_t *es,
const tele_command_t *post_command) {
int16_t a = cs_pop(cs);

if (!es->if_else_condition) {
if (!es_variables(es)->if_else_condition) {
if (a) {
es->if_else_condition = true;
es_variables(es)->if_else_condition = true;
process_command(ss, es, post_command);
}
}
@@ -79,8 +104,8 @@ static void mod_ELIF_func(scene_state_t *ss, exec_state_t *es,
static void mod_ELSE_func(scene_state_t *ss, exec_state_t *es,
command_state_t *NOTUSED(cs),
const tele_command_t *post_command) {
if (!es->if_else_condition) {
es->if_else_condition = true;
if (!es_variables(es)->if_else_condition) {
es_variables(es)->if_else_condition = true;
process_command(ss, es, post_command);
}
}
@@ -89,12 +114,84 @@ static void mod_L_func(scene_state_t *ss, exec_state_t *es, command_state_t *cs,
const tele_command_t *post_command) {
int16_t a = cs_pop(cs);
int16_t b = cs_pop(cs);
int16_t loop_size = a < b ? b - a : a - b;

for (int16_t i = 0; i <= loop_size; i++) {
ss->variables.i = a < b ? a + i : a - i;
// using a pointer means that the loop contents can a interact with the
// iterator, allowing users to roll back a loop or advance it faster
int16_t *i = &es_variables(es)->i;

// Forward loop
if (a < b) {
// continue the loop whenever the _pointed-to_ I meets the condition
// this means that I can be interacted with inside the loop command
for (*i = a; *i <= b; (*i)++) {
// the increment statement has careful syntax, because the
// ++ operator has precedence over the dereference * operator
process_command(ss, es, post_command);
if (es_variables(es)->breaking) break;
}

if (!es_variables(es)->breaking)
(*i)--; // past end of loop, leave I in the correct state
}
// Reverse loop (also works for equal values (either loop would))
else {
for (*i = a; *i >= b && !es_variables(es)->breaking; (*i)--)
process_command(ss, es, post_command);
if (!es_variables(es)->breaking) (*i)++;
}
}

static void mod_W_func(scene_state_t *ss, exec_state_t *es, command_state_t *cs,
const tele_command_t *post_command) {
int16_t a = cs_pop(cs);
if (a) {
process_command(ss, es, post_command);
es_variables(es)->while_depth++;
if (es_variables(es)->while_depth < WHILE_DEPTH)
es_variables(es)->while_continue = true;
else
es_variables(es)->while_continue = false;
}
else
es_variables(es)->while_continue = false;
}

static void mod_EVERY_func(scene_state_t *ss, exec_state_t *es,
command_state_t *cs,
const tele_command_t *post_command) {
int16_t mod = cs_pop(cs);
every_count_t *every = ss_get_every(ss, es_variables(es)->script_number,
es_variables(es)->line_number);
every_set_skip(every, false);
every_set_mod(every, mod);
every_tick(every);
if (every_is_now(ss, every)) process_command(ss, es, post_command);
}

static void mod_SKIP_func(scene_state_t *ss, exec_state_t *es,
command_state_t *cs,
const tele_command_t *post_command) {
int16_t mod = cs_pop(cs);
every_count_t *every = ss_get_every(ss, es_variables(es)->script_number,
es_variables(es)->line_number);
every_set_skip(every, true);
every_set_mod(every, mod);
every_tick(every);
if (skip_is_now(ss, every)) process_command(ss, es, post_command);
}

static void mod_OTHER_func(scene_state_t *ss, exec_state_t *es,
command_state_t *NOTUSED(cs),
const tele_command_t *post_command) {
if (!ss->every_last) process_command(ss, es, post_command);
}


static void op_SYNC_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t count = cs_pop(cs);
ss->every_last = false;
ss_sync_every(ss, count);
}

static void op_SCENE_get(const void *NOTUSED(data), scene_state_t *ss,
@@ -105,21 +202,45 @@ static void op_SCENE_get(const void *NOTUSED(data), scene_state_t *ss,
static void op_SCENE_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t scene = cs_pop(cs);
ss->variables.scene = scene;
tele_scene(scene);
if (!ss->initializing) {
ss->variables.scene = scene;
tele_scene(scene);
}
}

static void op_SCRIPT_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *es, command_state_t *cs) {
int16_t sn = es_variables(es)->script_number + 1;
if (sn == 11) sn = 0;
cs_push(cs, sn);
}

static void op_SCRIPT_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *es, command_state_t *cs) {
uint16_t a = cs_pop(cs) - 1;
if (a >= SCRIPT_COUNT || a == INIT_SCRIPT || a == METRO_SCRIPT) return;
if (a > TT_SCRIPT_8 || a < TT_SCRIPT_1) return;

run_script_with_exec_state(ss, es, a);
es_push(es);
// an overflow causes all future SCRIPT calls to fail
// indicates a bad user script
if (!es->overflow) run_script_with_exec_state(ss, es, a);
es_pop(es);
}

static void op_KILL_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *NOTUSED(cs)) {
// clear stack
ss->stack_op.top = 0;
tele_has_stack(false);
// disable metronome
ss->variables.m_act = 0;
tele_metro_updated();
clear_delays(ss);
tele_kill();
}

static void op_BREAK_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
exec_state_t *es, command_state_t *NOTUSED(cs)) {
es_variables(es)->breaking = true;
}
@@ -8,10 +8,17 @@ extern const tele_mod_t mod_IF;
extern const tele_mod_t mod_ELIF;
extern const tele_mod_t mod_ELSE;
extern const tele_mod_t mod_L;
extern const tele_mod_t mod_W;
extern const tele_mod_t mod_EVERY;
extern const tele_mod_t mod_SKIP;
extern const tele_mod_t mod_OTHER;

extern const tele_op_t op_SCRIPT;
extern const tele_op_t op_KILL;
extern const tele_op_t op_SCENE;
extern const tele_op_t op_BREAK;
extern const tele_op_t op_BRK;
extern const tele_op_t op_SYNC;


#endif
@@ -15,7 +15,7 @@ static void op_DEL_CLR_get(const void *data, scene_state_t *ss,
const tele_mod_t mod_DEL = MAKE_MOD(DEL, mod_DEL_func, 1);
const tele_op_t op_DEL_CLR = MAKE_GET_OP(DEL.CLR, op_DEL_CLR_get, 0, false);

static void mod_DEL_func(scene_state_t *ss, exec_state_t *NOTUSED(es),
static void mod_DEL_func(scene_state_t *ss, exec_state_t *es,
command_state_t *cs,
const tele_command_t *post_command) {
int16_t i = 0;
@@ -29,10 +29,11 @@ static void mod_DEL_func(scene_state_t *ss, exec_state_t *NOTUSED(es),

if (i < DELAY_SIZE) {
ss->delay.count++;
tele_has_delays(ss->delay.count > 0);
ss->delay.time[i] = a;
ss->delay.origin[i] = es_variables(es)->script_number;

copy_command(&ss->delay.commands[i], post_command);
tele_has_delays(ss->delay.count > 0);
}
}

@@ -74,6 +74,8 @@ static void op_VV_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_ER_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_BPM_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);


// clang-format off
@@ -111,6 +113,7 @@ const tele_op_t op_N = MAKE_GET_OP(N , op_N_get , 1, true);
const tele_op_t op_V = MAKE_GET_OP(V , op_V_get , 1, true);
const tele_op_t op_VV = MAKE_GET_OP(VV , op_VV_get , 1, true);
const tele_op_t op_ER = MAKE_GET_OP(ER , op_ER_get , 3, true);
const tele_op_t op_BPM = MAKE_GET_OP(BPM , op_BPM_get , 1, true);

const tele_op_t op_XOR = MAKE_ALIAS_OP(XOR, op_NE_get, NULL, 2, true);

@@ -277,7 +280,9 @@ static void op_QT_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),

static void op_AVG_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, (cs_pop(cs) + cs_pop(cs)) >> 1);
int32_t ret = (((int32_t)cs_pop(cs) * 2) + ((int32_t)cs_pop(cs) * 2)) / 2;
if (ret % 2) ret += 1;
cs_push(cs, (int16_t)(ret / 2));
}

static void op_EQ_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
@@ -454,3 +459,13 @@ static void op_ER_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
int16_t step = cs_pop(cs);
cs_push(cs, euclidean(fill, len, step));
}

static void op_BPM_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t a = cs_pop(cs);
uint32_t ret;
if (a < 2) a = 2;
if (a > 1000) a = 1000;
ret = ((((uint32_t)(1 << 31)) / ((a << 20) / 60)) * 1000) >> 11;
cs_push(cs, (int16_t)ret);
}
@@ -37,6 +37,7 @@ extern const tele_op_t op_N;
extern const tele_op_t op_V;
extern const tele_op_t op_VV;
extern const tele_op_t op_ER;
extern const tele_op_t op_BPM;

extern const tele_op_t op_XOR; // XOR alias NE

@@ -19,6 +19,7 @@
#include "ops/queue.h"
#include "ops/stack.h"
#include "ops/telex.h"
#include "ops/turtle.h"
#include "ops/variables.h"
#include "ops/whitewhale.h"

@@ -32,7 +33,13 @@ const tele_op_t *tele_ops[E_OP__LENGTH] = {
// variables
&op_A, &op_B, &op_C, &op_D, &op_DRUNK, &op_DRUNK_MAX, &op_DRUNK_MIN,
&op_DRUNK_WRAP, &op_FLIP, &op_I, &op_O, &op_O_INC, &op_O_MAX, &op_O_MIN,
&op_O_WRAP, &op_T, &op_TIME, &op_TIME_ACT, &op_X, &op_Y, &op_Z,
&op_O_WRAP, &op_T, &op_TIME, &op_TIME_ACT, &op_LAST, &op_X, &op_Y, &op_Z,

// turtle
&op_TURTLE, &op_TURTLE_X, &op_TURTLE_Y, &op_TURTLE_MOVE, &op_TURTLE_F,
&op_TURTLE_FX1, &op_TURTLE_FY1, &op_TURTLE_FX2, &op_TURTLE_FY2,
&op_TURTLE_SPEED, &op_TURTLE_DIR, &op_TURTLE_STEP, &op_TURTLE_BUMP,
&op_TURTLE_WRAP, &op_TURTLE_BOUNCE, &op_TURTLE_SCRIPT, &op_TURTLE_SHOW,

// metronome
&op_M, &op_M_SYM_EXCLAMATION, &op_M_ACT, &op_M_RESET,
@@ -57,17 +64,18 @@ const tele_op_t *tele_ops[E_OP__LENGTH] = {
&op_MIN, &op_MAX, &op_LIM, &op_WRAP, &op_QT, &op_AVG, &op_EQ, &op_NE,
&op_LT, &op_GT, &op_LTE, &op_GTE, &op_NZ, &op_EZ, &op_RSH, &op_LSH, &op_EXP,
&op_ABS, &op_AND, &op_OR, &op_JI, &op_SCALE, &op_N, &op_V, &op_VV, &op_ER,
&op_XOR, &op_SYM_PLUS, &op_SYM_DASH, &op_SYM_STAR, &op_SYM_FORWARD_SLASH,
&op_SYM_PERCENTAGE, &op_SYM_EQUAL_x2, &op_SYM_EXCLAMATION_EQUAL,
&op_SYM_LEFT_ANGLED, &op_SYM_RIGHT_ANGLED, &op_SYM_LEFT_ANGLED_EQUAL,
&op_SYM_RIGHT_ANGLED_EQUAL, &op_SYM_EXCLAMATION, &op_SYM_LEFT_ANGLED_x2,
&op_SYM_RIGHT_ANGLED_x2, &op_SYM_AMPERSAND_x2, &op_SYM_PIPE_x2,
&op_BPM, &op_XOR, &op_SYM_PLUS, &op_SYM_DASH, &op_SYM_STAR,
&op_SYM_FORWARD_SLASH, &op_SYM_PERCENTAGE, &op_SYM_EQUAL_x2,
&op_SYM_EXCLAMATION_EQUAL, &op_SYM_LEFT_ANGLED, &op_SYM_RIGHT_ANGLED,
&op_SYM_LEFT_ANGLED_EQUAL, &op_SYM_RIGHT_ANGLED_EQUAL, &op_SYM_EXCLAMATION,
&op_SYM_LEFT_ANGLED_x2, &op_SYM_RIGHT_ANGLED_x2, &op_SYM_AMPERSAND_x2,
&op_SYM_PIPE_x2,

// stack
&op_S_ALL, &op_S_POP, &op_S_CLR, &op_S_L,

// controlflow
&op_SCRIPT, &op_KILL, &op_SCENE,
&op_SCRIPT, &op_KILL, &op_SCENE, &op_BREAK, &op_BRK, &op_SYNC,

// delay
&op_DEL_CLR,
@@ -150,7 +158,8 @@ const tele_op_t *tele_ops[E_OP__LENGTH] = {

const tele_mod_t *tele_mods[E_MOD__LENGTH] = {
// controlflow
&mod_IF, &mod_ELIF, &mod_ELSE, &mod_L, &mod_PROB,
&mod_IF, &mod_ELIF, &mod_ELSE, &mod_L, &mod_W, &mod_EVERY, &mod_SKIP,
&mod_OTHER, &mod_PROB,

// delay
&mod_DEL,
@@ -24,9 +24,27 @@ typedef enum {
E_OP_T,
E_OP_TIME,
E_OP_TIME_ACT,
E_OP_LAST,
E_OP_X,
E_OP_Y,
E_OP_Z,
E_OP_TURTLE,
E_OP_TURTLE_X,
E_OP_TURTLE_Y,
E_OP_TURTLE_MOVE,
E_OP_TURTLE_F,
E_OP_TURTLE_FX1,
E_OP_TURTLE_FY1,
E_OP_TURTLE_FX2,
E_OP_TURTLE_FY2,
E_OP_TURTLE_SPEED,
E_OP_TURTLE_DIR,
E_OP_TURTLE_STEP,
E_OP_TURTLE_BUMP,
E_OP_TURTLE_WRAP,
E_OP_TURTLE_BOUNCE,
E_OP_TURTLE_SCRIPT,
E_OP_TURTLE_SHOW,
E_OP_M,
E_OP_M_SYM_EXCLAMATION,
E_OP_M_ACT,
@@ -110,6 +128,7 @@ typedef enum {
E_OP_V,
E_OP_VV,
E_OP_ER,
E_OP_BPM,
E_OP_XOR,
E_OP_SYM_PLUS,
E_OP_SYM_DASH,
@@ -134,6 +153,9 @@ typedef enum {
E_OP_SCRIPT,
E_OP_KILL,
E_OP_SCENE,
E_OP_BREAK,
E_OP_BRK,
E_OP_SYNC,
E_OP_DEL_CLR,
E_OP_WW_PRESET,
E_OP_WW_POS,
@@ -339,6 +361,10 @@ typedef enum {
E_MOD_ELIF,
E_MOD_ELSE,
E_MOD_L,
E_MOD_W,
E_MOD_EVERY,
E_MOD_SKIP,
E_MOD_OTHER,
E_MOD_PROB,
E_MOD_DEL,
E_MOD_S,
@@ -576,7 +576,7 @@ static void op_P_RM_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t pn = ss->variables.p_n;
int16_t a = cs_pop(cs);
cs_push(cs, p_rm_get(ss, pn, a));
cs_push(cs, p_rm_get(ss, pn, a - 1)); // a is 1-indexed
tele_pattern_updated();
}

@@ -36,13 +36,17 @@ static void op_Q_set(const void *NOTUSED(data), scene_state_t *ss,

static void op_Q_AVG_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t avg = 0;
int32_t avg = 0;
int16_t *q = ss->variables.q;
int16_t q_n = ss->variables.q_n;
for (int16_t i = 0; i < q_n; i++) { avg += q[i]; }

int16_t out = q_n != 0 ? avg / q_n : 0;
cs_push(cs, out);
if (q_n == 0)
cs_push(cs, 0);
else {
for (int16_t i = 0; i < q_n; i++) { avg += q[i]; }
avg = (avg * 2) / q_n;
if (avg % 2) avg += 1;
cs_push(cs, (int16_t)(avg / 2));
}
}

static void op_Q_AVG_set(const void *NOTUSED(data), scene_state_t *ss,
@@ -0,0 +1,306 @@
#include "ops/turtle.h"
#include "helpers.h"

#include "ops/op.h"
#include "state.h"
#include "teletype.h"
#include "teletype_io.h"
#include "turtle.h"

static void op_TURTLE_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_TURTLE_set(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_TURTLE_X_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_X_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_Y_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_Y_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_MOVE_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_F_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FX1_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FX1_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FY1_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FY1_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FX2_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FX2_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FY2_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_FY2_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_SPEED_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_SPEED_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_DIR_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_DIR_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_STEP_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_BUMP_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_BUMP_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_WRAP_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_WRAP_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_BOUNCE_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_BOUNCE_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_SCRIPT_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_SCRIPT_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_SHOW_set(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);
static void op_TURTLE_SHOW_get(const void *data, scene_state_t *ss,
exec_state_t *es, command_state_t *cs);

const tele_op_t op_TURTLE =
MAKE_GET_SET_OP(@, op_TURTLE_get, op_TURTLE_set, 0, true);
const tele_op_t op_TURTLE_X =
MAKE_GET_SET_OP(@X, op_TURTLE_X_get, op_TURTLE_X_set, 0, true);
const tele_op_t op_TURTLE_Y =
MAKE_GET_SET_OP(@Y, op_TURTLE_Y_get, op_TURTLE_Y_set, 0, true);
const tele_op_t op_TURTLE_MOVE =
MAKE_GET_OP(@MOVE, op_TURTLE_MOVE_get, 2, false);
const tele_op_t op_TURTLE_F = MAKE_GET_OP(@F, op_TURTLE_F_get, 4, false);
const tele_op_t op_TURTLE_FX1 =
MAKE_GET_SET_OP(@FX1, op_TURTLE_FX1_get, op_TURTLE_FX1_set, 0, true);
const tele_op_t op_TURTLE_FY1 =
MAKE_GET_SET_OP(@FY1, op_TURTLE_FY1_get, op_TURTLE_FY1_set, 0, true);
const tele_op_t op_TURTLE_FX2 =
MAKE_GET_SET_OP(@FX2, op_TURTLE_FX2_get, op_TURTLE_FX2_set, 0, true);
const tele_op_t op_TURTLE_FY2 =
MAKE_GET_SET_OP(@FY2, op_TURTLE_FY2_get, op_TURTLE_FY2_set, 0, true);
const tele_op_t op_TURTLE_SPEED =
MAKE_GET_SET_OP(@SPEED, op_TURTLE_SPEED_get, op_TURTLE_SPEED_set, 0, true);
const tele_op_t op_TURTLE_DIR =
MAKE_GET_SET_OP(@DIR, op_TURTLE_DIR_get, op_TURTLE_DIR_set, 0, true);
const tele_op_t op_TURTLE_STEP =
MAKE_GET_OP(@STEP, op_TURTLE_STEP_get, 0, false);
const tele_op_t op_TURTLE_BUMP =
MAKE_GET_SET_OP(@BUMP, op_TURTLE_BUMP_get, op_TURTLE_BUMP_set, 0, true);
const tele_op_t op_TURTLE_WRAP =
MAKE_GET_SET_OP(@WRAP, op_TURTLE_WRAP_get, op_TURTLE_WRAP_set, 0, true);
const tele_op_t op_TURTLE_BOUNCE = MAKE_GET_SET_OP(
@BOUNCE, op_TURTLE_BOUNCE_get, op_TURTLE_BOUNCE_set, 0, true);
const tele_op_t op_TURTLE_SCRIPT = MAKE_GET_SET_OP(
@SCRIPT, op_TURTLE_SCRIPT_get, op_TURTLE_SCRIPT_set, 0, true);
const tele_op_t op_TURTLE_SHOW =
MAKE_GET_SET_OP(@SHOW, op_TURTLE_SHOW_get, op_TURTLE_SHOW_set, 0, true);

static void op_TURTLE_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss_turtle_get_val(ss, &ss->turtle));
}

static void op_TURTLE_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
ss_turtle_set_val(ss, &ss->turtle, cs_pop(cs));
tele_pattern_updated();
}

static void op_TURTLE_X_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, turtle_get_x(&ss->turtle));
}

static void op_TURTLE_X_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
turtle_set_x(&ss->turtle, cs_pop(cs));
tele_pattern_updated();
}

static void op_TURTLE_Y_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, turtle_get_y(&ss->turtle));
}

static void op_TURTLE_Y_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
turtle_set_y(&ss->turtle, cs_pop(cs));
tele_pattern_updated();
}

static void op_TURTLE_MOVE_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t x = cs_pop(cs);
int16_t y = cs_pop(cs);
turtle_move(&ss->turtle, x, y);
tele_pattern_updated();
}

static void op_TURTLE_F_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t x1 = cs_pop(cs);
int16_t y1 = cs_pop(cs);
int16_t x2 = cs_pop(cs);
int16_t y2 = cs_pop(cs);

turtle_set_fence(&ss->turtle, x1, y1, x2, y2);
tele_pattern_updated();
}

static void op_TURTLE_FX1_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss->turtle.fence.x1);
}

static void op_TURTLE_FX1_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t v = cs_pop(cs);
ss->turtle.fence.x1 = v > 0 ? v : 0;
turtle_correct_fence(&ss->turtle);
tele_pattern_updated();
}

static void op_TURTLE_FY1_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss->turtle.fence.y1);
}

static void op_TURTLE_FY1_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t v = cs_pop(cs);
ss->turtle.fence.y1 = v > 0 ? v : 0;
turtle_correct_fence(&ss->turtle);
tele_pattern_updated();
}

static void op_TURTLE_FX2_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss->turtle.fence.x2);
}

static void op_TURTLE_FX2_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t v = cs_pop(cs);
ss->turtle.fence.x2 = v > 0 ? v : 0;
turtle_correct_fence(&ss->turtle);
tele_pattern_updated();
}

static void op_TURTLE_FY2_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss->turtle.fence.y2);
}

static void op_TURTLE_FY2_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t v = cs_pop(cs);
ss->turtle.fence.y2 = v > 0 ? v : 0;
turtle_correct_fence(&ss->turtle);
tele_pattern_updated();
}

static void op_TURTLE_SPEED_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *cs) {
cs_push(cs, turtle_get_speed(&ss->turtle));
}

static void op_TURTLE_SPEED_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *cs) {
turtle_set_speed(&ss->turtle, cs_pop(cs));
}

static void op_TURTLE_DIR_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, turtle_get_heading(&ss->turtle));
}

static void op_TURTLE_DIR_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
turtle_set_heading(&ss->turtle, cs_pop(cs));
}

static void op_TURTLE_STEP_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *NOTUSED(cs)) {
turtle_step(&ss->turtle);
tele_pattern_updated();
}

static void op_TURTLE_BUMP_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss->turtle.mode == TURTLE_BUMP);
}

static void op_TURTLE_BUMP_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
if (cs_pop(cs)) turtle_set_mode(&ss->turtle, TURTLE_BUMP);
tele_pattern_updated();
}

static void op_TURTLE_WRAP_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, ss->turtle.mode == TURTLE_WRAP);
}

static void op_TURTLE_WRAP_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
if (cs_pop(cs)) turtle_set_mode(&ss->turtle, TURTLE_WRAP);
tele_pattern_updated();
}

static void op_TURTLE_BOUNCE_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *cs) {
cs_push(cs, ss->turtle.mode == TURTLE_BOUNCE);
}

static void op_TURTLE_BOUNCE_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *cs) {
if (cs_pop(cs)) turtle_set_mode(&ss->turtle, TURTLE_BOUNCE);
tele_pattern_updated();
}

static void op_TURTLE_SCRIPT_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *cs) {
script_number_t s = turtle_get_script(&ss->turtle);
if (s == TEMP_SCRIPT)
cs_push(cs, 0);
else
cs_push(cs, turtle_get_script(&ss->turtle) + 1);
}

static void op_TURTLE_SCRIPT_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es),
command_state_t *cs) {
int16_t sn = cs_pop(cs);
if (sn == 0)
turtle_set_script(&ss->turtle, TEMP_SCRIPT); // magic number
else
turtle_set_script(&ss->turtle, sn - 1);
}

static void op_TURTLE_SHOW_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
cs_push(cs, turtle_get_shown(&ss->turtle) ? 1 : 0);
tele_pattern_updated();
}

static void op_TURTLE_SHOW_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t shown = cs_pop(cs);
turtle_set_shown(&ss->turtle, shown != 0);
tele_pattern_updated();
}
@@ -0,0 +1,26 @@
#ifndef _OPS_TURTLE_H
#define _OPS_TURTLE_H

#include "ops/op.h"

extern const tele_op_t op_TURTLE;
extern const tele_op_t op_TURTLE_X;
extern const tele_op_t op_TURTLE_Y;
extern const tele_op_t op_TURTLE_MOVE;
extern const tele_op_t op_TURTLE_F;
extern const tele_op_t op_TURTLE_FX1;
extern const tele_op_t op_TURTLE_FY1;
extern const tele_op_t op_TURTLE_FX2;
extern const tele_op_t op_TURTLE_FY2;
extern const tele_op_t op_TURTLE_SPEED;
extern const tele_op_t op_TURTLE_DIR;
extern const tele_op_t op_TURTLE_FRICTION;
extern const tele_op_t op_TURTLE_ACCEL;
extern const tele_op_t op_TURTLE_STEP;
extern const tele_op_t op_TURTLE_BUMP;
extern const tele_op_t op_TURTLE_WRAP;
extern const tele_op_t op_TURTLE_BOUNCE;
extern const tele_op_t op_TURTLE_SCRIPT;
extern const tele_op_t op_TURTLE_SHOW;

#endif
@@ -4,8 +4,11 @@

#include "helpers.h"
#include "ops/op.h"
#include "teletype.h"


static void op_LAST_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_DRUNK_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_DRUNK_set(const void *data, scene_state_t *ss, exec_state_t *es,
@@ -18,6 +21,10 @@ static void op_O_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_O_set(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_I_get(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);
static void op_I_set(const void *data, scene_state_t *ss, exec_state_t *es,
command_state_t *cs);

// clang-format off
const tele_op_t op_A = MAKE_SIMPLE_VARIABLE_OP(A , variables.a );
@@ -27,23 +34,30 @@ const tele_op_t op_D = MAKE_SIMPLE_VARIABLE_OP(D , variables.d
const tele_op_t op_DRUNK_MAX = MAKE_SIMPLE_VARIABLE_OP(DRUNK.MAX , variables.drunk_max );
const tele_op_t op_DRUNK_MIN = MAKE_SIMPLE_VARIABLE_OP(DRUNK.MIN , variables.drunk_min );
const tele_op_t op_DRUNK_WRAP = MAKE_SIMPLE_VARIABLE_OP(DRUNK.WRAP, variables.drunk_wrap);
const tele_op_t op_I = MAKE_SIMPLE_VARIABLE_OP(I , variables.i );
const tele_op_t op_O_INC = MAKE_SIMPLE_VARIABLE_OP(O.INC , variables.o_inc );
const tele_op_t op_O_MAX = MAKE_SIMPLE_VARIABLE_OP(O.MAX , variables.o_max );
const tele_op_t op_O_MIN = MAKE_SIMPLE_VARIABLE_OP(O.MIN , variables.o_min );
const tele_op_t op_O_WRAP = MAKE_SIMPLE_VARIABLE_OP(O.WRAP , variables.o_wrap );
const tele_op_t op_T = MAKE_SIMPLE_VARIABLE_OP(T , variables.t );
const tele_op_t op_TIME = MAKE_SIMPLE_VARIABLE_OP(TIME , variables.time );
const tele_op_t op_TIME_ACT = MAKE_SIMPLE_VARIABLE_OP(TIME.ACT , variables.time_act );
const tele_op_t op_LAST = MAKE_GET_OP(LAST , op_LAST_get, 1, true);
const tele_op_t op_X = MAKE_SIMPLE_VARIABLE_OP(X , variables.x );
const tele_op_t op_Y = MAKE_SIMPLE_VARIABLE_OP(Y , variables.y );
const tele_op_t op_Z = MAKE_SIMPLE_VARIABLE_OP(Z , variables.z );

const tele_op_t op_DRUNK = MAKE_GET_SET_OP(DRUNK, op_DRUNK_get, op_DRUNK_set, 0, true);
const tele_op_t op_FLIP = MAKE_GET_SET_OP(FLIP , op_FLIP_get , op_FLIP_set , 0, true);
const tele_op_t op_O = MAKE_GET_SET_OP(O , op_O_get , op_O_set , 0, true);
const tele_op_t op_I = MAKE_GET_SET_OP(I , op_I_get, op_I_set, 0, true);
// clang-format on

static void op_LAST_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
int16_t script_number = cs_pop(cs) - 1;
int16_t last = ss_get_script_last(ss, script_number);
cs_push(cs, last);
}

static void op_DRUNK_get(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
@@ -97,3 +111,13 @@ static void op_O_set(const void *NOTUSED(data), scene_state_t *ss,
exec_state_t *NOTUSED(es), command_state_t *cs) {
ss->variables.o = cs_pop(cs);
}

static void op_I_get(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
exec_state_t *es, command_state_t *cs) {
cs_push(cs, es_variables(es)->i);
}

static void op_I_set(const void *NOTUSED(data), scene_state_t *NOTUSED(ss),
exec_state_t *es, command_state_t *cs) {
es_variables(es)->i = cs_pop(cs);
}
@@ -21,6 +21,7 @@ extern const tele_op_t op_O_WRAP;
extern const tele_op_t op_T;
extern const tele_op_t op_TIME;
extern const tele_op_t op_TIME_ACT;
extern const tele_op_t op_LAST;
extern const tele_op_t op_X;
extern const tele_op_t op_Y;
extern const tele_op_t op_Z;
@@ -4,24 +4,24 @@

#include "teletype_io.h"


////////////////////////////////////////////////////////////////////////////////
// SCENE STATE /////////////////////////////////////////////////////////////////

// scene init

void ss_init(scene_state_t *ss) {
ss->initializing = true;
ss_variables_init(ss);
ss_patterns_init(ss);
ss->delay.count = 0;
for (size_t i = 0; i < TR_COUNT; i++) { ss->tr_pulse_timer[i] = 0; }
ss->stack_op.top = 0;
memset(&ss->scripts, 0, ss_scripts_size());
turtle_init(&ss->turtle);
}

void ss_variables_init(scene_state_t *ss) {
const scene_variables_t default_variables = {
// variables that haven't been explicitly initialised, will be set to 0
// TODO: verify no missing
.a = 1,
.b = 2,
.c = 3,
@@ -75,7 +75,7 @@ void ss_set_scene(scene_state_t *ss, int16_t value) {
}

// mutes

// TODO: size_t SHOULD be a script_number_t
bool ss_get_mute(scene_state_t *ss, size_t idx) {
return ss->variables.mutes[idx];
}
@@ -146,29 +146,50 @@ size_t ss_patterns_size() {

// script manipulation

uint8_t ss_get_script_len(scene_state_t *ss, size_t idx) {
uint8_t ss_get_script_len(scene_state_t *ss, script_number_t idx) {
return ss->scripts[idx].l;
}

// private
static void ss_set_script_len(scene_state_t *ss, size_t idx, uint8_t l) {
static void ss_set_script_len(scene_state_t *ss, script_number_t idx,
uint8_t l) {
ss->scripts[idx].l = l;
}

const tele_command_t *ss_get_script_command(scene_state_t *ss,
size_t script_idx, size_t c_idx) {
script_number_t script_idx,
size_t c_idx) {
return &ss->scripts[script_idx].c[c_idx];
}

// private
static void ss_set_script_command(scene_state_t *ss, size_t script_idx,
static void ss_set_script_command(scene_state_t *ss, script_number_t script_idx,
size_t c_idx, const tele_command_t *cmd) {
memcpy(&ss->scripts[script_idx].c[c_idx], cmd, sizeof(tele_command_t));
}

void ss_overwrite_script_command(scene_state_t *ss, size_t script_idx,
bool ss_get_script_comment(scene_state_t *ss, script_number_t script_idx,
size_t c_idx) {
return ss->scripts[script_idx].comment[c_idx];
}

void ss_toggle_script_comment(scene_state_t *ss, script_number_t script_idx,
size_t c_idx) {
ss->scripts[script_idx].comment[c_idx] =
!ss->scripts[script_idx].comment[c_idx];
}

void ss_overwrite_script_command(scene_state_t *ss, script_number_t script_idx,
size_t command_idx,
const tele_command_t *cmd) {
// Few of the commands in this file bounds-check.
// Are we trusting calling code in this file or not?
// If so, why here? If not, we need much more bounds-checking
// If we start running up against processor limits, we should not
// Well-validated upstream code doesn't _need_ bounds-checking here IMO
// -- burnsauce (sliderule)

// TODO: why check upper bound here but not lower?
if (command_idx >= SCRIPT_MAX_COMMANDS) return;

ss_set_script_command(ss, script_idx, command_idx, cmd);
@@ -180,7 +201,7 @@ void ss_overwrite_script_command(scene_state_t *ss, size_t script_idx,
}
}

void ss_insert_script_command(scene_state_t *ss, size_t script_idx,
void ss_insert_script_command(scene_state_t *ss, script_number_t script_idx,
size_t command_idx, const tele_command_t *cmd) {
if (command_idx >= SCRIPT_MAX_COMMANDS) return;

@@ -204,7 +225,7 @@ void ss_insert_script_command(scene_state_t *ss, size_t script_idx,
ss_overwrite_script_command(ss, script_idx, command_idx, cmd);
}

void ss_delete_script_command(scene_state_t *ss, size_t script_idx,
void ss_delete_script_command(scene_state_t *ss, script_number_t script_idx,
size_t command_idx) {
if (command_idx >= SCRIPT_MAX_COMMANDS) return;

@@ -226,6 +247,10 @@ void ss_delete_script_command(scene_state_t *ss, size_t script_idx,
}
}

void ss_clear_script(scene_state_t *ss, size_t script_idx) {
ss_set_script_len(ss, script_idx, 0);
}

scene_script_t *ss_scripts_ptr(scene_state_t *ss) {
return ss->scripts;
}
@@ -234,15 +259,121 @@ size_t ss_scripts_size() {
return sizeof(scene_script_t) * SCRIPT_COUNT;
}

int16_t ss_get_script_last(scene_state_t *ss, script_number_t idx) {
int16_t now = ss->variables.time;
if (idx < TT_SCRIPT_1) return 0;
if (idx > INIT_SCRIPT) return 0;
int16_t last = ss->scripts[idx].last_time;
if (now < last)
return (INT16_MAX - last) + (now - INT16_MIN); // I must be dense?
return now - last;
}

void ss_update_script_last(scene_state_t *ss, script_number_t idx) {
ss->scripts[idx].last_time = ss->variables.time;
}

every_count_t *ss_get_every(scene_state_t *ss, script_number_t idx,
uint8_t line_number) {
return &ss->scripts[idx].every[line_number];
}

void ss_sync_every(scene_state_t *ss, int16_t count) {
for (int script = 0; script < SCRIPT_COUNT; script++)
for (int line = 0; line < SCRIPT_MAX_COMMANDS; line++) {
int16_t count_e = count;
if (ss->scripts[script].every[line].mod == 0)
ss->scripts[script].every[line].mod = 1; // lazy init
while (count_e < 0) count_e += ss->scripts[script].every[line].mod;
ss->scripts[script].every[line].count = count_e;
}
}

bool every_is_now(scene_state_t *ss, every_count_t *e) {
ss->every_last = e->count == 0;
return e->count == 0;
}

bool skip_is_now(scene_state_t *ss, every_count_t *e) {
ss->every_last = e->count != 0;
return e->count != 0;
}


int16_t ss_turtle_get_val(scene_state_t *ss, scene_turtle_t *st) {
turtle_position_t p;
turtle_resolve_position(st, &st->position, &p);
if (p.x > 3 || p.x < 0 || p.y > 63 || p.y < 0) return 0;
return ss_get_pattern_val(ss, p.x, p.y);
}

void ss_turtle_set_val(scene_state_t *ss, scene_turtle_t *st, int16_t val) {
turtle_position_t p;
turtle_resolve_position(st, &st->position, &p);
if (p.x > 3 || p.x < 0 || p.y > 63 || p.y < 0) return;
ss_set_pattern_val(ss, p.x, p.y, val);
}

void ss_turtle_set(scene_state_t *ss, scene_turtle_t *ts) {
// TODO validate the turtle?
ss->turtle = *ts; // structs shallow copy with value assignment
}


scene_turtle_t *ss_turtle_get(scene_state_t *ss) {
return &ss->turtle;
}

////////////////////////////////////////////////////////////////////////////////
// EXEC STATE //////////////////////////////////////////////////////////////////

void es_init(exec_state_t *es) {
es->if_else_condition = true;
es->exec_depth = 0;
es->overflow = false;
}

size_t es_depth(exec_state_t *es) {
return es->exec_depth;
}

size_t es_push(exec_state_t *es) {
// I'd cache es->variables[es->exec_depth] as an optimization,
// but the compiler will probably do it for me?
if (es->exec_depth < EXEC_DEPTH) {
es->variables[es->exec_depth].delayed = false;
es->variables[es->exec_depth].while_depth = 0;
es->variables[es->exec_depth].while_continue = false;
es->variables[es->exec_depth].if_else_condition = true;
es->variables[es->exec_depth].i = 0;
es->variables[es->exec_depth].breaking = false;
es->exec_depth += 1; // exec_depth = 1 at the root
}
else
es->overflow = true;
return es->exec_depth;
}

size_t es_pop(exec_state_t *es) {
if (es->exec_depth > 0) es->exec_depth -= 1;
return es->exec_depth;
}

void es_set_script_number(exec_state_t *es, uint8_t script_number) {
if (!es_variables(es)->delayed)
es_variables(es)->script_number = script_number;
}

void es_set_line_number(exec_state_t *es, uint8_t line_number) {
es_variables(es)->line_number = line_number;
}

uint8_t es_get_line_number(exec_state_t *es) {
return es_variables(es)->line_number;
}

exec_vars_t *es_variables(exec_state_t *es) {
return &es->variables[es->exec_depth - 1]; // but array is 0-indexed
}

////////////////////////////////////////////////////////////////////////////////
// COMMAND STATE ///////////////////////////////////////////////////////////////
@@ -6,21 +6,22 @@
#include <stdint.h>

#include "command.h"
#include "every.h"
#include "turtle.h"

#define STACK_SIZE 8
#define CV_COUNT 4
#define Q_LENGTH 16
#define Q_LENGTH 64
#define TR_COUNT 4
#define TRIGGER_INPUTS 8
#define DELAY_SIZE 8
#define STACK_OP_SIZE 8
#define STACK_OP_SIZE 16
#define PATTERN_COUNT 4
#define PATTERN_LENGTH 64
#define SCRIPT_MAX_COMMANDS 6
#define SCRIPT_COUNT 10

#define METRO_SCRIPT 8
#define INIT_SCRIPT 9
#define SCRIPT_COUNT 11
#define EXEC_DEPTH 8
#define WHILE_DEPTH 10000

#define METRO_MIN_MS 25
#define METRO_MIN_UNSUPPORTED_MS 2
@@ -42,7 +43,6 @@ typedef struct {
int16_t drunk_min;
int16_t drunk_wrap;
int16_t flip;
int16_t i;
int16_t in;
int16_t m;
bool m_act;
@@ -78,8 +78,10 @@ typedef struct {
} scene_pattern_t;

typedef struct {
// TODO add a delay variables struct?
tele_command_t commands[DELAY_SIZE];
int16_t time[DELAY_SIZE];
uint8_t origin[DELAY_SIZE];
uint8_t count;
} scene_delay_t;

@@ -91,15 +93,21 @@ typedef struct {
typedef struct {
uint8_t l;
tele_command_t c[SCRIPT_MAX_COMMANDS];
bool comment[SCRIPT_MAX_COMMANDS];
every_count_t every[SCRIPT_MAX_COMMANDS];
int16_t last_time;
} scene_script_t;

typedef struct {
bool initializing;
scene_variables_t variables;
scene_pattern_t patterns[PATTERN_COUNT];
scene_delay_t delay;
scene_stack_op_t stack_op;
int16_t tr_pulse_timer[TR_COUNT];
scene_script_t scripts[SCRIPT_COUNT];
scene_turtle_t turtle;
bool every_last;
} scene_state_t;

extern void ss_init(scene_state_t *ss);
@@ -133,29 +141,65 @@ extern void ss_set_pattern_val(scene_state_t *ss, size_t pattern, size_t idx,
extern scene_pattern_t *ss_patterns_ptr(scene_state_t *ss);
extern size_t ss_patterns_size(void);

uint8_t ss_get_script_len(scene_state_t *ss, size_t idx);
uint8_t ss_get_script_len(scene_state_t *ss, script_number_t idx);
const tele_command_t *ss_get_script_command(scene_state_t *ss,
size_t script_idx, size_t c_idx);
void ss_overwrite_script_command(scene_state_t *ss, size_t script_idx,
script_number_t script_idx,
size_t c_idx);
bool ss_get_script_comment(scene_state_t *ss, script_number_t script_idx,
size_t c_idx);
void ss_toggle_script_comment(scene_state_t *ss, script_number_t script_idx,
size_t c_idx);
void ss_overwrite_script_command(scene_state_t *ss, script_number_t script_idx,
size_t command_idx, const tele_command_t *cmd);
void ss_insert_script_command(scene_state_t *ss, size_t script_idx,
void ss_insert_script_command(scene_state_t *ss, script_number_t script_idx,
size_t command_idx, const tele_command_t *cmd);
void ss_delete_script_command(scene_state_t *ss, size_t script_idx,
void ss_delete_script_command(scene_state_t *ss, script_number_t script_idx,
size_t command_idx);
void ss_clear_script(scene_state_t *ss, size_t script_idx);

scene_script_t *ss_scripts_ptr(scene_state_t *ss);
size_t ss_scripts_size(void);
int16_t ss_get_script_last(scene_state_t *ss, script_number_t idx);
void ss_update_script_last(scene_state_t *ss, script_number_t idx);
every_count_t *ss_get_every(scene_state_t *ss, script_number_t idx,
uint8_t line);
void ss_sync_every(scene_state_t *ss, int16_t count);
bool every_is_now(scene_state_t *ss, every_count_t *e);
bool skip_is_now(scene_state_t *ss, every_count_t *e);
scene_turtle_t *ss_turtle_get(scene_state_t *);
void ss_turtle_set(scene_state_t *, scene_turtle_t *);
int16_t ss_turtle_get_val(scene_state_t *, scene_turtle_t *);
void ss_turtle_set_val(scene_state_t *, scene_turtle_t *, int16_t);

////////////////////////////////////////////////////////////////////////////////
// EXEC STATE //////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

typedef struct {
bool if_else_condition;
int16_t i;
bool while_continue;
uint16_t while_depth;
bool breaking;
script_number_t script_number;
uint8_t line_number;
bool delayed;
} exec_vars_t;

typedef struct {
exec_vars_t variables[EXEC_DEPTH];
uint8_t exec_depth;
bool overflow;
} exec_state_t;

extern void es_init(exec_state_t *es);
extern size_t es_depth(exec_state_t *es);
extern size_t es_push(exec_state_t *es);
extern size_t es_pop(exec_state_t *es);
extern void es_set_script_number(exec_state_t *es, uint8_t script_number);
extern void es_set_line_number(exec_state_t *es, uint8_t line_number);
extern uint8_t es_get_line_number(exec_state_t *es);
extern exec_vars_t *es_variables(exec_state_t *es);

////////////////////////////////////////////////////////////////////////////////
// COMMAND STATE ///////////////////////////////////////////////////////////////
@@ -166,7 +210,9 @@ typedef struct {
int16_t top;
} command_state_stack_t;

typedef struct { command_state_stack_t stack; } command_state_t;
typedef struct {
command_state_stack_t stack;
} command_state_t;

extern void cs_init(command_state_t *cs);
extern int16_t cs_stack_size(command_state_t *cs);
@@ -12,6 +12,8 @@
#include "util.h"


bool processing_delays = false;

/////////////////////////////////////////////////////////////////
// DELAY ////////////////////////////////////////////////////////

@@ -134,35 +136,55 @@ error_t validate(const tele_command_t *c,
process_result_t run_script(scene_state_t *ss, size_t script_no) {
exec_state_t es;
es_init(&es);
es_push(&es);
return run_script_with_exec_state(ss, &es, script_no);
}

// Everything needs to call this to execute code. An execution
// context is required for proper operation of DEL, THIS, L, W, IF
process_result_t run_script_with_exec_state(scene_state_t *ss, exec_state_t *es,
size_t script_no) {
process_result_t result = {.has_value = false, .value = 0 };
process_result_t result = { .has_value = false, .value = 0 };

// increase the execution depth on each call (e.g. from SCRIPT)
es->exec_depth++;
// only allow the depth to reach 8
// (if we want to allow this number to be any bigger we really should
// convert this recursive call to use some sort of trampoline!)
if (es->exec_depth > 8) { return result; }
es_set_script_number(es, script_no);

for (size_t i = 0; i < ss_get_script_len(ss, script_no); i++) {
result =
process_command(ss, es, ss_get_script_command(ss, script_no, i));
es_set_line_number(es, i);

// Commented code doesn't run.
if (ss_get_script_comment(ss, script_no, i)) continue;

// BREAK implemented with break...
if (es_variables(es)->breaking) break;
do {
// TODO: Check for 0-length commands before we bother?
result = process_command(ss, es,
ss_get_script_command(ss, script_no, i));
// and WHILE implemented with while!
} while (es_variables(es)->while_continue &&
!es_variables(es)->breaking);
}

// decrease the depth once the commands have been run
es->exec_depth--;

es_variables(es)->breaking = false;
ss_update_script_last(ss, script_no);
return result;
}

// Only the test framework should call this, and it needs to follow up its
// es_init() with an es_push().
// es_variables()->script_number should be set to test SCRIPT
process_result_t run_command(scene_state_t *ss, const tele_command_t *cmd) {
exec_state_t es;
process_result_t o;
es_init(&es);
return process_command(ss, &es, cmd);
es_push(&es);
// the lack of a script number here is a bug, so if you use this code,
// something needs to set the script number
// es_variables(es)->script_number =
do {
o = process_command(ss, &es, cmd);
} while (es_variables(&es)->while_continue && !es_variables(&es)->breaking);
return o;
}


@@ -215,7 +237,8 @@ process_result_t process_command(scene_state_t *ss, exec_state_t *es,
// 3. Loop through each sub command and execute it
// -----------------------------------------------
// iterate through sub commands from left to right
for (ssize_t sub_idx = 0; sub_idx < sub_len; sub_idx++) {
for (ssize_t sub_idx = 0; sub_idx < sub_len && !es_variables(es)->breaking;
sub_idx++) {
const ssize_t sub_start = subs[sub_idx].start;
const ssize_t sub_end = subs[sub_idx].end;

@@ -254,11 +277,11 @@ process_result_t process_command(scene_state_t *ss, exec_state_t *es,
// ---------
// sometimes we have single value left of the stack, if so return it
if (cs_stack_size(&cs)) {
process_result_t o = {.has_value = true, .value = cs_pop(&cs) };
process_result_t o = { .has_value = true, .value = cs_pop(&cs) };
return o;
}
else {
process_result_t o = {.has_value = false, .value = 0 };
process_result_t o = { .has_value = false, .value = 0 };
return o;
}
}
@@ -268,9 +291,17 @@ process_result_t process_command(scene_state_t *ss, exec_state_t *es,
// TICK /////////////////////////////////////////////////////////

void tele_tick(scene_state_t *ss, uint8_t time) {
// inc time
// time is the basic resolution of all code henceforth called
// hardware 2.0: get an RTC!
if (ss->variables.time_act) ss->variables.time += time;

// could be a while() if there is reason to expect a user to cascade moves
// with SCRIPTs without the tick delay
if (ss->turtle.stepped && ss->turtle.script_number != TEMP_SCRIPT) {
ss->turtle.stepped = false;
run_script(ss, turtle_get_script(&ss->turtle));
}

// process delays
for (int16_t i = 0; i < DELAY_SIZE; i++) {
if (ss->delay.time[i]) {
@@ -279,9 +310,32 @@ void tele_tick(scene_state_t *ss, uint8_t time) {
// Workaround for issue #80. (0 is the signifier for "empty")
// Setting delay.time[i] to 1 prevents delayed delay commands
// from seeing a perfectly-timed delay slot as empty
// while it's still being processed.
// while it's still being processed.
ss->delay.time[i] = 1;
run_command(ss, &ss->delay.commands[i]);

// Instead of just running the command, we use the TEMP script
// to execute it. This is required for THIS to be tracked, as
// it needs to have a script number.
// TODO: dynamically allocate scripts to prevent waste
ss_clear_script(ss, TEMP_SCRIPT);
ss_overwrite_script_command(ss, TEMP_SCRIPT, 0,
&ss->delay.commands[i]);

// We always need to execute from within an execution context
// TODO: ensure all code does so!
// New execution context setup needs to es_push, but it's
// decoupled to allow SCRIPT to work
exec_state_t es;
es_init(&es);
es_push(&es);

// The delay flag is required to protect the script number
// TODO: investigate delayed nested SCRIPTs
es_variables(&es)->delayed = true;
es_variables(&es)->script_number = ss->delay.origin[i];

run_script_with_exec_state(ss, &es, TEMP_SCRIPT);

ss->delay.time[i] = 0;
ss->delay.count--;
if (ss->delay.count == 0) tele_has_delays(false);
@@ -8,7 +8,7 @@
#include "command.h"
#include "state.h"

#define TELETYPE_VERSION "TELETYPE 2.0.1"
#define TELETYPE_VERSION "TELETYPE 2.1.0"
#define TELE_ERROR_MSG_LENGTH 16

typedef enum {