Features · shipped and coming

Features: shipped and coming

Cascade is a native macOS app that drives an Audio Precision SYS-2522 audio analyzer over USB, with no Windows PC and no APWIN (AP's own Windows software) in the loop. Chris, a hardware engineer, built it with Claude Code between April and October 2026, reverse-engineering every command from USB captures of APWIN. This page lists what shipped, how sure we are that each piece works on the real instrument, and what comes next.

Terms used below. THD = total harmonic distortion (the harmonics of a pure tone, relative to the tone); THD+N adds noise. FFT = the spectrum of the signal, one magnitude per frequency bin. IMD = intermodulation distortion (what two tones do to each other). DUT = device under test (the preamp, amp or cable being measured). GenMon = generator monitor, the analyzer reading the AP's own generator internally, with no cable. Arb = arbitrary-waveform mode, where the generator plays samples we download instead of a sine. A blob is a recorded APWIN USB session that the app replays to bring the instrument into a known state.

Status key. HW-verified = checked on the 2522 against an independent reference (voltmeter, scope, APWIN, a known DUT). Offline-verified = tests or captures only. Unverified = shipped and never checked on the bench. Superseded = replaced, and by what.


Shipped, by the date each piece first worked

Hover for the feature, click to jump to it. 51 shipped, 20 coming.

Shipped

April–May: talking to the instrument

DateWhat a user can doStatusSource
04-16Xcode project created (empty SwiftUI app).—4309d16
05-15Connect to the 2522: the app replays a two-phase init (about 40k + 13k recorded packets) and the instrument comes up.HW-verified: in use on every run sincea98b1ab, project_cascade_app.md
05-15Frequency Response sweep: 31 points from 20 kHz to 20 Hz, dBV/Vrms display, crosshair readout, stop button.HW-verifieda98b1ab, project_cascade_app.md
05-15Generator level in 5 fixed steps (−20, −10, 0, +10, +20 dBV).Superseded by continuous amplitude (Aug)project_cascade_app.md
05-15Input switch between GenMon and the XLR (DUT) input, per channel.HW-verified 07-25 with a Schiit SYS preamp: flat 0 dB, followed the volume knobproject_dut_mode.md
05-15THD vs Frequency, first version: worked at 0 dBV, broken at −20 dBV.Superseded by the June decodea98b1ab, project_cascade_app.md

June: the frequency word and the THD decode

DateWhat a user can doStatusSource
06-16Generator frequency is computed, not looked up (closed-form formula for the frequency register).Offline-verified at 12 frequencies from captures; scope-checked from 2.5 kHz up, below 2.5 kHz not on a scope731f318, protocol_frequency.md
06-19THD reads correctly at any level: one universal formula, THD = 20·log10(residual / (1024·fundamental)).HW-verified against APWIN-logged readings, −70 to −101 dB; floor later matched APWIN's own export (−110 to −113 dB, 07-18)9e07514, protocol_thd_measurement.md, project_thd_floor_mystery.md

July: real DUTs, Channel B

DateWhat a user can doStatusSource
07-18THD sweep settles before each reading, so the first point no longer spikes.HW-verifiedd7226ec
07-25Level readings are logarithmic, fixing a −7.5 dB error at low levels; INPUT picker on the THD view.HW-verified: SYS readings matched at −46/−41 dB8c06473, parent 018b1fa
07-25Normalize a frequency response to a thru reference (SET REF / CLEAR NORM), so the plot shows true DUT gain.HW-verified: Schiit SYS at full volume reads about −0.04 dB flataaf4c20, project_dut_mode.md
07-25Channel B THD is a real measurement (it had been a copy of Channel A).HW-verified against APWIN's −100.8 dB0b20172, parent a1f8596
07-25THD of an external DUT through the XLR inputs, one or two channels.HW-verified: two-channel result matched APWIN's T61 capture6740635, 6326119, parent 27cd79b

August: any level, THD vs Level, the live meter, the FFT

DateWhat a user can doStatusSource
08-23Continuous generator level, any dBV, computed from a three-field model (range, code, fine trim).HW-verified by voltmeter at −20/−10/0/+6 dBV. Superseded 09-05 by the closed form1de667f, project_continuous_amplitude.md
08-23THD vs Level sweep at a fixed frequency (it had been mock data).HW-verified: textbook curve, 0.014 % at −30 dBV falling to 0.0007 % at +6 dBV4c50bfc, 4847296, project_thd_vs_level.md
08-23Closed-loop level calibration, saved between runs.Superseded and deleted 09-05; it later hid the closed form (below)182b882, dee70e4
08-23THD views: dB/% toggle, POINTS honoured, adaptive settling at each point.HW-verified: flat −87.9 dB, 20 Hz–20 kHz, at −20 dBV5210845, dea320e, e2a687d
08-24 → 08-29Power & THD live meter: drag the level and watch voltage, power and THD+N follow, both channels.HW-verified: Test 1 −20.04 dBV at −20 set; Test 4 followed a knob over about 70 dB. Test 6 (amplifier) not run4669232, project_live_power_meter.md
08-29FFT spectrum, native: the AP's computed bins decoded as block floating point.HW-verified: −20.02 dBV vs APWIN −19.994669232, ed49ff4, protocol_fft_readback.md, project_fft_state_replay_bind.md
08-30FFT reads one point per bin, so no harmonic falls between samples; sample rate calibrated to this unit (65546.5 Hz, counter-measured).HW-verified for the sample rate; the per-bin grid is correct by constructioned49ff4, 4669232, project_fft_resolution.md

September: trust, export, stereo, tests

DateWhat a user can doStatusSource
09-04EXPORT CSV with the instrument state in the header; every run auto-exported to a session folder; console written to a file.HW-verified: in use on every bench run since98663fb, 0f4eb4a, 4a6698d, project_csv_export.md
09-04Frequency sweep honours START/STOP direction (it always ran high to low).Fixed; no separate bench check recordede8ff717, project_sweep_direction.md
09-04Channel B level decoded with its own range exponent.Unverified: needs a CH B RANGING run on an unbalanced rig97baff1, project_thd_chb_duplicate_buffer.md
09-05Level register's low bits treated as flags, removing a 4.61 dB sweep offset.HW-verified: same rig went +4.474 → −0.092…0.000 dBV5f75f9e, project_octave_flag_bits.md
09-05Generator level is a closed-form formula, −50 to +22 dBV; AMPL VERIFY button checks it.HW-verified: 43 points, worst 0.037 dB. ⚠️ Shipped dormant: a saved table from 08-23 kept running the old model until the table was retired the same day2751541, e29437f, dee70e4, 976e01d, project_ampl_closed_form.md
09-05Unit tests with ⌘U (13 at the start, 123 by 10-04).Offline-verified7bda7af, 29f0e91
09-05Stereo FFT: Channel B acquired and plotted beside Channel A.HW-verified: channels agree to 0.004 dB; one-cable run showed 115 dB separationf896c11, project_stereo_fft.md
09-05STOP AT THD % on THD vs Level: the sweep halts at a distortion limit and drops the level.HW-verified 09-12 into a small RIAA inverse filter, rising sweep only. Falling sweep (88a8463) unverifiedb4666f8, project_pending_amp.md
09-05FFT finds the fundamental itself, so an external oscillator can be measured.HW-verified959ff6c, project_external_source.md
09-05Log/linear frequency axis; readout tiles show frequencies; crosshair snaps to peaks.HW-verified again 10-09: it broke because snap targets came from Channel A only; fixed in 44861fe (all traces), Chris hovered to confirmc854b41, 0fb9721, project_session_resume_20261004.md
09-07SINAD tile beside THD (signal vs everything else, 20 Hz–20 kHz, unweighted).HW-verified against APWIN to 0.047 dB, but only at N = 8192 with the tone on a bin; other sizes are wrongb263683, project_sinad_conditional.md
09-08THD vs Level x-axis in watts from the measured output, with a load picker.Unverified: needs an amplifier5eb5787, project_pending_amp.md
09-12FFT power averaging, and transform sizes up to 32768.HW-verified: bench runs V1 and V2 passedd47feac, BENCH_QUEUE.md
09-12THD checked in tests against AP's own known-distortion files.Offline-verified; passed to 0.06 dB14cfee2, project_sinad_conditional.md
09-12App can be quit again (it had hung on quit since 09-04).Fixed039968c, project_quit_deadlock.md
09-13READ UNIT EEPROM: read the per-unit memory the instrument exposes at startup.Reads on hardware; no field decoded to a unit yet33fed6a, project_session_resume_20260913.md
09-26Multitone analysis reachable from the app; external source via an exported WAV.Superseded by in-app playback (10-02) and the AP generator (10-03)9cf30e6, a366c6c, 25d79fa

October: non-sine signals, scripts

DateWhat a user can doStatusSource
10-02Multitone becomes its own tab (tones, gap, level, size, window, channels).See the next three rows1ba87a0
10-03Native IMD (SMPTE, CCIF, DIM) from the AP's own two-tone generator, no audio interface. A sidebar probe (T90 FULL STATE + MEASURE IMD), not a tab yet.❌ Withdrawn 10-18: T92 confirmed T90 was a D/A sine (Wfm 1, error 11013). Superseded by the T92 IMD probe below55c775e, 334bed3, project_session_resume_20261003.md
10-03Arb plays our own samples: first arb playback in the project.HW-verified: our 2 kHz tone read 2000 Hz at −20.01 dBV; scope agreed every run22ba95b, 5611eaf, project_session_resume_20261003.md
10-03Multitone from the AP generator (default source).HW-verified: 21/21 and 38/38 tones, flatness 0.01 dB, scope agreed4eaa92b, project_session_resume_20261003.md
10-04Multitone LEVEL picker (−40 to +6 dBV FS).HW-verified at −20/−10/0: tones within ±0.02 dB; Ch B = Ch A to 0.006 dB7912f6b, 5918c7a
10-04FFT range set per channel, as APWIN does; shared mode removed.HW-verified: forcing Ch B one range higher reads exactly −6.022 dB77e0134, 9569553
10-04Script runner: drop a .cscript in a folder and the app runs the procedure unattended, locks the controls, ends with the generator off.HW-verified: scripts 00–10 ran unattendede07083d, cbf3303, project_session_resume_20261004.md
10-04No AP-owned content shipped in the app bundle.Offline-verified (check_no_ap_code.py passes; it had been failing)7126c12
10-04FFT with an external oscillator on the DUT input no longer times out.HW-verified 3/3 after fresh reloads (script 09)a499749
10-17Post-change fixes: FFT after Multitone plays a sine again; window and axis kept per view; no demo data; HISTORY records real runs (display-only recall); DUT level offset removed (it read +1.24 dB high); dBr SET REF in FFT and Frequency Response; harmonics found at n × f0 ("below noise" tiles); multitone reloads after the generator was off and refuses a capture that is not the multitone.HW-verified: script 29 OK on the final build (7939f33); each fix checked by handc4de97c … 7939f33
10-18Real analog IMD (SMPTE 1:1, 60 Hz / 7 kHz) from Cascade: Developer-panel probe replays APWIN's T92 session.HW-verified: reference level 17,521 vs APWIN 17,520; raw residual 2.815e-3 vs 2.77e-3 %. Scale to APWIN's reported value is an estimate083c0fe, bench_T91_T92_results.md
10-24THD+N on a real DUT matches APWIN (Scott 350R): the function meter's own range, APWIN's filters and Ch A input byte, reading only fresh meter readings.HW-verified: −75.7 vs APWIN −75.8 dB; a loopback control showed the remaining bad points were the warm receivere47d579 … 64660bc, bench_receiver_plan.md
10-24IMD, all five modes, calibrated (raw ÷ per-mode factor) and checked on a real DUT by replaying APWIN's T93.HW-verified: every mode within 1.5× of APWIN, three runs, repeatable to 4 %78ceb39
10-24Generator OUTPUT config: Bal-Float, Bal-Gnd, Unbal-Float, Unbal-Gnd (DUT input).HW-verified: unbalanced bytes identical to APWIN's; level held across configs on the receivercba7e8c
10-04FFT INPUT = GenMon actually selects GenMon. Since the FFT tab shipped (08-30), the setup replay had silently left the input on XLR, and the CSV recorded the wrong input; found 09-12.HW-verified (script 10). Small open item: GenMon read −20.15 to −20.19 dBV at −20 set29f0e91, BENCH_QUEUE.md

Shipped, then broken or open now: the setup replay goes bad intermittently (arb and sine alike), and every capture then times out until the next replay. Cause found 10-10 (a refused DSP write at packet 108201); the wait-point patch ceced28 is in and being measured. Fixed 10-09: crosshair snap and Channel-A-only readouts (44861fe).


What the app does today

MeasurementInputs / channelsRange / limitsStatus
FFT SpectrumGenMon or XLR; A, or A+BSizes to 32768, 4 windows, averaging; reads to 20 kHz (Nyquist is 32 768 Hz)HW-verified; readouts follow the signal's channel (10-09); intermittent bad replay being fixed
THD, SINAD (from FFT)as aboveHarmonics 2–10, count shown; SINAD valid only at N = 8192 with the tone on a binHW-verified (SINAD conditional)
THD vs FrequencyGenMon or XLR; Ch B optional20 Hz–20 kHz, set POINTSHW-verified
THD vs LevelXLR (DUT) only since 10-18; Ch B optional−50 to +22 dBV; STOP AT THD %; x-axis dBV/Vrms/WHW-verified, on a real amplifier too (10-23/24: watts correct, STOP AT THD trips); GenMon disabled (stalls)
Frequency ResponseGenMon or XLR; A+BSTART/STOP/POINTS, dBr reference (10-17)HW-verified
Power & THD (live)GenMon or XLR; Ch B optionalLive level drag; power into a selected loadHW-verified Tests 1–4; amplifier untested
MultitoneAP generator (arb), external, or played from the Mac−40 to +6 dBV FS; XLR loopback needed in arbHW-verified; refuses a capture that is not the multitone (10-18)
IMD (SMPTE 1:1, SMPTE 4:1, CCIF, DFD, DIM 30)Developer-panel probes: T92 replay on GenMon, T93 replay on a real DUTAPWIN's own settingsHW-verified, calibrated to APWIN's reported % (10-24); not a tab yet
Generator OUTPUT configEvery test view, with INPUT = DUTBal-Float (default each launch), Bal-Gnd, Unbal-Float, Unbal-GndHW-verified on the Scott 350R (10-24)
Scripts, CSV exportAll tests≤ +6 dBV when unattendedHW-verified

Coming up

Next (ready; small, little or no hardware)

Needs the bench or a capture

Later / larger

Decided by Chris


Method

Dates are first-working dates taken from commits on the app's trunk (git -C Cascade log origin/cascade-main) and the parent repo. Features worked out between commits carry their memory-file date and the commit that landed them; for example, the 08-24…08-29 work landed in 4669232 on 08-30. Verification status comes from docs/memory/*.md, BENCH_QUEUE.md and the resume points, not from commit messages alone. The roadmap comes from docs/BACKLOG.md, DESIGN_DIFFERENCES.md, and the 10-03 and 10-04 resume points. Short hashes without a prefix are app (Cascade/) commits; "parent" marks a commit in the outer repo.