From 4773f6cb560cbe3c84e0049a9cee0608d3db8ce9 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Fri, 2 Oct 2026 14:15:45 -0400 Subject: [PATCH] American spelling Switch prose, comments and local names to American spelling so the org-wide spelling check passes. Claude-Session: https://claude.ai/code/session_01XGS7UUxZFhvaK4bnErEfvW --- .american-spelling-allow | 4 ++++ AGENTS.md | 4 ++-- CHANGELOG.md | 8 ++++---- KitchenSync/README.md | 2 +- KitchenSync/main/ks_display.h | 2 +- KitchenSync/main/ks_led.h | 2 +- KitchenSync/main/ks_main.c | 2 +- KitchenSync/main/ks_tick.c | 4 ++-- KitchenSync/main/ks_web.cpp | 14 +++++++------- KitchenSync/main/metronome_audio.c | 2 +- KitchenSync/main/usb_midi_host.c | 2 +- KitchenSyncTouch/KitchenSyncTouch.ino | 4 ++-- KitchenSyncTouch/ktouch_config_migrate.c | 2 +- KitchenSyncTouch/ktouch_config_nvs.cpp | 2 +- KitchenSyncTouch/ktouch_display.cpp | 2 +- KitchenSyncTouch/ktouch_midi_out.cpp | 4 ++-- KitchenSyncTouch/ktouch_status_rgb.h | 6 +++--- KitchenSyncTouch/ktouch_transport.h | 2 +- KitchenSyncTouch/ktouch_web.cpp | 4 ++-- README.md | 2 +- X32Link/X32Link.ino | 14 +++++++------- X32Link/app_config.c | 6 +++--- X32Link/app_config.h | 4 ++-- X32Link/app_config_nvs.cpp | 2 +- X32Link/beat_source.h | 2 +- X32Link/beat_synth.h | 4 ++-- X32Link/clock_ticker.c | 2 +- X32Link/clock_ticker.h | 2 +- X32Link/ks_config.c | 6 +++--- X32Link/ks_config.h | 4 ++-- X32Link/ks_config_json.h | 2 +- X32Link/ks_status.h | 2 +- X32Link/led_phase.h | 6 +++--- X32Link/metro_strip.h | 8 ++++---- X32Link/midi_bpm.cpp | 2 +- X32Link/midi_clock_out_io.cpp | 2 +- X32Link/touch_display.cpp | 4 ++-- X32Link/transport_launch.h | 2 +- X32Link/web_config.cpp | 16 ++++++++-------- .../adr/0008-p4-web-ui-is-the-brand-reference.md | 4 ++-- .../0009-converge-the-clock-box-on-esp-idf.md | 2 +- docs/adr/0015-extract-ks-core-in-repo.md | 4 ++-- .../2026-07-10-kitchensynctouch-sketch-design.md | 2 +- tasks/ARC-013.md | 4 ++-- tasks/ARC-014.md | 2 +- tasks/ARC-015.md | 2 +- tasks/ARC-017.md | 4 ++-- tasks/ARC-018.md | 2 +- tasks/ARC-020.md | 6 +++--- tasks/ARC-022.md | 12 ++++++------ tasks/ARC-025.md | 8 ++++---- tasks/CLK-001.md | 2 +- tasks/ESP-011.md | 2 +- tasks/ESP-014.md | 6 +++--- tasks/ESP-016.md | 6 +++--- tasks/ESP-018.md | 2 +- tasks/ESP-019.md | 8 ++++---- tasks/ESP-022.md | 6 +++--- tasks/ESP-024.md | 10 +++++----- tasks/ESP-026.md | 2 +- tasks/ESP-030.md | 6 +++--- tasks/ESP-034.md | 4 ++-- tasks/ESP-039.md | 4 ++-- tasks/ESP-040.md | 2 +- tasks/ESP-043.md | 10 +++++----- tasks/ESP-044.md | 2 +- tasks/LNK-001.md | 4 ++-- tasks/LNK-003.md | 2 +- tasks/LNK-005.md | 8 ++++---- tasks/LNK-009.md | 2 +- tasks/LNK-011.md | 2 +- tasks/LNK-012.md | 2 +- tasks/LNK-031.md | 2 +- tasks/LNK-039.md | 16 ++++++++-------- tasks/LNK-042.md | 2 +- tasks/MCK-005.md | 2 +- tasks/MCK-006.md | 2 +- tasks/P4-004.md | 2 +- tasks/P4-016.md | 4 ++-- tasks/P4-018.md | 2 +- tasks/P4-021.md | 2 +- tasks/P4-024.md | 2 +- tasks/P4-026.md | 2 +- tasks/P4-029.md | 2 +- tasks/P4-030.md | 2 +- tasks/P4-036.md | 2 +- tasks/P4-037.md | 2 +- tasks/P4-038.md | 2 +- test/test_app_config.c | 2 +- test/test_beat_synth.c | 2 +- test/test_clock_ticker.c | 4 ++-- test/test_config_persist.c | 4 ++-- test/test_ks_config.c | 6 +++--- test/test_ks_status.c | 2 +- test/test_led_phase.c | 14 +++++++------- test/test_link_measurement_session.c | 2 +- test/test_metro_strip.c | 10 +++++----- test/test_status_rgb.c | 8 ++++---- test/test_wifi_conn_policy.c | 2 +- tools/build-flasher-site.sh | 2 +- tools/linkcli/README.md | 2 +- 101 files changed, 210 insertions(+), 206 deletions(-) create mode 100644 .american-spelling-allow diff --git a/.american-spelling-allow b/.american-spelling-allow new file mode 100644 index 0000000..9194f54 --- /dev/null +++ b/.american-spelling-allow @@ -0,0 +1,4 @@ +# American spelling checker allowlist (see ericdahl-dev/.github scripts/american_spelling.py) + +# Vendored ThrowTheSwitch Unity test framework: third-party code, kept as upstream ships it. +path: lib/unity/ diff --git a/AGENTS.md b/AGENTS.md index 249f2b2..8bc729d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -144,11 +144,11 @@ Keys: `↑`/`↓` tempo ±1, `←`/`→` tempo ±0.1, `space` start/stop, `q`/`Q `p` force beat 0 onto *now* (a known downbeat to trigger the analyzer on), `s` start/stop sync, `a` Link on/off, `x` quit. Flags: `--tempo N`, `--quantum N`, `--play`. -Two behaviours that will otherwise read as firmware bugs: +Two behaviors that will otherwise read as firmware bugs: - **A peer that joins an already-playing session stays stopped.** This is Link's design, not a bug: start/stop is last-writer-wins by timestamp (`Controller.hpp:89`), and a - booting peer initialises its start/stop state with `timestamp = hostTime` + booting peer initializes its start/stop state with `timestamp = hostTime` (`Controller.hpp:67`) — so its fresh "stopped" is *newer* than an earlier "playing" and wins. Tempo, by contrast, *is* adopted by late joiners, which makes the asymmetry extra confusing. **Form the session first, then press play.** A device that reboots diff --git a/CHANGELOG.md b/CHANGELOG.md index ff60649..4ce1760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,10 @@ distributed .bin traces back to source. JSON `"fw"` field (fleet-auditable by script), and the `/update` OTA page shows the running version before you flash over it. One shared `fw_version.h` across X32Link, KitchenSync, and the X32 emulator. -- Beat LED colours (LNK-039): on RGB-LED boards (QT Py NeoPixel) the beat LED - follows the web UI's BEAT/BAR1 colour pickers — bar-1 downbeat gets the - accent colour, live-applied with no reboot. The WiFi-down diagnostic blink - stays fixed green so a dark user colour can never hide it. +- Beat LED colors (LNK-039): on RGB-LED boards (QT Py NeoPixel) the beat LED + follows the web UI's BEAT/BAR1 color pickers — bar-1 downbeat gets the + accent color, live-applied with no reboot. The WiFi-down diagnostic blink + stays fixed green so a dark user color can never hide it. - CI now compile-checks the QT Py NeoPixel board profile (`LED_RGB` path) alongside the headless and Waveshare touch variants. diff --git a/KitchenSync/README.md b/KitchenSync/README.md index 52efb23..df0183b 100644 --- a/KitchenSync/README.md +++ b/KitchenSync/README.md @@ -185,7 +185,7 @@ clock source to verify: **MIDI clock IN → BPM** (P4-011 stage 1). **Link Audio** (P4-032) — the P4 can stream its mic into an Ableton Live 12.4 session as a Link Audio channel, in sync. That lives in `../LinkAudioPoC/`, not in this firmware, because it links Ableton's **GPLv2** SDK. Integrating it means -clearing a commercial-licence conversation with Ableton first. Three tickets +clearing a commercial-license conversation with Ableton first. Three tickets (P4-032 productization, ESP-008 USB interface ingest, ESP-010 transport proposal) are blocked on that one email. diff --git a/KitchenSync/main/ks_display.h b/KitchenSync/main/ks_display.h index bddc44a..c26e3cd 100644 --- a/KitchenSync/main/ks_display.h +++ b/KitchenSync/main/ks_display.h @@ -17,7 +17,7 @@ extern "C" { #endif -// Initialise the panel + backlight and spawn the render task. Idempotent-safe to call +// Initialize the panel + backlight and spawn the render task. Idempotent-safe to call // once from app_main. No-op unless CONFIG_KS_TOUCH_DISPLAY. void ks_display_start(void); diff --git a/KitchenSync/main/ks_led.h b/KitchenSync/main/ks_led.h index fe0ec8c..c0e142a 100644 --- a/KitchenSync/main/ks_led.h +++ b/KitchenSync/main/ks_led.h @@ -7,7 +7,7 @@ extern "C" { #endif -// Initialise the RMT WS2812 device on `gpio` for `npix` pixels. Safe to call once +// Initialize the RMT WS2812 device on `gpio` for `npix` pixels. Safe to call once // at boot even if the LED feature is off; a failure logs and disables show/clear. void ks_led_start(int gpio, int npix); diff --git a/KitchenSync/main/ks_main.c b/KitchenSync/main/ks_main.c index 90f599b..513b6bc 100644 --- a/KitchenSync/main/ks_main.c +++ b/KitchenSync/main/ks_main.c @@ -145,7 +145,7 @@ static void status_task(void *arg) (unsigned long)s_stat.usb_dropped); /* P4-038: the counters are LIFETIME now (/status owns them), so this no longer * zeroes them. Log on the EDGE -- only when the count has grown since the last - * pass -- which keeps the old "shout when something happened" serial behaviour + * pass -- which keeps the old "shout when something happened" serial behavior * without a log per overrun. (A log per overrun is a blocking ~13 ms UART write * that delays the next tick, which overruns, which logs: the feedback loop that * manufactured 3945 phantom overruns in 40 s on the Touch.) */ diff --git a/KitchenSync/main/ks_tick.c b/KitchenSync/main/ks_tick.c index c4f6765..b4dab17 100644 --- a/KitchenSync/main/ks_tick.c +++ b/KitchenSync/main/ks_tick.c @@ -4,7 +4,7 @@ void ks_tick_reset(KsTickState* st, uint32_t cfg_gen) { beat_source_reset(&st->src); - /* P4-038: INITIALISE the lifetime counter. Callers do `KsTickState ts; + /* P4-038: INITIALIZE the lifetime counter. Callers do `KsTickState ts; * ks_tick_reset(&ts, gen);` — leaving this alone means it starts as stack garbage and * can never be trusted, the same trap clock_ticker.c calls out for `dropped`. The host * test caught precisely that: a phantom +1 on a fresh state. */ @@ -93,7 +93,7 @@ KsTickPlan ks_tick_step(KsTickState* st, const KsTickInputs* in) { // Arbitration, PER OUTPUT: exactly one master owns each output's transport. // Link owns an output once the session publishes StartStopState AND that // output is set to follow it (cfg.clock[o].follow_link, the default); such - // an output ignores manual presses and the UI greys its toggle. An output + // an output ignores manual presses and the UI grays its toggle. An output // with follow_link=0 is manual: its web toggle drives it and the session's // transport never touches it, so a drum machine can follow Ableton while a // synth is launched by hand two bars later (ESP-011's whole point). diff --git a/KitchenSync/main/ks_web.cpp b/KitchenSync/main/ks_web.cpp index da336aa..4731c4a 100644 --- a/KitchenSync/main/ks_web.cpp +++ b/KitchenSync/main/ks_web.cpp @@ -103,7 +103,7 @@ color:var(--ink);cursor:pointer} .tp.armed{border-color:var(--amber);color:var(--amber)} .tp.running{border-color:#7fbf1f;color:var(--led)} .tp:disabled{opacity:.4;cursor:not-allowed;border-color:var(--line);color:var(--mut)} -/* ESP-011: per-output transport toggle — one control, state is the label/colour +/* ESP-011: per-output transport toggle — one control, state is the label/color (the KitchenSync Touch device's full-screen toggle, shrunk to a row). */ .tgl{flex:1;font-family:var(--disp);font-weight:800;font-size:13px;letter-spacing:.14em; padding:11px 12px;border-radius:9px;cursor:pointer;border:1px solid var(--line); @@ -146,7 +146,7 @@ border:1px solid var(--line);border-radius:5px;padding:2px 6px;margin-left:8px;b .sect[data-when="clock_out"] .grid2{grid-template-columns:minmax(0,1fr)} /* Wide viewports (desktop): widen the card and pack the config form into two masonry-flow columns so it isn't a long, skinny strip. Column-flow (not grid) - keeps a short group from waiting on a tall neighbour's row height. Status + keeps a short group from waiting on a tall neighbor's row height. Status readout stays full-width above. */ @media (min-width:760px){ .unit{max-width:900px} @@ -258,7 +258,7 @@ fetch('/transport?out='+b.dataset.out+'&play='+b.dataset.play,{method:'POST'}).c // Per-output transport (ESP-011). Each output has ONE master: Link when its // "follow Link" switch is on and the session publishes transport, otherwise you. // A Link-owned output shows the SESSION's play state (its manual launch state is -// frozen and would lie) and its toggle greys out. +// frozen and would lie) and its toggle grays out. function outFollows(o){var cb=document.querySelector('input[name="clk'+o+'_follow"]');return !!(cb&&cb.checked)} function showLaunch(a,d){ Array.prototype.forEach.call(document.querySelectorAll('.tgl'),function(b){ @@ -396,7 +396,7 @@ static std::string build_outputs() + ">"; // ESP-011: one Touch-style toggle per output (stopped/arming/playing), plus // the per-output transport master. Follow Link on => this output tracks the - // session and its toggle greys out; off => the toggle is yours. + // session and its toggle grays out; off => the toggle is yours. // ESP-019: the key that fires this toggle is printed on it. Clock Out 1..4 are // data-out 0..3, so the badge is the 1-based label the user already reads. s += std::string("
RUN") @@ -456,7 +456,7 @@ static std::string build_voice() return s; } -// LED-strip customization controls (P4-019): brightness, pattern, fade, colours — +// LED-strip customization controls (P4-019): brightness, pattern, fade, colors — // all on the /live path so they apply instantly with no reboot. static std::string build_led() { @@ -748,7 +748,7 @@ void ks_web_config_persist_tick(void) if (!s_cfg) return; /* ESP-037: while a Link session is DRIVING, remember its tempo as ours -- the same - * behaviour the Touch has. Link teaches the box the tempo; when Link goes away the + * behavior the Touch has. Link teaches the box the tempo; when Link goes away the * box keeps playing it (the arbiter's free-run seed, applied by the clock task) AND * persists it, so a power-cycle comes back at the last tempo it actually played. * @@ -869,7 +869,7 @@ void ks_web_start(KsConfig* cfg, volatile uint32_t* gen, SemaphoreHandle_t cfg_m httpd_handle_t server = NULL; httpd_config_t hcfg = HTTPD_DEFAULT_CONFIG(); // P4-037: purge the least-recently-used connection when httpd's pool is full, rather - // than refusing the new one. Defence in depth beside the real fix (LWIP_MAX_SOCKETS=16 + // than refusing the new one. Defense in depth beside the real fix (LWIP_MAX_SOCKETS=16 // in sdkconfig.defaults): the pool is 7 with 3 reserved internally, so 4 client slots, // and a desktop browser opens up to 6 keep-alive connections to one host. hcfg.lru_purge_enable = true; diff --git a/KitchenSync/main/metronome_audio.c b/KitchenSync/main/metronome_audio.c index 6fef667..bb7032d 100644 --- a/KitchenSync/main/metronome_audio.c +++ b/KitchenSync/main/metronome_audio.c @@ -143,7 +143,7 @@ void metronome_audio_start(int volume, int voice) bool metronome_audio_ready(void) { return s_ready; } /* P4-029: re-apply volume + voice at runtime (no reboot) so the web /live path can - * preview them like the LED colours. No-op until _ready() — the codec/I2S are only + * preview them like the LED colors. No-op until _ready() — the codec/I2S are only * brought up at boot when the metronome was enabled, so enabling it still needs a * reboot; only volume/voice of an already-running metronome are live. Re-renders the * two short bursts and re-sets the codec volume; a click landing mid-re-render is at diff --git a/KitchenSync/main/usb_midi_host.c b/KitchenSync/main/usb_midi_host.c index cce2ff8..e4909d8 100644 --- a/KitchenSync/main/usb_midi_host.c +++ b/KitchenSync/main/usb_midi_host.c @@ -155,7 +155,7 @@ uint32_t usb_midi_host_tx(void) { return s_tx; } * with one in-flight transfer (~1 ms) and the fan-out calling it once per output * back-to-back inside a 1 ms tick, ~3 of every 4 packets were discarded and only the * first output ever reached USB gear. Callers must now batch a tick's events into ONE - * transfer (usb_midi_batch) and honour this return value. */ + * transfer (usb_midi_batch) and honor this return value. */ bool usb_midi_host_send(const uint8_t* data, int len) { if (!s_ready || len <= 0 || len > 64) return false; diff --git a/KitchenSyncTouch/KitchenSyncTouch.ino b/KitchenSyncTouch/KitchenSyncTouch.ino index d04a171..55d6535 100644 --- a/KitchenSyncTouch/KitchenSyncTouch.ino +++ b/KitchenSyncTouch/KitchenSyncTouch.ino @@ -191,7 +191,7 @@ void setup() { #ifdef HAS_STATUS_RGB // ESP-043: drive the Super Mini's onboard WS2812 (GPIO48) as a status light. The -// state->colour decision is the pure, host-tested ktouch_status_rgb(); this seam just +// state->color decision is the pure, host-tested ktouch_status_rgb(); this seam just // samples the live state and pushes the result to the LED. dark stopped, amber blink armed, // green beat-flash running, cyan when Link-locked, brighter accent on the bar-1 downbeat. #include "ktouch_status_rgb.h" @@ -202,7 +202,7 @@ static void status_rgb_tick() { ktouch_midi_locked() != 0, ktouch_midi_beats(), g_config.quantum_beats); - // Only touch the RMT when the colour actually changes -- loop() runs every ~5 ms and + // Only touch the RMT when the color actually changes -- loop() runs every ~5 ms and // rgbLedWrite reconfigures the peripheral on each call. static StatusRgb last = { 1, 1, 1 }; // impossible first value forces the first write if (c.r == last.r && c.g == last.g && c.b == last.b) return; diff --git a/KitchenSyncTouch/ktouch_config_migrate.c b/KitchenSyncTouch/ktouch_config_migrate.c index cdf11b1..530ff90 100644 --- a/KitchenSyncTouch/ktouch_config_migrate.c +++ b/KitchenSyncTouch/ktouch_config_migrate.c @@ -89,7 +89,7 @@ bool ktouch_migrate_legacy_config(KsConfig* out, const void* blob, size_t blob_l return false; // not a legacy Touch version } - // A migration is not a licence to skip the guard — bit-rot is bit-rot at any version. + // A migration is not a license to skip the guard — bit-rot is bit-rot at any version. // Same gate a verbatim KsConfig load runs through. On failure, restore clean defaults // so the caller never sees a half-migrated struct. if (!ks_config_valid(out)) { diff --git a/KitchenSyncTouch/ktouch_config_nvs.cpp b/KitchenSyncTouch/ktouch_config_nvs.cpp index 40476f4..17c34fe 100644 --- a/KitchenSyncTouch/ktouch_config_nvs.cpp +++ b/KitchenSyncTouch/ktouch_config_nvs.cpp @@ -43,7 +43,7 @@ static bool migrate_legacy_keys(KsConfig* cfg) { prefs.getString("wifi_ssid", cfg->wifi[0].ssid, sizeof(cfg->wifi[0].ssid)); prefs.getString("wifi_pass", cfg->wifi[0].pass, sizeof(cfg->wifi[0].pass)); - // A migration is not a licence to skip the guard: garbage in the old keys is still + // A migration is not a license to skip the guard: garbage in the old keys is still // garbage. Same gate the blob path runs through. return ks_config_valid(cfg); } diff --git a/KitchenSyncTouch/ktouch_display.cpp b/KitchenSyncTouch/ktouch_display.cpp index 051ba55..8b97dcb 100644 --- a/KitchenSyncTouch/ktouch_display.cpp +++ b/KitchenSyncTouch/ktouch_display.cpp @@ -99,7 +99,7 @@ static bool read_touch(axs_touch_t *t) { return axs5106l_parse(buf, sizeof(buf), t) == 0; } -// The whole lower area is one transport toggle; colour + label follow the state. +// The whole lower area is one transport toggle; color + label follow the state. static void draw_toggle(int state) { uint32_t bg; const char* big; const char* hint; if (state == TL_RUNNING) { bg = 0x1f7a0eu; big = "PLAYING"; hint = "tap to STOP"; } diff --git a/KitchenSyncTouch/ktouch_midi_out.cpp b/KitchenSyncTouch/ktouch_midi_out.cpp index 99b33e2..9c1d8d9 100644 --- a/KitchenSyncTouch/ktouch_midi_out.cpp +++ b/KitchenSyncTouch/ktouch_midi_out.cpp @@ -51,7 +51,7 @@ static MasterClock s_mc; // ESP-027 * * Worth knowing: this task is pinned to core 1, and Arduino's loopTask runs on core 1 * too (CONFIG_ARDUINO_RUNNING_CORE=1). So the MIDI writer SHARES a core with the web - * server, the LovyanGFX SPI blits and the I2C touch reads. It out-prioritises them + * server, the LovyanGFX SPI blits and the I2C touch reads. It out-prioritizes them * (6 vs 1), so plain preemption should cover it -- which is exactly why a large gap * would be interesting: it would mean something preemption cannot fix. * @@ -261,7 +261,7 @@ static void writer_task(void*) { if (gap > s_max_gap_us) s_max_gap_us = gap; if (work > s_max_work_us) { s_max_work_us = work; - // Stage split re-labelled to match the new order -- the probe must keep + // Stage split re-labeled to match the new order -- the probe must keep // measuring the stage it names, or ESP-018's next diagnosis reads a lie. w_beats = t_beats - tk0; // tempo_source_beats_now() w_tport = t_tport - t_beats; // clock_output_step + transport byte + publish diff --git a/KitchenSyncTouch/ktouch_status_rgb.h b/KitchenSyncTouch/ktouch_status_rgb.h index 8987c27..5d81dc6 100644 --- a/KitchenSyncTouch/ktouch_status_rgb.h +++ b/KitchenSyncTouch/ktouch_status_rgb.h @@ -1,6 +1,6 @@ #pragma once // ESP-043: the headless Super Mini's onboard WS2812 (GPIO48) as a status light. A screenless -// clock box has no other local feedback, so the colour must read at a glance across a room. +// clock box has no other local feedback, so the color must read at a glance across a room. // This is the STATE -> APPEARANCE decision as a PURE function, host-tested -- it does not live // in loop() (the same rule that keeps transport/tempo mapping out of the views). // @@ -9,7 +9,7 @@ // waiting for the bar line -- the DeviceDetail armed cadence) // running, free-run -> green beat flash // running, Link-locked -> cyan beat flash (phase-locked to a session) -// bar-1 downbeat -> a brighter accent of the running/armed colour +// bar-1 downbeat -> a brighter accent of the running/armed color // // The flash is HARD-EDGED (bright for the first slice of each beat, dark the rest), never a // soft throb -- a throb reads as "loading", and a beat is a hammer-fall. @@ -23,7 +23,7 @@ extern "C" { typedef struct { uint8_t r, g, b; } StatusRgb; -// Map the live state to the LED colour for THIS instant. `beats` is the writer's continuous +// Map the live state to the LED color for THIS instant. `beats` is the writer's continuous // beat counter (ktouch_midi_beats); `quantum` is beats per bar, for the downbeat accent. // link_locked is true only when the clock is phase-locked to a Link session. StatusRgb ktouch_status_rgb(TransportLaunchState transport, bool link_locked, diff --git a/KitchenSyncTouch/ktouch_transport.h b/KitchenSyncTouch/ktouch_transport.h index 5b4e759..c9314ec 100644 --- a/KitchenSyncTouch/ktouch_transport.h +++ b/KitchenSyncTouch/ktouch_transport.h @@ -13,7 +13,7 @@ void ktouch_transport_post(TransportLaunchIntent intent); TransportLaunchIntent ktouch_transport_take(void); // consumes; NONE when empty // The writer publishes the current launch state (TL_STOPPED/ARMED/RUNNING) each -// tick; the display reads it to colour the toggle and pick the tap direction. +// tick; the display reads it to color the toggle and pick the tap direction. void ktouch_transport_publish_state(int launch_state); int ktouch_transport_state(void); diff --git a/KitchenSyncTouch/ktouch_web.cpp b/KitchenSyncTouch/ktouch_web.cpp index d11f6ab..677b186 100644 --- a/KitchenSyncTouch/ktouch_web.cpp +++ b/KitchenSyncTouch/ktouch_web.cpp @@ -424,7 +424,7 @@ static void handle_status() { * defers to Link. * * ks_status.h defines link_owns as a LIVE claim: "when link_owns is - * true the manual PLAY/STOP buttons are ignored, so the UI greys + * true the manual PLAY/STOP buttons are ignored, so the UI grays * them". A client is entitled to take that literally, and the iOS app * does -- it stops SENDING the tap. * @@ -456,7 +456,7 @@ static void handle_status() { /* ESP-030 pt3: POST /live — apply a partial patch IMMEDIATELY, no reboot. * * This device had no /live at all. It grew bespoke one-off endpoints (/nudge, /bright) - * while the P4 generalised to /live plus a form grammar. Same drift as /status: the + * while the P4 generalized to /live plus a form grammar. Same drift as /status: the * clock ENGINE is shared (46 symlinked modules), the WEB layer never was. So every * live edit the iOS app made — nudge, swing, rate, cable — POSTed to /live, got a 404, * and was swallowed. The controls looked dead because the request landed nowhere. diff --git a/README.md b/README.md index 179aab9..9f53515 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ preserved; recovery tag `pre-split-2026-07-04` on the old repo). (P4-032). Streams the P4's onboard mic into an Ableton Live 12.4 session as a **Link Audio** channel, sample-rate-adaptive and calibrated to ~1 ms of Live's grid. Deliberately separate from KitchenSync: it links the **GPLv2** Ableton - Link SDK, which cannot ship in closed hardware without a commercial licence + Link SDK, which cannot ship in closed hardware without a commercial license from Ableton — a business gate nobody has cleared yet. See [`LinkAudioPoC/README.md`](LinkAudioPoC/README.md). diff --git a/X32Link/X32Link.ino b/X32Link/X32Link.ino index 4e0a604..37febfc 100644 --- a/X32Link/X32Link.ino +++ b/X32Link/X32Link.ino @@ -95,12 +95,12 @@ static WifiConnPolicy g_wifi_pol; #define LED_STATUS_GREEN 0x00FF00 // WiFi-down blink stays this, never user-configurable #if defined(LED_RGB) -static uint32_t s_led_rgb = 0; // current flash colour, set per flash (LNK-039) +static uint32_t s_led_rgb = 0; // current flash color, set per flash (LNK-039) #endif -// LNK-039: pick this flash's colour from the configured dot colours — -// bar-1 accent when bar phase is available, plain beat colour otherwise. -// Reads g_config at each flash, so /live colour edits apply immediately. +// LNK-039: pick this flash's color from the configured dot colors — +// bar-1 accent when bar phase is available, plain beat color otherwise. +// Reads g_config at each flash, so /live color edits apply immediately. static void led_pick_color(bool bar_valid) { #if defined(LED_RGB) float bar_phase = bar_valid ? tempo_source_phase((float)g_config.quantum_beats) : 0.0f; @@ -157,7 +157,7 @@ static void led_task(void*) { if (valid) { float phase = tempo_source_phase(1.0f); if (led_phase_should_flash(prev_phase, phase, valid)) { - led_pick_color(true); // LNK-039: bar-1 accent vs beat colour + led_pick_color(true); // LNK-039: bar-1 accent vs beat color led_set(true); vTaskDelay(pdMS_TO_TICKS(LED_FLASH_MS)); led_set(false); @@ -171,7 +171,7 @@ static void led_task(void*) { // against a stale value (no double-flash/stutter). prev_phase = -1.0f; if (tempo_source_beat()) { - led_pick_color(false); // no bar info in the sync gap: beat colour + led_pick_color(false); // no bar info in the sync gap: beat color led_set(true); vTaskDelay(pdMS_TO_TICKS(LED_FLASH_MS)); led_set(false); @@ -191,7 +191,7 @@ static void led_task(void*) { if (wifi_down_blink_due(now_ms, last_wifi_blink_ms, WIFI_DOWN_BLINK_INTERVAL_MS, wifi_connected)) { last_wifi_blink_ms = now_ms; - // Diagnostic blink keeps a fixed colour: a dark user beat colour + // Diagnostic blink keeps a fixed color: a dark user beat color // (down to #000000) must never hide the "WiFi is down" signal. #if defined(LED_RGB) s_led_rgb = led_flash_rgb(LED_STATUS_GREEN, LED_STATUS_GREEN, 0.0f, false, LED_BRIGHTNESS); diff --git a/X32Link/app_config.c b/X32Link/app_config.c index 6e109fa..38aa4a1 100644 --- a/X32Link/app_config.c +++ b/X32Link/app_config.c @@ -99,7 +99,7 @@ bool config_validate(const AppConfig* cfg) { } // ESP-040: an HTML posts "#rrggbb"; accept with or without the '#'. -// Rejects anything that isn't exactly six hex digits so a garbled colour never reaches +// Rejects anything that isn't exactly six hex digits so a garbled color never reaches // the config as a partial parse. static int parse_hex_color(const char* v, int* out) { if (v[0] == '#') v++; @@ -128,7 +128,7 @@ bool app_config_set_kv(AppConfig* cfg, const char* key, const char* val) { if (strcmp(key, "wifi_pass") == 0) return keep_or_copy(cfg->wifi_pass, sizeof(cfg->wifi_pass), val); if (strcmp(key, "mixer_ip") == 0) return keep_or_copy(cfg->mixer_ip, sizeof(cfg->mixer_ip), val); - // Colours: the SHARED key (led_beat/led_accent, what /config.json emits) and the + // Colors: the SHARED key (led_beat/led_accent, what /config.json emits) and the // device's own form key (dot_beat/dot_acc) are aliases for one field. if (strcmp(key, "led_beat") == 0 || strcmp(key, "dot_beat") == 0) { int c; if (!parse_hex_color(val, &c)) return false; @@ -163,7 +163,7 @@ void x32_form_merge(AppConfig* out, const AppConfig* base, if (full_form) { // The device's own page posted the whole form, so an unchecked (absent) checkbox // means "off". Pre-clear the two checkbox-backed booleans; a present key below - // flips its own back on. Value fields (input_source, colours, model, ...) are + // flips its own back on. Value fields (input_source, colors, model, ...) are // NEVER cleared by absence — only genuine checkboxes are, which is the whole point. out->midi_clock_out_enable = 0; out->phase_display_mode = 0; diff --git a/X32Link/app_config.h b/X32Link/app_config.h index 4a15bdf..322966f 100644 --- a/X32Link/app_config.h +++ b/X32Link/app_config.h @@ -71,7 +71,7 @@ bool app_config_set(AppConfig* cfg, AppConfigField field, int value); // quantum_beats), plus the X32-only fields (model, mixer_ip, fx_slot, input_source, // phase_flash). Int fields route through app_config_set (so config_validate stays the one // range owner); strings are bounded-copied and a BLANK ssid/pass/ip is a no-op "keep -// current" (a form can't show a password). Returns true iff the key was recognised and +// current" (a form can't show a password). Returns true iff the key was recognized and // the value applied. bool app_config_set_kv(AppConfig* cfg, const char* key, const char* val); @@ -88,7 +88,7 @@ typedef struct { const char* key; const char* val; } X32FormField; // full_form == true → the device's OWN page posted the whole form, so an ABSENT // checkbox-backed boolean (midi_clock_out_enable, phase_display_mode) // means "off" and is pre-cleared before the present keys reapply. -// Value fields (input_source, colours, ...) are NEVER zeroed by +// Value fields (input_source, colors, ...) are NEVER zeroed by // absence — only genuine checkboxes are. // // `out` is not validated here; the caller runs config_validate() exactly as before. diff --git a/X32Link/app_config_nvs.cpp b/X32Link/app_config_nvs.cpp index 0a03948..b7e10af 100644 --- a/X32Link/app_config_nvs.cpp +++ b/X32Link/app_config_nvs.cpp @@ -47,7 +47,7 @@ static bool migrate_legacy(AppConfig* cfg) { prefs.getString("wifi_ssid", legacy.wifi_ssid, sizeof(legacy.wifi_ssid)); prefs.getString("wifi_pass", legacy.wifi_pass, sizeof(legacy.wifi_pass)); - // A migration is not a licence to skip the guard: garbage in the old keys is + // A migration is not a license to skip the guard: garbage in the old keys is // still garbage. Same gate the blob path runs through. if (!config_validate(&legacy)) return false; diff --git a/X32Link/beat_source.h b/X32Link/beat_source.h index a928a58..0570a3f 100644 --- a/X32Link/beat_source.h +++ b/X32Link/beat_source.h @@ -31,7 +31,7 @@ extern "C" { // went silent for over TWO MINUTES while /status cheerfully reported sync:1 peers:1. // // 0.25 beat = 6 pulses at 24 PPQN. Smaller backsteps (an ordinary xform refinement, sub-ms) -// cost a few pulses of quantisation and are absorbed; anything past this is a re-origin. +// cost a few pulses of quantization and are absorbed; anything past this is a re-origin. #define BEAT_SOURCE_BACKSTEP_BEATS 0.25 typedef struct { diff --git a/X32Link/beat_synth.h b/X32Link/beat_synth.h index 78244fd..7c4ee00 100644 --- a/X32Link/beat_synth.h +++ b/X32Link/beat_synth.h @@ -16,14 +16,14 @@ typedef struct { uint32_t last_ms; // wall-clock ms of the last emitted beat } BeatSynth; -// Clear state (last_ms = 0). Behaviour then matches a fresh static counter: +// Clear state (last_ms = 0). Behavior then matches a fresh static counter: // the first step() with now_ms past one interval fires immediately. void beat_synth_reset(BeatSynth* s); // Advance to now_ms and return true if a beat is due at `bpm`. Fires when // (now_ms - last_ms) has reached the 60000/bpm interval (unsigned/wrap-safe), // then latches last_ms = now_ms. bpm <= 0 always returns false and leaves state -// untouched (so a resumed BPM fires promptly, matching the old inline behaviour). +// untouched (so a resumed BPM fires promptly, matching the old inline behavior). bool beat_synth_step(BeatSynth* s, uint32_t now_ms, float bpm); #ifdef __cplusplus diff --git a/X32Link/clock_ticker.c b/X32Link/clock_ticker.c index e33b410..86e1011 100644 --- a/X32Link/clock_ticker.c +++ b/X32Link/clock_ticker.c @@ -5,7 +5,7 @@ void clock_ticker_reset(ClockTicker* s) { s->last_tick = 0; s->primed = false; - /* Zeroed here so the counter is always INITIALISED -- callers do + /* Zeroed here so the counter is always INITIALIZED -- callers do * `ClockTicker t; clock_ticker_reset(&t);`, so leaving it alone means it starts as * stack garbage and can never be trusted. reset() only fires at startup and on Link * phase-invalid (no clock to drop anyway), so nothing real is lost. Accumulating a diff --git a/X32Link/clock_ticker.h b/X32Link/clock_ticker.h index 560c591..751125d 100644 --- a/X32Link/clock_ticker.h +++ b/X32Link/clock_ticker.h @@ -23,7 +23,7 @@ typedef struct { // burst -- the right musical call, but `return 0` throws every pending pulse // away, and until now it did so with NO trace: no burst on the analyzer, no // counter, no log. A stall long enough to trip it was simply invisible. - // reset() ZEROES this, so it is always initialised (callers declare the struct on + // reset() ZEROES this, so it is always initialized (callers declare the struct on // the stack and call reset on it). Keeping a LIFETIME total across resets is the // caller's job -- a pure struct cannot distinguish "first init" from "re-prime". uint32_t dropped; diff --git a/X32Link/ks_config.c b/X32Link/ks_config.c index 8019e4b..e3b31bf 100644 --- a/X32Link/ks_config.c +++ b/X32Link/ks_config.c @@ -203,7 +203,7 @@ bool ks_config_set(KsConfig* c, const char* key, const char* value) { c->led_fade = v; return true; } - // Colours arrive from an HTML as "#rrggbb" (the '#' is + // Colors arrive from an HTML as "#rrggbb" (the '#' is // url-decoded before we see it); accept with or without the leading '#'. if (strcmp(key, "led_beat") == 0 || strcmp(key, "led_accent") == 0) { const char* hex = (value[0] == '#') ? value + 1 : value; @@ -247,7 +247,7 @@ void ks_config_live_safe_copy(KsConfig* dst, const KsConfig* src) { for (int o = 0; o < KS_CLOCK_OUTPUTS; o++) dst->clock[o] = src->clock[o]; dst->tempo_mbpm = src->tempo_mbpm; /* ESP-037: a set tempo is a LIVE edit */ /* ESP-042: the fleet fields are all live -- no reboot to change quantize, the - * transport gate, the touch behaviour, or the backlight. */ + * transport gate, the touch behavior, or the backlight. */ dst->quantum_beats = src->quantum_beats; dst->transport_enable = src->transport_enable; dst->play_on_release = src->play_on_release; @@ -357,7 +357,7 @@ ks_decode_result ks_config_decode(KsConfig* out, const void* blob, size_t blob_l memcpy(&v1, blob, sizeof(v1)); KsConfig candidate; migrate_v1(&candidate, &v1); - // A migration is not a licence to skip the guard. + // A migration is not a license to skip the guard. if (!ks_config_valid(&candidate)) return KS_DECODE_DEFAULTED; *out = candidate; return KS_DECODE_MIGRATED; diff --git a/X32Link/ks_config.h b/X32Link/ks_config.h index 58bee24..97310f1 100644 --- a/X32Link/ks_config.h +++ b/X32Link/ks_config.h @@ -80,8 +80,8 @@ typedef struct { int led_brightness; // 0..100 — master strip brightness % (P4-019) int led_mode; // 0..2 — pattern: 0 chase, 1 flash, 2 fill int led_fade; // 0..100 — dim across a beat % (0 steady .. 100 dark by next) - int led_beat_color; // 0xRRGGBB — colour for beats 2..N - int led_accent_color; // 0xRRGGBB — colour for the bar-1 downbeat + int led_beat_color; // 0xRRGGBB — color for beats 2..N + int led_accent_color; // 0xRRGGBB — color for the bar-1 downbeat int follow_beat_enable; // 0/1 -- mic-based tempo detection (P4-020, display only) // ESP-037: settable free-run tempo, milli-BPM (128000 = 128.000 BPM). Drives the diff --git a/X32Link/ks_config_json.h b/X32Link/ks_config_json.h index 329aa65..22defa1 100644 --- a/X32Link/ks_config_json.h +++ b/X32Link/ks_config_json.h @@ -75,7 +75,7 @@ typedef struct { // // `wifi` has caps.wifi_slots entries and `clock` has caps.outputs entries -- the FITTED // counts, never padded, because a client renders a row/card per element and would -// otherwise draw slots and outputs the device cannot honour. Same order as the form's +// otherwise draw slots and outputs the device cannot honor. Same order as the form's // slot/output numbering (slot 0 is the unsuffixed "wifi_ssid"/"clk0_*" keys). // // Returns snprintf()'s return value (same truncation-detection convention as diff --git a/X32Link/ks_status.h b/X32Link/ks_status.h index 0753ca1..7b025f6 100644 --- a/X32Link/ks_status.h +++ b/X32Link/ks_status.h @@ -31,7 +31,7 @@ extern "C" { // so the UI can render "starting on next bar..." rather than a dead button. // `playing` is the session's real transport state and `link_owns` is true once a // Link peer has published StartStopState (ks_tick arbitration): when link_owns is -// true the manual PLAY/STOP buttons are ignored, so the UI greys them and shows +// true the manual PLAY/STOP buttons are ignored, so the UI grays them and shows // the session's `playing` state instead of the (frozen) manual launch state. // `tick` is the 1 ms clock task's health (P4-038), emitted as the same // "drop"/"burst"/"gap"/"work"/"over"/"core"/"w_beats"/"w_clock" block X32Link diff --git a/X32Link/led_phase.h b/X32Link/led_phase.h index 497c2ab..7925b7c 100644 --- a/X32Link/led_phase.h +++ b/X32Link/led_phase.h @@ -24,11 +24,11 @@ extern "C" { // decision is fully described by its inputs). bool led_phase_should_flash(float prev_phase, float phase, bool valid); -// LNK-039: which colour should this flash be, at what level? Picks the +// LNK-039: which color should this flash be, at what level? Picks the // accent for the bar downbeat (int part of `bar_phase` == 0, same rule as -// the touch dot) when `bar_valid`, else the beat colour; then scales each +// the touch dot) when `bar_valid`, else the beat color; then scales each // channel by bright/255 (40 reproduces the old hardcoded NeoPixel level). -// Colours are 0xRRGGBB, matching dot_beat_color/dot_accent_color. +// Colors are 0xRRGGBB, matching dot_beat_color/dot_accent_color. uint32_t led_flash_rgb(uint32_t beat_rgb, uint32_t accent_rgb, float bar_phase, bool bar_valid, uint8_t bright); diff --git a/X32Link/metro_strip.h b/X32Link/metro_strip.h index 8d78817..0321c79 100644 --- a/X32Link/metro_strip.h +++ b/X32Link/metro_strip.h @@ -5,7 +5,7 @@ // CHASE — one block per beat walks across the strip (npix/quantum px per beat) // FLASH — every pixel pulses together on each beat // FILL — pixels fill across the bar like a progress bar, resetting each downbeat -// The bar-1 downbeat uses the accent colour, other beats the beat colour. No I/O. +// The bar-1 downbeat uses the accent color, other beats the beat color. No I/O. // Host-tested in test/test_metro_strip.c. No Arduino/ESP-IDF dependency. #include @@ -23,8 +23,8 @@ extern "C" { typedef struct { uint8_t r, g, b; } RGB; typedef struct { - RGB beat; // colour for beats 2..N - RGB accent; // colour for the bar-1 downbeat + RGB beat; // color for beats 2..N + RGB accent; // color for the bar-1 downbeat uint8_t bright; // master brightness, 0..100 % uint8_t mode; // METRO_STRIP_CHASE / FLASH / FILL uint8_t fade; // dim across a beat, 0..100 % (0 = steady, 100 = dark by next beat) @@ -38,7 +38,7 @@ void metro_strip_render(double beats, int quantum, int npix, // Standby breath (ESP-009): session joined, transport stopped. `phase` is a // free-running 0..1 ramp (one full breath per period; the caller picks the -// rate). Every pixel pulses together in the beat colour, dim and never fully +// rate). Every pixel pulses together in the beat color, dim and never fully // dark -- a strip that reaches zero looks like a board that crashed between // blinks, which is the exact confusion this exists to prevent. Carries no beat // information: in standby there is no beat. diff --git a/X32Link/midi_bpm.cpp b/X32Link/midi_bpm.cpp index e05c9c6..370d1dc 100644 --- a/X32Link/midi_bpm.cpp +++ b/X32Link/midi_bpm.cpp @@ -5,7 +5,7 @@ #include void midi_bpm_init() { - // nothing to initialise — state lives in midi_clock module + // nothing to initialize — state lives in midi_clock module } float midi_bpm_update() { diff --git a/X32Link/midi_clock_out_io.cpp b/X32Link/midi_clock_out_io.cpp index 67794a0..3318b5a 100644 --- a/X32Link/midi_clock_out_io.cpp +++ b/X32Link/midi_clock_out_io.cpp @@ -141,7 +141,7 @@ void midi_clock_out_io_begin(void) { * * Core 1 is unchanged, and deliberately: Arduino's loopTask is on core 1 too * (CONFIG_ARDUINO_RUNNING_CORE=1), so the writer shares a core with the web server. - * It now out-prioritises everything there. What priority CANNOT fix is a flash-cache + * It now out-prioritizes everything there. What priority CANNOT fix is a flash-cache * stall, which freezes both cores regardless — which is exactly why the config write * in ARC-022 is debounced and kept on a low-priority task. */ xTaskCreatePinnedToCore(midi_clock_out_task, "midi_clk_out", 2048, NULL, 19, NULL, 1); diff --git a/X32Link/touch_display.cpp b/X32Link/touch_display.cpp index 8d2501c..fd70062 100644 --- a/X32Link/touch_display.cpp +++ b/X32Link/touch_display.cpp @@ -317,8 +317,8 @@ static void enter_keyboard(void) { // LNK-036: paint the phase indicator. Called every loop (not the 10 Hz text throttle) // so the beat flash lands on the beat instead of up to a throttle-length behind it. -// Mode 0 = sweeping marker; mode 1 = beat-flash dot: light on each beat (accent colour -// on the bar-1 downbeat, beat colour otherwise) then blank to black between beats so +// Mode 0 = sweeping marker; mode 1 = beat-flash dot: light on each beat (accent color +// on the bar-1 downbeat, beat color otherwise) then blank to black between beats so // each beat reads as a distinct flash. #define DOT_FLASH_ON_MS 70 static void render_phase_indicator(const TempoSnapshot &ts, uint32_t now) { diff --git a/X32Link/transport_launch.h b/X32Link/transport_launch.h index 39f2835..4b4db55 100644 --- a/X32Link/transport_launch.h +++ b/X32Link/transport_launch.h @@ -5,7 +5,7 @@ // grid, so gear started from the web UI lands in time with the Link session. // // Start is quantized, Stop is immediate. That asymmetry is deliberate: it is -// Ableton's own launch behaviour and what muscle memory expects. +// Ableton's own launch behavior and what muscle memory expects. // // One instance per clock output (P4-010 gives each output its own division, // phase and swing; ESP-011 gives each its own transport, so the drum machine diff --git a/X32Link/web_config.cpp b/X32Link/web_config.cpp index 7766ab4..b8f3530 100644 --- a/X32Link/web_config.cpp +++ b/X32Link/web_config.cpp @@ -22,7 +22,7 @@ extern AppConfig g_config; extern void config_save(const AppConfig*); // ARC-022: /live marks this dirty; web_config_handle() writes the blob once the edits -// settle. Marked rather than written because config is ONE nvs blob -- a colour picker +// settle. Marked rather than written because config is ONE nvs blob -- a color picker // dragged across the wheel emits a POST per frame. static ConfigPersist s_persist; // Live tempo comes from the tempo_snapshot seam (ARC-001): one atomic read @@ -128,7 +128,7 @@ form .row:nth-child(2){animation-delay:.10s}form .row:nth-child(3){animation-del
-
beat-flash dot vs sweep wheel · colours also set the beat LED
+
beat-flash dot vs sweep wheel · colors also set the beat LED
BEAT
BAR1
@@ -378,19 +378,19 @@ static void handle_status() { * led It really does have one (a NeoPixel beat LED). */ static void handle_config_json() { /* KsCaps.led means an ADDRESSABLE strip -- brightness, mode, fade, beat/accent - * colour. That is only true on the LED_RGB builds (the QT Py's NeoPixel). The + * color. That is only true on the LED_RGB builds (the QT Py's NeoPixel). The * headless board's LED is a plain on/off pin, and the Waveshare touch board has no * MCU-controlled LED at all (LED_NONE). * * So this is #if'd, not hardcoded true. Claiming `led` on a board with a bare - * indicator would make a client draw colour pickers and a brightness slider for - * hardware that cannot honour any of them -- which is the ENTIRE point of caps, and + * indicator would make a client draw color pickers and a brightness slider for + * hardware that cannot honor any of them -- which is the ENTIRE point of caps, and * the same lie as reporting a metronome on a board with no speaker. * * Capabilities are a property of the BUILD. Solder a WS2812 on and rebuild with * LED_RGB, and the LED section appears with no client change at all. */ /* ESP-037: the QT Py NeoPixel was the only addressable-strip X32Link and it's gone, - * so no X32Link build has a controllable colour strip: the Super Mini's LED is a + * so no X32Link build has a controllable color strip: the Super Mini's LED is a * bare on/off pin, the Waveshare has none. led = false. */ static const KsCaps caps = { .metronome = false, // no speaker @@ -484,11 +484,11 @@ static void handle_save() { } // LNK-037: apply live-safe fields to the running config with no reboot, mirroring the -// P4's /live. touch_display reads g_config each render, so the phase dot mode/colours +// P4's /live. touch_display reads g_config each render, so the phase dot mode/colors // update instantly. // // ARC-022: these edits are now KEPT. This handler had the same bug as the P4's and the -// Touch's -- it applied the value and returned, so a colour or quantum set here looked +// Touch's -- it applied the value and returned, so a color or quantum set here looked // right until the next power cycle and then quietly wasn't. It marks the config dirty // now; web_config_handle() writes the blob once the edits settle. static void handle_live() { diff --git a/docs/adr/0008-p4-web-ui-is-the-brand-reference.md b/docs/adr/0008-p4-web-ui-is-the-brand-reference.md index a915909..1d4e610 100644 --- a/docs/adr/0008-p4-web-ui-is-the-brand-reference.md +++ b/docs/adr/0008-p4-web-ui-is-the-brand-reference.md @@ -6,7 +6,7 @@ Status: accepted ## Context Three firmwares serve a rack-panel web config page and share one design system: -`ui_chrome.{h,c}` (ARC-017) owns the *look* (colour tokens, panel, screws, tempo +`ui_chrome.{h,c}` (ARC-017) owns the *look* (color tokens, panel, screws, tempo glass, switches, Save button) and the client plumbing (`poll()`, `showBpm`, `postLive`). Each firmware keeps its **own form** — the fields and the page-specific CSS/markup that extend the chrome — because the feature sets differ @@ -39,7 +39,7 @@ scaled to its own feature set: master toggle and reveal when it flips on (`syncSect`). - **Structured `.fld` fields** with `.pre` prefix labels — never inline `style=`. - Help text is a **token-driven class** (`.hint` / equivalent), never a - repurposed `.cap` with inline colour/spacing. + repurposed `.cap` with inline color/spacing. - A **responsive desktop treatment** (two-column form, 4-across status "meter bridge") when the form is long enough to benefit. diff --git a/docs/adr/0009-converge-the-clock-box-on-esp-idf.md b/docs/adr/0009-converge-the-clock-box-on-esp-idf.md index d79f5e0..785cc23 100644 --- a/docs/adr/0009-converge-the-clock-box-on-esp-idf.md +++ b/docs/adr/0009-converge-the-clock-box-on-esp-idf.md @@ -46,7 +46,7 @@ a *related* product as if it were the repo. **4. ESP-024 is a forcing function.** Its thesis is hardware-timed RMT edges and `IRAM_ATTR` placement. The Arduino core ships a **fixed sdkconfig** — no `menuconfig` — so the knobs that -decide jitter (IRAM, cache behaviour, interrupt allocation) are not reachable. Doing that work +decide jitter (IRAM, cache behavior, interrupt allocation) are not reachable. Doing that work on the framework that hides the controls, and then doing it a second time on the P4, is the worst of both. The timing work should happen once, on the framework that exposes the controls, in the writer that ships. diff --git a/docs/adr/0015-extract-ks-core-in-repo.md b/docs/adr/0015-extract-ks-core-in-repo.md index 37eb2a3..22a92cd 100644 --- a/docs/adr/0015-extract-ks-core-in-repo.md +++ b/docs/adr/0015-extract-ks-core-in-repo.md @@ -91,7 +91,7 @@ justify B's throwaway churn. Either way the destination (`shared/`) and manifest same; only the timing differs. **Decision (2026-07-16): Option A.** The `shared/` extraction is bundled with the Touch's -ESP-IDF convergence (ADR-0009 / ESP-026): when the Touch's 53 symlinks are deleted in favour +ESP-IDF convergence (ADR-0009 / ESP-026): when the Touch's 53 symlinks are deleted in favor of ESP-IDF path refs, the shared source relocates to `shared/` in the same change, so the move is symlink-neutral. This ADR therefore does not schedule a standalone move — it makes `shared/` the agreed destination and folds the mechanics into ESP-026's landing. The @@ -164,5 +164,5 @@ migration plan below is the checklist for that combined step. (and re-pointed Touch symlinks) until the Arduino consumers reach ESP-IDF. - The separate `ks-core` **repo** still waits for its own trigger; this ADR does not create one. Each later extraction gets its own ADR when its trigger fires. -- No behaviour changes: this is a move + re-point, gated on the full suite and every firmware +- No behavior changes: this is a move + re-point, gated on the full suite and every firmware target still building. diff --git a/docs/plans/2026-07-10-kitchensynctouch-sketch-design.md b/docs/plans/2026-07-10-kitchensynctouch-sketch-design.md index 51b69a4..64de04a 100644 --- a/docs/plans/2026-07-10-kitchensynctouch-sketch-design.md +++ b/docs/plans/2026-07-10-kitchensynctouch-sketch-design.md @@ -94,7 +94,7 @@ never on USB-host presence — the RC-505 runs headless. 3. **Trimmed web + captive portal + OTA.** `ktouch_web` (wifi + clock + transport + quantum; reuses `ui_chrome_css/js` + `ui_result_page/ui_update_page`). AP `KSTouch-Config`. `/status`, `/update`. Delete Inc1 compile-time creds. -4. Polish: touch quantum ±, phase-dot colour, mDNS (ESP-014), OTA rollback. +4. Polish: touch quantum ±, phase-dot color, mDNS (ESP-014), OTA rollback. ## Prereq refactor in X32Link (Inc2) diff --git a/tasks/ARC-013.md b/tasks/ARC-013.md index bd0cc2c..f400f6f 100644 --- a/tasks/ARC-013.md +++ b/tasks/ARC-013.md @@ -22,7 +22,7 @@ are untestable because they only exist next to `esp_wifi_*` calls. The *same* policy is hand-reimplemented on the S3 in a different (blocking) shape (`X32Link/X32Link.ino:181-233`), with the 30 s budget as a raw `> 30000` literal. `wifi_fallback.h:16` even documents "must stay in sync by hand." The "retry forever -after first IP" rule has no S3 analogue — behaviour already differs. +after first IP" rule has no S3 analogue — behavior already differs. Deletion test: delete the 4 statics and nothing reconstructs the policy — it's a state machine that wants to be a module, not scattered flags. @@ -40,6 +40,6 @@ rule (subsumes `wifi_fallback`). - Host-test the full transition table (`test/test_wifi_conn_policy.c`); the fallback test already proves the pattern. -- Migrate both targets; removes the "keep in sync by hand" seam. Behaviour on hardware +- Migrate both targets; removes the "keep in sync by hand" seam. Behavior on hardware should be unchanged — smoke-test STA connect, mid-session drop (retry-forever), and first-boot/no-AP fallback on the P4; the blocking connect + AP fallback on the S3. diff --git a/tasks/ARC-014.md b/tasks/ARC-014.md index 4a30bed..b8b30ed 100644 --- a/tasks/ARC-014.md +++ b/tasks/ARC-014.md @@ -34,5 +34,5 @@ concentrates complexity, doesn't just move it. - The pump can be host-tested against a fake ops (in-memory socket) — the poll sequence becomes testable for the first time. -- No behaviour change intended; diff the two current polls first to confirm they're +- No behavior change intended; diff the two current polls first to confirm they're byte-equivalent before collapsing (they should be after this session's changes). diff --git a/tasks/ARC-015.md b/tasks/ARC-015.md index 735c843..343cc85 100644 --- a/tasks/ARC-015.md +++ b/tasks/ARC-015.md @@ -38,5 +38,5 @@ gating become host-testable; the loop drops to thin I/O. - Sequence with ARC-016 (live-config snapshot) — `ks_tick_step` should take an atomically-snapshotted cfg, which is cleaner once the seqlock exists. -- `test/test_ks_tick.c` covers the fold + fan-out plan. No behaviour change intended; +- `test/test_ks_tick.c` covers the fold + fan-out plan. No behavior change intended; smoke-test on hardware (LEDs, sound, MIDI out) after. diff --git a/tasks/ARC-017.md b/tasks/ARC-017.md index f5fe5c0..5df33d5 100644 --- a/tasks/ARC-017.md +++ b/tasks/ARC-017.md @@ -155,7 +155,7 @@ assembly drops the largest heap allocation on the S3's web path entirely. - 2026-07-08 — Filed after an audit of the Arduino/ESP-IDF split. The pure-C sharing (1,024 lines compiled into both via `X32LINK_DIR`) is holding; the web chrome is the one place ADR-0003's discipline was never applied, and the - `--mut` colour drift is the first symptom. + `--mut` color drift is the first symptom. - 2026-07-09 — Repointed the sharing mechanism. The ticket originally said `ui_chrome.c` would reach the second Arduino sketch by symlink (ADR-0003 / ADR-0006); `X32MidiClock/` is being deleted, so CMake `SRCS` is now the only @@ -191,7 +191,7 @@ copies that had already drifted unnoticed. | `.unit{animation:rise}` | S3 only | S3's own block | an entry animation, not chrome | So the P4 *gains* the switch glow it had lost, and the S3's labels get the lighter -grey. Both were verified as deliberate, not assumed. +gray. Both were verified as deliberate, not assumed. **Chunked assembly.** Neither page copies itself any more. `handle_root()` / `root_handler()` send the head, then `ui_chrome_css()` straight from flash, then diff --git a/tasks/ARC-018.md b/tasks/ARC-018.md index 8396e77..625c05a 100644 --- a/tasks/ARC-018.md +++ b/tasks/ARC-018.md @@ -46,7 +46,7 @@ forked instead of joining that mechanism — an oversight, not a decision. Compile the single X32Link source into KitchenSyncTouch the same way the Link stack already arrives — committed relative symlinks (mode 120000) into `../X32Link/`, or the arduino-cli sketch build's existing shared-path scheme — -and delete the forked copies. No behaviour change: the bytes are identical today, +and delete the forked copies. No behavior change: the bytes are identical today, so this is a build-graph change plus five deletions. Confirm first that no copy carries a Touch-only edit (grep + `diff` in the diff --git a/tasks/ARC-020.md b/tasks/ARC-020.md index 3e2b602..6264155 100644 --- a/tasks/ARC-020.md +++ b/tasks/ARC-020.md @@ -36,14 +36,14 @@ Touch and X32 **hand-serialize every field into NVS twice**. The gap is stark: test can exist on host** because `.cpp` NVS glue is excluded from the test harness and there is no Preferences fake (grep: no fake/stub/mock anywhere). -**Deletion test:** the persistence behaviour must stay, but the field-by-field +**Deletion test:** the persistence behavior must stay, but the field-by-field enumeration inside `app_config_nvs.cpp` is removable duplication — KitchenSync proves the same job needs zero per-field NVS lines. ## Solution Adopt KitchenSync's model on Touch and X32: save the config struct as one -versioned blob, and move all accept/reject/migrate judgement into a pure +versioned blob, and move all accept/reject/migrate judgment into a pure `config_decode(blob, len, ver)` compiled into the host suite. The `.cpp` NVS glue shrinks to blob get/set + commit and "does no judging of its own". Add the `_Static_assert(sizeof(...) == N)` guard so a layout change is a compile error. @@ -108,7 +108,7 @@ defaults-reset*, because Dan's Touch (ESP-021) OTAs across this change and a sil wipe of his WiFi creds and nudge trim is unrecoverable in the field. So `config_load` reads the blob; if there is no blob but the **old field-keys** are present, it reads them once, runs them through `config_validate` (a migration is not -a licence to skip the guard), and writes the blob back. Next boot takes the blob path +a license to skip the guard), and writes the blob back. Next boot takes the blob path and the legacy keys are never read again. A fresh flash -- no blob, no legacy keys -- just gets defaults. diff --git a/tasks/ARC-022.md b/tasks/ARC-022.md index 2e7dd87..41be38a 100644 --- a/tasks/ARC-022.md +++ b/tasks/ARC-022.md @@ -88,7 +88,7 @@ so it is the part that gets tested. 1. As a performer, I want the outputs I set to *not* follow Link to still be set that way after a power cycle, so that my rig comes up the way I left it. -2. As a performer, I want per-output swing to survive a reboot, so that a groove I dialled in +2. As a performer, I want per-output swing to survive a reboot, so that a groove I dialed in at soundcheck is still there at the downbeat. 3. As a performer, I want per-output phase nudge to survive a reboot, so that I do not re-align every device against every other device at every gig. @@ -102,7 +102,7 @@ so it is the part that gets tested. 8. As a performer, I want metronome volume and accent to survive a reboot. 9. As a performer, I want LED brightness to survive a reboot, so that a unit I dimmed for a dark stage does not come back up at full brightness in front of an audience. -10. As a performer, I want LED mode, fade, and beat/accent colours to survive a reboot. +10. As a performer, I want LED mode, fade, and beat/accent colors to survive a reboot. 11. As a KitchenSync Touch owner, I want my nudge setting to survive a reboot, so that the device stays aligned to the gear it drives. 12. As a KitchenSync Touch owner, I want my screen brightness to survive a reboot, so that a @@ -125,7 +125,7 @@ so it is the part that gets tested. 20. As a developer, I want one persistence policy shared by both firmwares, so that fixing it on one device cannot leave the other broken. 21. As a developer, I want the "should this write now?" decision to be pure and host-tested, - so that its timing behaviour is provable without a bench, a device, or a power cycle. + so that its timing behavior is provable without a bench, a device, or a power cycle. 22. As a developer adding a new live-editable field, I want it persisted automatically by virtue of being live-safe, so that the next field added cannot silently reintroduce this exact bug. @@ -215,15 +215,15 @@ applied-but-not-saved is zero (story 18). ## Testing Decisions -**A good test here asserts observable behaviour through the public interface, and would survive +**A good test here asserts observable behavior through the public interface, and would survive a rewrite of the internals.** It says *"a burst of edits produces one write, after the edits stop"* -- not *"`dirty` was set to true."* The debounce module's public surface is `mark`/`due`/`reset` and a clock the test controls, which is the whole point of making time an -input rather than something the module reads: the timing behaviour is provable with no device, +input rather than something the module reads: the timing behavior is provable with no device, no flash, and no power cycle. **Module under test:** the new pure config-persist policy in the shared layer. Host-tested, -under `test/`, alongside the existing pure suites. Behaviours: +under `test/`, alongside the existing pure suites. Behaviors: - A single edit does not write immediately; it writes once the quiet window elapses. - A burst of edits within the quiet window produces **exactly one** write, after the last one diff --git a/tasks/ARC-025.md b/tasks/ARC-025.md index ea9c8ba..fd7c87f 100644 --- a/tasks/ARC-025.md +++ b/tasks/ARC-025.md @@ -24,7 +24,7 @@ What is left is not a bug. It is the architecture: | KitchenSync Touch / X32Link (S3) | **~500 µs** | All three firmwares generate MIDI clock from a **1 ms FreeRTOS task** (`vTaskDelayUntil`, tick -rate 1000 Hz). A pulse can only be emitted on a tick boundary. **A 1 ms quantisation grid cannot +rate 1000 Hz). A pulse can only be emitted on a tick boundary. **A 1 ms quantization grid cannot produce sub-tick placement**, so ~300-500 µs of jitter is not something we are doing wrong -- it is the direct, arithmetic consequence of the mechanism. @@ -67,11 +67,11 @@ punish a guess. Before writing the timer path: - [ ] Establish the current baseline on the analyzer, at a known BPM, under load. (The rig exists: P4 DIN mirror on GPIO20, downbeat strobe on GPIO21; MIDI jack **pin 5 = data, pin 2 = ground** -- pin 4 is +5 V and reads flat high, which has already cost one capture.) -- [ ] Confirm the hypothesis directly: **is the jitter distribution quantised to ~1 ms?** If the +- [ ] Confirm the hypothesis directly: **is the jitter distribution quantized to ~1 ms?** If the pulses are not landing on tick boundaries, the premise is wrong and this ticket should be closed rather than built. Do not skip this step. The ESP-018 ticket shipped with a **wrong premise** about core assignment and only measurement caught it. -- [ ] Then prototype on **one** device, one output, DIN only. Measure. Only generalise if the +- [ ] Then prototype on **one** device, one output, DIN only. Measure. Only generalize if the number actually moves. ## The question nobody has asked yet @@ -82,7 +82,7 @@ the skew-compensation feature was **killed rather than built**. That was a good measurement deleted a feature. The same discipline applies here. If a hardware timer buys 331 µs → 20 µs and no listener, no -sequencer, and no analyzer-visible artefact changes, then this is an engineering vanity metric and +sequencer, and no analyzer-visible artifact changes, then this is an engineering vanity metric and should be closed as such. **But we do not know**, and the honest reason to build it is that it is the last structural diff --git a/tasks/CLK-001.md b/tasks/CLK-001.md index ca2e91f..514b863 100644 --- a/tasks/CLK-001.md +++ b/tasks/CLK-001.md @@ -20,7 +20,7 @@ authoritative tempo source and everything else locks to it. Today the family is a *follower-and-bridge*: it receives a Link session and re-emits MIDI clock (and, on the roadmap, analog). The north star is a -*clock hub* — put it at the centre of a rig and every other box, modular or +*clock hub* — put it at the center of a rig and every other box, modular or software, syncs to it, whether the tempo originates from Link, from an external clock, from audio, or from the device's own internal/tap clock. diff --git a/tasks/ESP-011.md b/tasks/ESP-011.md index 2b3a51e..f6bbbcf 100644 --- a/tasks/ESP-011.md +++ b/tasks/ESP-011.md @@ -94,7 +94,7 @@ want it. Options, cheapest first: 1. **Manual only when Link isn't driving.** If no StartStopState has been seen (`link_proto_start_stop_seen() == false`), the web buttons own transport. - Once a peer publishes transport, Link wins and the buttons grey out. Simple, + Once a peer publishes transport, Link wins and the buttons gray out. Simple, no conflict, matches how the metronome already behaves. 2. **Manual always overrides**, until the next Link transport edge. More flexible, but two masters -- the classic source of "why did my gear stop". diff --git a/tasks/ESP-014.md b/tasks/ESP-014.md index 12ba47e..84b3d2c 100644 --- a/tasks/ESP-014.md +++ b/tasks/ESP-014.md @@ -45,8 +45,8 @@ Two differences from ESP-012 worth knowing before starting: discoverable via the `_http._tcp` service anyway. The name derivation is the same shape as `ks_hostname.{h,c}` (pure, -host-tested). Either generalise that module to take a prefix, or copy it -- -ARC-005/ARC-010 would prefer generalising, and it is a two-line change. +host-tested). Either generalize that module to take a prefix, or copy it -- +ARC-005/ARC-010 would prefer generalizing, and it is a two-line change. ## Scope @@ -80,4 +80,4 @@ ARC-005/ARC-010 would prefer generalising, and it is a two-line change. > *This was generated by AI during triage.* -**`ready-for-agent`.** Small, fully specified. Generalise `ks_hostname` to take a prefix (two-line change, the ticket's preferred option) rather than copying. Check `ESPmDNS` flash cost against the ~81%-full 1.25MB sketch partition BEFORE wiring it in — if it does not fit, stop and report; do not squeeze. Hardware ACs (.local resolution, two-unit collision): NOT VERIFIED convention. +**`ready-for-agent`.** Small, fully specified. Generalize `ks_hostname` to take a prefix (two-line change, the ticket's preferred option) rather than copying. Check `ESPmDNS` flash cost against the ~81%-full 1.25MB sketch partition BEFORE wiring it in — if it does not fit, stop and report; do not squeeze. Hardware ACs (.local resolution, two-unit collision): NOT VERIFIED convention. diff --git a/tasks/ESP-016.md b/tasks/ESP-016.md index 8bdbc41..e7598c9 100644 --- a/tasks/ESP-016.md +++ b/tasks/ESP-016.md @@ -163,7 +163,7 @@ Firmware Engineer). Increments: (1) boots + Link + DIN/USB clock + tempo display; (2) transport touch screen (PLAY/STOP -> bar-quantized 0xFA/0xFC); (3) trimmed web + captive portal + -OTA; (4) polish (mDNS/ESP-014, quantum, colours). +OTA; (4) polish (mDNS/ESP-014, quantum, colors). Bench findings so far (2026-07-10): flashed the S3-Touch; the blank screen was a build-target bug (build_opt.h was `-DBOARD_QTPY_ESP32S3`, no display) -- fixed by @@ -213,9 +213,9 @@ survives the boot (no composite drop). Two states (play/stop) = ONE full-screen toggle, not two buttons. Tap anywhere: STOPPED -> ARMING (amber, next bar) -> PLAYING (green); tap again -> STOPPED. One -huge target (fat-finger-proof), the screen colour IS the state, and it moots any +huge target (fat-finger-proof), the screen color IS the state, and it moots any left/right touch-mirror calibration. Pure logic is `ktouch_toggle_intent(state)` -(host-tested); the writer publishes launch state for the display to colour + toggle. +(host-tested); the writer publishes launch state for the display to color + toggle. **Trigger mode (web setting, Inc3):** `play_on_release` -- fire the toggle on touch-down (digital-DJ) or on release (turntable-DJ: hold, release to let the beat diff --git a/tasks/ESP-018.md b/tasks/ESP-018.md index 5128527..a94580f 100644 --- a/tasks/ESP-018.md +++ b/tasks/ESP-018.md @@ -72,7 +72,7 @@ overrun, and the log delayed the next tick, which logged again -- 3945 phantom - [ ] Tick-health probe in the Touch writer task, reported via `/status` - [ ] Root cause named with a measurement, not a hypothesis - [ ] Analyzer capture on GPIO11 over >=30s under web load: **0 stalls, 0 bursts** -- [ ] Clock jitter characterised. Today the Touch is **533 us stdev (min 15.4 / max +- [ ] Clock jitter characterized. Today the Touch is **533 us stdev (min 15.4 / max 24.4 ms)** vs the P4's **331 us (+-1 ms)** -- the S3 is measurably looser and we do not yet know why. Decide whether that is acceptable for the RC-505 or needs fixing too. diff --git a/tasks/ESP-019.md b/tasks/ESP-019.md index 0fe71a6..ad5f846 100644 --- a/tasks/ESP-019.md +++ b/tasks/ESP-019.md @@ -21,9 +21,9 @@ This is a performance tool. A mouse hunting for a toggle mid-set is the wrong in ## Why number keys Direct: one key = one output, no modifier, no chord, no mode. It maps to how the outputs -are already labelled on screen (Clock Out **1**..**4**), so there is nothing new to learn. +are already labeled on screen (Clock Out **1**..**4**), so there is nothing new to learn. -## Behaviour +## Behavior - **`1`..`4`** -> toggle that output's transport. Identical to clicking its toggle: stopped -> PLAY (arms, fires on the bar line); armed/playing -> STOP (immediate). @@ -67,7 +67,7 @@ same key map. ## Acceptance criteria - [x] `1`..`4` toggle Clock Out 1..4; `space` toggles all -- [x] Key and click go through the SAME path (`/transport?out=N&play=…`) -- one behaviour, +- [x] Key and click go through the SAME path (`/transport?out=N&play=…`) -- one behavior, not two implementations that can drift - [x] A key is INERT while focus is in any text input / select / textarea - [x] A key on a Link-owned output does not act, and SAYS why (does not fail silently) @@ -140,7 +140,7 @@ bar strobe: 5 rising edges, period mean 1.9980 s (expect 2.0000 s @120 BPM 4/4) DIN : 0xFA at 2.667299 s | bar line 2.666961 s -> START +0.338 ms from the bar line USBA : 0xFA at 2.667487 s | bar line 2.666961 s -> START +0.526 ms from the bar line USB-A lags the DIN wire by +0.188 ms (USB frame scheduling) -next bar is 1999.7 ms later -- it landed on THIS bar, not a neighbouring one +next bar is 1999.7 ms later -- it landed on THIS bar, not a neighboring one ``` **+0.338 ms is not merely "small", it is the structurally correct value.** The strobe rises on diff --git a/tasks/ESP-022.md b/tasks/ESP-022.md index d7f09fb..14936d1 100644 --- a/tasks/ESP-022.md +++ b/tasks/ESP-022.md @@ -25,7 +25,7 @@ no white), ~850 mA budget, ~43 fps. That is the reference target. ## Why - The LED visual-metronome logic already exists on the P4 (P4-018 bar-position - chase, P4-019 web customization, LNK-039 dot colours). This ticket productizes + chase, P4-019 web customization, LNK-039 dot colors). This ticket productizes it as a **standalone ESP32 node** rather than a P4 peripheral. - It's the visual output of **CLK-001** (the universal clock hub): "follow Link → paint the beat" on hardware that costs a few dollars. @@ -36,7 +36,7 @@ no white), ~850 mA budget, ~43 fps. That is the reference target. - **Link receive** — `link_protocol.c` (join session, tempo/phase). Portable. - **Beat/phase → LED** — the visual-metronome logic from P4-018/019 + the phase - math (`led_phase`, dot colours LNK-039). Pure, host-tested. + math (`led_phase`, dot colors LNK-039). Pure, host-tested. - **WS2812 driver** — RMT-based NeoPixel output already used on the QT Py path. ## What is new (the lift) @@ -54,7 +54,7 @@ no white), ~850 mA budget, ~43 fps. That is the reference target. flash over the air: WLED `/update` (upload `.bin`) or ArduinoOTA/`espota` (port 3232). Produce an OTA-compatible image + document the one-command push. 4. **Minimal config surface.** Reuse `ui_chrome` for a small web page (WiFi, LED - count/pin, brightness cap, pattern/colour) — per ADR-0008 mirror the P4 look. + count/pin, brightness cap, pattern/color) — per ADR-0008 mirror the P4 look. ## Acceptance Criteria diff --git a/tasks/ESP-024.md b/tasks/ESP-024.md index 2b4ea59..c067b76 100644 --- a/tasks/ESP-024.md +++ b/tasks/ESP-024.md @@ -30,7 +30,7 @@ spread 1124.0 us peak-to-peak ``` **The spread is one millisecond.** That is the 1 ms writer task rendered visible on the wire — -the edge can only land on a tick boundary, so the jitter IS the tick quantisation. Every jitter +the edge can only land on a tick boundary, so the jitter IS the tick quantization. Every jitter figure previously in this repo was the task's own view of itself; this is the first taken from the DIN wire, and it confirms the thesis below outright. @@ -76,13 +76,13 @@ exactly one embedded platform layer and it is ESP32. Do not trade that away on a ## The ceiling to keep straight -DIN MIDI is 31250 baud, so a clock byte takes **320 us to serialise**. Two things follow, +DIN MIDI is 31250 baud, so a clock byte takes **320 us to serialize**. Two things follow, and they are easy to conflate: - That 320 us is **fixed latency, not jitter.** It is constant, so it does not widen the distribution. The receiver clocks off the **start bit** — so start-bit jitter below 320 us is still real and still worth removing. -- It becomes a **quantiser** the moment a clock byte queues behind other MIDI traffic in +- It becomes a **quantizer** the moment a clock byte queues behind other MIDI traffic in the UART FIFO. Then the clock byte is delayed by up to a full byte time, per byte ahead of it. That is an architecture fix (clock bytes jump the queue), not a chip fix. @@ -118,7 +118,7 @@ RMT cannot drive a UART, so the DIN path needs a different fix: - Hand the `0xF8` to the UART from a **GPTimer ISR marked `IRAM_ATTR`**, so a flash cache miss can never delay the edge. ISR latency is a few us and deterministic. - **Give clock bytes priority over other MIDI traffic** in the FIFO — this is the - quantiser named above, and it is invisible on an idle wire but ugly under real traffic. + quantizer named above, and it is invisible on an idle wire but ugly under real traffic. If that is not enough, the escalation is to **encode the MIDI frame as an RMT waveform**: a UART byte is just 10 bit-times of 32 us, which RMT can emit with ~50 ns placement, @@ -183,7 +183,7 @@ it does not prove the RC-505 (Dan's actual box, which we also do not own) agrees - [ ] Baseline re-taken on the current build with `linkcli` in-session and the tempo read from `/status` (not hardcoded) - [ ] Analog trigger edges generated by RMT; measured on the analyzer -- [ ] DIN byte handed off from an `IRAM_ATTR` hardware-timer ISR; clock bytes prioritised +- [ ] DIN byte handed off from an `IRAM_ATTR` hardware-timer ISR; clock bytes prioritized in the UART FIFO; measured on the analyzer **and** at the ER-1's audio out - [ ] Before/after table: stdev, p99, min/max, stalls, dropped — idle **and** under web load, >= 10 min each diff --git a/tasks/ESP-026.md b/tasks/ESP-026.md index e1ebab0..eeb8705 100644 --- a/tasks/ESP-026.md +++ b/tasks/ESP-026.md @@ -84,7 +84,7 @@ See [ADR-0009](../docs/adr/0009-converge-the-clock-box-on-esp-idf.md). ESP-024's whole thesis is **hardware-timed RMT edges and `IRAM_ATTR` placement**. The Arduino core ships a **fixed sdkconfig** — there is no `menuconfig` — so the knobs that -actually decide jitter (IRAM, cache behaviour, interrupt allocation) are not reachable +actually decide jitter (IRAM, cache behavior, interrupt allocation) are not reachable from it. Doing that timing work on the framework that hides the controls, and then doing it a diff --git a/tasks/ESP-030.md b/tasks/ESP-030.md index 627e608..027368c 100644 --- a/tasks/ESP-030.md +++ b/tasks/ESP-030.md @@ -24,7 +24,7 @@ Delivered in two parts and fully verified: passwords as `pass_set` only, and capability flags so absent hardware is never faked. - **The storage + data model** converged in **ESP-042** (PR #130): the Touch adopted `KsConfig`, `AppConfig` was retired, and the migration-first hazard this ticket flags in - bold was honoured exactly — the AppConfig→KsConfig migration was written and host-tested + bold was honored exactly — the AppConfig→KsConfig migration was written and host-tested FIRST (`test_ktouch_migrate`), and the bench Super Mini kept its WiFi (`Skeyelab`) + a non-default 143.000 BPM across a real upgrade (confirmed by NVS dump). `quantum_beats` was promoted to the shared schema per item 4. @@ -91,8 +91,8 @@ unit that is an annoyance. On a unit at a venue it is a dead box. 3. **Touch adopts `KsConfig`**, sharing `ks_config.c` / `ks_config_json.c` / `ks_form.c` the way it already shares 46 other modules. Its web form and display code move to the shared model. 4. **Find a home for the fields that are genuinely device-specific.** `brightness` is an LCD the P4 - does not have; `play_on_release` is a touch-screen behaviour. `quantum_beats` is arguably useful - on BOTH (launch quantisation is a Link concept, not a screen concept) — consider promoting it to + does not have; `play_on_release` is a touch-screen behavior. `quantum_beats` is arguably useful + on BOTH (launch quantization is a Link concept, not a screen concept) — consider promoting it to the shared schema rather than treating it as Touch-only. 5. **Hardware a device does not have still must not be faked.** If Touch carries `metronome_enable` in a shared struct, the DEVICE must not claim it can click, and the app must not draw a control diff --git a/tasks/ESP-034.md b/tasks/ESP-034.md index ebfb93f..9c9af1b 100644 --- a/tasks/ESP-034.md +++ b/tasks/ESP-034.md @@ -23,7 +23,7 @@ reading the source and calling it verified. That is exactly how both bugs shippe `ks_status.h` defines it as one: *"link_owns is true once a Link peer has published StartStopState ... when link_owns is true the manual PLAY/STOP buttons are ignored, so -the UI greys them."* The iOS app takes that literally and **stops sending the tap**. +the UI grays them."* The iOS app takes that literally and **stops sending the tap**. The Touch was passing `link_proto_start_stop_seen()` — a STICKY flag ("has any peer EVER published a StartStopState", latched until the last peer leaves) — while @@ -43,7 +43,7 @@ it never knew to send. On the Touch this was not hypothetical: a POST carrying only `wifi`/`clk0_*` set `clock_enable` AND `transport_enable` to 0, killing play/stop on the touchscreen. And the iOS app's `saveFormFields` does not emit every P4 checkbox either — if `ks_web.cpp`'s save -handler has the same absent-means-off behaviour, **saving WiFi from the app will disable +handler has the same absent-means-off behavior, **saving WiFi from the app will disable whatever the app has never heard of.** The Touch's fix: absent = off applies ONLY when the sender declares the whole form diff --git a/tasks/ESP-039.md b/tasks/ESP-039.md index 5017e71..ea61142 100644 --- a/tasks/ESP-039.md +++ b/tasks/ESP-039.md @@ -30,7 +30,7 @@ files, and half of it has no owner and no test. `target=CONFIG_IDF_TARGET` identical at every site, only `model` varying. It has **zero test coverage** anywhere. -`ESP-014` already names the first half of the fix: *"Generalise `ks_hostname` to take a prefix +`ESP-014` already names the first half of the fix: *"Generalize `ks_hostname` to take a prefix … rather than copying."* This ticket does that and folds in the TXT tuple. This is the canonical shallow-duplication case: adding one TXT key (as ESP-031 did) means editing @@ -45,7 +45,7 @@ sketch root (ADR-0006/0007), symlinked into `KitchenSyncTouch/`, compiled-by-pat ```c // device_identity.h typedef struct { const char* dev; const char* model; const char* target; const char* fw; } DeviceIdentity; -void device_hostname(const char* prefix, const uint8_t mac[6], char* out, size_t cap); // generalises ks_hostname +void device_hostname(const char* prefix, const uint8_t mac[6], char* out, size_t cap); // generalizes ks_hostname // (advertise the TXT tuple via one helper so the key set lives in ONE place) ``` diff --git a/tasks/ESP-040.md b/tasks/ESP-040.md index 11a8c6b..eb52ea6 100644 --- a/tasks/ESP-040.md +++ b/tasks/ESP-040.md @@ -70,7 +70,7 @@ ONE form discipline without X32Link adopting `KsConfig`. `handle_live` was alrea ## Progress -- `app_config.c`: `app_config_set_kv` (shared + alias keys, blank-keeps-current strings, colours) and +- `app_config.c`: `app_config_set_kv` (shared + alias keys, blank-keeps-current strings, colors) and `x32_form_merge` (patch vs full-form pre-clear of the two checkboxes; applies `model` before `fx_slot` so the slot validates against the right max). 8 new host tests. - `web_config.cpp`: `handle_save` routes through `merge_post_into` → `x32_form_merge`; the form gained diff --git a/tasks/ESP-043.md b/tasks/ESP-043.md index d4c6d93..83864dd 100644 --- a/tasks/ESP-043.md +++ b/tasks/ESP-043.md @@ -30,7 +30,7 @@ DevKit's lit buttons). This adds the onboard single RGB as a distinct capability - `config.h`: `HAS_STATUS_RGB` + `STATUS_RGB_GPIO 48`, gated to `BOARD_S3_SUPERMINI` only. NOT the Waveshare (GPIO48 is the panel's there) or the DevKit. -- `ktouch_status_rgb.{c,h}`: the STATE -> COLOUR decision as a PURE, host-tested function — +- `ktouch_status_rgb.{c,h}`: the STATE -> COLOR decision as a PURE, host-tested function — it does not live in `loop()` (same rule that keeps tempo/transport mapping out of views): - stopped -> dark (the light reflects transport; a stopped box says nothing even while its clock free-runs) @@ -44,12 +44,12 @@ DevKit's lit buttons). This adds the onboard single RGB as a distinct capability not a "loading" throb - `.ino`: a thin `status_rgb_tick()` seam samples `ktouch_transport_state()` / `ktouch_midi_locked()` / `ktouch_midi_beats()` / `quantum_beats`, calls the pure function, - and writes the LED only when the colour changes (loop runs every ~5 ms; `rgbLedWrite` + and writes the LED only when the color changes (loop runs every ~5 ms; `rgbLedWrite` reconfigures RMT each call). ## Acceptance criteria -- [x] The state->colour mapping is a pure, host-tested function (`test_status_rgb.c`, 5 cases: +- [x] The state->color mapping is a pure, host-tested function (`test_status_rgb.c`, 5 cases: stopped-dark, flash-on/off, green-vs-cyan, downbeat brighter, armed amber). - [x] Gated to the Super Mini build; the Waveshare and DevKit builds are unaffected. - [x] Verified on the bench Super Mini (OTA): amber while armed, cyan beat-flash while @@ -60,7 +60,7 @@ DevKit's lit buttons). This adds the onboard single RGB as a distinct capability The operator has purchased **buttons with an RGB LED inside each**. The Super Mini's transport surface is coming (config.h already flags `HAS_BUTTONS` as expected). Those per-button RGBs -should reuse THIS colour vocabulary (`ktouch_status_rgb` or a sibling) so the onboard light -and the button lights say the same thing — one state->colour owner across the box. New ticket +should reuse THIS color vocabulary (`ktouch_status_rgb` or a sibling) so the onboard light +and the button lights say the same thing — one state->color owner across the box. New ticket when the buttons are wired. See also `transport_led.c` (the DevKit's on/off button lamps), which the RGB buttons would supersede. diff --git a/tasks/ESP-044.md b/tasks/ESP-044.md index ccbe17c..7992a29 100644 --- a/tasks/ESP-044.md +++ b/tasks/ESP-044.md @@ -57,7 +57,7 @@ stronger, always-awake beacon), so the fix is unconditional rather than board-ga - Reflashed the bench Super Mini (MAC `3c:0f:02:e2:b0:d4`), NVS erased + compile-time WiFi seed blanked so it falls to `start_ap()`. Serial: `start=1 ... ch=1`, board stable. - **`KSTouch-Config` now appears on the phone** after a WiFi rescan; onboarding proceeds. This - was confirmed on hardware — the previous behaviour (invisible) is gone. + was confirmed on hardware — the previous behavior (invisible) is gone. ## Notes / follow-ups diff --git a/tasks/LNK-001.md b/tasks/LNK-001.md index 06022b4..520ed5f 100644 --- a/tasks/LNK-001.md +++ b/tasks/LNK-001.md @@ -13,7 +13,7 @@ updated_at: 2026-06-26 ## Goal Create the `esp32/X32Link/` Arduino sketch directory with a minimal WiFi -sketch and a `config.h` that centralises all runtime parameters. Verify the +sketch and a `config.h` that centralizes all runtime parameters. Verify the bare sketch compiles and connects to WiFi before any Link or OSC code is added. This task targets any ESP32-S3 board (XIAO ESP32-S3, ESP32-S3 Super Mini, etc.). @@ -71,7 +71,7 @@ arduino-cli upload \ **Serial quirk**: On ESP32-S3 with USB CDC, open the serial port with DTR=True, RTS=False BEFORE pressing RST. Hardware reset required after upload -(same behaviour as ESP-001 / XIAO ESP32-S3). +(same behavior as ESP-001 / XIAO ESP32-S3). **config.h constants** — all values as `#define`: diff --git a/tasks/LNK-003.md b/tasks/LNK-003.md index ea0b04d..7e0a80d 100644 --- a/tasks/LNK-003.md +++ b/tasks/LNK-003.md @@ -1,6 +1,6 @@ --- id: LNK-003 -title: WiFi connection and Link session initialisation +title: WiFi connection and Link session initialization status: done assignee: null priority: high diff --git a/tasks/LNK-005.md b/tasks/LNK-005.md index 3695b6d..5833753 100644 --- a/tasks/LNK-005.md +++ b/tasks/LNK-005.md @@ -12,7 +12,7 @@ updated_at: 2026-06-26 ## Goal -Open a UDP socket to the XR18 and write the current BPM as a normalised float +Open a UDP socket to the XR18 and write the current BPM as a normalized float to `/fx/{slot}/par/02` whenever `g_bpm_changed` is set. This is the final output stage — Link tempo → XR18 delay time. @@ -23,13 +23,13 @@ output stage — Link tempo → XR18 delay time. - [ ] `send_bpm_osc()` builds a valid OSC `/fx/{slot}/par/02 ,f {val}` packet - [ ] OSC packet byte layout verified against `src/X32lib/Xsprint.c` reference - [ ] XR18 FX slot delay time visibly changes in X-Air Edit when DAW BPM changes -- [ ] BPM range tested: 60, 120, 180 BPM → correct normalised values (see Notes) +- [ ] BPM range tested: 60, 120, 180 BPM → correct normalized values (see Notes) ## Notes **Firmware scope**: `esp32/X32Link/` only. Do NOT touch `esp32/X32_emulator/`. -**BPM → normalised float** (inline — cannot share `src/x32tap_logic.c`): +**BPM → normalized float** (inline — cannot share `src/x32tap_logic.c`): ```c // Mirrors src/x32tap_logic.c: ms_to_normalized(), TAP_MAX_MS = 3000 static float bpm_to_normalized(float bpm) { @@ -41,7 +41,7 @@ static float bpm_to_normalized(float bpm) { ``` **Expected values**: -| BPM | delay_ms | normalised | +| BPM | delay_ms | normalized | |------|----------|------------| | 60 | 1000 ms | 0.3333 | | 120 | 500 ms | 0.1667 | diff --git a/tasks/LNK-009.md b/tasks/LNK-009.md index 6389502..79d9344 100644 --- a/tasks/LNK-009.md +++ b/tasks/LNK-009.md @@ -53,7 +53,7 @@ same knobs under different prefixes: ## Constraints -- No behaviour change: same values reach the same call sites, just sourced +- No behavior change: same values reach the same call sites, just sourced per-input instead of via a fixed macro. - `DEFAULT_WIFI_*` / `DEFAULT_MIXER_IP` / `DEFAULT_MODEL` / `DEFAULT_FX_SLOT` already share names — leave them. diff --git a/tasks/LNK-011.md b/tasks/LNK-011.md index 9a55ccc..f9cea73 100644 --- a/tasks/LNK-011.md +++ b/tasks/LNK-011.md @@ -60,7 +60,7 @@ PublishDecision bpm_publisher_step(float bpm, bool active, uint32_t now_ms); - Pure C, no Arduino deps — buildable and testable on host (follow the existing `test/test_link_protocol.c` + weak-`millis` pattern). -- Preserve current behaviour exactly: bpm=0 ⇒ no send (mixer holds last value); +- Preserve current behavior exactly: bpm=0 ⇒ no send (mixer holds last value); send only on change-past-threshold OR per-bar refresh; respect min interval. ## Acceptance Criteria diff --git a/tasks/LNK-012.md b/tasks/LNK-012.md index 3e18e22..5718e59 100644 --- a/tasks/LNK-012.md +++ b/tasks/LNK-012.md @@ -34,7 +34,7 @@ no input-source field yet. `TempoSourceKind` enum from LNK-008. Default = Link. 2. **NVS** (`app_config_nvs.cpp`): load/save the new key `input_src`; default to Link when absent (first boot / post factory-reset). -3. **Web form** (`web_config.cpp`): add a labelled dropdown +3. **Web form** (`web_config.cpp`): add a labeled dropdown "Tempo source: Ableton Link / USB MIDI clock"; submit persists it like the other fields, then reboots (existing save→restart flow). 4. **Boot** (LNK-010 `setup`): `tempo_source_select(cfg.input_source)` before diff --git a/tasks/LNK-031.md b/tasks/LNK-031.md index 65ef607..d974552 100644 --- a/tasks/LNK-031.md +++ b/tasks/LNK-031.md @@ -68,7 +68,7 @@ in `link_measurement.c` stay as-is and are called by the session or the glue. timeouts emit END_FAIL; different-peer and due-timer both retrigger; trigger gated by `active`; pong carries prev-ghost / commits at READY. - [x] `link_measurement_io.cpp` reduced to sockets + timing that translate the - session's actions (`run_action`); behaviour faithfully ported (LNK-026 + session's actions (`run_action`); behavior faithfully ported (LNK-026 fixes preserved as session actions). - [x] Full host suite green (20 suites); firmware compiles 86%; `AGENTS.md` updated. - [x] **Re-verified on hardware** (flashed; Link peer @ 127 BPM). Steady play: diff --git a/tasks/LNK-039.md b/tasks/LNK-039.md index 0ba36f9..135bd79 100644 --- a/tasks/LNK-039.md +++ b/tasks/LNK-039.md @@ -1,6 +1,6 @@ --- id: LNK-039 -title: "Beat LED uses the configured dot colours (bar-1 accent + brightness scale)" +title: "Beat LED uses the configured dot colors (bar-1 accent + brightness scale)" status: done assignee: null priority: medium @@ -14,21 +14,21 @@ updated_at: 2026-07-08 On RGB-LED boards (QT Py NeoPixel, `LED_RGB`) the beat LED is hardcoded green (`rgbLedWrite(pin, 0, 40, 0)`). The web/touch UI already has BEAT + BAR1 -colour pickers (LNK-036, `dot_beat_color`/`dot_accent_color`, NVS-persisted, +color pickers (LNK-036, `dot_beat_color`/`dot_accent_color`, NVS-persisted, live-applied via /live) — but they only drive the touch-display dot and web dot. The physical LED ignores them, and shows no bar-1 accent at all. -KitchenSync's strip (P4-019) already does colour + accent from the web UI. +KitchenSync's strip (P4-019) already does color + accent from the web UI. ## Solution - Pure `led_flash_rgb()` in `led_phase.{h,c}` (host-tested): pick accent vs - beat colour from bar phase (int part 0 = downbeat, same rule as the touch + beat color from bar phase (int part 0 = downbeat, same rule as the touch dot), scale channels by brightness/255 (40 keeps the old NeoPixel level). -- `led_set()` RGB path flashes a stored current colour; `led_task` sets it +- `led_set()` RGB path flashes a stored current color; `led_task` sets it per flash from `g_config` + `tempo_source_phase(quantum_beats)`. Fallback - beat / WiFi-down blink use the beat colour (no bar info there). -- Non-RGB boards unchanged (plain GPIO ignores colour). -- Web UI hint updated: colours drive dot + beat LED. + beat / WiFi-down blink use the beat color (no bar info there). +- Non-RGB boards unchanged (plain GPIO ignores color). +- Web UI hint updated: colors drive dot + beat LED. ## Deliverable diff --git a/tasks/LNK-042.md b/tasks/LNK-042.md index b297745..7531d79 100644 --- a/tasks/LNK-042.md +++ b/tasks/LNK-042.md @@ -61,7 +61,7 @@ inert for the other builds. Pick the scheme per board: ## Acceptance Criteria - [ ] `X32Link/partitions.csv` exists: 16 MB, dual-OTA, 4 MB app slots, NVS + coredump, - modelled on `KitchenSyncTouch/partitions.csv`. + modeled on `KitchenSyncTouch/partitions.csv`. - [ ] The Waveshare X32Link build uses `PartitionScheme=custom`; the 4 MB builds keep `min_spiffs` (LNK-041) and are unaffected by the new file. - [ ] CI compiles the Waveshare variant against the 16 MB table and reports usage diff --git a/tasks/MCK-005.md b/tasks/MCK-005.md index 735afcc..91c6ec3 100644 --- a/tasks/MCK-005.md +++ b/tasks/MCK-005.md @@ -56,7 +56,7 @@ buffer just stays empty when disconnected; `midi_bpm_update()` returns 0.0 after the timeout, and OSC stops. On reconnect, Ableton re-sends clock and the firmware picks it back up within one beat. -**Clock-stop behaviour (design decision)**: When the musician stops the DAW, +**Clock-stop behavior (design decision)**: When the musician stops the DAW, we intentionally do NOT reset the X32 tempo to zero or a default. The desk keeps the last known tempo — the band can keep playing in time if the DAW stops unexpectedly. Tempo only changes when a new valid BPM arrives. diff --git a/tasks/MCK-006.md b/tasks/MCK-006.md index 81dd3fc..0994288 100644 --- a/tasks/MCK-006.md +++ b/tasks/MCK-006.md @@ -27,7 +27,7 @@ DAW start/stop. - [ ] Enabling "Clock" output in Ableton and pressing Play causes FX delay time on XR18 to update within 1–2 beats - [ ] BPM accuracy: set Ableton to 80, 120, 140, 180 BPM → X-Air Edit delay time - matches expected normalised values (see LNK-005 reference table) + matches expected normalized values (see LNK-005 reference table) - [ ] Tempo change: drag Ableton BPM from 120 → 140 → X32 follows within 1 beat - [ ] DAW stop: FX delay time stays at last known value (does not reset) - [ ] DAW start after stop: tempo resumes correctly within 1 beat diff --git a/tasks/P4-004.md b/tasks/P4-004.md index d5dd928..be2659c 100644 --- a/tasks/P4-004.md +++ b/tasks/P4-004.md @@ -22,7 +22,7 @@ depend on a live tempo. The P4 SoC has no native WiFi radio — it's provided by the onboard ESP32-C6 over the hosted-WiFi stack. Everything downstream needs a tempo, and Link-over-WiFi is -the primary source (matching the shipped S3 behaviour). +the primary source (matching the shipped S3 behavior). ## Open questions (the real work is here) diff --git a/tasks/P4-016.md b/tasks/P4-016.md index 346851e..e87d469 100644 --- a/tasks/P4-016.md +++ b/tasks/P4-016.md @@ -14,7 +14,7 @@ updated_at: 2026-07-11 Stand up a **headless, always-on ground-truth Ableton Link peer** on the Arduino Uno Q's Linux side, running the **official Ableton Link SDK**, to verify the -ESP32 devices' Link behaviour against a real peer — not against our own +ESP32 devices' Link behavior against a real peer — not against our own clean-room implementation. ## Why this exists @@ -64,7 +64,7 @@ lock) against ground truth instead of self-comparison. - Pure bench tooling — no change to the ESP32 firmware or the shared pure modules. - Complements, doesn't replace, the host-tested pure logic; it covers exactly the - on-the-wire behaviour that host tests can't. + on-the-wire behavior that host tests can't. ## Triage — 2026-07-11 diff --git a/tasks/P4-018.md b/tasks/P4-018.md index c0c6b77..436e563 100644 --- a/tasks/P4-018.md +++ b/tasks/P4-018.md @@ -45,7 +45,7 @@ each beat so it pulses. You see where you are in the bar and the downbeat coming ## Status -Built, host-tested, and flashed. Colours kept below full-scale (~0.5 A max at 8 +Built, host-tested, and flashed. Colors kept below full-scale (~0.5 A max at 8 px) for the eyes and the 5 V rail. Needs an on-hardware eyeball: strip chases in time, amber downbeat, toggles live. 3.3 V data usually drives a short strip fine; add a level shifter if the first pixels flicker. diff --git a/tasks/P4-021.md b/tasks/P4-021.md index 1a46709..4442ccc 100644 --- a/tasks/P4-021.md +++ b/tasks/P4-021.md @@ -14,7 +14,7 @@ updated_at: 2026-07-11 Emit **analog sync pulses** (0/+5V clock/trigger) on several outputs, each with its own rate — to sync modular/Eurorack, vintage drum machines (DIN-sync / Sync24), -and pedals, alongside the USB/MIDI clock. Realises the long-planned LNK-028 on the +and pedals, alongside the USB/MIDI clock. Realizes the long-planned LNK-028 on the P4 tier. ## Inspiration diff --git a/tasks/P4-024.md b/tasks/P4-024.md index fde1404..438e8ec 100644 --- a/tasks/P4-024.md +++ b/tasks/P4-024.md @@ -12,7 +12,7 @@ updated_at: 2026-07-11 ## Goal -Save and recall whole-device configurations (tempo behaviour, outputs, metronome, +Save and recall whole-device configurations (tempo behavior, outputs, metronome, LED, meter, ...) so you can switch between songs/setups without re-entering settings. diff --git a/tasks/P4-026.md b/tasks/P4-026.md index 6a194fb..9f39650 100644 --- a/tasks/P4-026.md +++ b/tasks/P4-026.md @@ -21,7 +21,7 @@ that drives Link + all outputs. The tightest possible DAW sync. JMK CLOCKstep:MULTI "Sample Accurate Clocking" — a DAW plugin sends sample-accurate clock through the audio interface into the box's audio input to resolve USB-MIDI jitter/latency (https://jmkmusicpedals.com/products/clockstep-multi). Their users -call the audio-in sync a favourite feature. +call the audio-in sync a favorite feature. ## Hardware gate diff --git a/tasks/P4-029.md b/tasks/P4-029.md index c88fb76..4b81f17 100644 --- a/tasks/P4-029.md +++ b/tasks/P4-029.md @@ -13,7 +13,7 @@ updated_at: 2026-07-07 ## Problem The P4 metronome VOL and VOICE required a Save + reboot to take effect, while the -LED colours and the accent toggle already applied live via `/live`. There are no +LED colors and the accent toggle already applied live via `/live`. There are no audio files — the voice is a synthesized sine tone burst (`render_burst`, pitch + length from `metronome_voice_params`) and volume is an ES8311 codec register — so nothing needs "pulling"; they were just consumed once at boot in diff --git a/tasks/P4-030.md b/tasks/P4-030.md index 1add387..2ebb6cd 100644 --- a/tasks/P4-030.md +++ b/tasks/P4-030.md @@ -28,7 +28,7 @@ Follow the project's pure-engine / thin-glue split (ADR-0003): - **Pure `rtp_midi_session`** (host-tested, like `link_measurement_session` / `session_timeline`): owns the AppleMIDI protocol — the invitation handshake - (IN/OK/NO on the control port), clock synchronisation (CK0/1/2 timestamps), and + (IN/OK/NO on the control port), clock synchronization (CK0/1/2 timestamps), and RTP-MIDI packet framing (RTP header + MIDI command list; the recovery journal can be a later refinement — start journal-less, most receivers accept it). No sockets here; emit/consume actions like the other session state machines. diff --git a/tasks/P4-036.md b/tasks/P4-036.md index d35d59d..309098b 100644 --- a/tasks/P4-036.md +++ b/tasks/P4-036.md @@ -32,7 +32,7 @@ Two real obstacles, neither of them proven fatal: decision. 2. **The P4 is an ESP-IDF build, not Arduino.** The flasher script drives `arduino-cli compile` and harvests the Arduino export directory. The P4 needs `idf.py build` and a different set of - artefacts. The CI job would need an IDF toolchain alongside `arduino-cli`. + artifacts. The CI job would need an IDF toolchain alongside `arduino-cli`. ## Why it matters more than it looks diff --git a/tasks/P4-037.md b/tasks/P4-037.md index 95b7487..e182940 100644 --- a/tasks/P4-037.md +++ b/tasks/P4-037.md @@ -61,7 +61,7 @@ so with a budget of 10 the existing 7 was already the ceiling. The pool was neve ## The actual fix `CONFIG_LWIP_MAX_SOCKETS=16` in `KitchenSync/sdkconfig.defaults` — give the box enough sockets -for its real tenants. `lru_purge_enable = true` is kept alongside it as defence in depth: it +for its real tenants. `lru_purge_enable = true` is kept alongside it as defense in depth: it is what stops httpd's own pool from refusing a client once the pool (not LWIP) is the thing that fills. diff --git a/tasks/P4-038.md b/tasks/P4-038.md index 2768afa..caec3e9 100644 --- a/tasks/P4-038.md +++ b/tasks/P4-038.md @@ -42,7 +42,7 @@ reproducible on demand, and the trigger was never isolated.** **The clock engine itself is sound.** At idle every bar is *exactly* 96 clocks (29/29 bars, nothing dropped or doubled), mean 20.834 ms = 119.997 BPM, phase-locked with bounded error. -The ~0.5 ms sd at idle is NOT jitter — it is the 1 kHz tick quantising a 20.833 ms period into +The ~0.5 ms sd at idle is NOT jitter — it is the 1 kHz tick quantizing a 20.833 ms period into a 20/21 ms mix (histogram: 20 ms x535, 21 ms x2281, averaging to 20.833). That is `CONFIG_FREERTOS_HZ=1000` behaving exactly as P4-006 designed. Do not "fix" it. diff --git a/test/test_app_config.c b/test/test_app_config.c index 93f08f6..22eedf0 100644 --- a/test/test_app_config.c +++ b/test/test_app_config.c @@ -201,7 +201,7 @@ void test_set_model_ignores_invalid_model(void) { TEST_ASSERT_EQUAL_INT(7, cfg.fx_slot); } -// LNK-036: phase display mode + dot colours. +// LNK-036: phase display mode + dot colors. void test_defaults_phase_display_is_sweep(void) { AppConfig cfg; config_defaults(&cfg); TEST_ASSERT_EQUAL_INT(0, cfg.phase_display_mode); // 0 = sweep wheel diff --git a/test/test_beat_synth.c b/test/test_beat_synth.c index b011033..1461970 100644 --- a/test/test_beat_synth.c +++ b/test/test_beat_synth.c @@ -7,7 +7,7 @@ void setUp(void) { beat_synth_reset(&s); } void tearDown(void) {} void test_fresh_state_fires_on_first_step_past_interval(void) { - // Matches the old inline behaviour: static last=0, a large millis() reading + // Matches the old inline behavior: static last=0, a large millis() reading // is >= one interval so the first observed beat fires. TEST_ASSERT_TRUE(beat_synth_step(&s, 1000, 120.0f)); // interval 500, 1000-0 >= 500 } diff --git a/test/test_clock_ticker.c b/test/test_clock_ticker.c index 908a4de..915c9af 100644 --- a/test/test_clock_ticker.c +++ b/test/test_clock_ticker.c @@ -125,7 +125,7 @@ void test_normal_catchup_drops_nothing(void) { // trusted (this test caught exactly that -- it read 25 instead of 24). Keeping a // lifetime total across resets is the glue's job; a pure struct cannot tell "first // init" from "re-prime". -void test_reset_zeroes_dropped_so_it_is_always_initialised(void) { +void test_reset_zeroes_dropped_so_it_is_always_initialized(void) { ClockTicker t; clock_ticker_reset(&t); clock_ticker_ticks_due(&t, 0.0, 24, 4); clock_ticker_ticks_due(&t, 1.0, 24, 4); // 24 discarded @@ -151,6 +151,6 @@ int main(void) { RUN_TEST(test_bar_reset_bad_quantum); RUN_TEST(test_realign_counts_the_pulses_it_discards); RUN_TEST(test_normal_catchup_drops_nothing); - RUN_TEST(test_reset_zeroes_dropped_so_it_is_always_initialised); + RUN_TEST(test_reset_zeroes_dropped_so_it_is_always_initialized); return UNITY_END(); } diff --git a/test/test_config_persist.c b/test/test_config_persist.c index f488352..1781cb4 100644 --- a/test/test_config_persist.c +++ b/test/test_config_persist.c @@ -1,7 +1,7 @@ // Host tests for the ARC-022 debounced config write-through policy. // // The bug this exists to kill: /live applied a setting to the running config and -// never wrote NVS, so every live-safe field -- metronome voice, LED colours, and +// never wrote NVS, so every live-safe field -- metronome voice, LED colors, and // the whole clock[] array including follow_link -- silently reverted on reboot. // Writing on every /live POST is not the fix: config is ONE nvs blob, and a // slider drag emits dozens of POSTs, so that would blob-write flash tens of times @@ -10,7 +10,7 @@ // So a live edit marks the config dirty and the blob is written once the edits // settle. That debounce is the only part of the fix with real logic in it, which // is why it is the part that is pure and tested. Time is an INPUT: the test owns -// the clock, so the timing behaviour is provable with no device, no flash, and no +// the clock, so the timing behavior is provable with no device, no flash, and no // power cycle. #include "unity.h" #include "config_persist.h" diff --git a/test/test_ks_config.c b/test/test_ks_config.c index 78fce51..5c7a895 100644 --- a/test/test_ks_config.c +++ b/test/test_ks_config.c @@ -189,7 +189,7 @@ void test_led_toggle(void) { TEST_ASSERT_FALSE(ks_config_set(&c, "led", "2")); // only 0/1 } -// Strip customization (P4-019): brightness, mode, fade, and #rrggbb colours. +// Strip customization (P4-019): brightness, mode, fade, and #rrggbb colors. void test_led_customization(void) { TEST_ASSERT_TRUE(ks_config_set(&c, "led_bright", "40")); TEST_ASSERT_EQUAL_INT(40, c.led_brightness); @@ -272,7 +272,7 @@ void test_live_safe_copy_carries_live_fields_not_wifi(void) { // ks_config_decode owns the "is this persisted blob safe to load?" decision, so // a struct-layout change falls back to clean defaults instead of loading bytes // shifted into the wrong fields. Pure: the caller (ks_config_nvs.c) hands over -// whatever NVS held; every judgement happens here. +// whatever NVS held; every judgment happens here. // A blob that a CURRENT-version firmware would have written: right version, // right size, valid contents. @@ -465,7 +465,7 @@ void test_decode_unversioned_v1_sized_blob_still_defaults(void) { } // ESP-011 per-output transport master. Default 1 = Link owns transport once the -// session publishes it -- the behaviour before this flag existed, so an upgraded +// session publishes it -- the behavior before this flag existed, so an upgraded // unit acts exactly as it used to until the user opts an output into manual. void test_follow_link_defaults_to_link(void) { KsConfig c; ks_config_defaults(&c); diff --git a/test/test_ks_status.c b/test/test_ks_status.c index 6775353..55bc0db 100644 --- a/test/test_ks_status.c +++ b/test/test_ks_status.c @@ -75,7 +75,7 @@ void test_launch_state_array_present(void) { } // Transport state: the session's real playing flag and whether Link owns -// transport, so the UI can reflect play/stop and grey the manual buttons. +// transport, so the UI can reflect play/stop and gray the manual buttons. void test_transport_state_fields(void) { char b[300]; ks_status_json(b, sizeof(b), 126.0f, 0.0f, 2, false, 0, "2.1.0", false, 0.0f, 0.0f, false, kNoLaunch, 4, true, true, NULL, NULL, NULL, NULL, NULL); diff --git a/test/test_led_phase.c b/test/test_led_phase.c index 573bcf5..28020f6 100644 --- a/test/test_led_phase.c +++ b/test/test_led_phase.c @@ -44,16 +44,16 @@ void test_prev_phase_zero_is_a_real_reading(void) { // ---- led_flash_rgb (LNK-039) ---- -// Bar phase int part 0 (first beat of the bar) -> accent colour; any later -// beat -> beat colour. Same rule as the touch dot's `(int)ts.phase == 0`. +// Bar phase int part 0 (first beat of the bar) -> accent color; any later +// beat -> beat color. Same rule as the touch dot's `(int)ts.phase == 0`. void test_flash_rgb_accent_on_bar_downbeat(void) { TEST_ASSERT_EQUAL_HEX32(0xFF0000, led_flash_rgb(0x00FF00, 0xFF0000, 0.25f, true, 255)); TEST_ASSERT_EQUAL_HEX32(0x00FF00, led_flash_rgb(0x00FF00, 0xFF0000, 1.75f, true, 255)); TEST_ASSERT_EQUAL_HEX32(0x00FF00, led_flash_rgb(0x00FF00, 0xFF0000, 3.00f, true, 255)); } -// No bar phase available (sync gap / fallback beat) -> beat colour, never accent. -void test_flash_rgb_invalid_bar_uses_beat_colour(void) { +// No bar phase available (sync gap / fallback beat) -> beat color, never accent. +void test_flash_rgb_invalid_bar_uses_beat_color(void) { TEST_ASSERT_EQUAL_HEX32(0x00FF00, led_flash_rgb(0x00FF00, 0xFF0000, 0.0f, false, 255)); } @@ -65,12 +65,12 @@ void test_flash_rgb_negative_phase_is_not_downbeat(void) { } // Brightness scales each channel by bright/255; 40 reproduces the old -// hardcoded rgbLedWrite(pin, 0, 40, 0) level for a pure-green colour. +// hardcoded rgbLedWrite(pin, 0, 40, 0) level for a pure-green color. void test_flash_rgb_brightness_scales_channels(void) { TEST_ASSERT_EQUAL_HEX32(0x002800, led_flash_rgb(0x00FF00, 0xFF0000, 1.5f, true, 40)); // 0x28 = 40 TEST_ASSERT_EQUAL_HEX32(0x280000, led_flash_rgb(0x00FF00, 0xFF0000, 0.5f, true, 40)); TEST_ASSERT_EQUAL_HEX32(0x000000, led_flash_rgb(0xFFFFFF, 0xFFFFFF, 1.5f, true, 0)); - // per-channel: mixed colour scales independently, floors toward zero + // per-channel: mixed color scales independently, floors toward zero TEST_ASSERT_EQUAL_HEX32((0xB6 * 40 / 255) << 16 | (0xFF * 40 / 255) << 8 | (0x36 * 40 / 255), led_flash_rgb(0xB6FF36, 0xFF9D3B, 1.5f, true, 40)); } @@ -84,7 +84,7 @@ int main(void) { RUN_TEST(test_equal_phase_does_not_flash); RUN_TEST(test_prev_phase_zero_is_a_real_reading); RUN_TEST(test_flash_rgb_accent_on_bar_downbeat); - RUN_TEST(test_flash_rgb_invalid_bar_uses_beat_colour); + RUN_TEST(test_flash_rgb_invalid_bar_uses_beat_color); RUN_TEST(test_flash_rgb_negative_phase_is_not_downbeat); RUN_TEST(test_flash_rgb_brightness_scales_channels); return UNITY_END(); diff --git a/test/test_link_measurement_session.c b/test/test_link_measurement_session.c index 2ca72f4..40273be 100644 --- a/test/test_link_measurement_session.c +++ b/test/test_link_measurement_session.c @@ -24,7 +24,7 @@ static int start(uint32_t ip, uint16_t port, int64_t now) { static void run_acts(int n) { for (int i = 0; i < n; i++) { switch (acts[i].type) { - case LS_FLUSH_RX: break; // sockets: not modelled here + case LS_FLUSH_RX: break; // sockets: not modeled here case LS_SEND_PING: break; case LS_START_ATTEMPT: link_measurement_attempt_begin(); break; case LS_END_OK: link_measurement_attempt_end(true); break; diff --git a/test/test_metro_strip.c b/test/test_metro_strip.c index a5e4898..b484a08 100644 --- a/test/test_metro_strip.c +++ b/test/test_metro_strip.c @@ -1,5 +1,5 @@ // Host tests for the pure customizable visual-metronome renderer (P4-018/019): -// three patterns (chase / flash / fill), configurable colours, brightness, fade. +// three patterns (chase / flash / fill), configurable colors, brightness, fade. #include "unity.h" #include "metro_strip.h" #include @@ -30,7 +30,7 @@ void test_chase_downbeat_first_block_accent(void) { void test_chase_beat_two_block_beat_color(void) { metro_strip_render(1.0, 4, METRO_STRIP_PIXELS, &cfg, px); TEST_ASSERT_TRUE(px[2].g > 0 && px[3].g > 0); - TEST_ASSERT_EQUAL_UINT8(0, px[2].r); // beat colour is green (no red) + TEST_ASSERT_EQUAL_UINT8(0, px[2].r); // beat color is green (no red) TEST_ASSERT_EQUAL_INT(2, count()); } @@ -61,7 +61,7 @@ void test_flash_lights_all_pixels(void) { cfg.mode = METRO_STRIP_FLASH; metro_strip_render(1.0, 4, METRO_STRIP_PIXELS, &cfg, px); TEST_ASSERT_EQUAL_INT(METRO_STRIP_PIXELS, count()); - TEST_ASSERT_EQUAL_UINT8(0, px[5].r); // beat colour (green) + TEST_ASSERT_EQUAL_UINT8(0, px[5].r); // beat color (green) } void test_flash_downbeat_is_accent(void) { @@ -91,7 +91,7 @@ void test_fill_grows(void) { TEST_ASSERT_TRUE(count() > a); } -// --- BRIGHTNESS + COLOURS ------------------------------------------------ +// --- BRIGHTNESS + COLORS ------------------------------------------------ void test_brightness_scales_down(void) { metro_strip_render(2.0, 4, METRO_STRIP_PIXELS, &cfg, px); // bright 100 @@ -111,7 +111,7 @@ void test_custom_colors_used(void) { /* ---- ESP-009: standby heartbeat ---------------------------------------- */ // Separate entry point from metro_strip_render: standby is not a beat pattern, // it has no beat. Slow sinusoidal breath over the whole strip in the beat -// colour, dim, so "waiting for play" reads as alive-and-idle. +// color, dim, so "waiting for play" reads as alive-and-idle. // Trough and peak of the breath differ, and neither is fully dark -- a strip // that reaches zero looks like a board that crashed between blinks. diff --git a/test/test_status_rgb.c b/test/test_status_rgb.c index 2688701..8ede244 100644 --- a/test/test_status_rgb.c +++ b/test/test_status_rgb.c @@ -1,4 +1,4 @@ -// ESP-043: the Super Mini's onboard status RGB. The state->colour mapping is a pure +// ESP-043: the Super Mini's onboard status RGB. The state->color mapping is a pure // function so it can be asserted here rather than eyeballed on the glass -- a screenless // box's only feedback has to be right without a person watching it. #include "unity.h" @@ -30,14 +30,14 @@ void test_running_flashes_on_the_beat_and_is_dark_between(void) { // Free-run is green, Link-locked is cyan -- you can tell across the room whether it caught // the session. Green has no blue; cyan has blue. -void test_running_colour_tells_free_from_link(void) { +void test_running_color_tells_free_from_link(void) { StatusRgb freerun = ktouch_status_rgb(TL_RUNNING, false, ON_BEAT2, 4); StatusRgb locked = ktouch_status_rgb(TL_RUNNING, true, ON_BEAT2, 4); TEST_ASSERT_TRUE(freerun.g > 0 && freerun.b == 0); // green TEST_ASSERT_TRUE(locked.b > 0 && locked.g > 0); // cyan (green+blue) } -// The bar-1 downbeat is a brighter accent of the same colour, so the "one" reads without a +// The bar-1 downbeat is a brighter accent of the same color, so the "one" reads without a // hue change that would fight the free/Link signal. void test_downbeat_is_brighter_than_the_other_beats(void) { StatusRgb down = ktouch_status_rgb(TL_RUNNING, false, ON_BEAT, 4); // whole 4 => beat 1 @@ -58,7 +58,7 @@ int main(void) { UNITY_BEGIN(); RUN_TEST(test_stopped_is_dark_even_on_the_beat); RUN_TEST(test_running_flashes_on_the_beat_and_is_dark_between); - RUN_TEST(test_running_colour_tells_free_from_link); + RUN_TEST(test_running_color_tells_free_from_link); RUN_TEST(test_downbeat_is_brighter_than_the_other_beats); RUN_TEST(test_armed_blinks_amber_on_the_beat); return UNITY_END(); diff --git a/test/test_wifi_conn_policy.c b/test/test_wifi_conn_policy.c index 06df40f..dba8148 100644 --- a/test/test_wifi_conn_policy.c +++ b/test/test_wifi_conn_policy.c @@ -4,7 +4,7 @@ #include "wifi_conn_policy.h" static WifiConnPolicy p; -void setUp(void) { wifi_conn_policy_reset(&p, 0, 1); } // one slot = pre-ESP-013 behaviour +void setUp(void) { wifi_conn_policy_reset(&p, 0, 1); } // one slot = pre-ESP-013 behavior void tearDown(void) {} /* ---- cold-start budget (was wifi_fallback) ----------------------------- */ diff --git a/tools/build-flasher-site.sh b/tools/build-flasher-site.sh index 52bbe07..91d1c63 100755 --- a/tools/build-flasher-site.sh +++ b/tools/build-flasher-site.sh @@ -22,7 +22,7 @@ CACHE_ARG=() rm -rf "$SITE" mkdir -p "$SITE/firmware" -# boot_app0.bin initialises otadata to boot app0. The core writes it on a normal upload, +# boot_app0.bin initializes otadata to boot app0. The core writes it on a normal upload, # so we write it too. A blank otadata would ALSO boot app0 (the bootloader falls back), # but relying on fallback is not the same as stating intent -- and this is the file that # decides which slot a freshly-flashed device runs from. diff --git a/tools/linkcli/README.md b/tools/linkcli/README.md index 668f686..c6b9e88 100644 --- a/tools/linkcli/README.md +++ b/tools/linkcli/README.md @@ -41,7 +41,7 @@ with asio bundled under `modules/asio-standalone`. Override the SDK path with **A peer that joins a session that is already playing stays stopped.** This is Link's design, not a bug: start/stop is last-writer-wins by timestamp (`Controller.hpp:89`), and -a booting peer initialises its start/stop state with `timestamp = hostTime` +a booting peer initializes its start/stop state with `timestamp = hostTime` (`Controller.hpp:67`) — so its fresh "stopped" is *newer* than your earlier "playing" and wins. Tempo, by contrast, *is* adopted by late joiners, which makes the asymmetry extra confusing.