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
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/pianitoShell installer (macOS / Linux)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/4esv/pianito/releases/latest/download/pianito-installer.sh | shcargo
cargo install pianitocargo-binstall (prebuilt binary, no compile)
cargo binstall pianitoFrom 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-configmacOS needs nothing extra. It links CoreAudio directly.
git clone https://github.com/4esv/pianito.git
cd pianito
cargo build --releaseThe 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 --quickFlags:
--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 thereferenceandanalyzesubcommands. On--resume, the session's original A4 wins.--quickpreselects Quick Tune in the mode-select menu.--beepplays 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.profilefalls 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 resetConfiguration
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
- Pitch Detection: Uses the YIN algorithm to detect the fundamental frequency from microphone input
- Temperament: Calculates equal temperament frequencies with optional Railsback stretch curve
- 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
- 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