Basic Pitch Transcriber — User Guide
A Praat-to-Python polyphonic transcription workflow that sends a selected Sound to Spotify's Basic Pitch model, post-processes the detected note events, writes MusicXML notation, and returns editable note data to Praat as Table, TextGrid, Strings, statistics, and visualization objects.
What this does
Basic Pitch Transcriber converts a selected Praat Sound into symbolic note events and notation. It does not attempt to infer an instrument label or separate instruments into timbral tracks. Instead, it uses Basic Pitch as an instrument-agnostic polyphonic note transcription model and then adds AudioTools-specific post-processing and notation logic.
The principal symbolic output is MusicXML. The Praat-side Table and TextGrid are editing and inspection surfaces built from the same detected note list.
Architecture
Praat front end
BasicPitchTranscriber.praat handles:
- form parameters and sensitivity presets;
- Python discovery and dependency probing;
- temporary WAV export;
- subprocess execution without a shell command string;
- reading statistics and per-note CSV data;
- optional MusicXML file saving;
- creation of Praat Strings and TextGrid objects;
- the analysis visualization and Info-window report;
- temporary-file cleanup.
Python engine
basic_pitch_transcriber.py handles:
- Basic Pitch inference;
- normalization and sorting of note events;
- optional repeated-onset merging;
- voice allocation and polyphony statistics;
- optional sustain-to-next-onset extension;
- MusicXML notation through
music21or the built-in writer; - temporary MIDI generation;
stats.txtandnotes.csvfor the Praat front end.
predict() arguments by name after inspecting the installed function signature. This is deliberate compatibility code for API changes across Basic Pitch releases rather than a fixed positional call.
Requirements & setup
Python packages
The engine explicitly requires:
basic-pitchnumpysoundfile
music21 is optional but recommended because it provides the more complete notation backend. The script's own installation hint is:
pip install basic-pitch music21
Python executable discovery
The Praat front end currently tries:
| Platform | Executable search |
|---|---|
| Windows | python |
| Linux / other Unix | python3 |
| macOS | /opt/homebrew/bin/python3, then a Python-framework path, then /usr/local/bin/python3, then python3. |
Python engine location
The front end looks for:
The plugin copy has priority. The Info window prints the exact engine path used on every run, which is useful when a stale plugin copy and a newly edited local copy both exist.
Praat 7 trust
Praat 7 requires permission for temporary-file writing, deletion and subprocess calls. The script requests full trust automatically when needed. Praat 6.x bypasses that guard.
Quick start
- Select exactly one Sound object.
- Run
BasicPitchTranscriber.praat. - Leave Preset = Custom if you want the three sensitivity values to be used exactly as entered.
- Choose the transcription sensitivity, pitch range and Melodia option.
- Choose whether repeated model-window onsets should be repaired.
- Optionally extend note endings with Sustain_to_next_onset_percent.
- Set the score tempo and quantization grid.
- Use Notation_backend = auto unless you specifically need to force one backend.
- Choose which MusicXML/Praat outputs you want.
- Run. Praat remains busy while the Python model is processing.
Sensitivity presets
The default preset is Custom. A named preset overwrites exactly three fields:
- Onset threshold
- Frame threshold
- Minimum note length
All pitch-range, merge, sustain, notation and output settings remain as entered by the user.
| Preset | Onset | Frame | Min note | Intended use |
|---|---|---|---|---|
| Custom | 0.50 | 0.30 | 127.7 ms | Use the form values exactly. |
| Solo instrument (clean) | 0.60 | 0.40 | 120 ms | Cleaner monophonic or near-monophonic material; more conservative onset/frame activation. |
| Polyphonic / piano | 0.50 | 0.30 | 58 ms | Basic Pitch-style defaults with shorter events allowed. |
| Sensitive | 0.30 | 0.20 | 80 ms | Use when notes are being missed; expect more false positives. |
| Sparse | 0.75 | 0.55 | 250 ms | High-confidence skeletal reduction. |
Basic Pitch detection controls
Onset threshold
This is a Basic Pitch model-activation threshold, not dB. Lower values accept weaker onset activations and generally increase the number of detected note attacks.
Frame threshold
This is the activation threshold used while a note remains active. Lower values allow weaker frame activity to continue a note for longer and can also increase residual detections.
Minimum note length
Given in milliseconds. The Python engine passes it to Basic Pitch, which converts the requested duration to model frames internally. Notes at or below the resulting minimum duration are removed during decoding.
Pitch range
Minimum_frequency_Hz and Maximum_frequency_Hz can restrict the model output. A value of 0 means “use the model's default full range” for that boundary.
Melodia trick
When enabled, Basic Pitch performs its additional Melodia-style post-processing pass over residual frame activation after the normal onset-driven pass. In practical terms, it can recover note activity that remains strongly present even when a clean onset was not accepted.
Effective clamps
| Parameter | Effective range |
|---|---|
| Onset threshold | 0.05–0.95 |
| Frame threshold | 0.05–0.95 |
| Minimum note length | at least 10 ms |
| Negative frequency bounds | clamped to 0 = no explicit bound |
Repeated-onset repair
Basic Pitch analyses audio in overlapping inference windows. A sustained note can occasionally be re-decoded as a new onset near the model-window grid. AudioTools provides three post-processing choices for same-pitch events.
| Mode | Exact behavior |
|---|---|
| off | Keep the Basic Pitch note list unchanged. |
| window artifacts only (safe) | Merge only when the next same-pitch onset lies near the model-window grid, is temporally contiguous with the preceding note, and is not substantially louder. |
| any gap under N ms | Merge any same-pitch continuation whose gap is ≤ Merge_gap_ms. This is deliberately aggressive and can collapse genuine tremolo, ostinati or repeated notes. |
Safe-mode conditions
The model hop is derived from the installed Basic Pitch package when possible, with an engine fallback of approximately 1.64009 s. For a candidate repeated onset:
All three tests must pass.
Sustain to next onset
Sustain_to_next_onset_percent addresses a different problem from repeated-onset repair: Basic Pitch can end a decaying note early when frame activation falls below the frame threshold.
After voice allocation, every note is allowed to extend toward the next onset in its own non-overlapping layer:
- 0% = detected duration unchanged.
- 100% = extend fully to the next onset in the layer.
- The extension never crosses the next onset.
- A note with no later onset in its layer is left unchanged.
The ceiling uses the earliest next onset across both the uncapped notation-layer allocation and the TextGrid-layer allocation, preserving non-overlap in both representations.
Score tempo & quantization
Score_tempo_BPM is not tempo detection
The script does not estimate beat, meter or tempo from the audio. Score_tempo_BPM is supplied by the user and defines how detected seconds are mapped into symbolic quarter-note positions.
The tempo is clamped to 20–400 BPM.
Quantization grid
Both notation backends support:
- 1/4 note
- 1/8 note
- 1/16 note — default
- 1/32 note
Starts and ends are snapped to the chosen notation grid. A note that would collapse to zero duration after quantization is expanded to at least one grid unit.
Meter
The notation pipeline is fixed to 4/4. There is no meter-detection or meter-selection control in the current script.
Notation backends
Auto
Uses music21 when it is installed; otherwise uses the self-contained built-in MusicXML writer.
music21 — preferred
The engine first quantizes the note intervals and then allocates them greedily into non-overlapping notation layers on the quantized time base. Each layer becomes a separate music21.stream.Part named Voice 1, Voice 2, and so on.
- The notation allocation is uncapped: score notes are not discarded because the Praat TextGrid is limited to eight layers.
- Every Part receives 4/4.
- The first Part carries the requested metronome mark.
- Rests fill gaps.
- Parts are padded to the same whole number of bars.
makeNotation()is run before writing MusicXML.
Built-in writer — fallback
The built-in writer creates valid MusicXML 4.0 without requiring music21, but it is intentionally a simpler chordal reduction.
- One MusicXML Part named Transcription.
- Fixed 4/4.
- Fixed key signature: 0 fifths.
- Treble clef.
- Sharps used for chromatic spelling; no key-aware enharmonic spelling.
- Notes sharing a quantized onset become one chord.
- The chord duration is the shortest member's quantized duration.
- A chord is further truncated if necessary so it does not run beyond the next quantized onset.
- Gaps become rests.
- Durations are decomposed into representable values down to 32nd notes, with ties across bar lines when needed.
music21. The built-in writer prioritizes a valid, openable score over preserving independent asynchronous voice durations.
Outputs
1. MusicXML file
When Save_musicxml_to_file is enabled, the complete MusicXML is copied to the user-specified path. The path must be supplied explicitly.
2. MusicXML in the Info window
Print_musicxml_to_info is enabled by default. The complete XML text is appended to the Info window in one operation.
3. Praat Strings object
Create_musicxml_Strings is enabled by default and creates:
Each string is one raw MusicXML file line. Saving the Strings object as raw text reproduces the score text, and the object can be passed directly to AudioTools workflows such as OM Score Transformer.
4. Praat note Table
When detected notes exist, the per-note CSV is automatically read back as a Table named:
Columns:
| Column | Meaning |
|---|---|
start | Note start in seconds relative to the exported audio start. |
end | Note end after merge/sustain processing. |
midi | Integer MIDI note number. |
amp | Basic Pitch note activation amplitude. |
voice | 0-based TextGrid layer index, or −1 when beyond the TextGrid layer cap. |
name | Pitch name such as C4 or F#5. |
5. Praat TextGrid
When Create_note_TextGrid is enabled, the script creates one interval tier per allocated voice layer and labels note intervals with pitch names.
The Table and TextGrid can therefore have the same visible base name but are different Praat object types.
6. MIDI
Pitch bends
The symbolic note list used by the MusicXML, Table and TextGrid retains integer MIDI pitch, start, end and amplitude. Basic Pitch's optional pitch-bend trajectory is not carried into these Praat/MusicXML outputs.
Visualization
The Picture view is an analysis summary, not a rendered score page.
- Input waveform — the selected Praat Sound on its original time domain.
- Piano roll — detected notes shown as horizontal rectangles; vertical position = MIDI pitch, horizontal extent = note duration.
- Amplitude color — quiet model activations appear blue; stronger activations become warmer/redder.
- Polyphony over time — simultaneous-note count on a 240-point uniform time grid.
- Pitch-class distribution — detected MIDI notes collapsed into 12 pitch classes.
- Note-duration distribution — ten logarithmic duration bins between the detected minimum and maximum note durations.
- Summary — note count, pitch range, max polyphony, voice layers, thresholds, minimum note length, Melodia setting, score bars, tempo, grid, backend, XML size, duration statistics and timing.
Time-axis steps are rounded to a readable 1/2/5 × 10k sequence rather than using arbitrary duration fractions.
Limits & important distinctions
TextGrid voice cap versus score voices
The Python engine allocates at most 8 TextGrid voice layers. Notes requiring a ninth or later simultaneous layer are marked unplaced for the TextGrid and reported.
Per-note dump cap
The note CSV/Table is limited to the first 2000 notes. If the transcription exceeds that limit:
- the MusicXML score still uses the full detected note list;
- aggregate statistics such as the pitch histogram and polyphony curve are computed from the full note list;
- the Praat per-note Table, piano roll and TextGrid are limited to the dumped rows.
Tempo is supplied, not detected
The script performs note transcription but no beat tracking, downbeat tracking, meter estimation or tempo estimation.
Discrete pitches, not continuous pitch notation
The MusicXML score is based on integer MIDI-note events. It does not encode Basic Pitch's continuous pitch-bend trajectories.
Source channel count
The selected Praat Sound is exported as WAV with its original channel count. The AudioTools front end does not itself create separate transcriptions per channel or preserve channel identity as score parts.
No audio resynthesis
The script does not synthesize the MusicXML/MIDI back into a new Praat Sound. Play_input_sound simply plays the original selected Sound.
Temporary files
The workflow uses temporary WAV, MusicXML, MIDI, CSV, stats and log files. AudioTools removes these after the Praat objects and any requested external MusicXML copy have been created.
Error reporting
The Python engine mirrors console messages to a log file. If transcription fails, Praat prints that engine log into the Info window before raising the final error, so the Python traceback is visible without relying on an external terminal.
Further reading
- Bittner, R. M., Bosch, J. J., Rubinstein, D., Meseguer-Brocal, G., & Ewert, S. (2022). “A Lightweight Instrument-Agnostic Model for Polyphonic Note Transcription and Multipitch Estimation.” ICASSP 2022, pp. 781–785. DOI: 10.1109/ICASSP43922.2022.9746549.
- Spotify. Basic Pitch — official open-source Automatic Music Transcription library. GitHub repository.
- music21 Documentation — Stream, Part, notation and MusicXML writing. Official documentation.
- W3C Music Notation Community Group. MusicXML 4.0. Specification.