Auto-checks
Click Run All Checks in the header to evaluate every requirement at once. Tap an individual row to jump to its tab.
Quick read
Environment
Capability matrix
performance.now() resolution
Measured by sampling consecutive distinct values. Lower is better. WebKit usually reports β€100 Β΅s in non-cross-origin-isolated contexts.
localStorage
Web MIDI access
The PRD's Stage-1 form factor depends on this returning a usable MIDIAccess object. On iPad Chrome (WebKit) it is expected to be undefined.
Inputs
Outputs
Connection events
Live activity
Last hit
Per-note counters
Event log 0
| # | Time (s) | Ξ ms | Type | Ch | Note | Vel | Drum | Raw |
|---|
Pad β drum mapping
Tap a pad cell, then strike that drum on your kit. The next incoming note is recorded as the mapping. Tap again to remap.
Drums
Test mode
Strike pads on the kit. Each hit is decoded against the current mapping and shown below.
Latency calibration
Plays a click track at the configured BPM. Strike any pad on every click. The app measures the delta between scheduled click time and actual MIDI hit time, applies a median-absolute-deviation filter, and reports a recommended inputOffsetMs.
Result
Histogram (ms)
Per-click deltas
| # | Click time (s) | Hit time (s) | Ξ ms | Status |
|---|
Live clocks
- audioContext.state
- β
- currentTime
- β
- performance.now()
- β
- clockOffset (ms)
- β
- Ξ vs initial offset
- β
- Visibility
- β
Sample every second. Forces a known suspend/resume to verify recovery without breaking the run.
Offset over time (ms vs first sample)
Idle.
State / visibility log
Web Audio scheduler accuracy
Schedules N short clicks at exact future audio-clock times and measures (a) what the scheduler tick reported as the "due" time vs the requested time, and (b) animation-frame jitter during the run.
Scheduler results
requestAnimationFrame jitter
setTimeout(0) latency
Calibration values
Latency Test β Apply writes inputOffsetMs here. The Live Monitor and Latency Test both subtract this from incoming MIDI timestamps so you can preview the effect.
Tolerances (ms)
Click sound
Storage
Show raw dt:diag:*
β
Quick start
- Connect the Alesis Nitro Max via USB β Apple Camera Adapter β iPad / laptop.
- Open this page over
http://<lan-ip>:8000/diagnostics/(Web MIDI requires a real origin, notfile://;localhostalso works). - Click Run All Checks in the header.
- Read the Overview rows. Anything FAIL is a blocker; WARN needs attention.
What each tab does
Overview
Aggregate pass / warn / fail summary. Tap any row to jump to its tab.
Environment
User agent, browser detection (with explicit "iPad WebKit β no Web MIDI" call-out), secure-context status, AudioContext baseline numbers, performance.now() resolution test, localStorage write/read.
Web MIDI
Calls navigator.requestMIDIAccess(). Lists every input/output, their state and connection. Logs every statechange event with timestamp so you can see disconnects in real time.
Live Monitor
Streams MIDI events as they arrive. Filters by note-on / note / cc / all. Shows the last hit in big text with the decoded drum name, plus per-note counters and a rolling table of the last 200 events.
Pad Mapper
Walks through every canonical drum (kick, snare, hi-hat closed/open/pedal, tom1, tom2, floor, crash, ride, β¦) and records the MIDI note your kit emits for each one. Persists under dt:diag:padMap. Includes a "test mode" big readout so you can verify a strike on the kit produces the right drum name.
Latency Test
Implements the PRD's calibration algorithm: schedules clicks, captures hits within Β±200 ms (configurable), discards orphans, computes median, MAD-filters outliers β₯2Γ MAD, reports mean / median / Ο / MAD plus a histogram. The "Apply" button writes inputOffsetMs back into Settings.
Clock & Drift
Live audioContext.currentTime, performance.now(), computed clockOffset = audioContext.currentTime*1000 - performance.now(). Plots Ξ-vs-first-sample over time so you can spot the WebKit sleep-stop behaviour. Force suspend calls audioContext.suspend() to verify recovery; Resume calls resume() and recomputes the offset.
Scheduler
Schedules N AudioBufferSourceNodes at exact times into the future and records lateness vs. requested time at dispatch. Also samples requestAnimationFrame intervals during the run for visual-jitter estimation, and measures setTimeout(0) latency.
Settings
All persisted under dt:diag:* (separate from the main app's dt:* keys). inputOffsetMs / jitter / tolerances / click-sound parameters. Export / import JSON. "Reset to defaults" wipes the whole namespace.
Interpreting verdicts
- PASS β value is well inside the PRD's acceptance window.
- WARN β works, but at the edge: Bluetooth headphones may be in use; iPad may be in a low-power state; sample size too small.
- FAIL β blocker. Web MIDI absent, calibration above 100 ms offset, jitter above 25 ms, etc.
Common findings
- Web MIDI undefined on iPad Chrome. Expected β every iOS browser is a WebKit skin and WebKit has never shipped Web MIDI. See
plan.mdPhase -1 for fallback branches. - AudioContext is "suspended" until first tap. Expected β iOS auto-suspends until user gesture. The header buttons cover the unlock; if you see
suspendedpersisting, click anywhere. - Big positive offset (~50β100 ms). Real β that's the round-trip latency through USB MIDI β browser β audio output. Calibration captures it as
inputOffsetMs; main-app scoring will subtract it. - Jitter > 25 ms. Almost always Bluetooth audio. Switch to wired and re-calibrate.
Storage namespace
This page reads and writes only under dt:diag:*. The main Drum Trainer app uses dt:settings, dt:attempts:*, etc. The two cannot collide. To copy a calibrated inputOffsetMs into the main app, open Settings β Export, then paste the relevant fields into the main app's adult dashboard (or via DevTools).
Known limits
- This page cannot detect the actual audio-output device (built-in vs. wired vs. BT) β Web Audio doesn't expose that. We infer it from latency: >100 ms offset β almost certainly BT.
- Scheduler "lateness" measures scheduling lag, not the audio engine's render lag. The latter is captured indirectly by the latency test.
- The page does not write to the main app's
dt:*keys; copy values across manually if you want them applied there.