make-midi-episode11-demo.sh
bash
sha256:41074b7f9893918b26fe292dddb55c83bec08ae17aeb2acfd73d9fcc00587585
Add Episode 11 script (Muse Understands Music) and MIDI dem…
Sonnet 5
patch
11 hours ago
| 1 | #!/usr/bin/env bash |
| 2 | # Builds midi-episode11/ — a disposable MIDI-domain repo for Episode 11 |
| 3 | # ("Muse Understands Music"). Re-run to regenerate; never hand-edit the |
| 4 | # repo directory itself. |
| 5 | set -euo pipefail |
| 6 | |
| 7 | HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 8 | REPO="$HERE/midi-episode11" |
| 9 | |
| 10 | rm -rf "$REPO" |
| 11 | mkdir -p "$REPO" |
| 12 | cd "$REPO" |
| 13 | |
| 14 | export MUSE_ENABLE_MIDI=1 |
| 15 | |
| 16 | python3 - <<'PY' |
| 17 | import mido |
| 18 | from pathlib import Path |
| 19 | |
| 20 | TPB = 480 |
| 21 | |
| 22 | def build(path, bpm, notes): |
| 23 | mid = mido.MidiFile(type=0, ticks_per_beat=TPB) |
| 24 | track = mido.MidiTrack() |
| 25 | mid.tracks.append(track) |
| 26 | events = [(0, mido.MetaMessage("set_tempo", tempo=mido.bpm2tempo(bpm), time=0))] |
| 27 | for start, pitch, velocity, duration in notes: |
| 28 | events.append((start, mido.Message("note_on", channel=0, note=pitch, velocity=velocity, time=0))) |
| 29 | events.append((start + duration, mido.Message("note_off", channel=0, note=pitch, velocity=0, time=0))) |
| 30 | order = {"set_tempo": 0, "note_off": 1, "note_on": 2} |
| 31 | events.sort(key=lambda item: (item[0], order.get(item[1].type, 9))) |
| 32 | previous = 0 |
| 33 | for tick, msg in events: |
| 34 | track.append(msg.copy(time=tick - previous)) |
| 35 | previous = tick |
| 36 | track.append(mido.MetaMessage("end_of_track", time=0)) |
| 37 | mid.save(path) |
| 38 | |
| 39 | # C - E - G - C', quarter notes at 120bpm |
| 40 | BASE = [ |
| 41 | (0, 60, 80, 480), |
| 42 | (480, 64, 80, 480), |
| 43 | (960, 67, 80, 480), |
| 44 | (1440, 72, 80, 480), |
| 45 | ] |
| 46 | build(Path("phrase.mid"), 120, BASE) |
| 47 | PY |
| 48 | |
| 49 | muse init --domain midi --json | python3 -m json.tool |
| 50 | |
| 51 | # NOTE: non-code domains have no staging concept — files are tracked the |
| 52 | # moment they're on disk (muse status already shows "added": ["phrase.mid"] |
| 53 | # before any `add` step). `muse code add` is code-domain only and errors here. |
| 54 | muse commit -m "Add four-note phrase (C-E-G-C')" \ |
| 55 | --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 56 | |
| 57 | echo "--- domain-info (midi vs code contrast) ---" |
| 58 | muse domain-info --json | python3 -m json.tool |
| 59 | |
| 60 | # Branch 1: change the third's pitch (E -> F) |
| 61 | muse checkout -b feat/raise-third --intent "raise the third from E to F" --resumable |
| 62 | python3 - <<'PY' |
| 63 | import mido |
| 64 | mid = mido.MidiFile("phrase.mid") |
| 65 | for track in mid.tracks: |
| 66 | for msg in track: |
| 67 | if msg.type in ("note_on", "note_off") and msg.note == 64: |
| 68 | msg.note = 65 |
| 69 | mid.save("phrase.mid") |
| 70 | PY |
| 71 | muse commit -m "Raise the third from E to F" \ |
| 72 | --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 73 | |
| 74 | # Back to main, branch 2: raise velocity on every note |
| 75 | muse switch main |
| 76 | muse checkout -b feat/louder-melody --intent "raise velocity on the melody notes" --resumable |
| 77 | python3 - <<'PY' |
| 78 | import mido |
| 79 | mid = mido.MidiFile("phrase.mid") |
| 80 | for track in mid.tracks: |
| 81 | for msg in track: |
| 82 | if msg.type == "note_on": |
| 83 | msg.velocity = 110 |
| 84 | mid.save("phrase.mid") |
| 85 | PY |
| 86 | muse commit -m "Increase melody velocity to 110" \ |
| 87 | --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 88 | |
| 89 | # Fast-forward merge #1 (no conflict — only one side touched the file) |
| 90 | muse switch main |
| 91 | muse merge feat/raise-third |
| 92 | |
| 93 | # Merge #2 — genuine conflict: same note, different attribute changed on |
| 94 | # each side (pitch on one branch, velocity on the other) |
| 95 | muse merge feat/louder-melody --json | python3 -m json.tool || true |
| 96 | echo "--- conflicts ---" |
| 97 | muse conflicts --json | python3 -m json.tool |
| 98 | |
| 99 | # Manual resolution: incorporate BOTH sides' intent (F pitch + velocity 110) |
| 100 | python3 - <<'PY' |
| 101 | import mido |
| 102 | mid = mido.MidiFile("phrase.mid") |
| 103 | for track in mid.tracks: |
| 104 | for msg in track: |
| 105 | if msg.type == "note_on": |
| 106 | msg.velocity = 110 |
| 107 | mid.save("phrase.mid") |
| 108 | PY |
| 109 | muse resolve phrase.mid |
| 110 | muse commit -m "Merge feat/louder-melody: F pitch + velocity 110 on all notes" \ |
| 111 | --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 112 | |
| 113 | muse branch -d feat/raise-third |
| 114 | muse branch -d feat/louder-melody |
| 115 | |
| 116 | echo "--- final log ---" |
| 117 | muse log --json | python3 -m json.tool |
| 118 | |
| 119 | echo "Demo repo built at $REPO" |
File History
1 commit
sha256:41074b7f9893918b26fe292dddb55c83bec08ae17aeb2acfd73d9fcc00587585
Add Episode 11 script (Muse Understands Music) and MIDI dem…
Sonnet 5
patch
11 hours ago