Skip to main content

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

Composition Assistance

Use the browser UI when you want to keep iterating on one candidate instead of only generating a fresh run.

Start the API server from the repository root:

uv sync --group dev --extra solver --extra visual
uv run canonize-web --output-root ./runs --host 127.0.0.1 --port 8000

Start the frontend in another shell:

cd web
pnpm install --frozen-lockfile
pnpm dev

Open the Vite URL, upload a melody, and select a candidate. From there you can:

  • run local Phrase Polish over a bar range
  • keep one voice fixed and rewrite the other with fixed-voice invention
  • translate feedback such as bass too static or too repetitive into structured polish requests
  • trigger polish directly from phrase, cadence, and bass findings in the Analysis panel
  • keep saved experiment variants in Candidate Lab and reopen them after reload for more polish, compare, or export

See Visualization for the full browser workflow.

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.

Metadata

Release files for kithairon-canon 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kithairon-canon 0.1.2
File Size Uploaded Provenance Actions
kithairon_canon-0.1.2.tar.gz 74.3 kB Without provenance Details for kithairon_canon-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kithairon-canon 0.1.2
File Interpreter ABI Platform Actions
kithairon_canon-0.1.2-py3-none-any.whl Python 3 none any Details for kithairon_canon-0.1.2-py3-none-any.whl

Total release size: 179.4 kB

Release files / kithairon_canon-0.1.2.tar.gz

Download URL kithairon_canon-0.1.2.tar.gz
Size 74.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a795c3e8728e314cb676c059e781cfe0b697c84aefb3ae2586db5d9538d19ed7
BLAKE2b-256 checksum
How to use checksums
5f8f5ebdba437c3b2e4bd4b0d8b61ee67347d2ee8ecc13b877db687384c7e0e5
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / kithairon_canon-0.1.2-py3-none-any.whl

Download URL kithairon_canon-0.1.2-py3-none-any.whl
Size 105.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
01963e70efa67e1d1a7560a2da358531d4aef3a245dc787222180330f2963cb1
BLAKE2b-256 checksum
How to use checksums
a45d200972737bfa86e34e356fc7055e5f9aaf2d2377342efd248a2c85240d3f
Upload date
Uploaded using Trusted Publishing?
What is 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}

Security for kithairon-canon 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page