Find a file
2026-03-28 17:13:51 +01:00
legacy Add legacy code from compact_sets_sandbox 2026-03-13 18:45:13 +01:00
lilypond Add dynamic date placeholder to score template 2026-03-23 20:01:51 +01:00
src Add independent voice control to OSC sender with improved display 2026-03-28 17:13:51 +01:00
supercollider Clean up: remove ipynb/txt, move scd to supercollider/, update gitignore 2026-03-13 19:02:24 +01:00
tests Rename 'target range' to 'target register' throughout 2026-03-17 14:11:21 +01:00
.gitignore Clean up: remove ipynb/txt, move scd to supercollider/, update gitignore 2026-03-13 19:02:24 +01:00
compact_sets.py Refactor into src/ module with caching and CLI improvements 2026-03-13 18:38:38 +01:00
LICENSE Add README and GPLv3 LICENSE 2026-03-13 18:51:28 +01:00
README.md Add custom dims support via --dims-custom and update JSON output format 2026-03-28 15:12:14 +01:00

Compact Sets

A rational theory of harmony based on Michael Winter's theory of conjunct connected sets in harmonic space, combining ideas from Tom Johnson, James Tenney, and Larry Polansky.

Installation

# Activate virtual environment
source venv/bin/activate

# Run
python -m src.io --dims 4 --chord-size 3

CLI Options

Graph Structure

--symdiff-min N Minimum symmetric difference between chords (default: 2) --symdiff-max N Maximum symmetric difference between chords (default: 2) --dims N Number of prime dimensions: 3, 4, 5, 7, or 8 (default: 7) --dims-custom "()", Custom dims as tuple, e.g., "(2,3,7)" (overrides --dims) --chord-size N Number of voices per chord (default: 3)

Path Generation

--max-path N Maximum number of chords in path (default: 50) --seed N Random seed for reproducibility (default: random)

Hard Constraints

These constraints eliminate edges that fail the criteria.

--allow-voice-crossing Allow edges where voices cross (default: reject) --disable-direct-tuning Disable direct tuning requirement (default: require)

Soft Constraints

These constraints weigh edges. Set weight to 0 to disable.

--weight-melodic N Weight for melodic threshold (default: 1, 0=off) --weight-contrary-motion N Weight for contrary motion (default: 0, 0=off) --weight-dca-hamiltonian N Weight for visiting unvisited nodes (default: 1, 0=off) --weight-dca-voice-movement N Weight for moving voices (default: 1, 0=off) --weight-target-register N Weight for target register (default: 1, 0=off)

Melodic Range

--melodic-min N Minimum cents any voice must move (default: 0 = disabled) --melodic-max N Maximum cents any voice can move (default: 500)

Target Register

--target-register N Target register in octaves (default: disabled, 2 = 2 octaves rise)

Output Options

--output-dir PATH Where to write output files (default: output/) --stats Show analysis statistics after generation --cache-dir PATH Directory for graph cache (default: cache/) --rebuild-cache Force rebuild of graph cache --no-cache Disable caching

Factor Explanations

Direct Tuning

An edge is directly tunable if any pitches that change can be tuned directly to a pitch that remains the same from chord to chord. This ensures smooth voice-leading where moving voices connect to stationary ones.

Melodic Range

Controls the minimum and maximum distance any single voice can move between consecutive chords. The melodic-min prevents voices from staying too still, while melodic-max prevents leaps that are too large.

DCA Hamiltonian

DCA (Dynamic Constraint Adaptation) Hamiltonian factor favors edges that connect to nodes not visited recently. This promotes coverage of the harmonic space, encouraging the path to visit as many unique chords as possible.

DCA Voice Movement

DCA Voice Movement factor favors edges where voices change pitch rather than staying stationary. This encourages active voice-leading where all voices contribute to the harmonic motion.

Contrary Motion

