Skip to main content

Generate playable and explainable canon variants from a monophonic melody.

Project description

Kithairon

Kithairon is a small symbolic music compiler that turns a monophonic melody into several playable canon variants. It uses explicit canon transformations to guarantee strict canon candidates, then applies explainable harmony and voice-leading rules to score them. When strict candidates are weak, optional repair and CP-SAT solver engines can produce relaxed canon variants while preserving a clear link to the original melody.

Use it from the canonize CLI with MIDI or MusicXML input. Each generation run writes playable files, a machine-readable result file, the resolved config, and a Markdown report.

Read the hosted documentation at https://kithairon-docs.pages.dev.

30-Second Demo

Run Kithairon on a longer Bach-derived MusicXML fragment:

uv run canonize generate examples/melodies/bach_wtc1_c_major_prelude_upper.musicxml \
  --out tmp/demo \
  --engine repair \
  --top-k 12

The input is a two-measure monophonic incipit derived from the public-domain BWV 846 entry shipped in the music21 corpus. The generated output includes playable canon candidates with:

  • tmp/demo/candidates/*.musicxml: a score you can open in a notation editor.
  • tmp/demo/candidates/*.mid: a playable MIDI file.
  • tmp/demo/report.md: the score summary and rule penalties.
  • tmp/demo/visualization.json: data for the web visualization.

The preview below uses the rank 1 repair candidate from this run: repaired inversion, delay 4, score 86.0. In an external listening pass over the main demo candidates, this was the most pleasant clip even though a stricter transposition candidate scored higher under Kithairon's rule penalties.

Input score: Bach BWV 846 upper-voice incipit

Generated relaxed canon score: repaired inversion

Listen to the selected candidate: MP3 or MIDI.

To inspect the same run in the browser, start the visualization API and frontend as described in Visualization, then upload examples/melodies/bach_wtc1_c_major_prelude_upper.musicxml.

Kithairon web UI showing the selected repair candidate

The score breakdown view shows the tradeoff: the selected clip sounds better than the best strict fallback, but it still pays weak-beat dissonance penalties under the current scoring model.

Score breakdown with weak-beat dissonance penalties

Additional preview picks are available in docs/assets/demo: the best strict fallback, output-strict-transposition-delay4.*, and a second strict alternative with a wider delay.

Requirements

  • Python 3.12 or newer
  • uv

Install

Install the default package and development dependencies:

uv sync

After the package-index release is published, install the CLI from PyPI with:

uv tool install kithairon-canon

Install OR-Tools when you want the CP-SAT solver engine:

uv sync --extra solver

Install documentation dependencies only when previewing the MkDocs site:

uv sync --extra docs

Quickstart

Validate an input melody:

uv run canonize validate examples/melodies/scale_c_major.musicxml

Generate the top three strict canon candidates:

uv run canonize generate examples/melodies/scale_c_major.musicxml \
  --out tmp/strict \
  --engine strict \
  --top-k 3

The output directory contains:

  • results.json: full candidate data, scores, violations, transforms, and output paths.
  • report.md: a readable summary of top candidates and penalty reasons.
  • resolved_config.toml: the exact config used for the run.
  • visualization.json: normalized data for the web visualization.
  • artifact_index.json: the safe download map used by the API and web UI.
  • candidates/*.musicxml and candidates/*.mid: playable exports for each candidate.

A successful command returns a compact JSON payload:

{
  "status": "ok",
  "engine": "strict",
  "out": "tmp/strict",
  "candidates": 3,
  "results": "tmp/strict/results.json",
  "report": "tmp/strict/report.md",
  "resolved_config": "tmp/strict/resolved_config.toml",
  "visualization": "tmp/strict/visualization.json",
  "artifact_index": "tmp/strict/artifact_index.json"
}

If --out already exists and is not empty, Kithairon writes to a suffixed directory such as tmp/strict-1. To replace the existing output directory, put --overwrite before the subcommand:

uv run canonize --overwrite generate examples/melodies/scale_c_major.musicxml \
  --out tmp/strict \
  --engine strict

Engine Examples

Strict generation keeps the follower voice as an exact transform of the input melody:

uv run canonize generate examples/melodies/scale_c_major.musicxml \
  --out tmp/strict \
  --engine strict \
  --top-k 3

Repair generation starts from strict candidates and edits a limited number of follower notes when that improves the score:

uv run canonize generate examples/melodies/bad_for_canon.musicxml \
  --out tmp/repair \
  --engine repair \
  --top-k 3

Solver generation uses OR-Tools CP-SAT to search for a relaxed follower voice under edit limits:

uv sync --extra solver
uv run canonize generate examples/melodies/bad_for_canon.musicxml \
  --out tmp/solver \
  --engine solver \
  --top-k 1

Auto generation ranks strict candidates first. If the strict pool falls below the configured quality thresholds, it adds repair candidates. It may add solver candidates when the solver dependency is installed and [solver].enabled = true in the resolved config:

uv run canonize generate examples/melodies/bad_for_canon.musicxml \
  --out tmp/auto \
  --engine auto \
  --top-k 4

Strict And Relaxed Canons

A strict canon has strict_canon: true and canon_label: "strict canon" in results.json. The follower is exactly produced from the source melody by the recorded transform_spec, for example transposition, inversion, retrograde, augmentation, or diminution with a delay.

A relaxed canon has strict_canon: false and canon_label: "relaxed canon". It still records the source transform and candidate lineage, but the repair or solver engine may change follower pitches under configured edit limits. The report includes the edit plan and the rule penalties that remain after the edits.

For the musical assumptions behind the score, see Scoring And Rules.

Input Files

Supported input formats:

  • MIDI: .mid, .midi
  • MusicXML: .musicxml, .xml, .mxl

Inputs must be monophonic by default. If a MusicXML file contains chords and you want to extract one note from each chord, choose a chord policy:

uv run canonize validate path/to/melody.musicxml --chord-policy top-note

For multi-part scores, select a part with --part-policy and --part-index during validation, or put the same settings in a config file.

Config

Print the resolved default config:

uv run canonize config resolve

Write a TOML config and reuse it:

uv run canonize config resolve --out kithairon.toml
uv run canonize generate examples/melodies/scale_c_major.musicxml \
  --config kithairon.toml \
  --out tmp/from-config

CLI flags override config values for the same run:

uv run canonize generate examples/melodies/scale_c_major.musicxml \
  --config kithairon.toml \
  --engine repair \
  --score-profile renaissance-lite \
  --top-k 2 \
  --out tmp/repair-from-config

The scoring profile defaults to pop-lite. Use --score-profile permissive for looser ranking or --score-profile renaissance-lite for stricter counterpoint-style penalties. The selected profile is recorded in resolved_config.toml, results.json, and the visualization payload.

Errors

Common user errors return JSON diagnostics instead of Python tracebacks. Examples include missing input files, unsupported formats, chord or polyphonic input, invalid config, and missing solver dependencies.

To debug an unexpected exception, put --debug before the subcommand:

uv run canonize --debug generate examples/melodies/scale_c_major.musicxml \
  --out tmp/debug \
  --engine strict

Development

Run the full local gate:

uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run coverage run -m pytest
uv run coverage report
uv run pytest benchmarks/test_performance_budgets.py -q
bash scripts/build-docs.sh

Run the Cremona refactor audit:

uv run coverage run -m pytest -q
uv run coverage json -o coverage.json
uv run cremona scan --baseline quality/refactor-baseline.json --coverage-json coverage.json --fail-on-regression

Refresh the committed baseline only after structural debt is intentionally reduced or Cremona changes its baseline schema:

uv run cremona scan --update-baseline

Preview the documentation site:

uv run --extra docs mkdocs serve

Build the documentation site for deployment:

bash scripts/build-docs.sh

Deploy the documentation with Cloudflare Pages Git integration using pip install uv && uv run --extra docs mkdocs build --strict as the build command and site as the build output directory. See Deployment for the full Pages settings.

See Package Distribution before publishing to a Python package index. PyPI releases use the kithairon-canon distribution name.

See CONTRIBUTING.md for pull request checks and CHANGELOG.md for release notes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kithairon_canon-0.1.1.tar.gz (51.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kithairon_canon-0.1.1-py3-none-any.whl (73.6 kB view details)

Uploaded Python 3

File details

Details for the file kithairon_canon-0.1.1.tar.gz.

File metadata

  • Download URL: kithairon_canon-0.1.1.tar.gz
  • Upload date:
  • Size: 51.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for kithairon_canon-0.1.1.tar.gz
Algorithm Hash digest
SHA256 87d8b9cf439f933afe4c60e47dc2ca169ffa537d2bbf43b2a7de1f85c21eb40d
MD5 9080c6ede1d590d4fe49c94577a0e7c1
BLAKE2b-256 07f9c2a5cd0a109db5cf12a7634a3c35979f4663d072372286126f82772c5275

See more details on using hashes here.

File details

Details for the file kithairon_canon-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: kithairon_canon-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 73.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for kithairon_canon-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 76e6404783b9a3d7186ddbc4934dac71d0b74d1840d62fdeddbc22445f916a33
MD5 6f34ce068d47c65ad3be1417f9488c77
BLAKE2b-256 5b9c00e5e31e06effe0022b7e9de38cce95690f0f6c6abd393a35225753650d0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page