Spotify’s basic-pitch ships a TensorFlow.js build, so audio-to-MIDI can run entirely in a tab: no upload, mono 22050 Hz via OfflineAudioContext, model weights cached after the first run.

Two implementation details matter more than the demo MIDI file. First, the network emits per-frame note activations, onsets, and pitch contours; turning those into notes (onset/frame thresholds, minimum note length, pitch range) is cheap. Keep the raw matrices and only re-run extraction when a slider moves — do not re-infer. Copy the arrays first if the note decoder mutates them when a pitch range is set. Second, a piano roll is a weak check; playing the source and a simple synth of the detected notes on one playhead makes octave errors and dropped notes obvious immediately.

The operating range is a single line: a sung melody, a guitar riff, a piano part. On a dense full mix you get a rough sketch at best. Browser memory already pushes implementations toward 15–60 second clips, so dropping a stereo master into the tab and hoping the model “hears the topline” stacks two failure modes (polyphony / bleed, plus RAM).

A practical browser workflow:

  • Feed a dry take or a pre-isolated lead/vocal stem, not the commercial mix.
  • Convert a short segment first, A/B it, then widen.
  • Adjust onset/frame thresholds and minimum note length against the cached model output.
  • If you only have a mix and the tab cannot also run a separator comfortably (Demucs-class models are a different memory budget than a ~1 MB pitch model), isolate locally and bring just that stem into the browser.

This is the same “separate, then transcribe” switch as a Python Demucs → Basic Pitch script, shaped by what a tab can hold. The converter is not broken when it garbles a full mix; it was asked for a monophonic sketch of a polyphonic file.