music-query-dsl.md
markdown
| 1 | # Music Query DSL |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | The music query DSL allows agents and humans to search the Muse commit history |
| 6 | for specific musical content without parsing MIDI bytes for every commit. |
| 7 | |
| 8 | ```bash |
| 9 | muse music-query "note.pitch_class == 'Eb' and bar == 12" |
| 10 | muse music-query "harmony.quality == 'dim' and bar == 8" |
| 11 | muse music-query "agent_id == 'counterpoint-bot'" |
| 12 | muse music-query "note.velocity > 80 and track == 'cello.mid'" |
| 13 | ``` |
| 14 | |
| 15 | ## Grammar (EBNF) |
| 16 | |
| 17 | ``` |
| 18 | query = or_expr |
| 19 | or_expr = and_expr ( 'or' and_expr )* |
| 20 | and_expr = not_expr ( 'and' not_expr )* |
| 21 | not_expr = 'not' not_expr | atom |
| 22 | atom = '(' query ')' | comparison |
| 23 | comparison = FIELD OP VALUE |
| 24 | FIELD = <see field table below> |
| 25 | OP = '==' | '!=' | '>' | '<' | '>=' | '<=' |
| 26 | VALUE = QUOTED_STRING | INTEGER | FLOAT |
| 27 | ``` |
| 28 | |
| 29 | ## Supported fields |
| 30 | |
| 31 | | Field | Type | Description | |
| 32 | |-------|------|-------------| |
| 33 | | `note.pitch` | int | MIDI pitch (0–127) | |
| 34 | | `note.pitch_class` | str | Pitch class name ("C", "C#", …, "B") | |
| 35 | | `note.velocity` | int | MIDI velocity (0–127) | |
| 36 | | `note.channel` | int | MIDI channel (0–15) | |
| 37 | | `note.duration` | float | Duration in beats | |
| 38 | | `bar` | int | 1-indexed bar number (assumes 4/4) | |
| 39 | | `track` | str | Workspace-relative MIDI file path | |
| 40 | | `harmony.chord` | str | Detected chord name ("Cmaj", "Fdim7", …) | |
| 41 | | `harmony.quality` | str | Chord quality suffix ("maj", "min", "dim", "dim7", …) | |
| 42 | | `author` | str | Commit author string | |
| 43 | | `agent_id` | str | Agent ID from commit provenance | |
| 44 | | `model_id` | str | Model ID from commit provenance | |
| 45 | | `toolchain_id` | str | Toolchain ID from commit provenance | |
| 46 | |
| 47 | **Note fields** match if *any* note in the bar satisfies the predicate — |
| 48 | i.e. `note.pitch > 60` is true for a bar if it contains at least one note |
| 49 | with MIDI pitch > 60. |
| 50 | |
| 51 | ## Examples |
| 52 | |
| 53 | ```bash |
| 54 | # All bars where Eb appears. |
| 55 | muse music-query "note.pitch_class == 'Eb'" |
| 56 | |
| 57 | # Diminished chord in bar 8 specifically. |
| 58 | muse music-query "harmony.quality == 'dim' and bar == 8" |
| 59 | |
| 60 | # High-velocity notes in the cello part authored by an agent. |
| 61 | muse music-query "note.velocity > 100 and track == 'cello.mid' and agent_id == 'melody-agent'" |
| 62 | |
| 63 | # Notes outside a comfortable bass range. |
| 64 | muse music-query "note.pitch < 36 or note.pitch > 96" --track bass.mid |
| 65 | |
| 66 | # Everything from a particular AI model. |
| 67 | muse music-query "model_id == 'claude-4'" |
| 68 | ``` |
| 69 | |
| 70 | ## Architecture |
| 71 | |
| 72 | The DSL is implemented in three layers in `muse/plugins/music/_music_query.py`: |
| 73 | |
| 74 | 1. **Tokenizer** (`_tokenize`) — regex-based lexer producing `Token` objects. |
| 75 | 2. **Recursive descent parser** (`_Parser`) — produces an AST of `EqNode`, |
| 76 | `AndNode`, `OrNode`, `NotNode`. |
| 77 | 3. **Evaluator** (`evaluate_node`) — walks the AST against a `QueryContext` |
| 78 | that provides the bar's notes, chord, and commit provenance. |
| 79 | |
| 80 | The top-level `run_query()` function walks the commit DAG from HEAD, loading |
| 81 | each MIDI track from the object store, grouping notes by bar, and evaluating |
| 82 | the predicate. |
| 83 | |
| 84 | ## CLI flags |
| 85 | |
| 86 | ``` |
| 87 | muse music-query QUERY |
| 88 | --track PATH Restrict to one MIDI file |
| 89 | --from COMMIT Start commit (default: HEAD) |
| 90 | --to COMMIT Stop before this commit |
| 91 | -n N Max results (default: 100) |
| 92 | --json Machine-readable JSON output |
| 93 | ``` |
| 94 | |
| 95 | ## Related files |
| 96 | |
| 97 | | File | Role | |
| 98 | |------|------| |
| 99 | | `muse/plugins/music/_music_query.py` | Tokenizer, parser, evaluator, `run_query` | |
| 100 | | `muse/cli/commands/music_query.py` | CLI command `muse music-query` | |
| 101 | | `tests/test_music_query.py` | Unit tests | |