diff --git a/cli/XAir.c b/cli/XAir.c index 1ce2978..e929251 100644 --- a/cli/XAir.c +++ b/cli/XAir.c @@ -121,7 +121,7 @@ static void handle_fx_type(int slot) { } static void handle_fx_par(int slot) { - /* echo the set command back to caller (X32 behaviour) */ + /* echo the set command back to caller (X32 behavior) */ memcpy(s_buf, r_buf, r_len); s_len = r_len; xsend((struct sockaddr *)&client_ip, client_ip_len); diff --git a/cli/ringout_logic.c b/cli/ringout_logic.c index 2dfb5dd..c45e0e0 100644 --- a/cli/ringout_logic.c +++ b/cli/ringout_logic.c @@ -44,7 +44,7 @@ RingoutAction ringout_step(RingoutState *st, const float *bins, const float *bas } /* A notch placed last step takes priority: back the gain off and re-settle - * before analysing again, so the notch can take effect. */ + * before analyzing again, so the notch can take effect. */ if (st->pending_backoff) { st->pending_backoff = 0; st->cur_gain_db -= st->cfg.step_db; @@ -57,7 +57,7 @@ RingoutAction ringout_step(RingoutState *st, const float *bins, const float *bas return a; } - /* Settle: ignore frame content until the system has stabilised. */ + /* Settle: ignore frame content until the system has stabilized. */ if (st->phase == RO_PHASE_SETTLE) { if (st->settle_count > 0) { st->settle_count--; @@ -67,10 +67,10 @@ RingoutAction ringout_step(RingoutState *st, const float *bins, const float *bas st->confirm_count = 0; st->confirm_bin = -1; st->stable_count = 0; - /* fall through and analyse this frame */ + /* fall through and analyze this frame */ } - /* Analyse: is there a narrow ring? */ + /* Analyze: is there a narrow ring? */ int peak; int found = detect_peak(bins, baseline, st->cfg.threshold_db, &peak); int narrow = found && is_narrow_peak(bins, RTA_BINS, peak, diff --git a/cli/ringout_logic.h b/cli/ringout_logic.h index a73b2c1..abe49ef 100644 --- a/cli/ringout_logic.h +++ b/cli/ringout_logic.h @@ -52,7 +52,7 @@ typedef struct { int stable_frames; /* consecutive ring-free frames required before raising */ int narrow_skip; /* is_narrow_peak: skip */ int narrow_span; /* is_narrow_peak: span */ - float narrow_min_db; /* is_narrow_peak: min advantage over neighbours */ + float narrow_min_db; /* is_narrow_peak: min advantage over neighbors */ } RingoutConfig; typedef struct { @@ -73,7 +73,7 @@ typedef struct { float margin_db; /* gain-before-feedback margin achieved (cur - start) */ } RingoutState; -/* Initialise state from config. cfg is copied; sane fields are required (e.g. +/* Initialize state from config. cfg is copied; sane fields are required (e.g. * step_db > 0, ceiling_db >= start_gain_db). The bus is assumed to already be at * cfg.start_gain_db when the loop begins. */ void ringout_init(RingoutState *st, const RingoutConfig *cfg); diff --git a/cli/rta_bins.c b/cli/rta_bins.c index 8fa2c73..bf55d58 100644 --- a/cli/rta_bins.c +++ b/cli/rta_bins.c @@ -3,7 +3,7 @@ /* Log-frequency interpolation of the 31 ISO 1/3-octave GEQ anchors * (TOAST_GEQ_BIN ↔ ISO centers), 1 kHz pinned to bin 56. Off-anchor bins are - * modelled, not measured — verify with a console sweep via --rta-probe. */ + * modeled, not measured — verify with a console sweep via --rta-probe. */ const float RTA_BIN_FREQ[RTA_BIN_COUNT] = { 20.0f, 21.5f, 23.2f, 25.0f, 26.5f, 28.1f, 29.7f, 31.5f, 34.1f, 36.9f, 40.0f, 43.1f, 46.4f, 50.0f, 54.0f, 58.3f, diff --git a/cli/rta_probe.py b/cli/rta_probe.py index c1c04b6..16088bc 100644 --- a/cli/rta_probe.py +++ b/cli/rta_probe.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Live /meters/4 RTA peak-bin probe for X32/XAir — field tool for T-027. -Subscribe to the 100-bin RTA, print the peak bin (and its modelled frequency +Subscribe to the 100-bin RTA, print the peak bin (and its modeled frequency from cli/rta_bins.c). Use with a known tone to verify the bin->Hz table: a 1 kHz pistonphone must light bin 56; a console sine sweep verifies the rest. @@ -15,7 +15,7 @@ sys.exit("usage: rta_probe.py [seconds]") PORT = 10024 -# modelled bin->Hz, parsed from the C table so the two never drift +# modeled bin->Hz, parsed from the C table so the two never drift try: txt = pathlib.Path(__file__).with_name("rta_bins.c").read_text() BIN_FREQ = [float(x) for x in re.findall(r"([0-9]+\.[0-9]+)f", txt)[:100]] diff --git a/cli/test_ringout_emu.py b/cli/test_ringout_emu.py index bed84ce..90a0999 100644 --- a/cli/test_ringout_emu.py +++ b/cli/test_ringout_emu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """T-025 / T-032 ring-out safety harness: a mock X32 that models the feedback loop and asserts the ring-out control loop's safety properties (ceiling, notch, -restore, abort). No audio — feedback is modelled as a function of commanded +restore, abort). No audio — feedback is modeled as a function of commanded bus gain via a pluggable RoomModel. Run: diff --git a/docs/ringout-design.md b/docs/ringout-design.md index 7cd5546..d7cdc28 100644 --- a/docs/ringout-design.md +++ b/docs/ringout-design.md @@ -16,7 +16,7 @@ with a handoff: **static notch profile + the measured gain-before-feedback margin**. - **Reactive (protect)** — starts *from that profile* (static notches pre-placed) and dynamically defends the remaining margin during the show. This is the - existing ToastSaver behaviour, plus a new "load a profile at startup" step. + existing ToastSaver behavior, plus a new "load a profile at startup" step. > The proactive pass *produces* what the reactive pass *consumes*. "React as > planned" = the planned notches are already in when the show starts; live mode diff --git a/docs/system-tune-design.md b/docs/system-tune-design.md index 56fd61e..416ff03 100644 --- a/docs/system-tune-design.md +++ b/docs/system-tune-design.md @@ -55,7 +55,7 @@ generator and writes the GEQ. 1. **Mains response EQ** — pink noise → measure at a mic → flatten LR GEQ to the target curve. 2. **Per-monitor** — optionally pink-flatten each wedge, **then ring it out** - (the current work) to maximise gain-before-feedback. + (the current work) to maximize gain-before-feedback. 3. **Report** — final EQ curves + per-monitor headroom margins (and reuse the ring-out profile format from `T-024`). diff --git a/esp32/X32Link/beat_synth.h b/esp32/X32Link/beat_synth.h index 78244fd..7c4ee00 100644 --- a/esp32/X32Link/beat_synth.h +++ b/esp32/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/esp32/X32Link/web_config.cpp b/esp32/X32Link/web_config.cpp index 8dd1376..2baab96 100644 --- a/esp32/X32Link/web_config.cpp +++ b/esp32/X32Link/web_config.cpp @@ -178,7 +178,7 @@ if(curSlot>maxSlot){curSlot=maxSlot;hSlot.value=maxSlot}renderSlots()}); renderSlots(); var bpmEl=document.getElementById('bpm'),beatEl=document.getElementById('beat'); var seedBpm=parseFloat(bpmEl.textContent)||0,beatTimer=null,shownBpm=-1; -// Free-running fallback blink (pre-LNK-022 behaviour) — used whenever the +// Free-running fallback blink (pre-LNK-022 behavior) — used whenever the // server says phase isn't valid yet (sync gap / no source). Unchanged. function setBeat(bpm){if(beatTimer){clearInterval(beatTimer);beatTimer=null} if(bpm>0){beatTimer=setInterval(function(){beatEl.classList.add('on');setTimeout(function(){beatEl.classList.remove('on')},90)},60000/bpm)}} diff --git a/esp32/X32MidiClock/X32MidiClock.ino b/esp32/X32MidiClock/X32MidiClock.ino index bddd53b..e3c8e20 100644 --- a/esp32/X32MidiClock/X32MidiClock.ino +++ b/esp32/X32MidiClock/X32MidiClock.ino @@ -112,7 +112,7 @@ void setup() { g_config.fx_slot, g_config.mixer_ip, config_model_port(g_config.model)); - // USB MIDI must be initialised before WiFi to ensure USB enumeration is fast + // USB MIDI must be initialized before WiFi to ensure USB enumeration is fast // Device name is set in midi_clock.cpp via USBMIDI constructor USB.manufacturerName("X32Link"); midi_clock_init(); // calls MidiUSB.begin() + spawns poll task diff --git a/esp32/X32MidiClock/midi_bpm.cpp b/esp32/X32MidiClock/midi_bpm.cpp index e05c9c6..370d1dc 100644 --- a/esp32/X32MidiClock/midi_bpm.cpp +++ b/esp32/X32MidiClock/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/esp32/tasks/FDR-002.md b/esp32/tasks/FDR-002.md index fd5122f..eaec911 100644 --- a/esp32/tasks/FDR-002.md +++ b/esp32/tasks/FDR-002.md @@ -16,7 +16,7 @@ updated_at: 2026-06-29 ## Goal -Implement the FaderDisp behaviour: when a channel fader moves, show its value in +Implement the FaderDisp behavior: when a channel fader moves, show its value in **dB on that channel's scribble strip**, by writing `/ch/NN/config/name`. On a clean shutdown / disable, restore the original names. diff --git a/esp32/tasks/ITT-002.md b/esp32/tasks/ITT-002.md index 57a77a0..704fc18 100644 --- a/esp32/tasks/ITT-002.md +++ b/esp32/tasks/ITT-002.md @@ -45,5 +45,5 @@ send a MIDI message). This is the "If This Then That" core. and send as the raw OSC float (the mixer applies its own fader taper). - Keep the engine allocation-free at eval time; build the rule table once at load / on web save. -- This generalises X32Midi2OSC (MIDI→OSC) and X32Commander (OSC→MIDI/OSC) from +- This generalizes X32Midi2OSC (MIDI→OSC) and X32Commander (OSC→MIDI/OSC) from the upstream toolset into one bidirectional firmware. diff --git a/esp32/tasks/LNK-001.md b/esp32/tasks/LNK-001.md index 06022b4..520ed5f 100644 --- a/esp32/tasks/LNK-001.md +++ b/esp32/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/esp32/tasks/LNK-003.md b/esp32/tasks/LNK-003.md index ea0b04d..7e0a80d 100644 --- a/esp32/tasks/LNK-003.md +++ b/esp32/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/esp32/tasks/LNK-005.md b/esp32/tasks/LNK-005.md index 3695b6d..5833753 100644 --- a/esp32/tasks/LNK-005.md +++ b/esp32/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/esp32/tasks/LNK-009.md b/esp32/tasks/LNK-009.md index 6389502..79d9344 100644 --- a/esp32/tasks/LNK-009.md +++ b/esp32/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/esp32/tasks/LNK-011.md b/esp32/tasks/LNK-011.md index 9a55ccc..f9cea73 100644 --- a/esp32/tasks/LNK-011.md +++ b/esp32/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/esp32/tasks/LNK-012.md b/esp32/tasks/LNK-012.md index 3e18e22..5718e59 100644 --- a/esp32/tasks/LNK-012.md +++ b/esp32/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/esp32/tasks/LNK-031.md b/esp32/tasks/LNK-031.md index 566fb14..cc2d582 100644 --- a/esp32/tasks/LNK-031.md +++ b/esp32/tasks/LNK-031.md @@ -61,7 +61,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/esp32/tasks/MCK-005.md b/esp32/tasks/MCK-005.md index 735afcc..91c6ec3 100644 --- a/esp32/tasks/MCK-005.md +++ b/esp32/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/esp32/tasks/MCK-006.md b/esp32/tasks/MCK-006.md index f5d487f..2e67807 100644 --- a/esp32/tasks/MCK-006.md +++ b/esp32/tasks/MCK-006.md @@ -24,7 +24,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/esp32/test/test_beat_synth.c b/esp32/test/test_beat_synth.c index b011033..1461970 100644 --- a/esp32/test/test_beat_synth.c +++ b/esp32/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/ios/ToastSaverCore/Sources/ToastSaverCore/NarrownessFilter.swift b/ios/ToastSaverCore/Sources/ToastSaverCore/NarrownessFilter.swift index 96073a4..6ddd03d 100644 --- a/ios/ToastSaverCore/Sources/ToastSaverCore/NarrownessFilter.swift +++ b/ios/ToastSaverCore/Sources/ToastSaverCore/NarrownessFilter.swift @@ -1,11 +1,11 @@ // Distinguishes a narrow feedback ring from broadband content (e.g. a vocal -// formant): a real ring towers over its neighbours a few bins out. Ported from +// formant): a real ring towers over its neighbors a few bins out. Ported from // cli/toast_logic.c (is_narrow_peak). public enum NarrownessFilter { /// True if `bins[peakBin]` is at least `minDB` above the average of the - /// neighbour bins sampled `skip+1 … skip+span` positions away on each side - /// (clamped to the array). Needs ≥1 neighbour on each side, else false. + /// neighbor bins sampled `skip+1 … skip+span` positions away on each side + /// (clamped to the array). Needs ≥1 neighbor on each side, else false. public static func isNarrowPeak(bins: [Float], peakBin: Int, skip: Int = 2, span: Int = 3, minDB: Float = 10.0) -> Bool { let n = bins.count diff --git a/ios/ToastSaverCore/Sources/ToastSaverCore/RingoutController.swift b/ios/ToastSaverCore/Sources/ToastSaverCore/RingoutController.swift index 6ba0d20..7e1069f 100644 --- a/ios/ToastSaverCore/Sources/ToastSaverCore/RingoutController.swift +++ b/ios/ToastSaverCore/Sources/ToastSaverCore/RingoutController.swift @@ -21,7 +21,7 @@ public struct RingoutConfig { public var cutDB: Float /// Consecutive frames a ring must persist before a notch fires. public var confirmFrames: Int - /// Frames to wait at current gain after any raise or notch before analysing. + /// Frames to wait at current gain after any raise or notch before analyzing. public var settleFrames: Int /// Consecutive ring-free frames required before raising gain. public var stableFrames: Int @@ -29,7 +29,7 @@ public struct RingoutConfig { public var narrowSkip: Int /// Narrowness gate: bins to sample beyond the skip zone on each side. public var narrowSpan: Int - /// Narrowness gate: peak must exceed neighbour average by at least this many dB. + /// Narrowness gate: peak must exceed neighbor average by at least this many dB. public var narrowMinDB: Float public init( @@ -147,17 +147,17 @@ public final class RingoutController { return .backOff(to: currentGainDB) } - // Settling: hold until the system stabilises after a raise or notch. + // Settling: hold until the system stabilizes after a raise or notch. if phase == .settle { if settleCount > 0 { settleCount -= 1; return .hold } - // Settle complete — fall through and analyse this frame. + // Settle complete — fall through and analyze this frame. phase = .analyze confirmCount = 0 confirmBin = -1 stableCount = 0 } - // Analyse: look for a narrow ring. + // Analyze: look for a narrow ring. guard let peakIndex = PeakDetector.detectPeak( bins: geqLevels, baseline: baseline, threshold: config.thresholdDB) else { diff --git a/ios/ToastSaverCore/Tests/ToastSaverCoreTests/FeedbackDetectorTests.swift b/ios/ToastSaverCore/Tests/ToastSaverCoreTests/FeedbackDetectorTests.swift index d99c679..086653f 100644 --- a/ios/ToastSaverCore/Tests/ToastSaverCoreTests/FeedbackDetectorTests.swift +++ b/ios/ToastSaverCore/Tests/ToastSaverCoreTests/FeedbackDetectorTests.swift @@ -38,7 +38,7 @@ final class FeedbackDetectorTests: XCTestCase { var b = flat(); b[idx] = spike; return b } - // narrowSkip=1, narrowSpan=1: a spike with at least 1 clear neighbour each side qualifies. + // narrowSkip=1, narrowSpan=1: a spike with at least 1 clear neighbor each side qualifies. private func makeDetector(confirm: Int = 3) -> FeedbackDetector { FeedbackDetector(threshold: 20.0, confirmFrames: confirm, narrowSkip: 1, narrowSpan: 1, narrowMinDB: 5.0) diff --git a/ios/ToastSaverCore/Tests/ToastSaverCoreTests/RingoutControllerTests.swift b/ios/ToastSaverCore/Tests/ToastSaverCoreTests/RingoutControllerTests.swift index aeccd01..266cc6f 100644 --- a/ios/ToastSaverCore/Tests/ToastSaverCoreTests/RingoutControllerTests.swift +++ b/ios/ToastSaverCore/Tests/ToastSaverCoreTests/RingoutControllerTests.swift @@ -99,7 +99,7 @@ final class RingoutControllerTests: XCTestCase { _ = ctrl.step(geqLevels: quiet, baseline: baseline) // raise // Inject a narrow spike on par 10 (index 9). - // With narrowSkip=1, narrowSpan=1: neighbours are at ±2, which exist. + // With narrowSkip=1, narrowSpan=1: neighbors are at ±2, which exist. let spike = spikedLevels(at: 9, spike: -10.0, floor: -80.0) var action: RingoutAction = .hold diff --git a/ios/tasks/IOS-003.md b/ios/tasks/IOS-003.md index b55a4ec..0e77ea0 100644 --- a/ios/tasks/IOS-003.md +++ b/ios/tasks/IOS-003.md @@ -35,12 +35,12 @@ final class FFTEngine { func process(_ samples: [Float]) -> [Float] /// Map an FFT bin index (0 ..< fftSize/2) to the nearest GEQ par (1–31). - /// Uses the same 1/3-octave centre frequencies as GEQMap.bins, + /// Uses the same 1/3-octave center frequencies as GEQMap.bins, /// recalculated for this engine's fftSize and sampleRate. func geqPar(forBin bin: Int) -> Int /// Precomputed GEQ bin indices for fftSize and sampleRate. - /// geqBins[i] is the FFT bin index closest to GEQ par i+1's centre frequency. + /// geqBins[i] is the FFT bin index closest to GEQ par i+1's center frequency. private(set) var geqBins: [Int] // length 31 } ``` @@ -87,7 +87,7 @@ Create `fftSetup` with `vDSP_create_fftsetup(log2n, FFTRadix(kFFTRadix2))` in `i ## geqBins calculation -GEQ centre frequencies in Hz (31 bands, 1/3-octave, ISO): +GEQ center frequencies in Hz (31 bands, 1/3-octave, ISO): ``` [20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, @@ -115,7 +115,7 @@ private func computeGeqBins() -> [Int] { ## Acceptance Criteria -- [ ] `FFTEngine(fftSize: 2048, sampleRate: 48000)` initialises without crashing. +- [ ] `FFTEngine(fftSize: 2048, sampleRate: 48000)` initializes without crashing. - [ ] `process(_:)` with a 1 kHz sine wave at 48 kHz, 2048 samples: peak bin is within ±2 bins of `Int(round(1000 * 2048 / 48000))` = bin 43. - [ ] `geqBins[17]` (1 kHz, 0-based index) ≈ 43 for 2048/48000. - [ ] `process(_:)` with all-zero input returns all values ≤ -80 dBFS. diff --git a/ios/tasks/IOS-006.md b/ios/tasks/IOS-006.md index 794de0c..ddea900 100644 --- a/ios/tasks/IOS-006.md +++ b/ios/tasks/IOS-006.md @@ -147,7 +147,7 @@ final class DetectorViewModel: ObservableObject { } ``` -`BaselineTracker` state (a `[Float]` array initialised to `Array(repeating: -60, count: spectrum.count)`) lives in `DetectorViewModel`; it is updated each frame before calling `detector.process`. +`BaselineTracker` state (a `[Float]` array initialized to `Array(repeating: -60, count: spectrum.count)`) lives in `DetectorViewModel`; it is updated each frame before calling `detector.process`. --- diff --git a/ios/tasks/IOS-008.md b/ios/tasks/IOS-008.md index 79ceb9b..902b760 100644 --- a/ios/tasks/IOS-008.md +++ b/ios/tasks/IOS-008.md @@ -69,7 +69,7 @@ public struct RingoutConfig { /// Consecutive frames a peak must persist before a notch fires. public var confirmFrames: Int - /// Frames to wait (at current gain) after a raise before analysing again. + /// Frames to wait (at current gain) after a raise before analyzing again. public var settleFrames: Int /// Consecutive quiet frames required before declaring the gain level stable. @@ -81,7 +81,7 @@ public struct RingoutConfig { /// Narrowness gate: sample this many bins beyond the skip zone on each side. public var narrowSpan: Int - /// Narrowness gate: peak must exceed neighbour average by at least this many dB. + /// Narrowness gate: peak must exceed neighbor average by at least this many dB. public var narrowMinDB: Float public init( @@ -161,10 +161,10 @@ public final class RingoutController { Port the `ringout_step()` function from `cli/ringout_logic.c` faithfully. Key states: -1. **Settling** — wait `settleFrames` frames after each gain raise before analysing. +1. **Settling** — wait `settleFrames` frames after each gain raise before analyzing. Return `.hold` every frame. Decrement counter. -2. **Analysing** — look for a narrow peak above threshold: +2. **Analyzing** — look for a narrow peak above threshold: - Use `FeedbackDetector.detectPeak(bins:baseline:threshold:)` on the 31-element `geqLevels` array (it works on any `[Float]`, the 31 vs 100 length doesn't matter). - If a peak is found, pass through `NarrownessFilter.isNarrowPeak` on `geqLevels`. diff --git a/ios/tasks/IOS-009.md b/ios/tasks/IOS-009.md index e96b8e9..266723b 100644 --- a/ios/tasks/IOS-009.md +++ b/ios/tasks/IOS-009.md @@ -14,7 +14,7 @@ updated_at: 2026-06-29 Implement `MixerConnection.swift` in the iOS app target. This supersedes the `XR18Connection` design from IOS-005 — do not build `XR18Connection`; build -`MixerConnection` instead. It wraps `NWConnection` (UDP) and is parameterised by +`MixerConnection` instead. It wraps `NWConnection` (UDP) and is parameterized by `MixerModel` so it works with both the XR18 (port 10024) and X32 (port 10023) without branching in the caller. @@ -65,7 +65,7 @@ final class MixerConnection: ObservableObject { // MARK: - TEQ control /// Send /fx/{slot}/par/{par:02d} ,f {geqFloat(dB)}. - /// Fire-and-forget; does not wait for acknowledgement. + /// Fire-and-forget; does not wait for acknowledgment. func sendNotch(slot: Int, par: Int, dB: Float) /// Restore a TEQ band to flat (0 dB → geqFloat = 0.5). @@ -137,7 +137,7 @@ func connect() async { Send `/xremote` every 9 seconds while connected. This matches the CLI's `XREMOTE_TIMEOUT` constant and keeps the mixer from unsubscribing the client. -Both XR18 and X32 honour `/xremote`. +Both XR18 and X32 honor `/xremote`. ### Fader read (`readFader`) diff --git a/ios/tasks/IOS-010.md b/ios/tasks/IOS-010.md index 26d65bf..59b6af5 100644 --- a/ios/tasks/IOS-010.md +++ b/ios/tasks/IOS-010.md @@ -206,7 +206,7 @@ private func runLoop(config: Config, initialBaseline: [Float]) async { ### Revert notches on abort -Only revert if the run was aborted or interrupted — match CLI behaviour where +Only revert if the run was aborted or interrupted — match CLI behavior where notches are left in place on clean completion: ```swift diff --git a/tasks/T-021.md b/tasks/T-021.md index 878e10d..d0af904 100644 --- a/tasks/T-021.md +++ b/tasks/T-021.md @@ -54,7 +54,7 @@ RingoutAction ringout_step(RingoutState *st, const float *bins, const float *bas - [x] `cli/ringout_logic.h` + `.c` implement `ringout_init` and `ringout_step`, reusing `detect_peak` / `is_narrow_peak` from `toast_logic` for ring detection (do **not** re-implement detection). -- [x] State machine: after a gain change, wait `settle_frames`; analyse; if a +- [x] State machine: after a gain change, wait `settle_frames`; analyze; if a confirmed narrow peak (`confirm_frames`) → `RO_PLACE_NOTCH` on its band + `RO_BACK_OFF`; else if stable → `RO_RAISE_GAIN` by `step_db`. - [x] **Safety in the pure layer:** never emit `RO_RAISE_GAIN` that would exceed diff --git a/tasks/T-022.md b/tasks/T-022.md index 92b48dd..381ef88 100644 --- a/tasks/T-022.md +++ b/tasks/T-022.md @@ -49,7 +49,7 @@ Fader float↔dB conversion: use the piecewise formula in - [x] RTA-tap guard: logs the configured RTA source channel and a reminder to ensure it taps the target monitor mic before ramping (best-effort warning; auto-select deferred). -- [x] Reuses existing UDP send + `send_teq_band`; no change to reactive behaviour +- [x] Reuses existing UDP send + `send_teq_band`; no change to reactive behavior (ring-out is a separate `run_ringout` flow gated on `--ringout`). ## Progress diff --git a/tasks/T-023.md b/tasks/T-023.md index d289383..3f31cf2 100644 --- a/tasks/T-023.md +++ b/tasks/T-023.md @@ -28,7 +28,7 @@ See [`docs/ringout-design.md`](../docs/ringout-design.md). `RO_PLACE_NOTCH` → `send_teq_band(par, db_to_geq_float(cut_db))` and record in `notches[]`; `RO_HOLD` → wait; `RO_DONE`/`RO_ABORT` → finish. - [x] **Supervised:** `--ringout-supervised` pauses for Enter before each - `RO_RAISE_GAIN` (`q` aborts); without it runs auto but still honours Ctrl-C. + `RO_RAISE_GAIN` (`q` aborts); without it runs auto but still honors Ctrl-C. - [x] Live progress to the terminal: per-action log lines with current driven gain, margin to ceiling, and notch placements. - [x] On completion print a summary: list of notched bands, **gain-before-feedback diff --git a/tasks/T-024.md b/tasks/T-024.md index 831b8e1..0b52ccf 100644 --- a/tasks/T-024.md +++ b/tasks/T-024.md @@ -26,7 +26,7 @@ See [`docs/ringout-design.md`](../docs/ringout-design.md) ("One pipeline"). documented in `ringout_profile.c`. - [x] `--load-profile PATH` (reactive mode): parses the file, applies the saved notches via `send_teq_band`, then runs normal reactive detection on top. -- [x] Loaded static notches carry a `from_profile` flag; **v1 behaviour: they are +- [x] Loaded static notches carry a `from_profile` flag; **v1 behavior: they are pinned** (the release pass skips them) so the soundcheck notches stay put while live-detected notches still auto-release. - [x] Round-trip test: serialize → parse → re-serialize is byte-identical diff --git a/tasks/T-025.md b/tasks/T-025.md index afa64fc..693b0b4 100644 --- a/tasks/T-025.md +++ b/tasks/T-025.md @@ -24,7 +24,7 @@ See [`docs/ringout-design.md`](../docs/ringout-design.md) ("Safety"). **synthetic feedback source**: either inject crafted `/meters/4` frames (a narrowband peak whose level tracks the commanded bus gain), or a small script that raises a bin as gain rises. Document the rig in the task. -- [ ] **Closed-loop behaviour confirmed:** as driven gain rises the synthetic +- [ ] **Closed-loop behavior confirmed:** as driven gain rises the synthetic peak grows; the app notches the correct band; after the notch the peak stops growing; the ramp resumes. Capture a run log. - [ ] **Safety asserted (the point of this task):** @@ -41,7 +41,7 @@ See [`docs/ringout-design.md`](../docs/ringout-design.md) ("Safety"). ## Notes -- The emulator manages OSC state only (no audio), so the "feedback" is modelled +- The emulator manages OSC state only (no audio), so the "feedback" is modeled in the test harness, not acoustically — sufficient to validate control logic, ramp, ceiling, abort, and profile handoff. - Real-PA acoustic validation is a separate, supervised field test — out of scope diff --git a/tasks/T-027.md b/tasks/T-027.md index 5082d4d..b98cee5 100644 --- a/tasks/T-027.md +++ b/tasks/T-027.md @@ -88,9 +88,9 @@ Host-testable: spot-check `RTA_BIN_FREQ[56] ≈ 1000`, monotonic increasing, and + `rta_bin_for_freq()`. `cli/test_rta_bins.c` — **10 tests green** (anchor, endpoints, monotonic, GEQ-anchors→ISO, inverse round-trip + clamps). - `cli/rta_probe.py` — live field probe (subscribe `/meters/4`, print peak bin - + modelled Hz). + + modeled Hz). - **Verified on a real XR18 (fw 1.20):** 1 kHz pistonphone on ch1 → **peak bin 56** - (−51 dBFS, neighbours 30+ dB down) = the table's anchor. ✓ + (−51 dBFS, neighbors 30+ dB down) = the table's anchor. ✓ - **`/meters/4` IS the RTA** — blob `[BE size][LE count=100][int16 LE ×100]`, matching `parse_meters4_blob`. The `/meters/5` question is moot. diff --git a/tasks/T-029.md b/tasks/T-029.md index b6929e4..2adf424 100644 --- a/tasks/T-029.md +++ b/tasks/T-029.md @@ -172,7 +172,7 @@ Confirm `make XAir_ToastSaver` still compiles clean. now inactive). Verifies the re-trigger path. - Mid-release: feed the spike back before `release_frames` → assert snap-back to `cut_val` (ramp_val == cut_val with release_hold == 0). - - Narrowness gate: spike with a broadband neighbour shape → no notch + - Narrowness gate: spike with a broadband neighbor shape → no notch (`confirm_hold` never increments). - `from_profile` band: mark band 5 as `from_profile`; feed its bin below threshold for `release_frames + 1` frames → assert no `TOAST_RELEASE_*` diff --git a/tasks/T-033.md b/tasks/T-033.md index 46056f0..5657165 100644 --- a/tasks/T-033.md +++ b/tasks/T-033.md @@ -54,7 +54,7 @@ therefore checks the FX rack slot, not a bus insert. --model {xr18,x32} Mixer model (default: xr18) ``` -All existing behaviour is unchanged when `--model xr18` (or flag omitted). +All existing behavior is unchanged when `--model xr18` (or flag omitted). ## Changes @@ -65,7 +65,7 @@ All existing behaviour is unchanged when `--model xr18` (or flag omitted). | x32 | 10023 | ### RTA meter subscription (`osc_io.c`) -Parameterise `osc_subscribe_meters4()` to accept the meter path: +Parameterize `osc_subscribe_meters4()` to accept the meter path: ```c void osc_subscribe_meters(OscConn *c, const char *meter_path); diff --git a/tasks/T-034.md b/tasks/T-034.md index 7c95e86..33270fc 100644 --- a/tasks/T-034.md +++ b/tasks/T-034.md @@ -70,7 +70,7 @@ complexity 148 to ~30 and makes the reactive mode independently testable. The preflight `run_doctor` should stay inside `run_reactive` only — not run for `--ringout`. -3. No behaviour changes. All flags, defaults, and flow remain identical. The +3. No behavior changes. All flags, defaults, and flow remain identical. The only observable difference is that `osc_conn_close` for the ring-out path moves out of `run_ringout` and into `main()` (it is already there — check this, do not double-close). diff --git a/tasks/T-035.md b/tasks/T-035.md index f58845c..bb26cdc 100644 --- a/tasks/T-035.md +++ b/tasks/T-035.md @@ -95,7 +95,7 @@ All internal functions that currently take `AppState *s` become suffices, `(const AppConfig *cfg)`. `main()` declares `AppConfig cfg; AppRuntime rt; memset(&cfg, 0, ...); -memset(&rt, 0, ...);` and initialises them separately. +memset(&rt, 0, ...);` and initializes them separately. ## Mechanical substitution guide @@ -135,17 +135,17 @@ Every static function that takes `AppState *s` needs its signature updated: - [ ] `make test-ringout-emu-x32` passes - [ ] `AppState` no longer exists in `XAir_ToastSaver.c` - [ ] `AppConfig` fields are never mutated after `main()` finishes parsing -- [ ] No behaviour changes (no flag defaults changed, no logic changed) +- [ ] No behavior changes (no flag defaults changed, no logic changed) ## Notes - `load_mic_cal` is called during arg parsing (from `getopt` case - `OPT_MIC_CAL`), before `AppRuntime` is fully initialised except for + `OPT_MIC_CAL`), before `AppRuntime` is fully initialized except for `mic_corr`, which is zeroed by `memset`. This is fine — just call `load_mic_cal(&rt, optarg)` during parsing. - `make_reactive_ctx` builds a `TuiReactiveCtx` from AppState fields — update it to read from both `cfg` and `rt`. - This is a large mechanical change. Go function-by-function in file order. Compile after each function update to catch typos early (`make XAir_ToastSaver`). -- Do not change any logic, thresholds, flow, or external behaviour — only the +- Do not change any logic, thresholds, flow, or external behavior — only the struct split and signature updates. diff --git a/tasks/T-036.md b/tasks/T-036.md index 14ec13d..dd16259 100644 --- a/tasks/T-036.md +++ b/tasks/T-036.md @@ -109,12 +109,12 @@ if (s->model->check_outputs) { - If T-035 has landed, the signatures use `(const AppConfig*, AppRuntime*, ...)` instead of `AppState*`. Update `check_outputs_fn` accordingly. - `xr18_check_outputs` should be defined immediately before `MODEL_XR18` in the - file so the function pointer is valid at static initialisation time — but note - that in C, static struct initialisers with function pointers are fine as long + file so the function pointer is valid at static initialization time — but note + that in C, static struct initializers with function pointers are fine as long as the function is declared before the struct literal. A forward declaration works too. - The X32 INFO message can stay inline in `run_doctor` (no vtable slot needed for the fallback message) — it is a general "routing is user responsibility" - note, not model-specific behaviour. + note, not model-specific behavior. - Do not change the FX slot check — it already works for both models and needs no per-model dispatch. diff --git a/tasks/T-037.md b/tasks/T-037.md index 425106f..899a025 100644 --- a/tasks/T-037.md +++ b/tasks/T-037.md @@ -103,7 +103,7 @@ XAir_ToastSaver: X32lib - [ ] All `g_log_scroll` mutations go through `log_scroll_down()` / `log_scroll_reset()` (search for bare `g_log_scroll` in .c — none should remain) -- [ ] No behaviour change +- [ ] No behavior change ## Notes @@ -111,7 +111,7 @@ XAir_ToastSaver: X32lib `run_ringout` and `run_reactive`. The write path (`g_log_scroll++`) maps to `log_scroll_down()`. The reset (`g_log_scroll = 0`) can be handled in `log_push` internally (the current logic already does `if (g_log_scroll > 0) - g_log_scroll++` — keep this behaviour inside `log.c`). + g_log_scroll++` — keep this behavior inside `log.c`). - The accessors (`log_count`, `log_scroll`, `log_lines`) are intentionally simple getters — no locking needed (single-threaded). - Do not add a test binary for `log.c` in this task — the existing integration diff --git a/tasks/T-038.md b/tasks/T-038.md index c2c72e2..9abb305 100644 --- a/tasks/T-038.md +++ b/tasks/T-038.md @@ -124,7 +124,7 @@ Also rename the section comment from `/* ── /meters/4 blob handler */` to - [ ] `handle_calibration_frame` and `handle_detection_frame` are each ≤ 35 lines - [ ] The dispatcher `handle_meters` is ≤ 8 lines -- [ ] No behaviour change +- [ ] No behavior change ## Notes