Tuner

Real-time instrument tuner — microphone pitch tracking, instrument string presets, and the SSE/WebSocket pitch stream. See Listening — Microphone In for a tutorial.

Note

The microphone input requires PortAudio (via sounddevice).

Real-time instrument tuner.

Listens to your microphone, tracks pitch with the same YIN algorithm behind pytheory.audio.detect_pitch(), and tells you what note you’re playing and how many cents sharp or flat you are.

Terminal:

$ pytheory tune
$ pytheory tune --instrument guitar   # lock to the six strings

Browser / JavaScript:

$ pytheory tune --serve
# opens http://localhost:8123 — a strobe tuner page

The served page is a strobe tuner: a segmented disc that drifts clockwise when you’re sharp, counter-clockwise when you’re flat, and freezes when you’re in tune — the same display logic as a Peterson strobe, driven by the YIN pitch track.

The server speaks Server-Sent Events and WebSocket, so any web page or JS app can tap the pitch stream directly — no client library needed:

// SSE
const tuner = new EventSource("http://localhost:8123/stream");
tuner.onmessage = (e) => {
    const { freq, note, octave, cents, in_tune } = JSON.parse(e.data);
};

// WebSocket
const ws = new WebSocket("ws://localhost:8123/ws");
ws.onmessage = (e) => { const reading = JSON.parse(e.data); };

CORS is wide open on the stream, so a page served from anywhere (your dev server, a file:// page, CodePen) can connect.

With an instrument preset, readings lock to the nearest string — the target field says which one — so “tune the D string” never gets misread as “you’re 80 cents flat of E”:

tuner = Tuner(instrument="guitar")

With chords=True (CLI: pytheory tune --chords) the tuner also identifies what chord is sounding — strum and the page (and the stream’s chord field) names it:

$ pytheory tune --serve --chords
pytheory.tuner.INSTRUMENT_STRINGS = {'banjo': ['D3', 'G3', 'B3', 'D4', 'G4'], 'bass': ['E1', 'A1', 'D2', 'G2'], 'cello': ['C2', 'G2', 'D3', 'A3'], 'guitar': ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'], 'mandolin': ['G3', 'D4', 'A4', 'E5'], 'ukulele': ['G4', 'C4', 'E4', 'A4'], 'viola': ['C3', 'G3', 'D4', 'A4'], 'violin': ['G3', 'D4', 'A4', 'E5']}

Open-string targets for common instruments (low to high).

pytheory.tuner.string_targets(instrument, reference_pitch=440.0)[source]

The (name, frequency) tuning targets for an instrument preset.

>>> string_targets("guitar")[0]
('E2', 82.4068...)
pytheory.tuner.analyze_frame(frame, sample_rate=44100, *, reference_pitch=440.0, fmin=50.0, fmax=1500.0, targets=None)[source]

Analyze one audio frame: what note is this, and how far off?

Parameters:
  • frame – Mono float array (≥ ~2048 samples for reliable results).

  • sample_rate – Sample rate in Hz.

  • reference_pitch – Concert pitch for A4 (default 440; pass 442 for orchestras that tune high, 432 for the adventurous).

  • fmin/fmax – Pitch search range.

  • targets – Optional list of (name, freq) tuning targets (e.g. from string_targets()). The reading then reports cents relative to the nearest target instead of the nearest chromatic note, with the matched name in target.

Returns:

Dict with freq, note, octave, cents (signed, + is sharp), and in_tune (within ±5 cents) — or None if the frame has no confident pitch. With targets, also target and target_freq.

class pytheory.tuner.Tuner(*, reference_pitch=440.0, fmin=50.0, fmax=1500.0, device=None, sample_rate=44100, instrument=None, chords=False)[source]

Bases: object

Microphone-driven tuner — keeps the latest pitch reading.

Example:

tuner = Tuner()
tuner.start()
while True:
    reading = tuner.reading  # dict or None, updated live
start()[source]

Open the microphone and start analyzing.

stop()[source]
pytheory.tuner.serve(tuner, port=8123, open_browser=True)[source]

Serve the tuner over HTTP: a live strobe page at /, a Server-Sent Events pitch stream at /stream (CORS: any origin), and the same stream over WebSocket at /ws.

Blocks until Ctrl-C.

pytheory.tuner.run_terminal(tuner)[source]

ASCII needle tuner in the terminal. Blocks until Ctrl-C.