{"id":37071002,"url":"https://github.com/shakfu/aldakit","last_synced_at":"2026-01-14T08:17:38.280Z","repository":{"id":331583769,"uuid":"1127286805","full_name":"shakfu/aldakit","owner":"shakfu","description":"A zero-dependency Python parser and MIDI generator for the Alda music programming language","archived":false,"fork":false,"pushed_at":"2026-01-10T10:28:02.000Z","size":2873,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-11T01:57:20.647Z","etag":null,"topics":["alda","alda-lang","libremidi","midi","nanobind","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shakfu.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-03T15:18:44.000Z","updated_at":"2026-01-10T10:28:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shakfu/aldakit","commit_stats":null,"previous_names":["shakfu/aldakit"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/shakfu/aldakit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakfu%2Faldakit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakfu%2Faldakit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakfu%2Faldakit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakfu%2Faldakit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shakfu","download_url":"https://codeload.github.com/shakfu/aldakit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakfu%2Faldakit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alda","alda-lang","libremidi","midi","nanobind","python"],"created_at":"2026-01-14T08:17:33.213Z","updated_at":"2026-01-14T08:17:38.268Z","avatar_url":"https://github.com/shakfu.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aldakit\n\n[![PyPI version](https://badge.fury.io/py/aldakit.svg)](https://pypi.org/project/aldakit/)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA zero-dependency Python parser and MIDI generator for the [Alda](https://alda.io) music programming language[^1].\n\n[^1]: Includes a rich REPL, native MIDI, and built-in audio via bundled [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit), [libremidi](https://github.com/jcelerier/libremidi), and [TinySoundFont](https://github.com/schellingb/TinySoundFont) respectively.\n\n## Features\n\n- **Alda Parser** - Full parser for the Alda music language with AST generation\n- **MIDI Playback** - Low-latency playback via libremidi (CoreMIDI, ALSA, WinMM)\n- **Audio Playback** - Built-in synthesis via TinySoundFont (no external synth required)\n- **MIDI Export** - Save compositions as Standard MIDI Files\n- **MIDI Import** - Load MIDI files and convert to Alda notation\n- **Real-time Transcription** - Record from MIDI keyboards and convert to Alda\n- **Programmatic Composition** - Build music with Python using the compose module\n- **Music Theory** - Scale, chord, and interval utilities\n- **Transformers** - Transpose, invert, augment, diminish, and more\n- **Generative Music** - Markov chains, L-systems, cellular automata, Euclidean rhythms\n- **Interactive REPL** - Syntax highlighting, auto-completion, and live playback\n- **CLI Tools** - Play, transcribe, and convert from the command line\n\n## Installation\n\nRequires Python 3.10+\n\n```sh\npip install aldakit\n```\n\nOr with [uv](https://github.com/astral-sh/uv):\n\n```sh\nuv add aldakit\n```\n\n## Quick Start\n\n### Command Line\n\n```sh\n# Interactive REPL (default when no args)\naldakit\n\n# Evaluate inline code\naldakit eval \"piano: c d e f g\"\n\n# Play an Alda file\naldakit play examples/twinkle.alda\n\n# Export to MIDI file\naldakit play examples/bach-prelude.alda -o bach.mid\n\n# Use built-in audio (TinySoundFont) instead of MIDI\naldakit play -sf ~/Music/sf2/FluidR3_GM.sf2 examples/twinkle.alda\naldakit repl -sf ~/Music/sf2/FluidR3_GM.sf2\n\n# Use audio backend with pre-configured soundfont (from config or env)\naldakit play -a examples/twinkle.alda\naldakit repl -a\n\n# Create virtual MIDI port with custom name\naldakit repl -vp MyMIDI\n```\n\n### Python API\n\n```python\nimport aldakit\n\n# Play directly\naldakit.play(\"piano: c d e f g\")\n\n# Save to MIDI file\naldakit.save(\"piano: c d e f g\", \"output.mid\")\n\n# Play from file\naldakit.play_file(\"song.alda\")\n\n# List available MIDI ports\nprint(aldakit.list_ports())\n```\n\nFor more control, use the `Score` class:\n\n```python\nfrom aldakit import Score\n\nscore = Score(\"\"\"\npiano:\n  (tempo 120)\n  o4 c4 d e f | g a b \u003e c\n\"\"\")\n\n# Play with options\nscore.play(port=\"FluidSynth\", wait=False)\n\n# Save to file\nscore.save(\"output.mid\")\n\n# Access internals\nprint(f\"Duration: {score.duration}s\")\nprint(score.ast)   # Parsed AST\nprint(score.midi)  # MIDI sequence\n```\n\n### Concurrent Playback\n\nLayer multiple sequences for polyphonic REPL-style playback:\n\n```python\nfrom aldakit.midi.backends import LibremidiBackend\n\n# Create backend with concurrent mode (default)\nbackend = LibremidiBackend(concurrent=True)\n\n# Play multiple sequences - they layer on top of each other\nbackend.play(score1.midi)  # Starts immediately\nbackend.play(score2.midi)  # Layers on top of score1\nbackend.play(score3.midi)  # Up to 8 concurrent slots\n\n# Check status\nprint(f\"Active slots: {backend.active_slots}\")\nprint(f\"Playing: {backend.is_playing()}\")\n\n# Wait for all playback to complete\nbackend.wait()\n\n# Or stop all playback immediately\nbackend.stop()\n\n# Sequential mode - each play waits for previous to finish\nbackend.concurrent_mode = False\nbackend.play(score1.midi)  # Plays first\nbackend.play(score2.midi)  # Waits, then plays second\n```\n\n### MIDI Import\n\nImport existing MIDI files and work with them as Alda:\n\n```python\nfrom aldakit import Score\n\n# Import a MIDI file\nscore = Score.from_midi_file(\"recording.mid\")\n\n# Or use from_file (auto-detects .mid/.midi)\nscore = Score.from_file(\"song.mid\")\n\n# View as Alda source\nprint(score.to_alda())\n# piano:\n# o4 c4 d e f | g a b \u003e c\n\n# Play the imported MIDI\nscore.play()\n\n# Export to Alda file\nscore.save(\"song.alda\")\n\n# Re-export to MIDI\nscore.save(\"output.mid\")\n\n# Import with custom quantization grid\n# Default is 0.25 (16th notes), use 0.5 for 8th notes\nscore = Score.from_midi_file(\"recording.mid\", quantize_grid=0.5)\n```\n\nFeatures:\n- Multi-track MIDI files (each channel becomes a separate part)\n- Tempo detection and preservation\n- General MIDI instrument mapping\n- Chord detection for simultaneous notes\n- Configurable timing quantization\n\n### Real-Time MIDI Transcription\n\nRecord MIDI input from a keyboard or controller:\n\n```python\nimport aldakit\n\n# List available MIDI input ports\nprint(aldakit.list_input_ports())\n\n# Record for 10 seconds from the first available port\nscore = aldakit.transcribe(duration=10)\n\n# Play back what was recorded\nscore.play()\n\n# Export to Alda source\nprint(score.to_alda())\n\n# Record with options\nscore = aldakit.transcribe(\n    duration=30,\n    port_name=\"My MIDI Keyboard\",\n    instrument=\"piano\",\n    tempo=120,\n    quantize_grid=0.25,  # Quantize to 16th notes\n)\n```\n\nFor more control, use `TranscribeSession`:\n\n```python\nfrom aldakit.midi.transcriber import TranscribeSession\n\nsession = TranscribeSession(quantize_grid=0.25, default_tempo=120)\n\n# Set a callback for note events (optional)\nsession.on_note(lambda pitch, vel, on: print(f\"Note: {pitch}, vel={vel}, on={on}\"))\n\n# Start recording\nsession.start()\n\n# Poll periodically (in a loop or timer)\nimport time\nfor _ in range(100):\n    session.poll()\n    time.sleep(0.1)\n\n# Stop and get the recorded notes\nseq = session.stop()\nprint(seq.to_alda())\n```\n\n### Programmatic Composition\n\nBuild music programmatically using the compose module:\n\n```python\nfrom aldakit import Score\nfrom aldakit.compose import part, note, rest, chord, seq, tempo, volume\n\n# Create a score from compose elements\nscore = Score.from_elements(\n    part(\"piano\"),\n    tempo(120),\n    note(\"c\", duration=4),\n    note(\"d\"),\n    note(\"e\"),\n    chord(\"c\", \"e\", \"g\", duration=2),\n)\nscore.play()\n\n# Builder pattern with method chaining\nscore = (\n    Score.from_elements(part(\"violin\"))\n    .with_tempo(90)\n    .add(note(\"g\", duration=8), note(\"a\"), note(\"b\"))\n)\n\n# Note transformations\nc = note(\"c\", duration=4)\nc_sharp = c.sharpen()           # C#\nc_up_octave = c.transpose(12)   # Up one octave\n\n# Repeat syntax\npattern = seq(note(\"c\"), note(\"d\"), note(\"e\"))\nrepeated = pattern * 4  # Repeat 4 times\n\n# Export to Alda source\nprint(score.to_alda())  # \"violin: (tempo 90) g8 a b\"\n```\n\nAvailable compose elements:\n- **Notes**: `note(\"c\", duration=4, octave=5, accidental=\"+\", dots=1)`\n- **Rests**: `rest(duration=4)`, `rest(ms=500)`\n- **Chords**: `chord(\"c\", \"e\", \"g\")`, `chord(note(\"c\"), note(\"e\", accidental=\"+\"))`\n- **Sequences**: `seq(note(\"c\"), note(\"d\"))`, `Seq.from_alda(\"c d e\")`\n- **Parts**: `part(\"piano\")`, `part(\"violin\", alias=\"v1\")`\n- **Attributes**: `tempo(120)`, `volume(80)`, `octave(5)`, `panning(50)`\n- **Dynamics**: `pp()`, `p()`, `mp()`, `mf()`, `f()`, `ff()`\n- **Advanced**: `cram()`, `voice()`, `voice_group()`, `var()`, `var_ref()`, `marker()`, `at_marker()`\n\n### Scales and Chords\n\nBuild melodies and harmonies using music theory helpers:\n\n```python\nfrom aldakit import Score\nfrom aldakit.compose import part, tempo\nfrom aldakit.compose import (\n    # Scale functions\n    scale, scale_notes, scale_degree, mode,\n    relative_minor, relative_major,\n    # Chord builders\n    major, minor, dim, aug, maj7, min7, dom7,\n    arpeggiate, invert_chord, voicing,\n)\n\n# Get scale pitches\nc_major = scale(\"c\", \"major\")       # ['c', 'd', 'e', 'f', 'g', 'a', 'b']\na_blues = scale(\"a\", \"blues\")       # ['a', 'c', 'd', 'd+', 'e', 'g']\n\n# Generate scale as playable notes\nmelody = scale_notes(\"c\", \"pentatonic\", duration=8)\n\n# Key relationships\nrel_min = relative_minor(\"c\")  # 'a' (C major -\u003e A minor)\nrel_maj = relative_major(\"a\")  # 'c' (A minor -\u003e C major)\n\n# Build chords\nc_maj = major(\"c\")                    # C E G\na_min7 = min7(\"a\")                    # A C E G\ng_dom7 = dom7(\"g\", inversion=1)       # B D F G (first inversion)\n\n# Arpeggiate a chord\narp = arpeggiate(maj7(\"c\"), pattern=[0, 1, 2, 3, 2, 1], duration=16)\n\n# Custom voicing (spread chord across octaves)\nspread = voicing(major(\"c\"), [3, 4, 5])  # C3 E4 G5\n\n# Create a I-IV-V-I progression\npitches = scale(\"c\", \"major\")\nprogression = [\n    major(pitches[0], duration=2),  # C major (I)\n    major(pitches[3], duration=2),  # F major (IV)\n    major(pitches[4], duration=2),  # G major (V)\n    major(pitches[0], duration=1),  # C major (I)\n]\n\nscore = Score.from_elements(\n    part(\"piano\"),\n    tempo(100),\n    *progression,\n)\nscore.play()\n```\n\nAvailable scales: major, minor, harmonic-minor, melodic-minor, pentatonic, blues, chromatic, whole-tone, dorian, phrygian, lydian, mixolydian, locrian, japanese, arabic, hungarian-minor, spanish, bebop-dominant, bebop-major\n\nAvailable chords: major, minor, dim, aug, sus2, sus4, maj7, min7, dom7, dim7, half_dim7, min_maj7, aug7, maj6, min6, dom9, maj9, min9, add9, power\n\n### Transformers\n\nTransform sequences with pitch and structural operations:\n\n```python\nfrom aldakit.compose import (\n    note, seq,\n    transpose, invert, reverse, shuffle,\n    augment, diminish, fragment, loop, interleave,\n    pipe,\n)\n\n# Create a motif\nmotif = seq(note(\"c\", duration=8), note(\"d\", duration=8), note(\"e\", duration=8))\n\n# Pitch transformers\nup_fourth = transpose(motif, 5)      # Transpose up 5 semitones\ninverted = invert(motif)             # Invert intervals around first note\nbackwards = reverse(motif)           # Retrograde\n\n# Structural transformers\nlonger = augment(motif, 2)           # Double durations (8th -\u003e quarter)\nshorter = diminish(motif, 2)         # Halve durations (8th -\u003e 16th)\nfirst_two = fragment(motif, 2)       # Take first 2 elements\nrepeated = loop(motif, 4)            # Repeat 4 times (explicit)\n\n# Chain transformations with pipe\nresult = pipe(\n    motif,\n    lambda s: transpose(s, 5),\n    reverse,\n    lambda s: augment(s, 2),\n)\n\n# All transforms preserve to_alda() export\nprint(result.to_alda())\n```\n\n### MIDI Transformers\n\nFor post-MIDI-generation processing, use MIDI-level transformers that operate on absolute timing:\n\n```python\nfrom aldakit import Score\nfrom aldakit.midi.transform import (\n    quantize, humanize, swing, stretch,\n    accent, crescendo, normalize,\n    filter_notes, trim, merge,\n)\n\n# Get MIDI sequence from a score\nscore = Score(\"piano: c d e f g a b \u003e c\")\nmidi_seq = score.midi\n\n# Timing transformers\nquantized = quantize(midi_seq, grid=0.25, strength=0.8)  # Snap to quarter-note grid\nhumanized = humanize(midi_seq, timing=0.02, velocity=10)  # Add subtle variations\nswung = swing(midi_seq, grid=0.5, amount=0.3)            # Apply swing feel\n\n# Velocity transformers\naccented = accent(midi_seq, pattern=[1.0, 0.5, 0.5, 0.5])  # 4/4 accent pattern\ncrescendo_seq = crescendo(midi_seq, start_vel=50, end_vel=100)\nnormalized = normalize(midi_seq, target=100)\n\n# Filtering and combining\nfiltered = filter_notes(midi_seq, lambda n: n.pitch \u003e= 60)  # Keep notes \u003e= middle C\ntrimmed = trim(midi_seq, start=0.0, end=2.0)               # First 2 seconds\nmerged = merge(midi_seq, another_seq)                       # Combine sequences\n```\n\nNote: MIDI transformers operate on absolute timing (seconds) and cannot be converted back to Alda notation.\n\n### Generative Functions\n\nCreate algorithmic compositions with generative functions:\n\n```python\nfrom aldakit import Score\nfrom aldakit.compose import part, tempo\nfrom aldakit.compose.generate import (\n    random_walk, euclidean, markov_chain, lsystem, cellular_automaton,\n    shift_register, turing_machine,\n)\n\n# Random walk melody\nmelody = random_walk(\"c\", steps=16, intervals=[-2, -1, 1, 2], duration=8, seed=42)\n\n# Euclidean rhythms (e.g., Cuban tresillo: 3 hits over 8 steps)\nrhythm = euclidean(hits=3, steps=8, pitch=\"c\", duration=16)\n\n# Markov chain\nchain = markov_chain({\n    \"c\": {\"d\": 0.5, \"e\": 0.3, \"g\": 0.2},\n    \"d\": {\"e\": 0.6, \"c\": 0.4},\n    \"e\": {\"c\": 0.5, \"g\": 0.5},\n    \"g\": {\"c\": 1.0},\n})\nmarkov_melody = chain.generate(start=\"c\", length=16, duration=8, seed=42)\n\n# L-System (Fibonacci pattern)\nfrom aldakit.compose import note, rest\nfib = lsystem(\n    axiom=\"A\",\n    rules={\"A\": \"AB\", \"B\": \"A\"},\n    iterations=5,\n    note_map={\"A\": note(\"c\", duration=8), \"B\": note(\"e\", duration=8)},\n)\n\n# Cellular automaton (Rule 110)\nautomaton = cellular_automaton(rule=110, width=8, steps=4, pitch_on=\"c\", duration=16)\n\n# Shift register (LFSR) - classic analog sequencer\nlfsr = shift_register(16, bits=4, scale=[\"c\", \"e\", \"g\", \"b\"], duration=16)\n\n# Turing Machine - evolving loop (probability=0 for locked, higher for chaos)\nturing = turing_machine(32, bits=8, probability=0.1, seed=42)\n\n# Combine into a score\nscore = Score.from_elements(\n    part(\"piano\"),\n    tempo(120),\n    *melody.elements,\n)\nscore.play()\n```\n\n## CLI Reference\n\n```sh\naldakit [--version] [-h] {repl,play,eval,ports,transcribe} ...\n```\n\n### Subcommands\n\n| Command | Description |\n| ------- | ----------- |\n| (none) | Opens the interactive REPL (default when no args) |\n| `repl` | Interactive REPL with syntax highlighting and auto-completion |\n| `play` | Play an Alda file |\n| `eval` | Evaluate Alda code directly |\n| `ports` | List available MIDI ports (both input and output) |\n| `transcribe` | Record MIDI input and output Alda code |\n\n### Global Options\n\n| Option | Description |\n| ------ | ----------- |\n| `--version` | Show version number and exit |\n| `-h, --help` | Show help message |\n\n### `play` Subcommand\n\n```sh\naldakit play [-v] [-o FILE] [--port NAME|INDEX] [-sf FILE] [-a] [-vp NAME] [--stdin] [--parse-only] [--no-wait] FILE\n```\n\n| Option | Description |\n| ------ | ----------- |\n| `FILE` | Alda file to play (use `-` for stdin) |\n| `-v, --verbose` | Verbose output |\n| `-o, --output FILE` | Save to MIDI file instead of playing |\n| `--port NAME\\|INDEX` | MIDI port by name or index (see `aldakit ports`) |\n| `-sf, --soundfont FILE` | Use TinySoundFont audio backend with specified SoundFont |\n| `-a, --audio` | Use audio backend with pre-configured soundfont |\n| `-vp, --virtual-port NAME` | Custom virtual MIDI port name (default: AldakitMIDI) |\n| `--stdin` | Read from stdin (blank line to play) |\n| `--parse-only` | Print AST without playing |\n| `--no-wait` | Don't wait for playback to finish |\n\n### `eval` Subcommand\n\n```sh\naldakit eval [-v] [-o FILE] [--port NAME|INDEX] [-sf FILE] [-a] [-vp NAME] CODE\n```\n\n| Option | Description |\n| ------ | ----------- |\n| `CODE` | Alda code to evaluate |\n| `-v, --verbose` | Verbose output |\n| `-o, --output FILE` | Save to MIDI file instead of playing |\n| `--port NAME\\|INDEX` | MIDI port by name or index |\n| `-sf, --soundfont FILE` | Use TinySoundFont audio backend |\n| `-a, --audio` | Use audio backend with pre-configured soundfont |\n| `-vp, --virtual-port NAME` | Custom virtual MIDI port name (default: AldakitMIDI) |\n\n### `repl` Subcommand\n\n```sh\naldakit repl [-v] [--port NAME|INDEX] [-sf FILE] [-a] [-vp NAME] [--sequential]\n```\n\n| Option | Description |\n| ------ | ----------- |\n| `-v, --verbose` | Verbose output |\n| `--port NAME\\|INDEX` | MIDI port by name or index |\n| `-sf, --soundfont FILE` | Use TinySoundFont audio backend |\n| `-a, --audio` | Use audio backend with pre-configured soundfont |\n| `-vp, --virtual-port NAME` | Custom virtual MIDI port name (default: AldakitMIDI) |\n| `--sequential` | Start in sequential mode (wait for each input) |\n\n### `transcribe` Subcommand\n\n```sh\naldakit transcribe [-d SEC] [-i INST] [-t BPM] [-q GRID] [-o FILE] [--port NAME] [--play] [-v] [--alda-notes] [--feel FEEL] [--swing-ratio RATIO]\n```\n\n| Option | Description |\n| ------ | ----------- |\n| `-d, --duration SEC` | Recording duration in seconds (default: 10) |\n| `-i, --instrument NAME` | Instrument name (default: piano) |\n| `-t, --tempo BPM` | Tempo for quantization (default: 120) |\n| `-q, --quantize GRID` | Quantize grid in beats (default: 0.25 = 16th notes) |\n| `-o, --output FILE` | Save to file (.alda or .mid) |\n| `--port NAME` | MIDI input port name |\n| `--play` | Play back the recording after transcription |\n| `-v, --verbose` | Show notes as they are played |\n| `--alda-notes` | Show notes in Alda notation (with -v) |\n| `--feel FEEL` | Rhythm feel: straight, swing, triplet, quintuplet |\n| `--swing-ratio RATIO` | Swing ratio between 0 and 1 (default: 0.67) |\n\n### Examples\n\n```bash\n# Interactive REPL (default when no args)\naldakit\naldakit repl\n\n# Evaluate inline code\naldakit eval \"piano: c d e f g\"\n\n# Play a file\naldakit play examples/jazz.alda\naldakit play -v examples/jazz.alda  # verbose\n\n# Play to a specific port (by index or name)\naldakit play --port 0 examples/twinkle.alda\naldakit play --port FluidSynth examples/twinkle.alda\n\n# Use built-in audio (TinySoundFont) instead of MIDI\naldakit play -sf ~/Music/sf2/FluidR3_GM.sf2 examples/twinkle.alda\naldakit repl -sf ~/Music/sf2/FluidR3_GM.sf2\n\n# Read from stdin\necho \"piano: c d e f g\" | aldakit play -\naldakit play --stdin\n\n# Parse and show AST\naldakit play --parse-only examples/twinkle.alda\naldakit eval --parse-only \"piano: c/e/g\"\n\n# Export to MIDI file\naldakit play examples/twinkle.alda -o twinkle.mid\naldakit eval \"piano: c d e f g\" -o output.mid\n\n# List available MIDI ports\naldakit ports\naldakit ports -o  # output ports only\naldakit ports -i  # input ports only\n\n# Record MIDI input for 10 seconds (default)\naldakit transcribe\n\n# Record from a specific input port\naldakit transcribe --port 0\naldakit transcribe --port \"My MIDI Keyboard\"\n\n# Record for 30 seconds with verbose note display\naldakit transcribe -d 30 -v\n\n# Record with Alda-style note display\naldakit transcribe -d 10 -v --alda-notes\n\n# Record and save to file\naldakit transcribe -o recording.alda\naldakit transcribe -o recording.mid\n\n# Record and play back\naldakit transcribe --play\n\n# Record with custom settings (swing feel, triplet quantization)\naldakit transcribe -d 20 -t 90 -i guitar --feel triplet --play\n```\n\n## Configuration File\n\naldakit supports INI-format configuration files to set default values for common options. Configuration is loaded from these locations (in priority order):\n\n1. `./aldakit.ini` - Project-local config (current working directory)\n2. `~/.aldakit/config.ini` - User config (home directory)\n3. `ALDAKIT_SOUNDFONT` environment variable (for soundfont only)\n\nCLI arguments always override config file settings.\n\n### Example Configuration\n\nCreate `~/.aldakit/config.ini`:\n\n```ini\n[aldakit]\n# Default SoundFont for audio backend\nsoundfont = ~/Music/sf2/FluidR3_GM.sf2\n\n# Default backend: \"midi\" or \"audio\"\nbackend = midi\n\n# Default MIDI output port (name or index)\nport = FluidSynth\n\n# Default tempo for REPL (BPM)\ntempo = 120\n\n# Enable verbose output by default\nverbose = false\n```\n\n### Available Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `soundfont` | path | none | SoundFont path for audio backend |\n| `backend` | string | `midi` | `midi` = external synths/DAWs/virtual port; `audio` = built-in TinySoundFont |\n| `port` | string | none | Default MIDI output port name |\n| `tempo` | integer | `120` | Default tempo for REPL (BPM) |\n| `verbose` | boolean | `false` | Enable verbose output |\n\n**Backend values:**\n- `midi` (default): Uses libremidi for MIDI output. Sends to external synthesizers (FluidSynth, hardware), DAWs, or creates a virtual port (\"AldakitMIDI\") for routing.\n- `audio`: Uses built-in TinySoundFont for direct audio output. Requires a `soundfont` to be configured. No external MIDI setup needed.\n\n### Backend Selection Priority\n\n1. CLI `-sf /path/to/soundfont.sf2` forces audio backend with specified soundfont\n2. CLI `-a` / `--audio` forces audio backend using pre-configured soundfont\n3. Config `backend = audio` uses audio backend\n4. If MIDI ports are available, use MIDI (default)\n5. If no MIDI ports available and `soundfont` is configured, fall back to audio\n6. If no MIDI ports and no soundfont configured, create virtual MIDI port (\"AldakitMIDI\")\n\n### Project-Local Configuration\n\nCreate `aldakit.ini` in your project directory to override user settings:\n\n```ini\n[aldakit]\n# Use audio backend with project-specific SoundFont\nbackend = audio\nsoundfont = ./sounds/project-soundfont.sf2\ntempo = 140\n```\n\n## Interactive REPL\n\nThe REPL provides an interactive environment for composing and playing Alda code:\n\n```bash\naldakit repl\n```\n\nFeatures:\n\n- Syntax highlighting\n- Auto-completion for instruments (3+ characters)\n- Command history (persistent across sessions)\n- Multi-line paste (use platform-specific paste: ctrl-v, shift-ctrl-v, cmd-v, etc.)\n- Multi-line input (Alt+Enter)\n- MIDI playback control (Ctrl+C to stop)\n\nREPL Commands:\n\n- `:help` - Show help\n- `:quit` - Exit REPL\n- `:ports` - List MIDI ports\n- `:instruments` - List available instruments\n- `:tempo [BPM]` - Show/set default tempo\n- `:stop` - Stop playback\n\n## Alda Syntax Reference\n\n### Notes and Rests\n\n```alda\npiano:\n  c d e f g a b   # Notes\n  r               # Rest\n  c4 d8 e16       # With duration (4=quarter, 8=eighth, etc.)\n  c4. d4..        # Dotted notes\n  c500ms d2s      # Milliseconds and seconds\n```\n\n### Accidentals\n\n```alda\nc+    # Sharp\nc-    # Flat\nc_    # Natural\nc++   # Double sharp\n```\n\n### Octaves\n\n```alda\no4 c    # Set octave to 4\n\u003e c     # Octave up\n\u003c c     # Octave down\n```\n\n### Chords\n\n```alda\nc/e/g           # C major chord\nc1/e/g          # Whole note chord\nc/e/g/\u003ec        # With octave change\n```\n\n### Ties and Slurs\n\n```alda\nc1~1            # Tied notes (duration adds)\nc4~d~e~f        # Slurred notes (legato)\n```\n\n### Parts\n\n```alda\npiano: c d e\n\nviolin \"v1\": c d e    # With alias\n\nviolin/viola/cello \"strings\":   # Multi-instrument\n  c d e\n```\n\n### Attributes\n\n```alda\n(tempo 120)     # Set tempo (BPM)\n(tempo! 120)    # Global tempo\n\n(vol 80)        # Volume (0-100)\n(volume 80)\n\n(quant 90)      # Quantization/legato (0-100)\n\n(panning 50)    # Pan (0=left, 100=right)\n\n# Dynamic markings\n(pp) (p) (mp) (mf) (f) (ff)\n\n# Key signatures\n(key-sig '(g major))     # G major (F#)\n(key-sig '(d minor))     # D minor (Bb)\n(key-sig \"f+ c+\")        # Explicit accidentals\n\n# Transposition\n(transpose 5)   # Up 5 semitones\n(transpose -2)  # Down 2 semitones (Bb instrument)\n```\n\n### Variables\n\n```alda\nriff = c8 d e f g4\n\npiano:\n  riff riff \u003e riff\n```\n\n### Repeats\n\n```alda\nc*4             # Repeat note 4 times\n[c d e]*4       # Repeat sequence\n[c d e f]*8     # 8 times\n```\n\n### Cram (Tuplets)\n\n```alda\n{c d e}4        # Triplet in quarter note\n{c d e f g}2    # Quintuplet in half note\n{c {d e} f}4    # Nested cram\n```\n\n### Voices\n\n```alda\npiano:\n  V1: c4 d e f\n  V2: e4 f g a\n  V0:           # End voices\n```\n\n### Markers\n\n```alda\npiano:\n  c d e f\n  %chorus\n  g a b \u003e c\n\nviolin:\n  @chorus       # Jump to chorus marker\n  e f g a\n```\n\n## Supported Instruments\n\nAll 128 General MIDI instruments are supported. Common examples:\n\n- `piano`, `acoustic-grand-piano`\n- `violin`, `viola`, `cello`, `contrabass`\n- `flute`, `oboe`, `clarinet`, `bassoon`\n- `trumpet`, `trombone`, `french-horn`, `tuba`\n- `acoustic-guitar`, `electric-guitar-clean`, `electric-bass`\n- `choir`, `strings`, `brass-section`\n\nSee [midi/types.py](https://github.com/shakfu/aldakit/blob/main/src/aldakit/midi/types.py) for the complete mapping.\n\n## MIDI Backend\n\naldakit uses [libremidi](https://github.com/jcelerier/libremidi) via [nanobind](https://github.com/wjakob/nanobind) for cross-platform MIDI I/O:\n\n- Low-latency realtime playback\n- Virtual MIDI port support (AldakitMIDI), makes it easy to just send to your DAW.\n- Pure Python MIDI file writing (no external dependencies)\n- Cross-platform: macOS (CoreMIDI), Linux (ALSA), Windows (WinMM)\n- Supports hardware and software/virtual MIDI ports (FluidSynth, IAC Driver, etc.)\n\n```python\nimport aldakit\n\n# List available ports\nprint(aldakit.list_ports())\n\n# Play to virtual port (visible in DAWs like Ableton Live)\naldakit.play(\"piano: c d e f g\")\n\n# Play to a specific port\naldakit.play(\"piano: c d e f g\", port=\"FluidSynth\")\n\n# Save to MIDI file\naldakit.save(\"piano: c d e f g\", \"output.mid\")\n```\n\n## Audio Backend (Built-in)\n\nFor self-contained audio playback without external synthesizers, aldakit includes a built-in audio backend powered by [TinySoundFont](https://github.com/schellingb/TinySoundFont) and [miniaudio](https://github.com/mackron/miniaudio):\n\n- Direct audio output (no FluidSynth or DAW required)\n- Cross-platform: macOS (CoreAudio), Linux (ALSA/PulseAudio), Windows (WASAPI)\n- Requires a SoundFont file (.sf2) for instrument sounds\n- Header-only libraries for minimal binary size\n\n### Basic Usage\n\n```python\nfrom aldakit import Score\n\n# Play with built-in audio (requires SoundFont)\nscore = Score(\"piano: c d e f g\")\nscore.play(backend=\"audio\")\n\n# Specify SoundFont explicitly\nscore.play(backend=\"audio\", soundfont=\"/path/to/FluidR3_GM.sf2\")\n```\n\n### SoundFont Setup\n\nThe audio backend requires a General MIDI SoundFont file. aldakit searches these locations automatically:\n\n- `$ALDAKIT_SOUNDFONT` environment variable\n- `~/Music/sf2/`\n- `~/.aldakit/soundfonts/`\n- `/usr/share/soundfonts/` (Linux)\n\n**Option 1: Download manually**\n\nDownload a SoundFont and place it in a folder such as `~/Music/sf2/`:\n\n- [FluidR3_GM.sf2](https://musical-artifacts.com/artifacts/738/FluidR3_GM.sf2) (142 MB, high quality)\n- [GeneralUser-GS.sf2](https://musical-artifacts.com/artifacts/6789/GeneralUser-GS.sf2) (31 MB, balanced)\n- [TimGM6mb.sf2](https://musical-artifacts.com/artifacts/7293/TimGM6mb.sf2) (5.8 MB, compact)\n\nSuggest using a `sha256sum` (macOs or Linux) or similar to verify file integrity after download:\n\n```sh\n% sha256sum FluidR3_GM.sf2\n74594e8f4250680adf590507a306655a299935343583256f3b722c48a1bc1cb0  FluidR3_GM.sf2\n\n% sha256sum GeneralUser-GS.sf2\nc278464b823daf9c52106c0957f752817da0e52964817ff682fe3a8d2f8446ce  GeneralUser-GS.sf2\n\n% sha256sum TimGM6mb.sf2\n82475b91a76de15cb28a104707d3247ba932e228bada3f47bba63c6b31aaf7a1  TimGM6mb.sf2\n```\n\nOn Windows (PowerShell): `Get-FileHash -Algorithm SHA256`\n\n**Option 2: Auto-download**\n\n```python\nfrom aldakit.midi.soundfont import setup_soundfont, setup_all_soundfonts\n\n# Downloads TimGM6mb.sf2 (~6 MB) to ~/.aldakit/soundfonts/\nsetup_soundfont()\n\n# Or download all available SoundFonts from the catalog\nsetup_all_soundfonts()\n```\n\n**Option 3: Using SoundFontManager**\n\nFor more control, use the `SoundFontManager` class:\n\n```python\nfrom aldakit.midi.soundfont import SoundFontManager\n\nmanager = SoundFontManager()\n\n# Find existing SoundFont\nsf = manager.find()\n\n# List all found SoundFonts\nfor path in manager.list():\n    print(path)\n\n# Download a specific SoundFont (with SHA256 verification)\npath = manager.download(\"FluidR3_GM\")\n\n# Download all SoundFonts from catalog\npaths = manager.setup_all()\n\n# Verify checksums of downloaded files\nresults = manager.verify_checksums()\nfor name, valid in results.items():\n    print(f\"{name}: {'OK' if valid else 'FAILED'}\")\n\n# List available downloads\nfor name, info in manager.list_available_downloads().items():\n    print(f\"{name}: {info['size_mb']} MB - {info['description']}\")\n```\n\n**Option 4: Environment variable**\n\n```bash\nexport ALDAKIT_SOUNDFONT=/path/to/your/soundfont.sf2\n```\n\n### Using TsfBackend Directly\n\n```python\nfrom aldakit import Score\nfrom aldakit.midi.backends import TsfBackend\n\n# Create backend with specific SoundFont\nwith TsfBackend(soundfont=\"~/Music/sf2/FluidR3_GM.sf2\") as backend:\n    score = Score(\"piano: c/e/g\")\n    backend.play(score.midi)\n    backend.wait()  # Block until playback completes\n\n# Inspect SoundFont presets\nbackend = TsfBackend()\nprint(f\"Presets: {backend.preset_count}\")\nfor i in range(min(10, backend.preset_count)):\n    print(f\"  {i}: {backend.preset_name(i)}\")\n```\n\n### Audio vs MIDI Backend\n\n| Feature | Audio (`backend=\"audio\"`) | MIDI (`backend=\"midi\"`) |\n|---------|---------------------------|-------------------------|\n| External synth required | No | Yes (FluidSynth, DAW, hardware) |\n| Setup complexity | Just needs SoundFont | Requires MIDI routing |\n| Sound quality | Depends on SoundFont | Depends on synth |\n| DAW integration | No | Yes (virtual port) |\n| Latency | Very low | Very low |\n| Effects (reverb, etc.) | No | Depends on synth |\n\n**Recommendation:** Use `backend=\"audio\"` for quick playback and standalone use. Use `backend=\"midi\"` (default) for DAW integration, hardware synths, or when you need effects.\n\n## MIDI Playback Setup\n\n### Virtual Port (Recommended)\n\nWhen no hardware MIDI ports are available, aldakit creates a virtual port named \"AldakitMIDI\". This port is visible to DAWs and other MIDI software:\n\n1. Start the REPL: `aldakit repl`\n2. In your DAW (Ableton Live, Logic Pro, etc.), look for \"AldakitMIDI\" in MIDI input settings\n3. Play code in the REPL - notes will be sent to your DAW\n\n### Software Synthesizer (FluidSynth)\n\nFor high-quality General MIDI playback without hardware, use [FluidSynth](https://www.fluidsynth.org/):\n\n```sh\n# Install FluidSynth (macOS)\nbrew install fluidsynth\n\n# Install FluidSynth (Debian/Ubuntu)\nsudo apt install fluidsynth \n\n# Download a SoundFont (e.g., FluidR3_GM.sf2)\n# eg. sudo apt install fluid-soundfont-gm\n# Place in ~/Music/sf2/\n\n# Start FluidSynth with CoreMIDI (macOS)\nfluidsynth -a coreaudio -m coremidi ~/Music/sf2/FluidR3_GM.sf2\n\n# In another terminal, start aldakit\naldakit repl\n# aldakit\u003e piano: c d e f g\n```\n\nA helper script is available in the [repository](https://github.com/shakfu/aldakit/tree/main/scripts):\n\n```sh\n# Set the SoundFont directory (add to your shell profile)\nexport ALDAPY_SF2_DIR=~/Music/sf2\n\n# Run with default SoundFont (FluidR3_GM.sf2)\npython scripts/fluidsynth-gm.py\n\n# Or specify a SoundFont directly\npython scripts/fluidsynth-gm.py /path/to/soundfont.sf2\n\n# List available SoundFonts\npython scripts/fluidsynth-gm.py --list\n```\n\n### Hardware MIDI\n\nConnect a USB MIDI interface or synthesizer, then:\n\n```sh\n# List available ports\naldakit ports\n\n# Play to a specific port\naldakit --port \"My MIDI Device\" examples/twinkle.alda\n```\n\n### MIDI File Export\n\nIf you don't have MIDI playback set up, export to a file:\n\n```bash\n# Save to MIDI file\naldakit examples/twinkle.alda -o twinkle.mid\n\n# Open with default app\nopen twinkle.mid\n```\n\n## Development\n\n### Setup\n\n```sh\ngit clone https://github.com/shakfu/aldakit.git\ncd aldakit\nmake  # Build the libremidi extension\n```\n\n### Run Tests\n\n```sh\nmake test\n# or\nuv run pytest tests/ -v\n```\n\n### Architecture\n\n![aldakit architecture](https://raw.githubusercontent.com/shakfu/aldakit/main/docs/assets/architecture.svg)\n\n## License\n\nMIT\n\n## See Also\n\n- [Alda](https://alda.io) - The original Alda language and reference implementation\n- [Alda Cheat Sheet](https://alda.io/cheat-sheet/) - Syntax reference\n- [Extending aldakit](https://github.com/shakfu/aldakit/blob/main/docs/extending-aldakit.md) - Design document for programmatic API\n- [libremidi](https://github.com/celtera/libremidi) - A modern C++ MIDI 1 / MIDI 2 real-time \u0026 file I/O library. Supports Windows, macOS, Linux and WebMIDI.\n- [TinySoundFont](https://github.com/schellingb/TinySoundFont) - SoundFont2 synthesizer library in a single C/C++ header\n- [miniaudio](https://github.com/mackron/miniaudio) - Single-header audio playback and capture library\n- [nanobind](https://github.com/wjakob/nanobind) - a tiny and efficient C++/Python bindings","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshakfu%2Faldakit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshakfu%2Faldakit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshakfu%2Faldakit/lists"}