Musicology·A Field Guide

§ 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.

XI. cli / 01-interval-histogram.js

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
XII. cli / 02-scaler.js

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
XIII. cli / 03-meter.js

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)
XIV. cli / 04-euclid.js

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.
XV. cli / 05-sieve.js

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
XVI. cli / 06-cantus.js

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
XVII. cli / 07-musicbrainz.js

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.