Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

piano

piano in practice mode

A playable piano in the terminal. Notes are held and released with the computer keyboard, and a small keyboard drawn with the kitty graphics protocol shows which key is which. It draws inline, in eight rows under the prompt that launched it, and erases itself on exit.

Features

  • Polyphonic additive synthesis, 16 voices, rendered directly in the CoreAudio callback
  • Seven voices — piano, felt, epiano, clav, vibes, organ, pad — switchable while playing
  • Shared hall reverb, eight damped combs into four allpasses
  • True note-off through the kitty keyboard protocol: a held key sustains
  • Sustain pedal
  • Two key layouts, tracker and GarageBand-style; transposable ±2 octaves
  • Kitty graphics keyboard with the pressed key highlighted; falls back to coloured cells where graphics are unavailable
  • Tune files: play one back, or practise it a note at a time
  • Offline rendering to WAV and PPM
  • Works under tmux (graphics and keyboard both use DCS passthrough)

Keys

Two layouts, selected with layout in the config or --layout NAME. They disagree about z and x — notes in one, octave shift in the other.

tracker (default)

FastTracker II, Impulse Tracker, Renoise, OpenMPT, MilkyTracker, LMMS. Two rows of white keys, each with its black keys on the row above. C3–E5.

black:  2 3   5 6 7   9 0
white: q w e r t y u i o p        C4 ............. E5

black:  s d   g h j   l   ;
white: z x c v b n m , . /        C3 ......... B3

, l . ; / repeat the notes of q 2 w 3 e. Only the upper row's key is drawn as the label. f, k, 1, 4 and 8 are unmapped — the gaps where E–F and B–C have no black key between them.

garageband

Apple's Musical Typing, and the same shape in Ableton Live and FL Studio. C4–E5, and the drawn keyboard shrinks to match.

black:  w e   t y u   o p
white: a s d f g h j k l ;        C4 ............. E5

Controls

Key Action
space sustain pedal (held; toggles without note-off)
[ / ] previous / next voice
tab switch layout
- / = octave down / up
z / x octave down / up (garageband layout only)
/ volume up / down
' play the tune / stop
\ practise the tune / stop
` restart the tune from the top
esc, ctrl-c, ctrl-q quit

Options

Option Effect
--volume N 0–100
--octave N -2..2
--layout NAME tracker (default) or garageband
--voice NAME piano felt epiano clav vibes organ pad
--tune FILE load a tune
--autoplay start the tune playing at once
--practice start the tune in practice mode
--device NAME output device name substring (--probe lists them)
--unmute unmute that device while running, restore it on exit
--no-graphics draw with coloured cells instead of kitty graphics
--no-kbd skip the kitty keyboard protocol
--tmux-kbd inside tmux, reach past it for note-off (the default)
--no-tmux-kbd leave tmux's keyboard alone, giving up sustain
--cell WxH override the terminal's cell pixel size
--probe report terminal and audio capabilities, then exit
--fix-terminal pop a stranded keyboard protocol mode and exit
--dump-wav FILE render --play, or --tune, offline to a WAV and exit
--dump-ppm FILE render the keyboard image to a PPM and exit
--play KEYS keys to render for --dump-wav, e.g. zxcvb
--pressed KEYS keys drawn as held for --dump-ppm
--chord strike all of --play at once
--repeat N repeat the --play sequence N times
--hold-ms N note length for --dump-wav (default 300)
--gap-ms N gap between notes (default 100)
--tail-ms N silence after the last note (default 3000)
--help usage

Voices

[ and ] cycle while playing; voice = NAME sets the starting one.

Voice
piano struck string: strike-point comb, stiffness, detuned unison rank
felt soft hammers, dark, wide unison beat, heavy room send
epiano Rhodes: near-pure fundamental with a 9.2× tine
clav struck near the end of the string; bright, short
vibes bar modes at 1 : 4 : 10.7, long ring
organ drawbars at 8' 4' 5⅓' 2' 1⅗' 1⅓' 1'; holds until released
pad bowed strings: slow attack, full harmonic series, detuned second rank

brightness, strike, inharmonicity, detune_cents and decay_scale apply to piano; decay_scale also scales the other decaying voices. reverb = 0 turns the room off, reverb = 1.5 makes it cavernous.

Tunes

--tune FILE loads a tune. ' plays it, \ practises it, ` restarts. --tune FILE --dump-wav OUT.wav renders it offline instead of playing it. tunes/ode-to-joy.tune is included as an example.

A tune is a text file. # starts a comment, | is an ignored barline, and whitespace separates events:

tempo 120
voice felt
gate 0.85

E4 E4 F4 G4 | G4 F4 E4 D4
C4 C4 D4 E4 | E4/4. D4/8 D4/2
Token
C4 a quarter note; octave 4 is middle C
F#3 Bb5 Cs4 sharp and flat; s also means sharp
G4/2 half note — /1 whole, /4 quarter, /8 eighth, /16 sixteenth
A4/4. dotted, so half as long again; dots stack
r r/2 a rest
C4+E4+G4 a chord, struck together, up to 8 notes
Directive
tempo 120 quarter notes per minute (10–400, default 100)
voice felt which voice to play it with
gate 0.85 fraction of each note's slot that sounds (0.05–1, default 0.9)

In play mode the tune runs on the clock. In practice mode it waits at each event and advances only once you are holding exactly the notes it asked for; rests pass on their own. Practice matches the note you sounded rather than the key you pressed, so transposing or switching layout mid-tune keeps working.

The drawn keys colour as:

Colour
amber the tune's note — waiting for it in practice, sounding it in play
blue a key you are holding
green both at once: you played the note it asked for

Notes outside the drawn range are named in the status line rather than marked.

Config

~/.config/piano/config, written with commented defaults on first run.

Key Default Effect
volume 80 0–100
sample_rate 0 0 follows the output device
buffer_frames 256 lower is less latency, more risk of crackle
velocity 100 0–127; louder and brighter
device output device name substring; empty follows the system default
unmute false clear that device's mute while running
voice piano starting voice
release_ms 150 damper time after key-up
decay_scale 1.0 multiplies every string decay time
brightness 1.0 above 1 brighter, below 1 warmer
strike 0.125 hammer position; notches out the 1/strike'th harmonic
inharmonicity 0.0002 string stiffness; 0 is perfectly harmonic
detune_cents 0.6 unison detune, the source of the beating
stereo_spread 0.15 0 is mono
reverb 1.0 scales every voice's room send; 0 is completely dry
layout tracker tracker or garageband
octave 0 starting transpose, -2..2
kbd auto auto or off — kitty keyboard protocol
tmux_keyboard true reach past tmux for real key releases
graphics auto auto, on or off
transfer auto hand pixels to the terminal as a temp file; auto is off over ssh
cell override cell pixels, e.g. 8x17

Notes

Without the kitty keyboard protocol there is no note-off: notes decay on their own and the sustain pedal toggles instead of being held. --probe reports whether the terminal negotiated it.

Note-off inside tmux

The keyboard mode belongs to the terminal running tmux, not to any one pane, and tmux does not carry key releases itself — extended-keys on and always were both tested against tmux 3.7 and neither delivers them, nor does tmux report pane focus that could scope the mode. Reaching past tmux is therefore the only way to get note-off there, and it is on by default:

  • tmux_keyboard = true (default) — holding a key sustains. While piano runs, every other pane receives keys as escape sequences and tmux stops seeing its own bindings, prefix included.
  • tmux_keyboard = false, or --no-tmux-kbd — nothing touches the shared terminal. Notes decay on their own and the pedal toggles.

Outside tmux none of this applies.

If a run is killed before it can pop the mode, the session stays that way. Fix it with piano --fix-terminal, or by hand:

printf '\033Ptmux;\033\033[<1u\033\\'     # inside tmux
printf '\033[<1u'                          # outside

Most keyboards register at most about six simultaneous keys.

Latency

piano --probe reports the figure and lists every output with its latency:

output device    External Headphones
buffer frames    256 requested, 256 granted (5.3 ms)
callback         9472 frames in 300 ms  ok
output latency   165.3 ms  (buffer + driver safety + device)

available outputs (latency at 256 frames):
  External Headphones             165.3 ms  vol 100%  (system default)
  MacBook Air Speakers              8.4 ms  MUTED

Bluetooth adds 100–200 ms that no buffer size removes. Pin a wired or built-in output with device = speakers in the config, or --device speakers, and everything else keeps using the system default.

macOS keeps volume and mute per device, so an output you are not otherwise using may be sitting muted. --probe shows each device's level, and piano says so in the status line rather than going silently quiet. --unmute clears it for the run and restores it on exit.

Build

make
make install     # to ~/.local/bin

Requires macOS (CoreAudio). No dependencies beyond the system frameworks; src/term.h and src/kitty.h are vendored single-header libraries.

License

MIT

About

A playable piano in the terminal, with a kitty-graphics keyboard.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages