#!/usr/bin/env bash set -euo pipefail DEMO_DIR="${1:-muse-midi-episode00}" command -v muse >/dev/null 2>&1 || { echo "error: muse not found on PATH" >&2; exit 1; } command -v python3 >/dev/null 2>&1 || { echo "error: python3 not found on PATH" >&2; exit 1; } python3 - <<'PY' >/dev/null 2>&1 || { import mido PY echo "error: Python package 'mido' is not importable by this python3." >&2 echo "Run from your Muse development environment / venv, where mido is installed." >&2 exit 1 } rm -rf "$DEMO_DIR" mkdir -p "$DEMO_DIR" cd "$DEMO_DIR" cat > make_phrase.py <<'PY' #!/usr/bin/env python3 from pathlib import Path import argparse import mido TPB = 480 BASE = [ (0, 60, 80, 360), # C4 (480, 64, 80, 360), # E4 (960, 67, 80, 360), # G4 (1440, 72, 80, 360), # C5 ] SOL = [ (0, 60, 80, 360), # unchanged (480, 65, 80, 360), # E4 -> F4: pitch (1080, 67, 80, 360), # moved later: position (1440, 72, 108, 360), # velocity 80 -> 108 ] def build(path: Path, bpm: int, notes): mid = mido.MidiFile(type=0, ticks_per_beat=TPB) track = mido.MidiTrack() mid.tracks.append(track) events = [ (0, mido.MetaMessage("track_name", name="Episode 00 phrase", time=0)), (0, mido.MetaMessage("set_tempo", tempo=mido.bpm2tempo(bpm), time=0)), (0, mido.MetaMessage( "time_signature", numerator=4, denominator=4, clocks_per_click=24, notated_32nd_notes_per_beat=8, time=0, )), ] for start, pitch, velocity, duration in notes: events.append((start, mido.Message("note_on", channel=0, note=pitch, velocity=velocity, time=0))) events.append((start + duration, mido.Message("note_off", channel=0, note=pitch, velocity=0, time=0))) order = {"track_name": 0, "set_tempo": 1, "time_signature": 2, "note_off": 3, "note_on": 4} events.sort(key=lambda item: (item[0], order.get(item[1].type, 99))) previous = 0 for tick, msg in events: track.append(msg.copy(time=tick - previous)) previous = tick track.append(mido.MetaMessage("end_of_track", time=0)) mid.save(path) parser = argparse.ArgumentParser() parser.add_argument("variant", choices=["base", "tempo", "sol"]) parser.add_argument("--out", default="phrase.mid") args = parser.parse_args() if args.variant == "base": build(Path(args.out), 120, BASE) elif args.variant == "tempo": build(Path(args.out), 100, BASE) else: build(Path(args.out), 120, SOL) PY echo echo "==> Initializing Muse MIDI repo" muse init --domain midi echo echo "==> Baseline: 4-note phrase at 120 BPM" python3 make_phrase.py base muse commit -m "Base: four-note phrase at 120 BPM" echo echo "==> Gabriel branch: tempo only" muse checkout -b gabriel-tempo python3 make_phrase.py tempo muse commit -m "Gabriel: slow tempo to 100 BPM" echo echo "==> Sol branch: note semantics only" muse checkout main muse checkout -b sol-notes python3 make_phrase.py sol muse commit -m "Sol: reshape phrase notes" echo echo "==> Return to main" muse checkout main cat <<'EOF' Ready. Suggested shots: muse branch muse log --oneline muse diff main gabriel-tempo muse diff main sol-notes muse diff main sol-notes --format json muse checkout main muse merge gabriel-tempo muse merge sol-notes muse log --oneline muse status EOF