Contrary motion occurs when some voices move up while others move down. This factor boosts such edges, creating more interesting and balanced harmonic motion.

Target Register

The average pitch of all voices should rise toward the target (specified in octaves) over the course of the path. This factor encourages upward or downward register movement depending on the target.

Examples

# Basic usage
python -m src.io

# Rising register to 2 octaves
python -m src.io --target-register 2

# Heavy target register, no DCA voice movement
python -m src.io --target-register 2 --weight-target-register 10 --weight-dca-voice-movement 0

# Disable DCA Hamiltonian (allow revisiting nodes freely)
python -m src.io --weight-dca-hamiltonian 0

# Allow voice crossing
python -m src.io --allow-voice-crossing

# Disable direct tuning requirement
python -m src.io --disable-direct-tuning

# Stricter melodic constraints (max 200 cents per voice)
python -m src.io --melodic-max 200

# Require at least 30 cents movement per voice
python -m src.io --melodic-min 30 --melodic-max 200

# Use 4 voices with 4 dimensions
python -m src.io --dims 4 --chord-size 4

# Use custom dims (e.g., exclude prime 5)
python -m src.io --dims-custom "(2,3,7)" --chord-size 4

Legacy

The legacy/ folder contains earlier versions of this code, used to create the musical works published at unboundedpress.org/works/compact_sets_1_3.

Output

Generated files go to output/:

  • output_chords.json - Chord data (includes dims and chords)
  • output_chords.txt - Human-readable chords
  • output_frequencies.txt - Frequencies in Hz
  • graph_path.json - Hashes of graph nodes visited (for DCA Hamiltonian analysis)

OSC Sender

The OSC sender allows real-time playback of generated chords via OSC messages.

Usage

# Direct usage
python src/osc_sender.py

# Via compact_sets.py (playback only)
python compact_sets.py --osc-play

# Generate and then playback
python compact_sets.py --osc-play --osc-ip 192.168.4.200 --osc-port 54001

Options

--osc-play FILE Enable OSC playback (default: output/output_chords.json) --osc-ip IP Destination IP address (default: 192.168.4.200) --osc-port PORT Destination port (default: 54001) --fundamental FREQ Fundamental frequency in Hz (default: 100 Hz)

Interactive Controls

Key Action
/ Navigate backward/forward
p Toggle preview mode
Enter Send current chord (when not in preview mode)
[n] + Enter Jump to chord n
k Kill soft (15.0 Hz to all voices)
K Kill hard (0.0 Hz to all voices)
Escape Quit

Preview Mode

Preview mode ([PREVIEW]) disables sending; toggle with p to enable ([SEND]).

OSC Protocol

Messages sent to /freq with: voice number (int), frequency (float).

Test Receiver

For debugging, run python src/test_receiver.py in a separate terminal.

LilyPond Transcriber

⚠️ IN DEVELOPMENT - This feature is experimental and may change.

The LilyPond transcriber converts generated chord data into music notation.

Usage

# Generate path first, then transcribe
python compact_sets.py --dims 4 --chord-size 4 --symdiff-min 4 --symdiff-max 4 --max-path 200 --target-register 3 --weight-target-register 10
python compact_sets.py --transcribe

# Custom input and output name
python compact_sets.py --transcribe output/output_chords.json my_piece

# From any chords file
python compact_sets.py --transcribe path/to/chords.json

Output

Generated files go to lilypond/{name}/:

  • score_template.ly - Template file
  • {name}.ly - Full score
  • {name}.pdf - PDF output
  • includes/part_I.ly through part_IV.ly - Individual voice parts

What It Generates

The transcriber produces:

  • 4 voice parts (I, II, III, IV) with automatic clef changes
  • Cent deviation markings showing how far each pitch is from equal temperament
  • Dimension markup showing which prime dimension changed and by how much (e.g., "III 5↑" = 5th dimension up from voice III)

Requirements

  • LilyPond must be installed and in PATH