§ I.5 · The CLI Workshop
Seven instruments
at the prompt.
A field guide's batch-processing wing. Where the Workshop is interactive and pedagogical, the CLI tools are pipe-friendly and corpus-scale — small Node scripts that operationalise a single scholarly idea each, take pitches or onsets on stdin, and emit structured text on stdout. The first six share a token convention — whitespace- or comma-separated MIDI numbers and note names — so output from one tool feeds another with no shell glue. The seventh, a MusicBrainz query client, joins them to the open music encyclopedia for corpus-scale lookups.
¶ Install
Each file is standalone Node — no npm, no external requires, no build step. Node 18+ is sufficient. All seven carry a #!/usr/bin/env node shebang and are chmod +x, so either invoke directly or via node. Pull a single script with curl:
$ curl -O https://musicology.rosskuehl.dev/cli/01-interval-histogram.js $ chmod +x 01-interval-histogram.js $ echo "C4 E4 G4 B4" | ./01-interval-histogram.js --histogram
The full bibliography lives at /cli/REFERENCES.md.
Interval-Class Histogram
After Forte 1973 · cf. Lewin 1959
Reads pitch tokens — MIDI numbers 0–127 or note names with multi-accidentals (F##4, Cbb3) — from stdin or a file, and computes the complete interval-class distribution over the distinct pitch-class set. Output is the six-place Forte vector [ic1 ic2 ic3 ic4 ic5 ic6], the cardinality, and an optional ASCII histogram. The set theorist's first move, automated for the corpus: profile a Webern opus, a twelve-tone row library, or an atonal archive in a one-liner. --from-midi FILE extracts pitch classes from a Standard MIDI File via a minimal type-0/1 reader.
Source: cli/01-interval-histogram.js
¶ Invocation
$ node 01-interval-histogram.js < pitches.txt $ echo "C4 E4 G4 B4" | node 01-interval-histogram.js --histogram $ node 01-interval-histogram.js --from-midi chorale.mid
¶ Sample output
cardinality: 4 interval-vector: [1 0 1 2 2 0] ic1: xxxxxxxxxxxxxxxxxxxx.................... 1 ic2: ........................................ 0 ic3: xxxxxxxxxxxxxxxxxxxx.................... 1 ic4: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2 ic5: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2 ic6: ........................................ 0
Microtonality Scaler
After Blackwood 1986 · Xenakis 1971
Retunes a stream of MIDI integers into a non-12-TET system specified through a small mini-language: edo:N for equal divisions of the octave, ji:p1/q1,p2/q2,… for just intonation by rational ratios, cents:c1,c2,… for arbitrary cent deviations, and meantone:p/q for fifths narrowed by a fraction of the syntonic comma. JI uses exact rational arithmetic via BigInt until the final Hz conversion, so a 5-limit triad's frequencies are mathematically clean rather than rounded. --ref=<hz> sets the reference for MIDI 69. Output is one frequency per line, four decimal places, ready to feed an oscillator or the Tuning Lab.
Source: cli/02-scaler.js
¶ Invocation
$ echo "60 64 67" | node 02-scaler.js --tuning=ji:1/1,5/4,3/2 $ echo "60 62 64 65 67 69 71 72" | node 02-scaler.js --tuning=edo:31 $ node 02-scaler.js --tuning=meantone:1/4 --ref=415 < chant.txt
¶ Sample output
# tuning: ji:1/1,5/4,3/2 ref=440 261.6256 327.0320 392.4383
Onset / Meter Profiler
After Huron 2006 · cf. Large & Palmer 2002
Reads inter-onset intervals — in milliseconds (default) or proportional beat units — from stdin or a file, estimates the tactus by mode of the IOIs, and computes onset autocorrelation on a sixteenth-note grid. Candidate time signatures are then ranked by Lerdahl–Jackendoff metric-weight correlation. Built for what rule-based heuristics can't handle: rubato passages, polyrhythmic material, and field-recorded transcriptions where a clean barline is a fiction. --profile dumps the full autocorrelation as lag\tweight for piping into a plotter.
Source: cli/03-meter.js
¶ Invocation
$ echo "500 500 500 250 250 500" | node 03-meter.js $ node 03-meter.js --unit=beat < ratios.txt $ node 03-meter.js --profile < iois.txt | sort -k2 -rn
¶ Sample output
tempo: 120 bpm meter: 2/4 (confidence 0.84) alt: 3/4 (confidence 0.80) peaks: [1.00, 2.00, 3.00, 4.00, 5.00] (beats)
Euclidean Rhythm Generator
After Toussaint 2005 · Bjorklund 2003
Distributes k pulses as evenly as possible across n steps via Bjorklund's algorithm — the result, as Toussaint observed, recovers traditional rhythms across world music: the Cuban tresillo (3,8), the cinquillo (5,8), the bossa-nova (3,16). The tool prints a one-line header naming the canonical pattern when (k,n) matches. Output formats include text (x.x.xx.x), packed binary, comma-separated pulse offsets, LilyPond rhythm, and a Standard MIDI File written as type-0 binary to stdout. --rotate shifts the cycle origin.
Source: cli/04-euclid.js
¶ Invocation
$ node 04-euclid.js 3 8 $ node 04-euclid.js 5 16 --format=lily $ node 04-euclid.js 7 12 --rotate=2 --format=pulses
¶ Sample output
# e(3,8) - tresillo x..x..x.
Sieve Generator
After Xenakis 1990
Evaluates a Xenakis sieve expression as a boolean algebra over residue classes (M,I): union |, intersection &, complement ~, with parenthetic grouping. A tiny recursive-descent parser handles the expression, and the result is enumerated over a half-open integer range. Output formats include comma-separated integers, mod-12 pitch-class sets (sorted, deduped), a binary string of length (hi−lo), and a Standard MIDI File on stdout. Output of one sieve can be re-piped into the histogram or the scaler — algebra over the integers, set in motion as pitches or as time-points.
Source: cli/05-sieve.js
¶ Invocation
$ node 05-sieve.js "(7,0) | (12,1)" --range=0:60 --format=pcs $ node 05-sieve.js "(2,0) & (3,0)" --range=0:48 $ node 05-sieve.js "~(2,0)" --range=0:24 --format=binary
¶ Sample output
0,1,2,4,7,9,11
Cantus-Firmus Search
After Mongeau & Sankoff 1990 · cf. Jeppesen 1930
Searches a haystack pitch sequence for occurrences of a needle melody. Matching is done on consecutive interval sequences rather than absolute pitch, so transposition is free by construction — a Lassus motif found in an Ockeghem chanson lights up regardless of mode. --inversions additionally searches the negated form, --retrograde the reversed-negated form, and --threshold=N permits up to four edit-distance mismatches via a bounded Levenshtein on the interval string. Output is one tab-delimited match per line: position, form, distance, transposition offset, and the matching pitch run. The find-and-replace of melodic analysis.
Source: cli/06-cantus.js
¶ Invocation
$ node 06-cantus.js --needle="60 62 64 67" < corpus.txt $ node 06-cantus.js --needle=lassus.txt --inversions --retrograde < mass.txt $ node 06-cantus.js --needle="C D E G" --threshold=2 < chorale.txt
¶ Sample output
pos=0 form=prime distance=0 transposition=+0 pitches: 60 62 64 67 pos=7 form=prime distance=0 transposition=+5 pitches: 65 67 69 72 pos=11 form=prime distance=0 transposition=+0 pitches: 60 62 64 67 # 3 matches in 15 notes
MusicBrainz Query
After MusicBrainz Foundation 2000– · cf. Swartz 2002 · ws/2 JSON
Queries the public MusicBrainz ws/2 API for artists, works, recordings, release-groups, releases, labels, and areas. Reads the search string from --query, a positional argument, or stdin; looks up a single record with --mbid=<id>. Output is tab-separated by default (one record per line, columns chosen for the entity), or pretty-printed JSON, or a compact --format=tree view that fits on a screen. Sets a User-Agent per the MusicBrainz policy, retries once on 503, and stays inside the public polite-pool rate limit. The CLI sibling of prototype XI · the Lens: same data, different shell.
Source: cli/07-musicbrainz.js
¶ Invocation
$ node 07-musicbrainz.js --type=artist "Bartók" $ node 07-musicbrainz.js --type=work "Eroica" --limit=5 $ node 07-musicbrainz.js --type=recording --mbid=<id> --format=tree $ echo "Hilliard Ensemble" | node 07-musicbrainz.js --type=artist --limit=3
¶ Sample output
#mbid score name disambig type country life-span tags fd14da1b-3c2d-4cc8-9ca6-fc8c62ce6988 100 Bartók Béla composer Person HU 1881-03-25–1945-09-26 classical|hungarian|composer 94e48a88-b8c7-4347-861b-036fbb66dc35 80 Bartók Italian indie band Group IT 1999–2004
¶ Pipeable examples six tools, one shell
The CLIs share a common token convention so output from one tool feeds another with no shell glue. A few canonical chains:
Profile the interval content of a Xenakis sieve
$ node 05-sieve.js "(7,0) | (12,1)" --range=0:60 --format=pcs \
| node 01-interval-histogram.js --histogram
Render a 5-in-13 Euclidean rhythm in 31-edo
Treat the pulse offsets as MIDI semitones above middle C, then retune.
$ node 04-euclid.js 5 13 --format=pulses \
| grep -v '^#' | tr ',' ' ' \
| awk '{for(i=1;i<=NF;i++)print $i+60}' \
| node 02-scaler.js --tuning=edo:31
Profile the meter of a tresillo
Take the 3-in-8 pattern as inter-onset intervals (250 ms sixteenths at 120 bpm); the closing IOI wraps the cycle.
$ node 04-euclid.js 3 8 --format=pulses \
| grep -v '^#' | tr ',' '\n' \
| awk 'NR==1{first=$1;prev=$1;next}
{print ($1-prev)*250; prev=$1}
END{print (8-prev+first)*250}' \
| node 03-meter.js
Search a sieve-derived corpus for a transposed cantus motif
$ node 05-sieve.js "(7,0) | (12,5)" --range=48:96 \
| node 06-cantus.js --needle="60 62 64 67"
Diff the just major triad against equal-tempered reference
$ echo "60 64 67" | node 02-scaler.js --tuning=ji:1/1,5/4,3/2 > ji.txt $ echo "60 64 67" | node 02-scaler.js --tuning=edo:12 > et.txt $ paste ji.txt et.txt
Pull a composer's MBID and walk the works tree
The TSV header line begins with #, so a single grep -v drops it before cut. The first column is the MBID; piping it back into --mbid with --format=tree produces the artist's relations in a screen-sized view.
$ node 07-musicbrainz.js --type=artist "Bartók" --limit=1 \
| grep -v '^#' | head -1 | cut -f1 \
| xargs -I{} node 07-musicbrainz.js --type=artist --mbid={} --format=tree
§ References a short bibliography
Each tool operationalises a single scholarly idea; the full bibliography, with annotations connecting each reference to its tool, is at /cli/REFERENCES.md.
- Bjorklund, E. “The Euclidean Algorithm Generates Rhythmic Patterns.” Per Finland 9 (2003).
- Blackwood, Easley. The Structure of Recognizable Diatonic Tunings. Princeton: Princeton University Press, 1986.
- Forte, Allen. The Structure of Atonal Music. New Haven: Yale University Press, 1973.
- Huron, David. Sweet Anticipation: Music and the Psychology of Expectation. Cambridge, MA: MIT Press, 2006.
- Jeppesen, Knud. Counterpoint: The Polyphonic Vocal Style of the Sixteenth Century. New York: Dover, 1992 [1930].
- Large, Edward W., and Caroline Palmer. “Perceiving Temporal Regularity in Music.” Cognitive Psychology 45, no. 3 (2002): 305–337.
- Lewin, David. “Re: Intervallic Relations between Two Collections of Notes.” Journal of Music Theory 3, no. 2 (1959): 298–301.
- MetaBrainz Foundation. MusicBrainz Database & Web Service. Open music encyclopedia (CC0 data, ws/2 JSON). musicbrainz.org, accessed 2026.
- Mongeau, Marcelle, and David Sankoff. “Comparison of Musical Sequences.” Computers and the Humanities 24, no. 3–4 (1990): 161–175.
- Swartz, Aaron. “MusicBrainz: A Semantic Web Service.” IEEE Intelligent Systems 17, no. 1 (2002): 76–77.
- Toussaint, Godfried T. “The Euclidean Algorithm Generates Traditional Musical Rhythms.” In Proceedings of BRIDGES: Mathematical Connections in Art, Music and Science. Banff, 2005.
- Xenakis, Iannis. Formalized Music: Thought and Mathematics in Composition. Bloomington: Indiana University Press, 1971; rev. ed. Stuyvesant, NY: Pendragon, 1992.
- Xenakis, Iannis. “Sieves.” Perspectives of New Music 28, no. 1 (1990): 58–78.