From ad358cdb203ac741b438e39026c70579fabf79ec Mon Sep 17 00:00:00 2001 From: Poindexter Frink Date: Thu, 12 Oct 2017 17:22:02 -0400 Subject: [PATCH] 2.1.0 Release (#114) * Fixes #35 by guarding SCENE from initial I script Added an initialization flag to the scene state to denote when a scene is first loading. SCENE operator aborts if the flag is present. * Fixes #63 by overhauling data entry in tracker Buffered input Clear-on-new-entry Apply-on-Enter Abort-on-navigation Incrementers wrap around 16-bit limits Number entry blocks if it would exceed limits * New Op: MSPB (milliseconds per beat) * Fixes #14 by allowing code commenting with alt-/ * New Op: LAST - delta between script run times Now tracking script number in the execution state and script last run times in the script state. One line tap tempo (hit F1 twice): 1: M LAST M: TR.P 1 * Fixes #38 by making AVG round UP AVG -32767 -32768 == -32767 AVG 32767 32766 == 32767 * Fixes #38 by making Q.AVG round UP * New Op: BREAK (alias BRK): Terminates a script * New Op: W (while loop) Maximum depth set by WHILE_DEPTH (default 10000) * Bugfix for W op: while counter for each exec_depth SCRIPT was breaking W. Now it doesn't. * Added documentation for W op * Added docs for BREAK op * Added docs for LAST op * Updated keystroke documentation * Updated keystroke documentation * Updated SCENE documentation to reflect I behaviour * Fixes #94 by tracking execution states by depth If conditions and for loop iterators do not transcend their execution depth. Live mode now behaves like this: > L 1 4: A I > A => 4 > I => 0 I is 0 because it exists in a different context than the first command. * Bugfix: fixes recursion crash As a result, the code is cleaner and easier to read. Short story: only SCRIPT should call es_push and es_pop * Formatting mistake * Renamed MSPB to BPM * Fixes #2 by correcting fencepost error in P.RM * Teletype 2.1 beta 1 build * Poor merging discipline problems fixed PEBCAK / ID 10 T error * Added built zip. * Fixes M in beta1 * Changed LAST, added THIS, fixed behaviour * Added beta2 build * Code cleanup, commenting * Half-fixes operator table in documentation * Beta2 tag in Makefile * Forced L to respect BREAK's authority * Fixes #93 by clearing TRs in tele_kill * Added turtle structures and function prototypes * Added turtle function bodies, updated prototypes. * Added Turtle operators to turtle.c * Finished up operators, needs testing. * Fixed WRAP and BUMP turtle behaviour and various other things that needed fixing. * Preparing for beta3 release * Released beta3 * A turtle that works and is partially tested Fixed WRAP behaviour Changed UP to N, etc. Added FX1, FY1, FX2, FY2 individual get/set for fence Added HX, HY for home get/set Added tests for fence and WRAP behaviour (more to come) * Test revealed bug with new operator * Fixed test framework and some bugs with turtle * Test naming fix * Fixed bugs with WRAP mode * Prevent a crash with bounds checking * Fixed confusion between Q15 and Q12 in sin call * Updated makefile for beta4 release * Added beta4 build * Added some tests, fixed others * Fixed divide by zero in Q.AVG * Modified operators to match approved set * Fixed a test * Added es_push() after es_init() * Cleaned up test file. * Implemented @SCRIPT * Updated to approved syntax and began testing * Moved turtle stuff out of state.h/c into new files * Committed to sin() logic for all calculations * Added release files for beta5 * Fixed various turtle bugs * Added final beta5 build * Trigger Travis build * Added turtle object files * Ninja patch to beta5 CTRL-F numbers now enable disable scripts CTRL-F9 toggles the metro KILL updated * Adjusted travis build to detect test errors * Updated changelog * Updated broken tests to reflect bounce behaviour Also to test travis-ci * Fixed travis-test Makefile deps * Added a few tests and squashed the sin() bug Should really have tested it this way sooner. * Removed hex file from repo. * Beta 6 prep * Removed hex file. * Implemented @SHOW @SHOW 1 now produces an @ sign beside the current turtle cell in pattern (tracker) mode. * Temp script no longer accessible via [ and ] * Changed THIS -> SCRIPT getter Now instead of SCRIPT THIS for recursion, SCRIPT SCRIPT. Per @tehn. * Fixes fix of [ and ] accessing temp script. A shameful commit. * Changed Turtle @SHOW symbol to < * Changed COMMAND_MAX_LENGTH to 16 * Changed STACK_OP_SIZE to 16 * Changed Q_LENGTH to 64 * Updated CHANGELOG * Fixed [ still showing temp script The second such shameful commit. * SCRIPT now returns 0 in live mode * Added more 2.1 documentation - Turtle operator - Minor other fixes * Changed @BOUNCE and @BUMP behaviour at fence @BUMP won't actually change behaviour, but its internal fraction will remain at a 0.5 offset. * Fixed @BOUNCE where x2 = x1 + 1 * Fixed test case * Added Shift-2 = Toggle @SHOW in TRACKER Plus a little refactor of access to turtle.shown * Reverted to git dirty flag for release * Initial implementation of EVERY * Fixed simulator Makefile for EVERY * Added SKIP, OTHER, and SYNC * Changed OTHER to reflect any last EVERY or SKIP OTHER now makes sense at the top of the script. * Cleared the every_last flag on SYNC * Tracker entry mode changes Enter no longer moves down a cell Increment and decrement no longer exceed int16 limits * Unified in/decrement between edit and nav modes Now wraps around limits instead of bumping * Updated CHANGELOG for 2.1.0-rc1 * Removed references to @ACCEL and @FRICT * Fixed formatting for 2.1 release * Included EVERY in changelog * Removed old, commented-out code. * Updated help mode for 2.1 * Fixed line overflow in @F doc line * Fixed formatting One more pass at make format * Changed sin() to _sin() to avoid warning Not like we were linking against libmath but GCC won't shut up. * Changed documentation header for Turtle removed word 'operator'. * Added some missing documentation @STEP, BRK were missing TR.P alias was broken * Fixed ss_clear_script() I must have been out of it when I wrote that code! --- .travis.yml | 2 +- CHANGELOG.md | 23 ++- docs/intro.md | 2 +- docs/keys.md | 12 +- docs/ops/controlflow.toml | 105 +++++++++++-- docs/ops/hardware.toml | 2 +- docs/ops/maths.toml | 4 + docs/ops/turtle.md | 4 + docs/ops/turtle.toml | 81 ++++++++++ docs/ops/variables.toml | 15 ++ docs/quickstart.md | 2 +- docs/whats_new.md | 55 +++++++ module/config.mk | 7 +- module/edit_mode.c | 21 ++- module/edit_mode.h | 1 + module/help_mode.c | 76 +++++++--- module/live_mode.c | 9 +- module/main.c | 61 +++++--- module/pattern_mode.c | 178 ++++++++++++++++++---- module/preset_r_mode.c | 2 + module/preset_w_mode.c | 4 +- simulator/Makefile | 6 +- src/command.h | 2 +- src/every.c | 24 +++ src/every.h | 19 +++ src/match_token.rl | 30 +++- src/ops/controlflow.c | 149 +++++++++++++++++-- src/ops/controlflow.h | 7 + src/ops/delay.c | 5 +- src/ops/maths.c | 17 ++- src/ops/maths.h | 1 + src/ops/op.c | 25 +++- src/ops/op_enum.h | 26 ++++ src/ops/patterns.c | 2 +- src/ops/queue.c | 14 +- src/ops/turtle.c | 306 ++++++++++++++++++++++++++++++++++++++ src/ops/turtle.h | 26 ++++ src/ops/variables.c | 26 +++- src/ops/variables.h | 1 + src/state.c | 155 +++++++++++++++++-- src/state.h | 72 +++++++-- src/teletype.c | 92 +++++++++--- src/teletype.h | 2 +- src/turtle.c | 281 ++++++++++++++++++++++++++++++++++ src/turtle.h | 99 ++++++++++++ tests/Makefile | 10 +- tests/log.c | 95 ++++++++++++ tests/log.h | 22 +++ tests/main.c | 2 + tests/op_mod_tests.c | 10 +- tests/process_tests.c | 9 +- tests/turtle_tests.c | 302 +++++++++++++++++++++++++++++++++++++ tests/turtle_tests.h | 8 + utils/common/__init__.py | 19 ++- utils/docs.py | 7 +- 55 files changed, 2339 insertions(+), 198 deletions(-) create mode 100644 docs/ops/turtle.md create mode 100644 docs/ops/turtle.toml create mode 100644 src/every.c create mode 100644 src/every.h create mode 100644 src/ops/turtle.c create mode 100644 src/ops/turtle.h create mode 100644 src/turtle.c create mode 100644 src/turtle.h create mode 100644 tests/log.c create mode 100644 tests/log.h create mode 100644 tests/turtle_tests.c create mode 100644 tests/turtle_tests.h diff --git a/.travis.yml b/.travis.yml index 002204e9..ef6968c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,4 @@ script: - cd ../simulator - make clean && make - cd ../tests - - make clean && make test + - make clean && make test-travis diff --git a/CHANGELOG.md b/CHANGELOG.md index bef175af..4ff5ca3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,28 @@ # Changelog -## v2.0.1 +## V2.1 +- **BREAKING**: the `I` variable is now scoped to the `L` loop, and does not exist outside of an execution context. Scripts using `I` as a general-purpose variable will be broken. +- **FIX**: `SCENE` will not run from `INIT` script during scene load. +- **NEW**: Tracker data entry overhaul. Type numbers, press enter to commit. +- **NEW**: new op: `BPM` to get milliseconds per beat in given BPM +- **NEW**: script lines can be disabled / enabled with ctrl-/ +- **NEW**: shift-enter in scene write mode now inserts a line +- **NEW**: new ops: `LAST x` for the last time script `x` was called +- **NEW**: `SCRIPT` with no arguments gets the current script number. +- **FIX**: `AVG` and `Q.AVG` now round up properly +- **NEW**: new op: `BREAK` to stop the remainder of the script +- **NEW**: new mod: `W [condition]: [statement]` will execute `statement` as long as `condition` is true (up to an iteration limit). +- **NEW**: new mods: `EVERY x:`, `SKIP x:`, `OTHER:` to alternately execute or not execute a command. +- **NEW**: new op: `SYNC x` will synchronize all `EVERY` and `SKIP` line to the same step. +- **NEW**: new feature: @ - the turtle. Walks around the pattern grid. Many ops, see documentation. +- **OLD**: ctrl-F1 to F8 mute/unmute scripts. +- **NEW**: ctrl-F9 enables/disables METRO. +- **FIX**: recursive delay fix. Now you can `1: DEL 500: SCRIPT 1` for temporal recursion. +- **FIX**: KILL now clears TR output as well as disabling the METRO script. +- **FIX**: if / else conditions no longer transcend their script +- **IMP**: functional exectuion stack for `SCRIPT` operations +## v2.0.1 - **FIX**: update IRQ masking which prevents screen glitches and crashing under heavy load ## v2.0 diff --git a/docs/intro.md b/docs/intro.md index 30159259..8cc1fcde 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -8,7 +8,7 @@ Teletype is a dynamic, musical event triggering platform. — [PDF scene recall sheet](https://monome.org/docs/modular/teletype/TT_scene_RECALL_sheet.pdf) — [Default scenes](http://monome.org/docs/modular/teletype/scenes-1.0/) -* Current version: 2.0.1 +* Current version: 2.1.0 — [Firmware update procedure](https://monome.org/docs/modular/update/) diff --git a/docs/keys.md b/docs/keys.md index 2fb71cf1..15f621d4 100644 --- a/docs/keys.md +++ b/docs/keys.md @@ -17,6 +17,8 @@ These bindings work everywhere. | `alt-` to `alt-` | edit corresponding script | | `alt-` | edit metro script | | `alt-` | edit init script | +| `ctrl-` to `ctrl-` | mute/unmute corresponding script | +| `ctrl-` | enable/disable metro script | | `` to `` | run corresponding script | | `` / `` | jump to pattern mode | | `` / `` | jump to live mode | @@ -61,13 +63,14 @@ In most cases, the clipboard is shared between _live_, _edit_ and the 2 _preset_ | `]` | next script | | `` | enter command | | `shift-` | insert command | +| `alt-/` | toggle line comment | ## Tracker mode The tracker mode clipboard is independent of text and code clipboard. -| Key | Action | -|---------------------|-------------------------------------------------| +| Key | Action | +|---------------------|---------------------------------------------------------------------------------------| | `` | move down | | `alt-` | move a page down | | `` | move up | @@ -80,8 +83,8 @@ The tracker mode clipboard is independent of text and code clipboard. | `]` | increment by 1 | | `` | delete a digit | | `shift-` | delete an entry, shift numbers up | -| `` | move down (increase length only if on the entry immediately after the current length) | -| `shift-` | duplicate entry and shift downwards (increase length as ``) | +| `` | commit edit (increase length if cursor in position after last entry) | +| `shift-` | commit edit, then duplicate entry and shift downwards (increase length as ``) | | `alt-x` | cut value (n.b. `ctrl-x` not supported) | | `alt-c` | copy value (n.b. `ctrl-c` not supported) | | `alt-v` | paste value (n.b. `ctrl-v` not supported) | @@ -95,6 +98,7 @@ The tracker mode clipboard is independent of text and code clipboard. | `-` | negate value | | `` | toggle non-zero to zero, and zero to 1 | | `0` to `9` | numeric entry | +| `shift-2` (`@`) | toggle turtle display marker (`<`) | ## Preset read mode diff --git a/docs/ops/controlflow.toml b/docs/ops/controlflow.toml index 2ae75407..12d5ad0c 100644 --- a/docs/ops/controlflow.toml +++ b/docs/ops/controlflow.toml @@ -41,17 +41,6 @@ If `x` is not zero execute command (see example 2) ``` - 5. Dependent scripts - - ```text - SCRIPT 1: - IF 0: TR.P 1 => do nothing - SCRIPT 2 => will pulse output 2 - - SCRIPT 2: - ELSE: TR.P 2 => will not pulse output 2 if called directly, - but will if called from script 1 - ``` """ [ELIF] @@ -76,13 +65,90 @@ L 4 1: TR.PULSE I => pulse outputs 4, 3, 2 and 1 ``` """ +[W] +prototype = "W x: ..." +short = "run the command while condition x is true" +description = """ +Runs the command while the condition `x` is true or the loop iterations exceed 10000. + +For example, to find the first iterated power of 2 greater than 100: + +``` +A 2 +W LT A 100: A * A A +``` + +A will be 256. +""" + +[EVERY] +prototype = "EVERY x: ..." +short = "run the command every `x` times the command is called" +description = """ +Runs the command every `x` times the line is executed. This is tracked on a per-line basis, so each script can have 6 different "dividers". + +Here is a 1-script clock divider: + +``` +EVERY 2: TR.P 1 +EVERY 4: TR.P 2 +EVERY 8: TR.P 3 +EVERY 16: TR.P 4 +``` + +The numbers do _not_ need to be evenly divisible by each other, so there is no problem with: + +``` +EVERY 2: TR.P 1 +EVERY 3: TR.P 2 +``` +""" + +[SKIP] +prototype = "SKIP x: ..." +short = "run the command every time except the `x`th time." +description = """ +This is the corollary function to `EVERY`, essentially behaving as its exact opposite. +""" + + +[OTHER] +prototype = "OTHER: ..." +short = "runs the command when the previous `EVERY`/`SKIP` did not run its command." +description = """ +`OTHER` can be used to do somthing alternately with a preceding `EVERY` or `SKIP` command. + +For example, here is a script that alternates between two triggers to make a four-on-the-floor beat with hats between the beats: + +``` +EVERY 4: TR.P 1 +OTHER: TR.P 2 +``` + +You could add snares on beats 2 and 4 with: + +``` +SKIP 2: TR.P 3 +``` +""" + +[SYNC] +prototype = "SYNC x" +short = "synchronizes _all_ `EVERY` and `SKIP` counters to offset `x`." +description = """ +Causes all of the `EVERY` and `SYNC` counters to synchronize their offsets, respecting their individual divisor values. + +Negative numbers will synchronize to to the divisor value, such that `SYNC -1` causes all every counters to be 1 number before their divisor, causing each `EVERY` to be true on its next call, and each `SKIP` to be false. +""" + [PROB] prototype = "PROB x: ..." short = "potentially execute command with probability `x` (0-100)" [SCRIPT] -prototype = "SCRIPT x" -short = "execute script `x` (1-8), recursion allowed" +prototype = "SCRIPT" +prototype_set = "SCRIPT x" +short = "get current script number, or execute script `x` (1-8), recursion allowed" description = """ Execute script `x` (1-8), recursion allowed. @@ -90,16 +156,23 @@ There is a limit of 8 for the maximum number of nested calls to `SCRIPT` to stop """ [SCENE] -prototype = "SCENE x" -short = "load scene `x` (0-31)" +prototype = "SCENE" +prototype_set = "SCENE x" +short = "get the current scene number, or load scene `x` (0-31)" description = """ Load scene `x` (0-31). Does _not_ execute the `I` script. +Will _not_ execute from the `I` script on scene load. Will execute on subsequent calls to the `I` script. **WARNING**: You will lose any unsaved changes to your scene. """ [KILL] prototype = "KILL" -short = "clears stack, clears delays, cancels pulses, cancels slews" \ No newline at end of file +short = "clears stack, clears delays, cancels pulses, cancels slews, disables metronome" + +[BREAK] +prototype = "BREAK" +aliases = ["BRK"] +short = "halts execution of the current script" diff --git a/docs/ops/hardware.toml b/docs/ops/hardware.toml index ebd2c783..5828b8cf 100644 --- a/docs/ops/hardware.toml +++ b/docs/ops/hardware.toml @@ -85,7 +85,7 @@ Flip the state of trigger output `x`. ["TR.PULSE"] prototype = "TR.PULSE x" -aliases = ["TR.P x"] +aliases = ["TR.P"] short = "Pulse trigger output x" description = """ Pulse trigger output x. diff --git a/docs/ops/maths.toml b/docs/ops/maths.toml index b38bf48b..a9313531 100644 --- a/docs/ops/maths.toml +++ b/docs/ops/maths.toml @@ -161,6 +161,10 @@ For more info, see the post on [samdoshi.com][samdoshi_com_euclidean] [^euclidean_rhythm_citation]: Toussaint, G. T. (2005, July). The Euclidean algorithm generates traditional musical rhythms. _In Proceedings of BRIDGES: Mathematical Connections in Art, Music and Science_ (pp. 47-56). """ +[BPM] +prototype = "BPM x" +short = "milliseconds per beat in BPM `x`" + [N] prototype = "N x" short = "converts an equal temperament note number to a value usable by the CV outputs (`x` in the range `-127` to `127`)" diff --git a/docs/ops/turtle.md b/docs/ops/turtle.md new file mode 100644 index 00000000..2520783d --- /dev/null +++ b/docs/ops/turtle.md @@ -0,0 +1,4 @@ +## Turtle + +A 2-dimensional, movable index into the pattern values as displayed on the TRACKER screen. + diff --git a/docs/ops/turtle.toml b/docs/ops/turtle.toml new file mode 100644 index 00000000..57530112 --- /dev/null +++ b/docs/ops/turtle.toml @@ -0,0 +1,81 @@ +["@"] +prototype = "@" +prototype_set = "@ x" +short = "get or set the current pattern value under the turtle" + +["@X"] +prototype = "@X" +prototype_set = "@X x" +short = "get the turtle X coordinate, or set it to x" + +["@Y"] +prototype = "@Y" +prototype_set = "@Y x" +short = "get the turtle Y coordinate, or set it to x" + +["@MOVE"] +prototype = "@MOVE x y" +short = "move the turtle x cells in the X axis and y cells in the Y axis" + +["@F"] +prototype = "@F x1 y1 x2 y2" +short = "set the turtle's fence to corners x1,y1 and x2,y2" + +["@FX1"] +prototype = "@FX1" +prototype_set = "@FX1 x" +short = "get the left fence line or set it to x" + +["@FX2"] +prototype = "@FX2" +prototype_set = "@FX2 x" +short = "get the right fence line or set it to x" + +["@FY1"] +prototype = "@FY1" +prototype_set = "@FY1 x" +short = "get the top fence line or set it to x" + +["@FY2"] +prototype = "@FY2" +prototype_set = "@FY2 x" +short = "get the bottom fence line or set it to x" + +["@SPEED"] +prototype = "@SPEED" +prototype_set = "@SPEED x" +short = "get the speed of the turtle's @STEP in cells per step or set it to x" + +["@DIR"] +prototype = "@DIR" +prototype_set = "@DIR x" +short = "get the direction of the turtle's @STEP in degrees or set it to x" + +["@STEP"] +prototype = "@STEP" +short = "move `@SPEED`/100 cells forward in `@DIR`, triggering `@SCRIPT` on cell change" + +["@BUMP"] +prototype = "@BUMP" +prototype_set = "@BUMP 1" +short = "get whether the turtle fence mode is BUMP, or set it to BUMP with 1" + +["@WRAP"] +prototype = "@WRAP" +prototype_set = "@WRAP 1" +short = "get whether the turtle fence mode is WRAP, or set it to WRAP with 1" + +["@BOUNCE"] +prototype = "@BOUNCE" +prototype_set = "@BOUNCE 1" +short = "get whether the turtle fence mode is BOUNCE, or set it to BOUNCE with 1" + +["@SCRIPT"] +prototype = "@SCRIPT" +prototype_set = "@SCRIPT x" +short = "get which script runs when the turtle changes cells, or set it to x" + +["@SHOW"] +prototype = "@SHOW" +prototype_set = "@SHOW 0/1" +short = "get whether the turtle is displayed on the TRACKER screen, or turn it on or off" diff --git a/docs/ops/variables.toml b/docs/ops/variables.toml index ebb56543..7f809802 100644 --- a/docs/ops/variables.toml +++ b/docs/ops/variables.toml @@ -126,6 +126,21 @@ prototype = "TIME.ACT" prototype_set = "TIME.ACT x" short = "enable or disable timer counting, default `1`" +[LAST] +prototype = "LAST x" +short = "get value in milliseconds since last script run time" +description = """ +Gets the number of milliseconds since the current script was run. From the live mode, shows time elapsed since last run of I script. + +For example, one-line tap tempo: + +``` +M LAST SCRIPT +``` + +Running this script twice will set the metronome to be the time between runs. +""" + [X] prototype = "X" prototype_set = "X x" diff --git a/docs/quickstart.md b/docs/quickstart.md index 0a5f7e61..a793caba 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -178,7 +178,7 @@ The *INIT* script (represented as `I`) is executed when a preset is recalled. Th * VALUE -- a number * OPERATOR -- a function, may need value(s) as argument(s), may return value * VARIABLE -- named memory storage -* MOD -- condition/rule that applies to rest of the *command*, ie del, prob, if, s +* MOD -- condition/rule that applies to rest of the *command*, e.g.: del, prob, if, s ### Syntax diff --git a/docs/whats_new.md b/docs/whats_new.md index 4f993176..2c539b9f 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -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. diff --git a/module/config.mk b/module/config.mk index 6ec5bd34..b2498cdd 100644 --- a/module/config.mk +++ b/module/config.mk @@ -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 diff --git a/module/edit_mode.c b/module/edit_mode.c index 2fd2a5c2..e464b2c1 100644 --- a/module/edit_mode.c +++ b/module/edit_mode.c @@ -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); } } diff --git a/module/edit_mode.h b/module/edit_mode.h index f7c3fadd..92e584af 100644 --- a/module/edit_mode.h +++ b/module/edit_mode.h @@ -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 diff --git a/module/help_mode.c b/module/help_mode.c index e0e15c02..6de7b0f4 100644 --- a/module/help_mode.c +++ b/module/help_mode.c @@ -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,6 +46,7 @@ const char* help1[HELP1_LENGTH] = { "1/7 HELP", "ENTER|ADD/OVERWRITE", "SH-ENTER|INSERT", "SH-BSP|CLEAR", + "ALT-SLASH|DISABLE LINE", " ", "// PATTERN", "ARROWS|NAVIGATE", @@ -51,6 +54,7 @@ const char* help1[HELP1_LENGTH] = { "1/7 HELP", "0-9|NUMERIC ENTRY", "-|FLIP SIGN", "SPACE|TOGGLE 0/1", + "ENTER|COMMIT CHANGE", "[ ]|NUDGE UP, DOWN", "SH-ALT-V|INSERT PASTE", "SH-BSP|DELETE", @@ -58,10 +62,11 @@ const char* help1[HELP1_LENGTH] = { "1/7 HELP", "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,11 +100,12 @@ 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", @@ -107,10 +113,11 @@ const char* help4[HELP4_LENGTH] = { "4/7 DATA AND TABLES", "// 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; diff --git a/module/live_mode.c b/module/live_mode.c index a5793acf..06014ce1 100644 --- a/module/live_mode.c +++ b/module/live_mode.c @@ -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; diff --git a/module/main.c b/module/main.c index bccb895c..9b89483b 100644 --- a/module/main.c +++ b/module/main.c @@ -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- through ctrl- mute triggers + // ctrl- 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; + } // through : 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(); } } diff --git a/module/pattern_mode.c b/module/pattern_mode.c index f97a787b..9d51ae7b 100644 --- a/module/pattern_mode.c +++ b/module/pattern_mode.c @@ -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) { // : 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-: 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) { } // : 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-: 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) { } // : move left else if (match_no_mod(m, k, HID_LEFT)) { + editing_number = false; if (pattern > 0) pattern--; dirty = true; } // alt-: move to the very left else if (match_alt(m, k, HID_LEFT)) { + editing_number = false; base = 0; offset = 0; dirty = true; } // : move right else if (match_no_mod(m, k, HID_RIGHT)) { + editing_number = false; if (pattern < 3) pattern++; dirty = true; } // alt-: 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; } } // : 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-: 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; } - // : move down (increase length only if on the entry immediately - // after the current length) + // : 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-: 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; } // : 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; diff --git a/module/preset_r_mode.c b/module/preset_r_mode.c index 446179b5..0e657e62 100644 --- a/module/preset_r_mode.c +++ b/module/preset_r_mode.c @@ -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(); } diff --git a/module/preset_w_mode.c b/module/preset_w_mode.c index 1d27f27f..96fcf228 100644 --- a/module/preset_w_mode.c +++ b/module/preset_w_mode.c @@ -84,8 +84,8 @@ void process_preset_w_keys(uint8_t k, uint8_t m, bool is_held_key) { } // shift-: 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; } diff --git a/simulator/Makefile b/simulator/Makefile index 936febd8..29a7a328 100644 --- a/simulator/Makefile +++ b/simulator/Makefile @@ -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 \ diff --git a/src/command.h b/src/command.h index e16c6e9d..ff6fc991 100644 --- a/src/command.h +++ b/src/command.h @@ -3,7 +3,7 @@ #include -#define COMMAND_MAX_LENGTH 12 +#define COMMAND_MAX_LENGTH 16 typedef enum { NUMBER, OP, MOD, PRE_SEP, SUB_SEP } tele_word_t; diff --git a/src/every.c b/src/every.c new file mode 100644 index 00000000..22722212 --- /dev/null +++ b/src/every.c @@ -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; +} diff --git a/src/every.h b/src/every.h new file mode 100644 index 00000000..1be800c8 --- /dev/null +++ b/src/every.h @@ -0,0 +1,19 @@ +#ifndef _EVERY_H +#define _EVERY_H + +#include +#include + +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 diff --git a/src/match_token.rl b/src/match_token.rl index 82c6b36c..adbd6a56 100644 --- a/src/match_token.rl +++ b/src/match_token.rl @@ -1,5 +1,5 @@ #include "match_token.h" - +# #include // isdigit #include // 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); }; diff --git a/src/ops/controlflow.c b/src/ops/controlflow.c index bf4e6c3d..73762127 100644 --- a/src/ops/controlflow.c +++ b/src/ops/controlflow.c @@ -20,6 +20,17 @@ 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); @@ -27,8 +38,14 @@ 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); @@ -36,11 +53,19 @@ 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; +} diff --git a/src/ops/controlflow.h b/src/ops/controlflow.h index 17f90184..08057461 100644 --- a/src/ops/controlflow.h +++ b/src/ops/controlflow.h @@ -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 diff --git a/src/ops/delay.c b/src/ops/delay.c index 36068d58..09a79dbb 100644 --- a/src/ops/delay.c +++ b/src/ops/delay.c @@ -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); } } diff --git a/src/ops/maths.c b/src/ops/maths.c index 880e5b01..77dfaed3 100644 --- a/src/ops/maths.c +++ b/src/ops/maths.c @@ -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); +} diff --git a/src/ops/maths.h b/src/ops/maths.h index 7009c85b..b22ef81c 100644 --- a/src/ops/maths.h +++ b/src/ops/maths.h @@ -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 diff --git a/src/ops/op.c b/src/ops/op.c index f656ab3c..3c3c97fd 100644 --- a/src/ops/op.c +++ b/src/ops/op.c @@ -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, diff --git a/src/ops/op_enum.h b/src/ops/op_enum.h index 95bcc3fa..0b38bafd 100644 --- a/src/ops/op_enum.h +++ b/src/ops/op_enum.h @@ -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, diff --git a/src/ops/patterns.c b/src/ops/patterns.c index 08276878..0f7adf26 100644 --- a/src/ops/patterns.c +++ b/src/ops/patterns.c @@ -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(); } diff --git a/src/ops/queue.c b/src/ops/queue.c index 9af5ee60..df320cd2 100644 --- a/src/ops/queue.c +++ b/src/ops/queue.c @@ -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, diff --git a/src/ops/turtle.c b/src/ops/turtle.c new file mode 100644 index 00000000..5742edd9 --- /dev/null +++ b/src/ops/turtle.c @@ -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(); +} diff --git a/src/ops/turtle.h b/src/ops/turtle.h new file mode 100644 index 00000000..9e4febad --- /dev/null +++ b/src/ops/turtle.h @@ -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 diff --git a/src/ops/variables.c b/src/ops/variables.c index cd64e074..bf16adb1 100644 --- a/src/ops/variables.c +++ b/src/ops/variables.c @@ -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,7 +34,6 @@ 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 ); @@ -35,6 +41,7 @@ const tele_op_t op_O_WRAP = MAKE_SIMPLE_VARIABLE_OP(O.WRAP , variables.o_ 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 ); @@ -42,8 +49,15 @@ 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); +} diff --git a/src/ops/variables.h b/src/ops/variables.h index 6bb1d1c9..bf31e307 100644 --- a/src/ops/variables.h +++ b/src/ops/variables.h @@ -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; diff --git a/src/state.c b/src/state.c index 69de8281..42dcb78f 100644 --- a/src/state.c +++ b/src/state.c @@ -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 /////////////////////////////////////////////////////////////// diff --git a/src/state.h b/src/state.h index 6f25a877..96201315 100644 --- a/src/state.h +++ b/src/state.h @@ -6,21 +6,22 @@ #include #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,18 +141,35 @@ 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 ////////////////////////////////////////////////////////////////// @@ -152,10 +177,29 @@ size_t ss_scripts_size(void); 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); diff --git a/src/teletype.c b/src/teletype.c index c82f3d0a..22f94898 100644 --- a/src/teletype.c +++ b/src/teletype.c @@ -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); diff --git a/src/teletype.h b/src/teletype.h index b9263953..614b341c 100644 --- a/src/teletype.h +++ b/src/teletype.h @@ -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 { diff --git a/src/turtle.c b/src/turtle.c new file mode 100644 index 00000000..30c3a972 --- /dev/null +++ b/src/turtle.c @@ -0,0 +1,281 @@ +#include "turtle.h" + +#define min(X, Y) ((X) < (Y) ? (X) : (Y)) +#define max(X, Y) ((X) > (Y) ? (X) : (Y)) + +void turtle_init(scene_turtle_t *st) { + scene_turtle_t t = { .fence = { .x1 = 0, .y1 = 0, .x2 = 3, .y2 = 63 }, + .mode = TURTLE_BUMP, + .heading = 180, + .speed = 100, + .stepped = false, + .script_number = TEMP_SCRIPT }; + memcpy(st, &t, sizeof(t)); + turtle_set_x(st, 0); + turtle_set_y(st, 0); + st->last = st->position; +} + +typedef struct { + QT x1, y1, x2, y2; +} Q_fence_t; + +static inline Q_fence_t normalize_fence(turtle_fence_t in, turtle_mode_t mode) { + Q_fence_t out; + + if (mode == TURTLE_WRAP) { + out.x1 = TO_Q(in.x1); + out.x2 = TO_Q((in.x2 + 1)); + out.y1 = TO_Q(in.y1); + out.y2 = TO_Q((in.y2 + 1)); + } + else { + out.x1 = TO_Q(in.x1) + (Q_1 >> 1); + out.x2 = TO_Q((in.x2 + 1)) - (Q_1 >> 1); + out.y1 = TO_Q(in.y1) + (Q_1 >> 1); + out.y2 = TO_Q((in.y2 + 1)) - (Q_1 >> 1); + } + + return out; +} + +void turtle_check_step(scene_turtle_t *t) { + turtle_position_t here; + turtle_resolve_position(t, &t->position, &here); + if (here.x != t->last.x || here.y != t->last.y) { + t->last = here; + t->stepped = true; + } +} + +void turtle_normalize_position(scene_turtle_t *t, turtle_position_t *tp, + turtle_mode_t mode) { + Q_fence_t f = normalize_fence(t->fence, mode); + + QT fxl = f.x2 - f.x1; + QT fyl = f.y2 - f.y1; + + if (mode == TURTLE_WRAP) { + if (fxl > Q_1 && tp->x < f.x1) + tp->x = f.x2 + ((tp->x - f.x1) % fxl); + else if (fxl > Q_1 && tp->x > f.x2) + tp->x = f.x1 + ((tp->x - f.x1) % fxl); + + if (fyl > Q_1 && tp->y < f.y1) + tp->y = f.y2 + ((tp->y - f.y1) % fyl); + else if (fyl > Q_1 && tp->y > f.y2) + tp->y = f.y1 + ((tp->y - f.y1) % fyl); + } + else if (mode == TURTLE_BOUNCE) { + // pretty sure you can do this with a % but I couldn't get it right + // what with the edge bounceback effectively changing the length + // so here's a crappy while() loop wavefolder. --burnsauce / sliderule + turtle_position_t last, here; + turtle_resolve_position(t, &t->position, &last); + while (tp->x > f.x2 || tp->x < f.x1) { + if (tp->x > f.x2) { // right fence + if (t->stepping) turtle_set_heading(t, 360 - t->heading); + // right extent minus how far past we are + tp->x = f.x2 - (tp->x - f.x2); + } + else if (tp->x < f.x1) { // left fence + if (t->stepping) turtle_set_heading(t, 360 - t->heading); + // left extent minus how far past we are + tp->x = f.x1 + (f.x1 - tp->x); + } + turtle_resolve_position(t, &t->position, &here); + if (here.x == last.x) break; + last = here; + } + while (tp->y > f.y2 || tp->y < f.y1) { + if (tp->y >= f.y2) { // top fence + if (t->stepping) turtle_set_heading(t, 180 - t->heading); + tp->y = f.y2 - (tp->y - f.y2); + } + else if (tp->y < f.y1) { // bottom fence + if (t->stepping) turtle_set_heading(t, 180 - t->heading); + tp->y = f.y1 + (f.y1 - tp->y); + } + turtle_resolve_position(t, &t->position, &here); + if (here.y == last.y) break; + last = here; + } + if (tp->x == f.x2) tp->x -= 1; + if (tp->y == f.y2) tp->y -= 1; + } + // either mode is TURTLE_BUMP or something above is broken + // both cases call for constraining the turtle to the fence + tp->x = min(f.x2 - 1, max(f.x1, tp->x)); + tp->y = min(f.y2 - 1, max(f.y1, tp->y)); + turtle_check_step(t); +} + +// Produce Q0 positions in dst +void turtle_resolve_position(scene_turtle_t *t, turtle_position_t *src, + turtle_position_t *dst) { + dst->x = TO_I(src->x); + dst->y = TO_I(src->y); +} + +uint8_t turtle_get_x(scene_turtle_t *st) { + turtle_position_t t; + turtle_resolve_position(st, &st->position, &t); + return t.x; +} + +void turtle_set_x(scene_turtle_t *st, int16_t x) { + st->position.x = TO_Q(x) + Q_05; // standing in the middle of the cell + turtle_normalize_position(st, &st->position, TURTLE_BUMP); +} + +uint8_t turtle_get_y(scene_turtle_t *st) { + turtle_position_t t; + turtle_resolve_position(st, &st->position, &t); + return t.y; +} + +void turtle_set_y(scene_turtle_t *st, int16_t y) { + st->position.y = TO_Q(y) + Q_05; + turtle_normalize_position(st, &st->position, TURTLE_BUMP); +} + +void turtle_move(scene_turtle_t *st, int16_t x, int16_t y) { + st->position.y += TO_Q(y); + st->position.x += TO_Q(x); + turtle_normalize_position(st, &st->position, st->mode); +} + +/// http://www.coranac.com/2009/07/sines/ +/// A sine approximation via a third-order approx. +/// @param x Angle (with 2^15 units/circle) +/// @return Sine value (Q12) +static inline int32_t _sin(int32_t x) { + // S(x) = x * ( (3<>r) ) >> s + // n : Q-pos for quarter circle 13 + // A : Q-pos for output 12 + // p : Q-pos for parentheses intermediate 15 + // r = 2n-p 11 + // s = A-1-p-n 17 + + // int qN = 13, qA = 12, qP = 15, qR = 2 * qN - qP, qS = qN + qP + 1 - qA; + static const int qN = 13, qP = 15, qR = 11, qS = 17; + + x = x << (30 - qN); // shift to full s32 range (Q13->Q30) + + if ((x ^ (x << 1)) < 0) // test for quadrant 1 or 2 + x = (1 << 31) - x; + + x = x >> (30 - qN); + + return x * ((3 << qP) - (x * x >> qR)) >> qS; +} + +void turtle_step(scene_turtle_t *st) { + // watch out, it's a doozie ;) + QT dx = 0, dy = 0; + QT h1 = st->heading, h2 = st->heading; + + h1 = ((h1 % 360) << 15) / 360; + h2 = (((h2 + 360 - 90) % 360) << 15) / 360; + + int32_t dx_d_Q12 = (st->speed * _sin(h1)) / 100; + int32_t dy_d_Q12 = (st->speed * _sin(h2)) / 100; + + + if (dx_d_Q12 < 0) + // delta x = round(v *sin(heading)) + dx = ((dx_d_Q12 >> (11 - Q_BITS)) - 1) >> 1; + else + dx = ((dx_d_Q12 >> (11 - Q_BITS)) + 1) >> 1; + if (dy_d_Q12 < 0) + dy = ((dy_d_Q12 >> (11 - Q_BITS)) - 1) >> 1; + else + dy = ((dy_d_Q12 >> (11 - Q_BITS)) + 1) >> 1; + + + st->position.x += dx; + st->position.y += dy; + st->stepping = true; + turtle_normalize_position(st, &st->position, st->mode); + st->stepping = false; +} + +inline void turtle_correct_fence(scene_turtle_t *st) { + int16_t t; + st->fence.x1 = min(3, max(0, st->fence.x1)); + st->fence.x2 = min(3, max(0, st->fence.x2)); + st->fence.y1 = min(63, max(0, st->fence.y1)); + st->fence.y2 = min(63, max(0, st->fence.y2)); + + if (st->fence.x1 > st->fence.x2) { + t = st->fence.x2; + st->fence.x2 = st->fence.x1; + st->fence.x1 = t; + } + if (st->fence.y1 > st->fence.y2) { + t = st->fence.y2; + st->fence.y2 = st->fence.y1; + st->fence.y1 = t; + } + turtle_normalize_position(st, &st->position, TURTLE_BUMP); +} + +void turtle_set_fence(scene_turtle_t *st, int16_t x1, int16_t y1, int16_t x2, + int16_t y2) { + st->fence.x1 = min(3, max(0, x1)); + st->fence.x2 = min(3, max(0, x2)); + st->fence.y1 = min(63, max(0, y1)); + st->fence.y2 = min(63, max(0, y2)); + turtle_correct_fence(st); +} + +turtle_mode_t turtle_get_mode(scene_turtle_t *st) { + return st->mode; +} + +void turtle_set_mode(scene_turtle_t *st, turtle_mode_t m) { + if (m != TURTLE_WRAP && m != TURTLE_BUMP && m != TURTLE_BOUNCE) + m = TURTLE_BUMP; + st->mode = m; + turtle_normalize_position(st, &st->position, m); +} + +uint16_t turtle_get_heading(scene_turtle_t *st) { + return st->heading; +} + +void turtle_set_heading(scene_turtle_t *st, int16_t h) { + while (h < 0) h += 360; + st->heading = h % 360; +} + +int16_t turtle_get_speed(scene_turtle_t *st) { + return st->speed; +} + +void turtle_set_speed(scene_turtle_t *st, int16_t v) { + st->speed = v; +} + +void turtle_set_script(scene_turtle_t *st, script_number_t sn) { + if (sn >= METRO_SCRIPT) + st->script_number = TEMP_SCRIPT; + else + st->script_number = sn; + st->stepped = false; +} + +script_number_t turtle_get_script(scene_turtle_t *st) { + return st->script_number; +} + +bool turtle_get_shown(scene_turtle_t *st) { + return st->shown; +} + +void turtle_set_shown(scene_turtle_t *st, bool shown) { + st->shown = shown; +} + +#undef min +#undef max diff --git a/src/turtle.h b/src/turtle.h new file mode 100644 index 00000000..13f9fe2f --- /dev/null +++ b/src/turtle.h @@ -0,0 +1,99 @@ +#ifndef _TURTLE_H_ +#define _TURTLE_H_ +#include +#include +#include +#include + +// In this code, we use signed fixed-point maths. This means that the int16_t +// is used to store a signed number with both an integer and a fraction part. +// +// Q_BITS represents how many are used for the fractional component. Because +// we work with signed integers, 1 bit facilitates the sign component. +// Therefore, INT_BITS = 16 - Q_BITS - 1 +// +// The main thing we do in fixed-point is keep a fractional index into the +// pattern data, so it will be our main reference point. +// +// 63 is 6 bits, so Q_BITS = 16 - 6 - 1 = 9 +// The notation for this system is Q6.9 + +#define Q_BITS 9 +#define Q_1 (1 << Q_BITS) // 1.0 +#define Q_05 (1 << (Q_BITS - 1)) // 0.5 +#define Q_ROUND(X) \ + ((((X >> (Q_BITS - 1)) + 1) >> 1) << Q_BITS) // (int)(X + 0.5) +#define QT int32_t +#define TO_Q(X) (X << Q_BITS) +#define TO_I(X) ((X >> Q_BITS) & 0xFFFF) + +// Can't include state.h first, so put script_number_t here +// really should be in a different header file? + +typedef enum { + TT_SCRIPT_1 = 0, + TT_SCRIPT_2, + TT_SCRIPT_3, + TT_SCRIPT_4, + TT_SCRIPT_5, + TT_SCRIPT_6, + TT_SCRIPT_7, + TT_SCRIPT_8, + METRO_SCRIPT, + INIT_SCRIPT, + TEMP_SCRIPT +} script_number_t; + +typedef struct { + int32_t x; // higher resolution to permit fixed-point math + int32_t y; +} turtle_position_t; + +typedef struct { + uint8_t x1; + uint8_t y1; + uint8_t x2; + uint8_t y2; +} turtle_fence_t; + +typedef enum { TURTLE_WRAP, TURTLE_BUMP, TURTLE_BOUNCE } turtle_mode_t; + +typedef struct { + turtle_position_t position; + turtle_position_t last; + turtle_fence_t fence; + turtle_mode_t mode; + uint16_t heading; + int16_t speed; + script_number_t script_number; + bool stepping; + bool stepped; + bool shown; +} scene_turtle_t; + +void turtle_init(scene_turtle_t*); +void turtle_normalize_position(scene_turtle_t*, turtle_position_t*, + turtle_mode_t); +void turtle_resolve_position(scene_turtle_t*, turtle_position_t*, + turtle_position_t*); +uint8_t turtle_get_x(scene_turtle_t*); +void turtle_set_x(scene_turtle_t*, int16_t); +uint8_t turtle_get_y(scene_turtle_t*); +void turtle_set_y(scene_turtle_t*, int16_t); +void turtle_move(scene_turtle_t*, int16_t, int16_t); +void turtle_step(scene_turtle_t*); +turtle_fence_t* turtle_get_fence(scene_turtle_t*); +void turtle_correct_fence(scene_turtle_t*); +void turtle_set_fence(scene_turtle_t*, int16_t, int16_t, int16_t, int16_t); +turtle_mode_t turtle_get_mode(scene_turtle_t*); +void turtle_set_mode(scene_turtle_t*, turtle_mode_t); +uint16_t turtle_get_heading(scene_turtle_t*); +void turtle_set_heading(scene_turtle_t*, int16_t); +int16_t turtle_get_speed(scene_turtle_t*); +void turtle_set_speed(scene_turtle_t*, int16_t); +script_number_t turtle_get_script(scene_turtle_t*); +void turtle_set_script(scene_turtle_t*, script_number_t); +void turtle_check_step(scene_turtle_t*); +bool turtle_get_shown(scene_turtle_t*); +void turtle_set_shown(scene_turtle_t*, bool); +#endif diff --git a/tests/Makefile b/tests/Makefile index 669d6aae..39d92cee 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -2,17 +2,20 @@ CFLAGS = -std=c99 -g -Wall -fno-common -DSIM -I../src -I../libavr32/src tests: main.o \ + log.o \ match_token_tests.o op_mod_tests.o \ parser_tests.o process_tests.o \ + turtle_tests.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/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 \ + ../src/ops/turtle.o \ ../libavr32/src/euclidean/data.o ../libavr32/src/euclidean/euclidean.o \ ../libavr32/src/util.o $(CC) -o $@ $^ $(CFLAGS) @@ -26,6 +29,9 @@ tests: main.o \ test: tests @./tests | greatest/greenest +test-travis: tests + @./tests + clean: rm -f tests rm -rf tests.dSYM diff --git a/tests/log.c b/tests/log.c new file mode 100644 index 00000000..2f3aa306 --- /dev/null +++ b/tests/log.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +#include "log.h" + +static inline log_t *get_log() { + static log_t log; + return &log; +} + +#define L get_log() + +void log_init() { + L->size = LOG_SIZE; + L->buf = calloc(LOG_SIZE, sizeof(char)); + L->end = L->buf + LOG_SIZE; +} + +void log_grow() { + char *new = calloc(L->size * 2, sizeof(char)); + memcpy(new, L->buf, L->size); + free(L->buf); + L->buf = new; + L->size *= 2; + L->end = L->buf + L->size; +} + +log_position_t *log_seek() { + log_position_t *here = get_log()->buf; + log_position_t *last = here; + + while (*here != 0) { + last = here; + here += *here + 1; + } + return last; +} + + +log_position_t *log_seek_next() { + log_position_t *here = get_log()->buf; + + while (*here != 0) here += *here + 1; + return here; +} + +bool catting = false; + +void lprintf(const char *format, ...) { + log_t *log = L; + va_list arg; + char buf[LOG_LINE_SIZE]; + va_start(arg, format); + size_t len = vsnprintf(buf, LOG_LINE_SIZE, format, arg); + va_end(arg); + log_position_t *here = log_seek_next(); + if (here + len + 2 > log->end) log_grow(); + here = log_seek_next(); + strcpy(here + 1, buf); + *here = len + 1; + catting = false; +} + +void lcat(const char *str) { + log_position_t *(*seek)(); // Function ponters: useful! + if (catting) + seek = log_seek; + else + seek = log_seek_next; + log_position_t *here = seek(); + if (here + strlen(str) + 1 > L->end) { + log_grow(); + here = seek(); + } + + char *new = strcat(here + 1, str); + *here = strlen(new) + 1; + catting = true; +} + +void log_clear() { + memset(L->buf, 0, L->size); +} + +void log_print() { + puts("\n"); // two newlines, actually. + log_position_t *here = get_log()->buf; + while (*here != 0) { + puts(here + 1); + here += *here + 1; + } +} diff --git a/tests/log.h b/tests/log.h new file mode 100644 index 00000000..8d2ce815 --- /dev/null +++ b/tests/log.h @@ -0,0 +1,22 @@ +#ifndef _LOG_H +#define _LOG_H + +#define LOG_SIZE 2048 +#define LOG_LINE_SIZE 255 + +typedef char log_position_t; + +typedef struct { + char *buf; + char *end; + size_t size; +} log_t; + + +void log_init(void); +void lprintf(const char *, ...); +void lcat(const char *); +void log_print(void); +void log_clear(void); + +#endif diff --git a/tests/main.c b/tests/main.c index 705d7c99..27c78503 100644 --- a/tests/main.c +++ b/tests/main.c @@ -9,6 +9,7 @@ #include "op_mod_tests.h" #include "parser_tests.h" #include "process_tests.h" +#include "turtle_tests.h" void tele_metro_updated() {} void tele_metro_reset() {} @@ -37,6 +38,7 @@ int main(int argc, char **argv) { RUN_SUITE(op_mod_suite); RUN_SUITE(parser_suite); RUN_SUITE(process_suite); + RUN_SUITE(turtle_suite); GREATEST_MAIN_END(); } diff --git a/tests/op_mod_tests.c b/tests/op_mod_tests.c index 54b84e11..0f402f55 100644 --- a/tests/op_mod_tests.c +++ b/tests/op_mod_tests.c @@ -39,6 +39,8 @@ TEST op_stack_size() { // (needs dedicated initaliser) exec_state_t es; es_init(&es); + es_push(&es); + es_variables(&es)->script_number = 1; command_state_t cs; cs_init(&cs); @@ -105,10 +107,10 @@ TEST mod_stack_size() { for (int j = 0; j < mod->params + stack_extra; j++) cs_push(&cs, 0); // execute func - const tele_command_t sub_command = {.length = 1, - .separator = 0, - .data = { {.tag = OP, - .value = E_OP_A } } }; + const tele_command_t sub_command = { .length = 1, + .separator = 0, + .data = { { .tag = OP, + .value = E_OP_A } } }; mod->func(&ss, &es, &cs, &sub_command); // check that the stack has the correct number of items in it diff --git a/tests/process_tests.c b/tests/process_tests.c index 522dc753..3125aa8d 100644 --- a/tests/process_tests.c +++ b/tests/process_tests.c @@ -11,9 +11,11 @@ // correct (allows contiuation of state) TEST process_helper_state(scene_state_t* ss, size_t n, char* lines[], int16_t answer) { - process_result_t result = {.has_value = false, .value = 0 }; + process_result_t result = { .has_value = false, .value = 0 }; exec_state_t es; es_init(&es); + es_push(&es); + es_variables(&es)->script_number = 1; for (size_t i = 0; i < n; i++) { tele_command_t cmd; char error_msg[TELE_ERROR_MSG_LENGTH]; @@ -231,9 +233,8 @@ TEST test_Q() { // 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16 = 136 // 136 / 16 = 8.5 - // TODO fix Q.AVG to return 9 in this circumstance char* test4[1] = { "Q.AVG" }; - CHECK_CALL(process_helper_state(&ss, 1, test4, 8)); + CHECK_CALL(process_helper_state(&ss, 1, test4, 9)); char* test5[2] = { "Q.AVG 5", "Q.AVG" }; CHECK_CALL(process_helper_state(&ss, 2, test5, 5)); @@ -278,6 +279,8 @@ TEST test_blank_command() { ss_init(&ss); exec_state_t es; es_init(&es); + es_push(&es); + es_variables(&es)->script_number = 1; tele_command_t cmd; char error_msg[TELE_ERROR_MSG_LENGTH]; diff --git a/tests/turtle_tests.c b/tests/turtle_tests.c new file mode 100644 index 00000000..cba7a621 --- /dev/null +++ b/tests/turtle_tests.c @@ -0,0 +1,302 @@ +#include "turtle_tests.h" +#include +#include +#include // ssize_t + +#include "greatest/greatest.h" + +#include "log.h" +#include "teletype.h" + +int32_t count = 0; + +#define NEWTEST() count = 0; + +static const char *error_message(error_t e) { + static const char *em[] = { "E_OK", + "E_PARSE", + "E_LENGTH", + "E_NEED_PARAMS", + "E_EXTRA_PARAMS", + "E_NO_MOD_HERE", + "E_MANY_PRE_SEP", + "E_NEED_PRE_SEP", + "E_PLACE_PRE_SEP", + "E_NO_SUB_SEP_IN_PRE", + "E_NOT_LEFT", + "E_NEED_SPACE_PRE_SEP", + "E_NEED_SPACE_SUB_SEP" }; + return em[e]; +} + +// runs multiple lines of commands and then asserts that the final answer is +// correct (allows contiuation of state) +TEST process_helper_state(scene_state_t *ss, size_t n, char *lines[], + int16_t answer) { + count++; + process_result_t result = { .has_value = false, .value = 0 }; + exec_state_t es; + memset(&es, 0, sizeof(es)); + es_init(&es); + es_push(&es); + es_variables(&es)->script_number = 1; + + log_clear(); + lprintf("---- Test #%d ----", count); + lcat("Command: "); + // Format multi-line commands into one line + for (size_t i = 0; i < n; i++) { + lcat(lines[i]); + if (i < n - 1) lcat(" | "); + } + + for (size_t i = 0; i < n; i++) { + tele_command_t cmd; + char error_msg[TELE_ERROR_MSG_LENGTH]; + error_t error = parse(lines[i], &cmd, error_msg); + if (error != E_OK) { + lprintf("%s: %s", error_message(error), error_msg); + log_print(); + FAILm("Parser failure."); + } + error = validate(&cmd, error_msg); + if (error != E_OK) { + lprintf("%s: %s", error_message(error), error_msg); + log_print(); + FAILm("Validation failure"); + } + result = process_command(ss, &es, &cmd); + } + + if (result.has_value != true) { + lprintf("Expected a value, found none."); + log_print(); + FAIL(); + } + if (result.value != answer) { + lprintf("Value incorrect. Expected %d, got %d", answer, result.value); + log_print(); + FAIL(); + } + + PASS(); +} + +// runs multiple lines of commands and then asserts that the final answer is +// correct +TEST process_helper(size_t n, char *lines[], int16_t answer) { + scene_state_t ss; + memset(&ss, 0, sizeof(ss)); + ss_init(&ss); + + CHECK_CALL(process_helper_state(&ss, n, lines, answer)); + + PASS(); +} + +TEST test_turtle_fence_normal() { + NEWTEST(); + char *test1[2] = { "@F 1 2 3 4", "@FX1" }; + CHECK_CALL(process_helper(2, test1, 1)); + char *test2[2] = { "@F 1 2 3 4", "@FY1" }; + CHECK_CALL(process_helper(2, test2, 2)); + char *test3[2] = { "@F 1 2 3 4", "@FX2" }; + CHECK_CALL(process_helper(2, test3, 3)); + char *test4[2] = { "@F 1 2 3 4", "@FY2" }; + CHECK_CALL(process_helper(2, test4, 4)); + PASS(); +} +TEST test_turtle_fence_swapped() { + NEWTEST(); + char *test1[2] = { "@F 3 4 1 2", "@FX1" }; + CHECK_CALL(process_helper(2, test1, 1)); + char *test2[2] = { "@F 3 4 1 2", "@FY1" }; + CHECK_CALL(process_helper(2, test2, 2)); + char *test3[2] = { "@F 3 4 1 2", "@FX2" }; + CHECK_CALL(process_helper(2, test3, 3)); + char *test4[2] = { "@F 3 4 1 2", "@FY2" }; + CHECK_CALL(process_helper(2, test4, 4)); + PASS(); +} +TEST test_turtle_fence_oob() { + NEWTEST(); + char *test1[2] = { "@F -1 -1 4 100", "@FX1" }; + CHECK_CALL(process_helper(2, test1, 0)); + char *test2[2] = { "@F -1 -1 4 100", "@FY1" }; + CHECK_CALL(process_helper(2, test2, 0)); + char *test3[2] = { "@F -1 -1 4 100", "@FX2" }; + CHECK_CALL(process_helper(2, test3, 3)); + char *test4[2] = { "@F -1 -1 4 100", "@FY2" }; + CHECK_CALL(process_helper(2, test4, 63)); + PASS(); +} +TEST test_turtle_fence_individual() { + NEWTEST(); + char *test1[2] = { "@FX1 1", "@FX1" }; + CHECK_CALL(process_helper(2, test1, 1)); + char *test2[2] = { "@FY1 1", "@FY1" }; + CHECK_CALL(process_helper(2, test2, 1)); + char *test3[2] = { "@FX2 1", "@FX2" }; + CHECK_CALL(process_helper(2, test3, 1)); + char *test4[2] = { "@FY2 1", "@FY2" }; + CHECK_CALL(process_helper(2, test4, 1)); + PASS(); +} +TEST test_turtle_fence_ind_swapped() { + NEWTEST(); + char *test1[3] = { "@FX1 1", "@FX2 0", "@FX1" }; + CHECK_CALL(process_helper(3, test1, 0)); + char *test2[3] = { "@FY1 1", "@FY2 0", "@FY1" }; + CHECK_CALL(process_helper(3, test2, 0)); + char *test3[3] = { "@FX1 1", "@FX2 0", "@FX2" }; + CHECK_CALL(process_helper(3, test3, 1)); + char *test4[3] = { "@FY1 1", "@FY2 0", "@FY2" }; + CHECK_CALL(process_helper(3, test4, 1)); + PASS(); +} +TEST test_turtle_fence_ind_oob() { + NEWTEST(); + char *test1[2] = { "@FX1 -1", "@FX1" }; + CHECK_CALL(process_helper(2, test1, 0)); + char *test2[2] = { "@FY1 -1", "@FY1" }; + CHECK_CALL(process_helper(2, test2, 0)); + char *test3[2] = { "@FX2 4", "@FX2" }; + CHECK_CALL(process_helper(2, test3, 3)); + char *test4[2] = { "@FY2 63", "@FY2" }; + CHECK_CALL(process_helper(2, test4, 63)); + // TODO more tests + PASS(); +} + +TEST test_turtle_wrap() { + NEWTEST(); + char *test1[3] = { "@WRAP 1", "@MOVE 0 -1", "@Y" }; + CHECK_CALL(process_helper(3, test1, 63)); + char *test1b[3] = { "@WRAP 1", "@MOVE 0 -1", "@X" }; + CHECK_CALL(process_helper(3, test1b, 0)); + char *test1c[3] = { "@WRAP 1", "@F 0 0 1 1; @MOVE 0 3", "@Y" }; + CHECK_CALL(process_helper(3, test1c, 1)); + char *test1d[3] = { "@WRAP 1", "@F 0 0 1 1; @MOVE 0 3", "@X" }; + CHECK_CALL(process_helper(3, test1d, 0)); + char *test1e[3] = { "@WRAP 1", "@STEP", "@X" }; + CHECK_CALL(process_helper(3, test1e, 0)); + char *test2[4] = { "@WRAP 1", "@FY2 1", "@MOVE 0 -1", "@Y" }; + CHECK_CALL(process_helper(4, test2, 1)); + char *test2b[4] = { "@WRAP 1", "@FY2 1", "@MOVE 0 -1", "@X" }; + CHECK_CALL(process_helper(4, test2b, 0)); + char *test3[3] = { "@WRAP 1", "@MOVE -1 0", "@X" }; + CHECK_CALL(process_helper(3, test3, 3)); + char *test4[4] = { "@WRAP 1", "@FX1 1", "@MOVE -1 0", "@X" }; + CHECK_CALL(process_helper(4, test4, 3)); + PASS(); +} + +TEST test_turtle_bounce() { + NEWTEST(); + char *test1[3] = { "@BOUNCE 1", "@MOVE 0 -2", "@Y" }; + CHECK_CALL(process_helper(3, test1, 2)); + char *test2[3] = { "@BOUNCE 1", "@MOVE 0 64", "@Y" }; + CHECK_CALL(process_helper(3, test2, 62)); + char *test3[3] = { "@BOUNCE 1", "@MOVE -1 0", "@X" }; + CHECK_CALL(process_helper(3, test3, 1)); + char *test4[3] = { "@BOUNCE 1", "@MOVE -2 0", "@X" }; + CHECK_CALL(process_helper(3, test4, 2)); + char *test5[3] = { "@BOUNCE 1", "@MOVE 4 0", "@X" }; + CHECK_CALL(process_helper(3, test5, 2)); + char *test6[3] = { "@BOUNCE 1", "@MOVE 0 -1", "@Y" }; + CHECK_CALL(process_helper(3, test6, 1)); + char *test7[3] = { "@BOUNCE 1", "@MOVE 0 126", "@Y" }; + CHECK_CALL(process_helper(3, test7, 0)); + char *test8[3] = { "@BOUNCE 1", "@MOVE 0 130", "@Y" }; + CHECK_CALL(process_helper(3, test8, 4)); + char *test9[3] = { "@BOUNCE 1", "@MOVE 3 0", "@X" }; + CHECK_CALL(process_helper(3, test9, 3)); + char *test10[4] = { "@BOUNCE 1", "@F 0 0 1 1", "@STEP", "@Y" }; + CHECK_CALL(process_helper(4, test10, 1)); + char *test10b[4] = { "@BOUNCE 1", "@F 0 0 1 1", "@STEP", "@DIR" }; + CHECK_CALL(process_helper(4, test10b, 180)); + + // The following tests reveal the charade that is the length between fences +#if 0 + char *test10c[4] = { "@BOUNCE 1", "@F 0 0 1 1", "L 1 2: @STEP", "@DIR" }; + CHECK_CALL(process_helper(4, test10c, 0)); + char *test10d[4] = { "@BOUNCE 1", "@F 0 0 1 1", "L 1 3: @STEP", "@SPEED" }; + CHECK_CALL(process_helper(4, test10d, 100)); + char *test13[5] = { + "@F 0 0 1 1", + "@BOUNCE 1", + "@SPEED 300", + "@STEP", + "@DIR" + }; + CHECK_CALL(process_helper(5, test13, 0)); + char *test14[6] = { + "@F 0 0 1 1", + "@BOUNCE 1", + "@SPEED 400", + "@DIR 135", + "@STEP", + "@DIR" + }; + CHECK_CALL(process_helper(6, test14, 315)); + char *test15[6] = { + "@F 0 0 1 1", + "@BOUNCE 1", + "@SPEED 141", + "@DIR 135", + "@STEP", + "@Y" + }; + CHECK_CALL(process_helper(6, test15, 1)); +#endif + + char *test12[4] = { "@BOUNCE 1; @SPEED 141", "@DIR 135", "@STEP", "@X" }; + CHECK_CALL(process_helper(4, test12, 1)); + PASS(); +} + +/* + +TEST test_turtle_F() { + char *testX[Y] = { + "@", + "@", + "@", + "@", + "@", + "@" + }; + CHECK_CALL(process_helper(Y, testX, xpct)); + PASS(); +} + +*/ + +TEST test_turtle_vars() { + NEWTEST(); + char *test1[2] = { "@X 1", "@X" }; + CHECK_CALL(process_helper(2, test1, 1)); + PASS(); +} + +TEST test_turtle_step() { + char *test1[2] = { "@STEP", "@Y" }; + CHECK_CALL(process_helper(2, test1, 1)); + char *test2[2] = { "@STEP", "@X" }; + CHECK_CALL(process_helper(2, test2, 0)); + PASS(); +} + +SUITE(turtle_suite) { + log_init(); + RUN_TEST(test_turtle_fence_normal); + RUN_TEST(test_turtle_fence_swapped); + RUN_TEST(test_turtle_fence_oob); + RUN_TEST(test_turtle_fence_individual); + RUN_TEST(test_turtle_fence_ind_swapped); + RUN_TEST(test_turtle_fence_ind_oob); + RUN_TEST(test_turtle_wrap); + RUN_TEST(test_turtle_bounce); + RUN_TEST(test_turtle_vars); + RUN_TEST(test_turtle_step); +} diff --git a/tests/turtle_tests.h b/tests/turtle_tests.h new file mode 100644 index 00000000..20635b45 --- /dev/null +++ b/tests/turtle_tests.h @@ -0,0 +1,8 @@ +#ifndef _OP_TURTLE_TESTS_H_ +#define _OP_TURTLE_TESTS_H_ + +#include "greatest/greatest.h" + +SUITE_EXTERN(turtle_suite); + +#endif diff --git a/utils/common/__init__.py b/utils/common/__init__.py index 66c0c30e..a88101c5 100644 --- a/utils/common/__init__.py +++ b/utils/common/__init__.py @@ -64,7 +64,24 @@ def _convert_struct_name_to_op_name(name): "SYM_RIGHT_ANGLED_x2": ">>", "SYM_AMPERSAND_x2": "&&", "SYM_PIPE_x2": "||", - "M_SYM_EXCLAMATION": "M!" + "M_SYM_EXCLAMATION": "M!", + "TURTLE": "@", + "TURTLE_X": "@X", + "TURTLE_Y": "@Y", + "TURTLE_MOVE": "@MOVE", + "TURTLE_DIR": "@DIR", + "TURTLE_SPEED": "@SPEED", + "TURTLE_STEP": "@STEP", + "TURTLE_F": "@F", + "TURTLE_FX1": "@FX1", + "TURTLE_FX2": "@FX2", + "TURTLE_FY1": "@FY1", + "TURTLE_FY2": "@FY2", + "TURTLE_BUMP": "@BUMP", + "TURTLE_WRAP": "@WRAP", + "TURTLE_BOUNCE": "@BOUNCE", + "TURTLE_SCRIPT": "@SCRIPT", + "TURTLE_SHOW": "@SHOW" } if stripped in MAPPINGS: diff --git a/utils/docs.py b/utils/docs.py index 69391a3e..3cdefd02 100755 --- a/utils/docs.py +++ b/utils/docs.py @@ -23,7 +23,9 @@ autoescape=False, loader=jinja2.FileSystemLoader(str(TEMPLATE_DIR)), trim_blocks=True, - lstrip_blocks=True + lstrip_blocks=True, + cache_size=0, + auto_reload=True ) # determines the order in which sections are displayed @@ -37,6 +39,7 @@ "delay", "stack", "queue", + "turtle", "ansible", "whitewhale", "meadowphysics", @@ -136,7 +139,7 @@ def common_md(): sorted_ops = [kv[1] for kv in sorted(all_ops_dict.items())] output += op_table_template.render(ops=sorted_ops) - output += "# Missing documentation\n\n" + output += "\n\n# Missing documentation\n\n" missing_ops = all_ops - ops_with_docs output += ", ".join([f"`{o}`" for o in sorted(missing_ops)]) + "\n\n"