Skip to content

Repository files navigation

harmonic-space

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.

What it does

Given a set of pitch classes (e.g. 0 3 5 7 9), the tool:

  1. Builds chord material — enumerates subsets, scores each chord by consonance, set-theoretic richness, and recognisable chord type (via musx and music21).
  2. 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.
  3. Searches the corpus — matches generated progressions (and optionally individual chords) against a pre-built index of Roman-numeral events from When-in-Rome.
  4. Exports — prints ranked tables, writes structured JSON, and optionally produces MusicXML for MuseScore or other notation software.

Requirements

  • 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)

Installation

git clone https://github.com/ayk-caglayan/harmonic-space.git
cd harmonic-space
pip install -r requirements.txt

Clone the When-in-Rome corpus (one-time):

git clone https://github.com/MarkGotham/When-in-Rome.git ~/When-in-Rome-master

Build the corpus index

The 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-master

This 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.json

Quick start

Generate progressions from pitch classes:

python main.py --pcs 0 1 6 11

Search the corpus for matches (default when --search is passed):

python main.py --pcs 0 1 6 11 --search

Also match individual chords and export JSON:

python main.py --pcs 0 1 6 11 --search --search-chords --json-out results.json

Export 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

CLI reference (main.py)

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

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 excerpts

Options include --out-dir, --corpus, --matches-per-progression, and --length 4.

PDF export (optional)

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 11

Set MUSESCORE_BIN to point at your MuseScore binary if it is not at ./musescore.AppImage or ~/musescore.AppImage.

Project layout

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)

Scoring overview

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.

Examples

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

Acknowledgements

Corpus data and Roman-numeral analyses from When-in-Rome (Mark Gotham et al.).

About

Generate and rank chord progressions from given pitch classes, then match them against the When-in-Rome corpus.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages