muse-variation-spec.md
markdown
| 1 | # Muse Variation Spec — Music Domain Reference |
| 2 | |
| 3 | > **Scope:** This spec describes the *Variation* UX pattern as implemented |
| 4 | > for the MIDI domain. It is not part of the core Muse VCS engine. |
| 5 | > |
| 6 | > For the domain-agnostic VCS protocol, see [muse-protocol.md](muse-protocol.md). |
| 7 | > For a discussion of how "Variation" might generalize across domains, see |
| 8 | > [muse-domain-concepts.md](muse-domain-concepts.md). |
| 9 | |
| 10 | --- |
| 11 | |
| 12 | ## What a Variation Is |
| 13 | |
| 14 | A Variation is a **proposed musical change set awaiting human review** before |
| 15 | being committed to the Muse DAG. It is the MIDI plugin's implementation of the |
| 16 | propose → review → commit pattern. |
| 17 | |
| 18 | ``` |
| 19 | VCS equivalent mapping: |
| 20 | Variation = A staged diff |
| 21 | Phrase = A hunk (contiguous group of changes within a region) |
| 22 | Accept = muse commit |
| 23 | Discard = Discard working-tree changes (no commit) |
| 24 | Undo = muse revert |
| 25 | ``` |
| 26 | |
| 27 | The defining characteristic: a Variation is *heard before committed* — the |
| 28 | musician auditions the proposed change in the DAW's playback engine and then |
| 29 | decides whether to accept or discard. This is layered on top of the VCS DAG, |
| 30 | not part of it. |
| 31 | |
| 32 | --- |
| 33 | |
| 34 | ## Lifecycle |
| 35 | |
| 36 | ``` |
| 37 | 1. Variation Proposed — AI or user creates a proposed change set |
| 38 | 2. Stream — phrases stream to the DAW in SSE events |
| 39 | 3. Review Mode Active — DAW shows proposed state alongside canonical state |
| 40 | 4. Accept (or partial) — accepted phrases are committed; discarded phrases are dropped |
| 41 | 5. Canonical state updates — only after commit |
| 42 | ``` |
| 43 | |
| 44 | Canonical state MUST NOT change during review. The proposed state is always |
| 45 | ephemeral. |
| 46 | |
| 47 | --- |
| 48 | |
| 49 | ## Terminology |
| 50 | |
| 51 | | Term | Definition | |
| 52 | |---|---| |
| 53 | | **Variation** | A proposed set of musical changes, organized as phrases | |
| 54 | | **Phrase** | A bounded group of note/controller changes within one region | |
| 55 | | **NoteChange** | An atomic note delta: added, removed, or modified | |
| 56 | | **Canonical State** | The actual committed project state in the Muse DAG | |
| 57 | | **Proposed State** | The ephemeral preview state during variation review | |
| 58 | | **Accept** | Committing accepted phrases to the Muse DAG | |
| 59 | | **Discard** | Dropping the variation; canonical state unchanged | |
| 60 | |
| 61 | --- |
| 62 | |
| 63 | ## Execution Mode Policy |
| 64 | |
| 65 | | User Intent | Mode | Result | |
| 66 | |---|---|---| |
| 67 | | Composing / generating | `variation` | Produces a Variation for human review | |
| 68 | | Editing (add track, set tempo) | `apply` | Applied immediately, no review | |
| 69 | | Reasoning / chat only | `reasoning` | No state mutation | |
| 70 | |
| 71 | The backend enforces execution mode. Frontends MUST NOT override it. |
| 72 | |
| 73 | --- |
| 74 | |
| 75 | ## Data Shapes |
| 76 | |
| 77 | ### Variation (meta event) |
| 78 | |
| 79 | ```json |
| 80 | { |
| 81 | "variationId": "...", |
| 82 | "intent": "add countermelody to verse", |
| 83 | "aiExplanation": "I added a countermelody in the upper register...", |
| 84 | "noteCounts": { "added": 12, "removed": 0, "modified": 3 } |
| 85 | } |
| 86 | ``` |
| 87 | |
| 88 | ### Phrase |
| 89 | |
| 90 | ```json |
| 91 | { |
| 92 | "phraseId": "...", |
| 93 | "trackId": "...", |
| 94 | "regionId": "...", |
| 95 | "startBeat": 1.0, |
| 96 | "endBeat": 5.0, |
| 97 | "label": "Verse countermelody", |
| 98 | "noteChanges": [...], |
| 99 | "controllerChanges": [...] |
| 100 | } |
| 101 | ``` |
| 102 | |
| 103 | ### NoteChange |
| 104 | |
| 105 | ```json |
| 106 | { |
| 107 | "noteId": "...", |
| 108 | "changeType": "added", |
| 109 | "before": null, |
| 110 | "after": { |
| 111 | "startBeat": 1.5, |
| 112 | "durationBeats": 0.5, |
| 113 | "pitch": 72, |
| 114 | "velocity": 80 |
| 115 | } |
| 116 | } |
| 117 | ``` |
| 118 | |
| 119 | Rules: |
| 120 | - `added` → `before` MUST be `null` |
| 121 | - `removed` → `after` MUST be `null` |
| 122 | - `modified` → both `before` and `after` MUST be present |
| 123 | |
| 124 | --- |
| 125 | |
| 126 | ## SSE Streaming Contract |
| 127 | |
| 128 | Events stream in this order (strictly): |
| 129 | |
| 130 | ``` |
| 131 | meta → phrase* → done |
| 132 | ``` |
| 133 | |
| 134 | | Event type | When sent | |
| 135 | |---|---| |
| 136 | | `meta` | First event; carries variation summary | |
| 137 | | `phrase` | One per phrase; may be many | |
| 138 | | `done` | Last event; signals review mode is active | |
| 139 | |
| 140 | Event envelope: |
| 141 | ```json |
| 142 | { |
| 143 | "type": "phrase", |
| 144 | "sequence": 3, |
| 145 | "variationId": "...", |
| 146 | "projectId": "...", |
| 147 | "baseStateId": "...", |
| 148 | "timestampMs": 1234567890, |
| 149 | "payload": { ...phrase... } |
| 150 | } |
| 151 | ``` |
| 152 | |
| 153 | `sequence` is strictly increasing. `baseStateId` is the Muse snapshot ID the |
| 154 | variation was computed against. |
| 155 | |
| 156 | --- |
| 157 | |
| 158 | ## API Endpoints |
| 159 | |
| 160 | | Method | Path | Purpose | |
| 161 | |---|---|---| |
| 162 | | `POST` | `/variation/propose` | Propose a new variation | |
| 163 | | `GET` | `/variation/stream` | SSE stream of meta + phrase events | |
| 164 | | `POST` | `/variation/commit` | Accept (all or partial phrases) | |
| 165 | | `POST` | `/variation/discard` | Discard without committing | |
| 166 | | `GET` | `/variation/{id}` | Poll status / reconnect | |
| 167 | |
| 168 | ### Commit request |
| 169 | |
| 170 | ```json |
| 171 | { |
| 172 | "variationId": "...", |
| 173 | "acceptedPhraseIds": ["phrase-1", "phrase-3"] |
| 174 | } |
| 175 | ``` |
| 176 | |
| 177 | Partial acceptance is supported: only listed phrases are committed. |
| 178 | |
| 179 | --- |
| 180 | |
| 181 | ## Audition Modes |
| 182 | |
| 183 | | Mode | What plays | |
| 184 | |---|---| |
| 185 | | Original | Canonical state only | |
| 186 | | Variation | Proposed state (with changes applied) | |
| 187 | | Delta Solo | Only the added/modified notes | |
| 188 | |
| 189 | --- |
| 190 | |
| 191 | ## Safety Rules |
| 192 | |
| 193 | 1. Review mode is isolated — destructive edits are blocked during review. |
| 194 | 2. Canonical state MUST NOT mutate during proposal. |
| 195 | 3. Commit is a single undo boundary: `muse revert` can undo the entire commit. |
| 196 | 4. If the stream fails mid-phrase, keep received phrases and allow the user to |
| 197 | discard or commit what arrived. |
| 198 | |
| 199 | --- |
| 200 | |
| 201 | ## Relationship to Muse VCS |
| 202 | |
| 203 | A committed Variation becomes a standard `muse commit` in the DAG: |
| 204 | |
| 205 | ```bash |
| 206 | muse log --oneline |
| 207 | ``` |
| 208 | |
| 209 | ``` |
| 210 | a1b2c3d4 (HEAD -> main) Add countermelody to verse |
| 211 | ``` |
| 212 | |
| 213 | From Muse's perspective, a committed Variation is indistinguishable from any |
| 214 | other commit. The Variation UX is a midi-domain layer on top of the standard |
| 215 | VCS commit cycle. |