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.
Coming up
Next
Prove the setup-replay fixChannel A noise floor ~16 dB above BRead the FFT to Nyquist (32 768 Hz), not 20 kHzSeparate Channel B generator levelShow "not measured" explicitlyList what each protocol capture analysis skippedDecode the unit EEPROM| Date | What a user can do | Status | Source |
|---|---|---|---|
| 04-16 | Xcode project created (empty SwiftUI app). | — | 4309d16 |
| 05-15 | Connect 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 since | a98b1ab, project_cascade_app.md |
| 05-15 | Frequency Response sweep: 31 points from 20 kHz to 20 Hz, dBV/Vrms display, crosshair readout, stop button. | HW-verified | a98b1ab, project_cascade_app.md |
| 05-15 | Generator level in 5 fixed steps (−20, −10, 0, +10, +20 dBV). | Superseded by continuous amplitude (Aug) | project_cascade_app.md |
| 05-15 | Input 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 knob | project_dut_mode.md |
| 05-15 | THD vs Frequency, first version: worked at 0 dBV, broken at −20 dBV. | Superseded by the June decode | a98b1ab, project_cascade_app.md |
| Date | What a user can do | Status | Source |
|---|---|---|---|
| 06-16 | Generator 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 scope | 731f318, protocol_frequency.md |
| 06-19 | THD 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 |
| Date | What a user can do | Status | Source |
|---|---|---|---|
| 07-18 | THD sweep settles before each reading, so the first point no longer spikes. | HW-verified | d7226ec |
| 07-25 | Level 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 dB | 8c06473, parent 018b1fa |
| 07-25 | Normalize 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 flat | aaf4c20, project_dut_mode.md |
| 07-25 | Channel B THD is a real measurement (it had been a copy of Channel A). | HW-verified against APWIN's −100.8 dB | 0b20172, parent a1f8596 |
| 07-25 | THD of an external DUT through the XLR inputs, one or two channels. | HW-verified: two-channel result matched APWIN's T61 capture | 6740635, 6326119, parent 27cd79b |
| Date | What a user can do | Status | Source |
|---|---|---|---|
| 08-23 | Continuous 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 form | 1de667f, project_continuous_amplitude.md |
| 08-23 | THD 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 dBV | 4c50bfc, 4847296, project_thd_vs_level.md |
| 08-23 | Closed-loop level calibration, saved between runs. | Superseded and deleted 09-05; it later hid the closed form (below) | 182b882, dee70e4 |
| 08-23 | THD views: dB/% toggle, POINTS honoured, adaptive settling at each point. | HW-verified: flat −87.9 dB, 20 Hz–20 kHz, at −20 dBV | 5210845, dea320e, e2a687d |
| 08-24 → 08-29 | Power & 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 run | 4669232, project_live_power_meter.md |
| 08-29 | FFT spectrum, native: the AP's computed bins decoded as block floating point. | HW-verified: −20.02 dBV vs APWIN −19.99 | 4669232, ed49ff4, protocol_fft_readback.md, project_fft_state_replay_bind.md |
| 08-30 | FFT 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 construction | ed49ff4, 4669232, project_fft_resolution.md |
| Date | What a user can do | Status | Source |
|---|---|---|---|
| 09-04 | EXPORT 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 since | 98663fb, 0f4eb4a, 4a6698d, project_csv_export.md |
| 09-04 | Frequency sweep honours START/STOP direction (it always ran high to low). | Fixed; no separate bench check recorded | e8ff717, project_sweep_direction.md |
| 09-04 | Channel B level decoded with its own range exponent. | Unverified: needs a CH B RANGING run on an unbalanced rig | 97baff1, project_thd_chb_duplicate_buffer.md |
| 09-05 | Level 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 dBV | 5f75f9e, project_octave_flag_bits.md |
| 09-05 | Generator 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 day | 2751541, e29437f, dee70e4, 976e01d, project_ampl_closed_form.md |
| 09-05 | Unit tests with ⌘U (13 at the start, 123 by 10-04). | Offline-verified | 7bda7af, 29f0e91 |
| 09-05 | Stereo FFT: Channel B acquired and plotted beside Channel A. | HW-verified: channels agree to 0.004 dB; one-cable run showed 115 dB separation | f896c11, project_stereo_fft.md |
| 09-05 | STOP 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) unverified | b4666f8, project_pending_amp.md |
| 09-05 | FFT finds the fundamental itself, so an external oscillator can be measured. | HW-verified | 959ff6c, project_external_source.md |
| 09-05 | Log/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 confirm | c854b41, 0fb9721, project_session_resume_20261004.md |
| 09-07 | SINAD 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 wrong | b263683, project_sinad_conditional.md |
| 09-08 | THD vs Level x-axis in watts from the measured output, with a load picker. | Unverified: needs an amplifier | 5eb5787, project_pending_amp.md |
| 09-12 | FFT power averaging, and transform sizes up to 32768. | HW-verified: bench runs V1 and V2 passed | d47feac, BENCH_QUEUE.md |
| 09-12 | THD checked in tests against AP's own known-distortion files. | Offline-verified; passed to 0.06 dB | 14cfee2, project_sinad_conditional.md |
| 09-12 | App can be quit again (it had hung on quit since 09-04). | Fixed | 039968c, project_quit_deadlock.md |
| 09-13 | READ UNIT EEPROM: read the per-unit memory the instrument exposes at startup. | Reads on hardware; no field decoded to a unit yet | 33fed6a, project_session_resume_20260913.md |
| 09-26 | Multitone 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 |
| Date | What a user can do | Status | Source |
|---|---|---|---|
| 10-02 | Multitone becomes its own tab (tones, gap, level, size, window, channels). | See the next three rows | 1ba87a0 |
| 10-03 | Native 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 below | 55c775e, 334bed3, project_session_resume_20261003.md |
| 10-03 | Arb 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 run | 22ba95b, 5611eaf, project_session_resume_20261003.md |
| 10-03 | Multitone from the AP generator (default source). | HW-verified: 21/21 and 38/38 tones, flatness 0.01 dB, scope agreed | 4eaa92b, project_session_resume_20261003.md |
| 10-04 | Multitone 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 dB | 7912f6b, 5918c7a |
| 10-04 | FFT range set per channel, as APWIN does; shared mode removed. | HW-verified: forcing Ch B one range higher reads exactly −6.022 dB | 77e0134, 9569553 |
| 10-04 | Script 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 unattended | e07083d, cbf3303, project_session_resume_20261004.md |
| 10-04 | No AP-owned content shipped in the app bundle. | Offline-verified (check_no_ap_code.py passes; it had been failing) | 7126c12 |
| 10-04 | FFT with an external oscillator on the DUT input no longer times out. | HW-verified 3/3 after fresh reloads (script 09) | a499749 |
| 10-17 | Post-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 hand | c4de97c … 7939f33 |
| 10-18 | Real 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 estimate | 083c0fe, bench_T91_T92_results.md |
| 10-24 | THD+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 receiver | e47d579 … 64660bc, bench_receiver_plan.md |
| 10-24 | IMD, 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-24 | Generator 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 receiver | cba7e8c |
| 10-04 | FFT 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 set | 29f0e91, 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).
| Measurement | Inputs / channels | Range / limits | Status |
|---|---|---|---|
| FFT Spectrum | GenMon or XLR; A, or A+B | Sizes 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 above | Harmonics 2–10, count shown; SINAD valid only at N = 8192 with the tone on a bin | HW-verified (SINAD conditional) |
| THD vs Frequency | GenMon or XLR; Ch B optional | 20 Hz–20 kHz, set POINTS | HW-verified |
| THD vs Level | XLR (DUT) only since 10-18; Ch B optional | −50 to +22 dBV; STOP AT THD %; x-axis dBV/Vrms/W | HW-verified, on a real amplifier too (10-23/24: watts correct, STOP AT THD trips); GenMon disabled (stalls) |
| Frequency Response | GenMon or XLR; A+B | START/STOP/POINTS, dBr reference (10-17) | HW-verified |
| Power & THD (live) | GenMon or XLR; Ch B optional | Live level drag; power into a selected load | HW-verified Tests 1–4; amplifier untested |
| Multitone | AP generator (arb), external, or played from the Mac | −40 to +6 dBV FS; XLR loopback needed in arb | HW-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 DUT | APWIN's own settings | HW-verified, calibrated to APWIN's reported % (10-24); not a tab yet |
| Generator OUTPUT config | Every test view, with INPUT = DUT | Bal-Float (default each launch), Bal-Gnd, Unbal-Float, Unbal-Gnd | HW-verified on the Scott 350R (10-24) |
| Scripts, CSV export | All tests | ≤ +6 dBV when unattended | HW-verified |
ceced28 in; needs a cold-start hunt to show it works. (memory: project_dut_ab_fft_timeout)AP.Gen.ChBAmpl. (BACKLOG §9)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.