Generate and rank chord progressions from a pitch-class pool, then search the When-in-Rome harmonic corpus for real-world matches. Export results as text, JSON, or MusicXML.
Given a set of pitch classes (e.g. 0 3 5 7 9), the tool:
- Builds chord material — enumerates subsets, scores each chord by consonance, set-theoretic richness, and recognisable chord type (via musx and music21).
- Generates progressions — permutes top-ranked chords into 3- or 4-chord sequences and scores them on tension arc, voice leading, variety, leading-tone resolution, root-motion grammar, and functional transitions.
- Searches the corpus — matches generated progressions (and optionally individual chords) against a pre-built index of Roman-numeral events from When-in-Rome.
- Exports — prints ranked tables, writes structured JSON, and optionally produces MusicXML for MuseScore or other notation software.
- Python 3.10+
- musx — pitch-class set operations (prime form, interval vector)
- music21 — tonal labeling and MusicXML export
- When-in-Rome corpus (cloned separately; not included in this repo)
git clone https://github.com/ayk-caglayan/harmonic-space.git
cd harmonic-space
pip install -r requirements.txtClone the When-in-Rome corpus (one-time):
git clone https://github.com/MarkGotham/When-in-Rome.git ~/When-in-Rome-masterThe index is generated locally and is not committed to git (it is large and reproducible).
python main.py --build-index --wir-root ~/When-in-Rome-masterThis writes corpus_index.json in the project root (~2–3 minutes). You can also build via the standalone module:
python wir_index.py --wir-root ~/When-in-Rome-master --index-file ./corpus_index.jsonGenerate progressions from pitch classes:
python main.py --pcs 0 1 6 11Search the corpus for matches (default when --search is passed):
python main.py --pcs 0 1 6 11 --searchAlso match individual chords and export JSON:
python main.py --pcs 0 1 6 11 --search --search-chords --json-out results.jsonExport MusicXML:
# All progressions in one file
python main.py --pcs 0 1 6 11 --musicxml-out progressions.musicxml
# One file per progression (+ corpus match when --search is used)
python main.py --pcs 0 1 6 11 --search --musicxml-dir ./out/Show the full chord table with interval vectors:
python main.py --pcs 0 3 5 7 9 --show-chords --show-iv| Flag | Description |
|---|---|
--pcs INT ... |
Pitch classes 0–11 (at least 3 distinct values) |
--length {3,4} |
Chords per progression (default: 3) |
--top N |
Top N progressions to display (default: 5) |
--max-chords N |
Max chords used for generation (default: 10) |
--search |
Search corpus for progression matches |
--search-chords |
Also search for individual chord matches |
--corpus NAME |
Filter to a sub-corpus (e.g. Piano_Sonatas, Early_Choral) |
--build-index |
Build/rebuild corpus_index.json and exit |
--wir-root PATH |
Path to When-in-Rome (default: ~/When-in-Rome-master) |
--index-file PATH |
Corpus index path (default: ./corpus_index.json) |
--json-out PATH |
JSON export path (default: ./results.json) |
--musicxml-out PATH |
Single MusicXML file |
--musicxml-dir DIR |
Directory for per-progression MusicXML files |
--format {text,json,both} |
Output format (default: both) |
Run python main.py --help for the full option list.
corpus_export_cli.py runs the full pipeline (generate → search → MusicXML) in one step and writes output to a PCS-named directory:
python corpus_export_cli.py --pcs 0 1 6 11
# → creates 01611/ with progressions.musicxml and corpus match excerptsOptions include --out-dir, --corpus, --matches-per-progression, and --length 4.
run_export_and_pdf_linux.sh converts corpus-match MusicXML files to PDF via MuseScore AppImage and xvfb-run:
./run_export_and_pdf_linux.sh 0 3 4 5 7 8 9 11Set MUSESCORE_BIN to point at your MuseScore binary if it is not at ./musescore.AppImage or ~/musescore.AppImage.
harmonic-space/
├── main.py # Primary CLI
├── theory.py # Chord building, scoring, tonal labeling
├── material.py # Shared dataclasses (MaterialChord, CorpusMatch, …)
├── progression.py # Progression generation and scoring
├── constraints.py # Voice-leading and functional constraints
├── search.py # Corpus chord/progression search
├── wir_index.py # When-in-Rome index builder/loader
├── output.py # Text/JSON/MusicXML output helpers
├── corpus_export.py # MusicXML bar-extraction export
├── corpus_export_cli.py # Standalone export CLI
├── run_export_and_pdf*.sh # MuseScore PDF helpers
├── examples/ # Sample outputs (committed)
│ ├── progression_01.musicxml
│ └── results.json
└── corpus_index.json # Generated locally (gitignored)
Chord ranking (theory.py): weighted combination of consonance, interval-vector richness, and named chord-type bonus. Tunable via W_CONSONANCE, W_RICHNESS, W_CHORD_TYPE.
Progression ranking (progression.py): tension arc, voice-leading smoothness, variety, leading-tone resolution, root-motion grammar, and functional transitions (W_ARC, W_VOICE, W_VARIETY, W_LT, W_RM, W_FUNC).
Corpus matching (search.py): hybrid score over prime-form match, chord quality, Roman-numeral overlap, beat strength, leading-tone resolution, and root-motion grammar in the corpus window.
See examples/ for a sample MusicXML progression export and a full JSON results file produced by:
python main.py --pcs 0 3 4 5 7 8 9 --search --search-chords --json-out examples/results.jsonCorpus data and Roman-numeral analyses from When-in-Rome (Mark Gotham et al.).