Back Menu

pianito

Piano tuner for the terminal - guided 88-key tuning with real-time pitch detection

Stars Language Updated License
3 Rust 2026-10-09 MIT
  • Topics: audio, cli, music, piano-tuner, piano-tuning, pitch-detection, ratatui, rust, tui, tuner

GitHub

A piano tuner for the terminal. macOS and Linux, with guided coaching.

pianito is a command-line piano tuner. It does real-time pitch detection (the YIN algorithm), measures each piano's inharmonicity from its own overtones, and tunes to an equal-tempered scale with a Railsback stretch curve. Then it walks you through the strings note by note. No GUI, no phone, no account. A microphone, a terminal, and a piano.

If you searched "CLI piano tuner" or "open source piano tuning app" and landed here, there isn't another terminal piano tuner as far as I can tell, so this is the one.

What it stacks up against

The professional apps are Entropy Piano Tuner (open source), Verituner, TuneLab and PianoMeter. They set the vocabulary piano technicians scan for: inharmonicity measurement, per-piano stretch, temperaments, pitch raise, a live partial display. Here is where pianito stands against that bar today.

Capability pianito Notes
Real-time pitch detection ✅ YIN fundamental + FFT partial analysis, with an octave sanity check
Inharmonicity measurement ✅ fits the stiffness coefficient B per note from recorded partials during Profile mode
Per-piano stretch tuning ✅ --stretch profile drives the target frequencies from the measured curve; the default is a generic Railsback curve
Equal temperament ✅ A4 configurable (concert pitch or the piano's own center)
Guided multi-string coaching ✅ the mute-and-match unison workflow – pianito's own thing; the pro apps measure, they don't coach
Scriptable CLI ✅ analyze a WAV, emit a reference tone, dump session history – composable with the rest of your shell
Historical / well temperaments ❌ not yet Verituner and TuneLab ship dozens; pianito is equal-temperament only
Pitch raise / overpull ❌ not yet TuneLab and Verituner compute overpull targets; pianito does not
Live spectrum / phase scope ❌ partials are measured during profiling, not shown as a running analyzer
Mobile / desktop GUI ❌ by design it runs in a terminal

pianito already does the two things that separate a real tuner from a chromatic-tuner app: it measures a specific piano's inharmonicity and stretches the scale to fit it, and it adds step-by-step unison coaching the others don't. It does not yet do historical temperaments or pitch-raise overpull, and it will never be a phone app.

Installation

Homebrew (macOS)

brew install 4esv/tap/pianito

Shell installer (macOS / Linux)

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/4esv/pianito/releases/latest/download/pianito-installer.sh | sh

cargo

cargo install pianito

cargo-binstall (prebuilt binary, no compile)

cargo binstall pianito

From source

Requires Rust 1.82+. On Linux, first install the ALSA development headers and pkg-config. cpal's ALSA backend links against libasound at build time:

# Debian/Ubuntu
sudo apt install libasound2-dev pkg-config

macOS needs nothing extra. It links CoreAudio directly.

git clone https://github.com/4esv/pianito.git
cd pianito
cargo build --release

The binary will be at target/release/pianito.

On a minimal Linux install, the runtime also needs libasound2 (present on every desktop distro; sudo apt install libasound2 if it isn't).

Usage

Interactive tuning

# Start interactive tuning (opens the mode-select menu)
pianito

# Resume an interrupted session
pianito --resume

# Tune to A4 = 442 Hz with a lock beep
pianito --a4 442 --beep

# Open the menu with Quick Tune preselected
pianito --quick

Flags:

  • --a4 <HZ> sets the reference frequency for Concert Pitch and Profile sessions. It is also the fallback when you skip Quick Tune calibration, and it applies to the reference and analyze subcommands. On --resume, the session's original A4 wins.
  • --quick preselects Quick Tune in the mode-select menu.
  • --beep plays a short beep the moment a string first enters the in-tune zone (once per strike; silence re-arms it). It needs an audio output device. Without one, tuning continues and the status line shows a warning.
  • --stretch <off|railsback|profile> picks how tuning targets are stretched: pure equal temperament, the built-in Railsback curve (the default), or the per-piano curve measured in Profile mode. profile falls back to Railsback until a profile is loaded.

Keyboard controls

Key Action
↑/↓ / Tab Navigate menu options
Enter Select / Confirm
Space Confirm current step / note
B Back to previous step / note
P Toggle piano progress display
? Toggle the help overlay
S Skip current note (on the Quick Tune calibration screen: skip calibration, use the configured A4 reference)
Q / Esc Quit (progress is saved after each confirmed note)

Reference tones are played with the pianito reference subcommand, not from the tuning screen.

Commands

# Analyze a WAV file for pitch content
pianito analyze recording.wav

# Play a reference tone
pianito reference A4
pianito reference C5 --duration 3.0

# Show tuning session history
pianito history

# Clear all saved sessions
pianito reset

Configuration

Configuration is read from the pianito config directory:

  • macOS: ~/Library/Application Support/pianito/config.toml
  • Linux: ~/.config/pianito/config.toml (or $XDG_CONFIG_HOME/pianito/config.toml)
# Default A4 reference frequency (CLI --a4 overrides)
a4 = 440.0

# Tolerance in cents for the "in tune" zone on the meter,
# direction hints, and the lock beep
tolerance = 5.0

# Beep once when a note locks into the in-tune zone
# (CLI --beep also enables this)
beep = false

# Mode preselected in the menu: "concert" or "quick"
# (CLI --quick preselects quick)
default_mode = "concert"

# How stretch is applied to tuning targets: "off", "railsback", or "profile"
# (CLI --stretch overrides)
stretch = "railsback"

How it works

  1. Pitch Detection: Uses the YIN algorithm to detect the fundamental frequency from microphone input
  2. Temperament: Calculates equal temperament frequencies with optional Railsback stretch curve
  3. Tuning Order: Follows traditional piano tuning order for stability:
    • Temperament octave (F3-F4): 13 notes
    • Octaves upward (F#4-C8): 43 notes
    • Octaves downward (E3-A0): 32 notes
  4. Multi-string Coaching: Coaches each note based on its string count:
    • A0-A#1 (1 string): tune directly
    • B1-G#3 (bichord, 2 strings): 2 steps. Mute the right string, tune the left, then unmute and match the right
    • A3-C8 (trichord, 3 strings): 4 steps. Mute the outer strings, tune the center, then left and right unisons

Profile mode

Profile mode measures the whole piano before tuning it. Play all 88 keys (A0→C8) one at a time, and pianito records each note's deviation in cents. The profile is saved under the pianito data directory:

  • macOS: ~/Library/Application Support/pianito/profiles
  • Linux: ~/.local/share/pianito/profiles (or $XDG_DATA_HOME/pianito/profiles)

Tuning then starts in a new order. The temperament octave (F3-F4) stays first, and the remaining notes follow worst-deviation-first.

On the profiling screen: Space confirms the current note, B goes back, S skips a note, Q/Esc quits.

Profiling also records each note's partial spectrum, and pianito fits those into a per-piano inharmonicity curve. Tune with --stretch profile (or stretch = "profile" in config.toml) to have that curve drive the tuning targets. docs/inharmonicity.md explains why the stretch is necessary and how it's measured.

Requirements

  • Working microphone with permissions granted
  • Terminal with Unicode support
macOS Linux
Audio backend CoreAudio, via cpal – works out of the box ALSA, via cpal
Build-time deps none beyond Rust libasound2-dev, pkg-config
Runtime deps none beyond the OS libasound2

The codebase has no cfg(target_os) branches and no macOS-specific APIs. Audio I/O goes through cpal and paths through the directories crate, so Linux runs the same code rather than a separate port. CI builds and tests on both macOS and Ubuntu (see .github/workflows/ci.yml).

ALSA loads its plugins via dlopen at runtime, so statically-linked musl builds aren't supported. Use the glibc target (the prebuilt Linux binary already does).

License

MIT