Session story · 2026-08-24
Session: 2026-08-24 → 2026-08-27 (one conversation, two sittings, a day of API outages between them) · Outcome: the Power & THD live meter wired to hardware, and the FFT magnitude readback decoded offline from captures already on disk.
The Power & THD tab looked finished in mock mode. Nobody had checked what it did on the instrument. The FFT had been stuck for weeks at one missing piece: the per-bin magnitude. A written plan (docs/fft_native_decode_plan.md) sat at its first step. Three families of candidate reads had been ruled out, including a module-0x30 read that moved only about 1 dB when the tone dropped 40 dB.
The working belief was: the magnitude lives in some read we haven't found yet, and module 0x30 is not it.
Chris opened with: "is the "power & thd" module fully fuctional? could i plug an amp into the 2522 and slide the input power up to test thd and power output"
Can the live meter show THD as the level slider moves, on real hardware? And, two days later, where does the FFT keep its magnitudes?
The meter was mostly a stub. Claude read PowerTHDView.swift, AppState.swift and APDevice.swift before answering, and the answer was no. The slider had no didSet, so moving it did nothing until the meter restarted. The hardware poll hardcoded thd: nil. OUTPUT and POWER used raw 1.23 fixed-point DSP fractions as volts, with no range scaling.
Chris set the target. "my aim is to be able to see the thd live as i adjust the amplitude of the 2522 output and send it through the DUT. this will give me an interactive dashboard to test a device for power handling." He also asked for a THD abort limit on the THD vs Level tab, for later. Claude saved that to memory first.
The build, and an outage. Claude reused the validated THD sweep setup with a new live: true flag, so no new register writes were guessed. Then the API returned 529 Overloaded six times between 04:55 and 06:43. Chris sent the same line six times: "when you think it's good to resume please do". The sixth, that evening, got through, and the work resumed and built clean.
One safety change. _ensureAnlrCal always jumped the generator to 0 dBV for its reference, and the generator stays live on the XLRs during that step. With an amp connected that is 1 Vrms into its input. Claude made the reference level a parameter, and the live meter now passes the level already dialled in. Claude also flagged that POWER is V²/R from the LOAD field, since the AP does not sense current. Nothing was committed; Claude left that until the meter had run against an amp.
Two days later, the FFT. "where are you with planning for functional FFT? what is the next step in that?" Claude proposed a reframe: APWIN drew 512 values on screen, so they crossed the wire. Treat the exported CSV as known plaintext and search the byte stream for it. Chris: "i am actually not connected to anything. since you said it is all offline analysis, go ahead and start."
The search, in one sitting. It ran as a chain of small findings, several of them dead ends:
E9 40 0A was a busy-wait poll, not data.0xEF was a 31 KB DSP upload. The 64-byte bulk reads were also program uploads.Claude stopped itself once. Having seen 498 of 499 points "solve to an integer exponent", Claude noted that normalized mantissas make that a tautology, not evidence, and ran proper correlation tests instead. Several more hypotheses died: 0B 01 37 was an echo of the next point, and module 0x48 reads were mode-select echoes. Then the word itself: 170 distinct raw words against 169 distinct dB values suggested the word alone carried the level. The exponent was packed in the low 6 bits.
dBV = 20·log10(word >> 6) + 6.020599913 · (word & 0x3F) − 479.1008
The constant was fitted on T65 alone. On T66 both passes matched APWIN's CSV to 0.00001 dB on 511/511 points, and the two-level test gave 39.9992 dB against an expected 40. T64 (N=4096), never fitted, gave the predicted 16 Hz bins.
Why module 0x30 had been ruled out. The earlier "moved only ~1 dB" observation was real. At the tone bin the words read as a −2.15 dB change, but the exponent had stepped 7 counts, which is the missing 42.14 dB. The reading was right and the interpretation dropped the exponent. Claude tied it to the same shape as the app's earlier val16ToDBV bug: a normalized mantissa read without its range byte looks like a stuck meter.
A procedure for what the captures could not answer. "can you write an AP procedure like you've done before with T66 etc, to accomplish what you need?" Before asking for bench time, Claude checked whether T66 already answered the range question. It had zero 8A relay writes between passes, so it did not. T67 was written with four fixed ranges, three transform lengths, two windows and a deliberate half-bin pass. Claude also found that T65 and T66's "on-bin" 1031.25 Hz had been chosen for fs=48000 and was bin 16.11 at the real rate. T67 used 1024 Hz. Chris then asked for the bench list, and Claude wrote docs/bench_checklist_T67_and_power_meter.md, with T67 first so the capture was banked before the native app could wedge the adapter.
| Believed | Then | What overturned it |
|---|---|---|
| Power & THD tab is working | slider inert, THD nil, volts uncalibrated | reading the view and AppState |
| FFT fs = 48 kHz | fs = 65536 Hz | a round 1000.000 Hz frequency write |
| module 0x30 is not the magnitude | it is, block float | exponent found in the low 6 bits |
| the exponent is a separate status byte | it is inside the 24-bit word | 170 words ↔ 169 dB values; exact decode |
| T65/T66 tones were on-bin | 1031.25 Hz is bin 16.11 | recomputing at fs=65536 |
Cascade/Cascade/PowerTHDView.swift, AppState.swift, APDevice.swiftdocs/T64, T65, T66 pcaps and CSVsdocs/protocol_fft_readback.md, docs/fft_native_decode_plan.md (HEAD b4aec72 at the start of the FFT work), docs/bench_checklist_T67_and_power_meter.mdanalysis/fft/ (fftdecode.py)docs/T67_FFT_P2_range_N_window_v1.apbThe live meter was built and untested on hardware. The FFT decode was solved offline. Open: whether the −479.1008 constant shifts with analyzer range, the N and window registers, the setup/trigger sequence, and the whole Power & THD bench checklist.