livepilot 1.23.5 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +59 -13
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/__init__.py +17 -3
- package/mcp_server/atlas/demo_story.py +9 -3
- package/mcp_server/atlas/device_atlas.json +1 -1
- package/mcp_server/atlas/extract_chain.py +17 -2
- package/mcp_server/audit/__init__.py +6 -0
- package/mcp_server/audit/checks.py +618 -0
- package/mcp_server/audit/tools.py +232 -0
- package/mcp_server/composer/branch_producer.py +5 -2
- package/mcp_server/composer/develop/__init__.py +19 -0
- package/mcp_server/composer/develop/apply.py +217 -0
- package/mcp_server/composer/develop/brief_builder.py +269 -0
- package/mcp_server/composer/develop/seed_introspector.py +195 -0
- package/mcp_server/composer/engine.py +15 -521
- package/mcp_server/composer/fast/__init__.py +62 -0
- package/mcp_server/composer/fast/apply.py +533 -0
- package/mcp_server/composer/fast/brief_builder.py +1479 -0
- package/mcp_server/composer/fast/tier_classification.py +159 -0
- package/mcp_server/composer/framework/__init__.py +0 -0
- package/mcp_server/composer/framework/applier.py +179 -0
- package/mcp_server/composer/framework/artist_loader.py +63 -0
- package/mcp_server/composer/framework/brief.py +79 -0
- package/mcp_server/composer/framework/event_lexicon.py +71 -0
- package/mcp_server/composer/framework/genre_loader.py +77 -0
- package/mcp_server/composer/framework/intent_source.py +137 -0
- package/mcp_server/composer/framework/knowledge_pack.py +49 -0
- package/mcp_server/composer/framework/plan_compiler.py +10 -0
- package/mcp_server/composer/full/__init__.py +10 -0
- package/mcp_server/composer/full/apply.py +1139 -0
- package/mcp_server/composer/full/brief_builder.py +144 -0
- package/mcp_server/composer/full/engine.py +541 -0
- package/mcp_server/composer/full/layer_planner.py +491 -0
- package/mcp_server/composer/layer_planner.py +19 -465
- package/mcp_server/composer/sample_resolver.py +80 -7
- package/mcp_server/composer/tools.py +626 -28
- package/mcp_server/server.py +1 -0
- package/mcp_server/splice_client/client.py +7 -0
- package/mcp_server/tools/_analyzer_engine/sample.py +162 -6
- package/mcp_server/tools/_planner_engine.py +25 -63
- package/mcp_server/tools/analyzer.py +57 -6
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/mixing.py +29 -3
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.24.0 — 2026-05-02
|
|
4
|
+
|
|
5
|
+
Compose framework rebuild — fast / full / develop modes share an Applier substrate; full mode is a clean-room rewrite around an LLM-creative two-phase brief flow (LLM provides FORM, framework provides VOCABULARY).
|
|
6
|
+
|
|
7
|
+
### Added — compose framework
|
|
8
|
+
|
|
9
|
+
- **`mcp_server/composer/framework/applier.py`** — shared pre-flight + post-flight skeleton. `preflight()` loads analyzer, reconnects bridge, retries handshake up to 3× with 200ms gaps (fixes the M4L UDP-bind race that previously left "bridge not connected" failures on the first instrument-load call). `postflight()` sets monitoring=Auto on every newly-created track and calls `back_to_arranger`. Functions are dependency-injected so each mode's apply.py wires its own analyzer/bridge funcs.
|
|
10
|
+
- **`mcp_server/composer/framework/knowledge_pack.py`** — `event_lexicon` (42 structural events across 7 categories: drum_density, harmonic, texture, vocal, rhythm_feel, tension, fx_gesture), `genre_context` loader (parses `livepilot/skills/livepilot-core/references/genre-vocabularies.md`, 15 genres), `artist_context` loader (parses `artist-vocabularies.md`, ~25 producers). `atlas_candidates_per_role` is scaffolded but **left as empty stub** — see v1.25 plan.
|
|
11
|
+
- **`mcp_server/composer/full/apply.py::apply_full_plan_v2`** — full-mode rebuild. Takes an LLM-authored plan with `form` (sections), `tracks` (instruments + variants + arrangement_clips), `events`. Per-section variants prevent the BUG-FULL-MODE-18 flat tile. Native arrangement clip flow via `create_native_arrangement_clip` + `add_arrangement_notes` + `set_clip_loop` produces a single arrangement clip per section instead of 32 tiny tiles (BUG-FULL-MODE-23).
|
|
12
|
+
- **`mcp_server/composer/develop/`** — develop mode (extend an existing 8-bar loop). `seed_introspector.py` classifies tracks by name + content; `brief_builder.py` pulls artist references from the user prompt + research hooks; `apply.py` writes per-track variants without disturbing the seed.
|
|
13
|
+
- **`mcp_server/composer/fast/brief_builder.py`** — fast-mode brief authoring with tier classification (Tier-A curated > Tier-B audible default > Tier-C never). Hunt order: `search_browser(path="sounds")` first to surface curated `.adg` chains, then atlas, then bare instruments only as last resort. Bans bare-melodic Tier-B when curated alternatives exist (§1 violation prevention).
|
|
14
|
+
|
|
15
|
+
### Fixed — live-test wave
|
|
16
|
+
|
|
17
|
+
- **BUG-FULL-MODE-14: bridge UDP race** — `apply_full_plan` returned success on `bridge.connect()` but the M4L JS listener takes 100-500ms to bind the UDP socket; next bridge call ("UDP bridge is not connected"). Fixed in `Applier.preflight` with handshake retry loop.
|
|
18
|
+
- **BUG-FULL-MODE-17: monitoring=In on all tracks** — Phase 4 Task 4 set monitoring `state=0` ("In", always passes input) instead of `state=1` ("Auto", default). Live screenshot showed every track armed-red. Fixed: state=1 in `Applier.postflight`.
|
|
19
|
+
- **BUG-FULL-MODE-18: flat tiling instead of per-section variants** — full mode reused one MIDI variant for the whole arrangement. Fixed: per-section `variant_id` in plan + per-section variant resolution in apply.
|
|
20
|
+
- **BUG-FULL-MODE-19: `track_index` vs `index` field** — `apply_full_plan_v2` read `result["track_index"]` from `create_midi_track`, but the Remote Script returns `result["index"]`. Same bug class as the v1.23.x `parameter_name` vs `name_or_index` fix.
|
|
21
|
+
- **BUG-FULL-MODE-20: zombie/leftover tracks from previous sessions** — postflight only deleted default-named tracks (`1-MIDI`, `2-Audio`). Now also deletes tracks with no clips AND no instrument device (true-empty zombies) regardless of name.
|
|
22
|
+
- **BUG-FULL-MODE-21: drum-pitch super-low** — Simpler default root C3=60, drum clips firing at MIDI 36 = 24 semitones below native pitch. Drum-role repair (Volume=0, Snap=Off, Transpose=+24) was already in fast mode; now ported to full mode for parity. (`composer/fast/apply._apply_drum_role_repair` is imported by full mode.)
|
|
23
|
+
- **BUG-FULL-MODE-22: arrangement clip length wrong** — fixed alongside BUG-FULL-MODE-23.
|
|
24
|
+
- **BUG-FULL-MODE-23: 32-tile arrangement** — `create_arrangement_clip` duplicated the session clip every `loop_length` beats, producing a tile-grid arrangement. Switched to `create_native_arrangement_clip` + `add_arrangement_notes` + `set_clip_loop` for native arrangement clip authoring; one clip per section, looped to fill section length.
|
|
25
|
+
|
|
26
|
+
### Known gaps (deferred to v1.25)
|
|
27
|
+
|
|
28
|
+
- **`KnowledgePack.atlas_candidates_per_role` is empty stub** — agent still falls through to `search_browser` filename matching instead of consulting the indexed atlas. Ranking + truncation per role needs careful design (1-2 days). Documented as **BUG-FULL-MODE-24** and is the headline feature of v1.25.
|
|
29
|
+
- **Reasoning loop (Scope B)** — full mode currently does best-effort `analyze_mix`/`analyze_loudness`/`analyze_sound_design` calls but doesn't iterate on findings. v1.25 wires an analyze→adjust→re-analyze loop with a budget.
|
|
30
|
+
- **Drum craft + bass craft sophistication passes** — full mode produces correct but generic drum/bass programming. v1.25 lifts these to "poweruser" depth (per-bar variation, sidechain wiring, ghost notes, swing curves, sub/mid/click frequency separation).
|
|
31
|
+
- **`verify_track_audible` MCP tool** — Phase 4 Task 18c, deferred.
|
|
32
|
+
|
|
33
|
+
### Tool count
|
|
34
|
+
- Net delta: **453 → 459** (+6: compose framework expansion).
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
- `tests/composer/` — 184 passing across fast / full / develop / framework subdirectories.
|
|
38
|
+
- `tests/test_tools_contract.py` — 459 tools verified.
|
|
39
|
+
|
|
40
|
+
## v1.23.6 — 2026-04-30
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **`ensure_analyzer_on_master` cold-start disambiguation.** On a fresh Live boot the User Library browser cache is uncached, and `find_and_load_device("LivePilot_Analyzer")` can exceed the 20s recv timeout while BFS-ing the user library tree. The previous catch-block surfaced `status="install_required"` even though the .amxd is sitting at the canonical install path — sending the agent into a reinstall loop. New `_analyzer_amxd_installed_at_user_library()` filesystem check disambiguates: when the .amxd is present at `~/Music/Ableton/User Library/Presets/Audio Effects/Max Audio Effect/`, the tool now returns `status="cache_cold"` with a retry hint instead. Genuine missing-.amxd path still returns `install_required` correctly. (`mcp_server/tools/analyzer.py`)
|
|
45
|
+
- **`set_compressor_sidechain` diagnostic depth.** When `_find_sidechain_surface` returns None on Compressor2 (Live 12.3.6+), the raised error now includes `class=...` + `canonical_parent.class=...` + a widened child-attribute walk (`input_routings`, `routing_inputs` added to the probe). Lets the next live failure leave a tighter trail so the actual Compressor2 LOM shape can be confirmed in one round trip rather than a separate probe session. No behavior change for legacy Compressor (I) or the two known Compressor2 hypotheses. (`remote_script/LivePilot/mixing.py`)
|
|
46
|
+
- **`atlas_demo_story` production_decision class-name leak.** `harmonic-foundation` and `rhythmic-driver` role branches fell back to `primary_cls` when `user_name` was empty — producing useless prose like "InstrumentGroupDevice chosen as harmonic spine." Both branches now use `primary_uname or t_name or primary_cls`, matching the texture-role fallback that was already correct. (`mcp_server/atlas/demo_story.py`)
|
|
47
|
+
- **`atlas_extract_chain` Macro N labeling on M4L devices.** The rack-device path already resolved producer-named macros via `resolve_preset_for_device`, but the M4L (.amxd) device path emitted raw `Macro N`. Now both paths cross-reference the preset sidecar — PitchLoop89's "Spectral Stretch" gets the proper name instead of "Macro 2" so `set_device_parameter` resolves at execution time. (`mcp_server/atlas/extract_chain.py`)
|
|
48
|
+
- **Bundled atlas stats refresh.** `stats.enriched_devices` was stale at `87` (from a v1.21.x scan); the actual `enriched=True` flag count had grown to `135`. Recounted in place. No tool consumes the field with semantically-different behavior, but the stale stat had drifted past the soft-warn threshold's intent. (`mcp_server/atlas/device_atlas.json`)
|
|
49
|
+
|
|
50
|
+
### Docs / drift
|
|
51
|
+
|
|
52
|
+
- **`docs/M4L_BRIDGE.md`**: amxd ping reference and bridge-cmd count corrected — was `version: "1.23.1"` and "30 commands" from prior releases; now `1.23.6` and `32`.
|
|
53
|
+
- **`livepilot/skills/livepilot-core/references/genre-vocabularies.md`**: added Synthwave / Retrowave / Outrun entry to match the 15-genre claim in CLAUDE.md/AGENTS.md (file had 14; the YAML packet at `concepts/genres/synthwave.yaml` had been the source-of-truth for v1.18+).
|
|
54
|
+
|
|
55
|
+
### Tests
|
|
56
|
+
|
|
57
|
+
- `tests/test_ensure_analyzer_on_master.py` — 2 new tests under `TestColdBrowserCacheDisambiguation` covering the cache-cold branch (`.amxd` present → cache_cold) and the install-required branch (`.amxd` absent → install_required). Existing `test_returns_install_required_when_device_not_in_browser` updated to monkeypatch the new path-check helper so it exercises the genuinely-not-installed path regardless of dev-machine state.
|
|
58
|
+
- Total: **3409 passing**, 1 skipped, 0 failed (up from 3407 in v1.23.5).
|
|
59
|
+
|
|
60
|
+
### Audit notes
|
|
61
|
+
|
|
62
|
+
A full read-only audit pass against `BUGS.md` + `BUGS_TESTING_2026-04-30.md` confirmed that 14 of the bugs documented in those files were already fixed in v1.23.4's `bugfixes-2026-04-26` commit batch but never marked closed in the testing doc. No-ops here, but worth noting that the 38-bug list in the testing file is now ~5 genuinely-open items.
|
|
63
|
+
|
|
3
64
|
## v1.23.5 — 2026-04-30
|
|
4
65
|
|
|
5
66
|
### Fixed (Remote Script reliability — credit: PR #35 reporter)
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
An agentic production system for Ableton Live 12.<br>
|
|
20
|
-
|
|
20
|
+
459 tools. 54 domains. Device atlas. Plan-aware Splice integration. Auto-composition. Spectral perception. Technique memory. Drum-rack pad builder. Live dead-device detection.
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
<br>
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
> Side effects that touch state outside the Live project — Splice downloads, memory/ledger writes,
|
|
30
30
|
> installer actions, atlas scans, filesystem writes — persist beyond undo.
|
|
31
31
|
|
|
32
|
+
> [!WARNING]
|
|
33
|
+
> LivePilot is actively in development. Tools, behavior, and APIs change frequently between versions.
|
|
34
|
+
> Pin to a specific version for stable work. Known gaps and in-progress features are documented in
|
|
35
|
+
> each release's CHANGELOG entry.
|
|
36
|
+
|
|
32
37
|
<br>
|
|
33
38
|
|
|
34
39
|
---
|
|
@@ -54,7 +59,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
54
59
|
|
|
55
60
|
## Two Ways to Talk to LivePilot
|
|
56
61
|
|
|
57
|
-
Pick whichever is faster for the idea in your head — both reach the same
|
|
62
|
+
Pick whichever is faster for the idea in your head — both reach the same 459-tool surface.
|
|
58
63
|
|
|
59
64
|
### Route A — Artist / aesthetic shorthand
|
|
60
65
|
|
|
@@ -107,7 +112,7 @@ Most sessions do both. Lead with shorthand to anchor the aesthetic, then refine
|
|
|
107
112
|
│ └─────────────────┼──────────────────┘ │
|
|
108
113
|
│ ▼ │
|
|
109
114
|
│ ┌─────────────────┐ │
|
|
110
|
-
│ │
|
|
115
|
+
│ │ 459 MCP Tools │ │
|
|
111
116
|
│ │ 54 domains │ │
|
|
112
117
|
│ └────────┬────────┘ │
|
|
113
118
|
│ │ │
|
|
@@ -148,7 +153,7 @@ Most sessions do both. Lead with shorthand to anchor the aesthetic, then refine
|
|
|
148
153
|
|
|
149
154
|
## The Intelligence Layer
|
|
150
155
|
|
|
151
|
-
12 engines sit on top of the
|
|
156
|
+
12 engines sit on top of the 459 tools. They give the AI musical judgment, not just musical execution.
|
|
152
157
|
|
|
153
158
|
### SongBrain — What the Song Is
|
|
154
159
|
|
|
@@ -200,7 +205,7 @@ Every engine follows: **measure before → act → measure after → compare**.
|
|
|
200
205
|
|
|
201
206
|
## Tools
|
|
202
207
|
|
|
203
|
-
|
|
208
|
+
459 tools across 54 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
|
|
204
209
|
|
|
205
210
|
<br>
|
|
206
211
|
|
|
@@ -425,9 +430,9 @@ LivePilot reads Splice's local SQLite database to search your downloaded samples
|
|
|
425
430
|
|
|
426
431
|
<br>
|
|
427
432
|
|
|
428
|
-
### Composer —
|
|
433
|
+
### Composer — three modes (v1.24.0)
|
|
429
434
|
|
|
430
|
-
Prompt-to-plan auto-composition engine.
|
|
435
|
+
Prompt-to-plan auto-composition engine. Three modes share a common Applier substrate (preflight: bridge handshake retry + analyzer load; postflight: monitoring=Auto on new tracks + back_to_arranger). All modes return executable plans — the agent executes each step, it does not run autonomously.
|
|
431
436
|
|
|
432
437
|
```
|
|
433
438
|
"dark minimal techno 128bpm with industrial textures and ghostly vocals"
|
|
@@ -451,10 +456,51 @@ Prompt-to-plan auto-composition engine.
|
|
|
451
456
|
└─────────────────┘
|
|
452
457
|
```
|
|
453
458
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
459
|
+
#### fast mode — `compose_fast_apply`
|
|
460
|
+
|
|
461
|
+
Quick loop sketch. Single scene in session view. Intended for roughing out ideas quickly.
|
|
462
|
+
|
|
463
|
+
- Hunt order: curated `.adg` chains from the browser first, atlas devices second, bare instruments only as last resort
|
|
464
|
+
- Drum-role pitch repair included (Simpler default root vs. MIDI 36 offset)
|
|
465
|
+
- 4 genre defaults: house, techno, trap, ambient (unknown genres fall back to a neutral layer plan)
|
|
466
|
+
- Invoke with: *"Make me a [genre] loop at [tempo] BPM"*
|
|
467
|
+
|
|
468
|
+
#### full mode — `compose_full_apply`
|
|
469
|
+
|
|
470
|
+
Full track with song form: intro, verse, hook, breakdown, outro. Uses a two-phase LLM-creative brief flow — the LLM authors the form (sections, track list, per-section variants); the framework supplies the vocabulary (device hunt order, MIDI generation rules, arrangement conventions).
|
|
471
|
+
|
|
472
|
+
- Per-section MIDI variants prevent repeated tiles across the arrangement
|
|
473
|
+
- Native arrangement clips via `create_native_arrangement_clip` (one clip per section, looped to fill section length)
|
|
474
|
+
- Zombie-track cleanup in postflight (removes tracks with no clips and no instrument device)
|
|
475
|
+
- Drum-role pitch repair ported from fast mode
|
|
476
|
+
- **Known gap (v1.25):** `KnowledgePack.atlas_candidates_per_role` is an empty stub — the agent currently falls through to `search_browser` filename matching instead of consulting the indexed atlas. This is BUG-FULL-MODE-24 and is the headline feature of v1.25.
|
|
477
|
+
- Invoke with: *"Write a full [genre] track at [tempo] BPM"* or *"Build a full arrangement"*
|
|
478
|
+
|
|
479
|
+
#### develop mode — `develop_apply`
|
|
480
|
+
|
|
481
|
+
Extends an existing 8-bar loop without disturbing the seed material.
|
|
482
|
+
|
|
483
|
+
- Introspects the existing session (classifies tracks by name and content)
|
|
484
|
+
- Pulls artist and stylistic references from the user prompt
|
|
485
|
+
- Writes per-track variants and new supporting layers alongside the seed
|
|
486
|
+
- Invoke with: *"Develop this loop"* or *"Extend what's here into a longer idea"*
|
|
487
|
+
|
|
488
|
+
#### KnowledgePack scaffolding (v1.24.0)
|
|
489
|
+
|
|
490
|
+
All three modes share a `KnowledgePack` that provides structured creative context at runtime:
|
|
491
|
+
|
|
492
|
+
- `event_lexicon` — 42 structural events across 7 categories (drum density, harmonic, texture, vocal, rhythm feel, tension, fx gesture)
|
|
493
|
+
- `genre_context` — parses the 15-genre `genre-vocabularies.md` at load time
|
|
494
|
+
- `artist_context` — parses the ~25-producer `artist-vocabularies.md` at load time
|
|
495
|
+
- `atlas_candidates_per_role` — **stubbed in v1.24.0**, will be populated in v1.25
|
|
496
|
+
|
|
497
|
+
#### Core composer tools
|
|
498
|
+
|
|
499
|
+
- `compose` — plan a multi-layer composition from text prompt (entry point, mode-agnostic)
|
|
500
|
+
- `compose_fast_apply` — execute fast mode directly
|
|
501
|
+
- `compose_full_apply` — execute full mode directly
|
|
502
|
+
- `develop_apply` — execute develop mode directly
|
|
503
|
+
- `augment_with_samples` — plan sample-based layers for an existing session
|
|
458
504
|
- `get_composition_plan` — dry-run preview (see the plan without credit checks)
|
|
459
505
|
|
|
460
506
|
<br>
|
|
@@ -494,7 +540,7 @@ The V2 intelligence layer. These tools analyze, diagnose, plan, evaluate, and le
|
|
|
494
540
|
| Creative Constraints | 5 | constraint activation, reference-inspired variants |
|
|
495
541
|
| Preview Studio | 5 | variant creation, preview rendering, comparison, commit |
|
|
496
542
|
|
|
497
|
-
> **[View all
|
|
543
|
+
> **[View all 459 tools →](docs/manual/tool-catalog.md)**
|
|
498
544
|
|
|
499
545
|
<br>
|
|
500
546
|
|
|
@@ -721,7 +767,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture details, code guidelines
|
|
|
721
767
|
|
|
722
768
|
| Document | What's inside |
|
|
723
769
|
|----------|---------------|
|
|
724
|
-
| [Manual](docs/manual/index.md) | Complete reference: architecture, all
|
|
770
|
+
| [Manual](docs/manual/index.md) | Complete reference: architecture, all 459 tools, workflows |
|
|
725
771
|
| [Intelligence Layer](docs/manual/intelligence.md) | How the 12 engines connect — conductor, moves, preview, evaluation |
|
|
726
772
|
| [Device Atlas](docs/manual/device-atlas.md) | 5264 devices indexed — search, suggest, chain building |
|
|
727
773
|
| [Samples & Slicing](docs/manual/samples.md) | 3-source search, fitness critics, slice workflows |
|
|
Binary file
|
|
@@ -34,7 +34,7 @@ outlets = 2; // 0: to udpsend (responses), 1: to buffer~/status
|
|
|
34
34
|
// Single source of truth for the bridge version — bumped alongside the
|
|
35
35
|
// rest of the release manifest. Surfaced in the UI via messnamed("livepilot_version", ...)
|
|
36
36
|
// so the frozen .amxd visibly reports which build it was last exported from.
|
|
37
|
-
var VERSION = "1.23.
|
|
37
|
+
var VERSION = "1.23.6";
|
|
38
38
|
|
|
39
39
|
// ── State ──────────────────────────────────────────────────────────────────
|
|
40
40
|
|
package/mcp_server/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.24.0"
|
|
@@ -57,9 +57,23 @@ class AtlasManager:
|
|
|
57
57
|
if dev_category:
|
|
58
58
|
self._by_category.setdefault(dev_category, []).append(dev)
|
|
59
59
|
|
|
60
|
-
# Tag index
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
# Tag index — pull from BOTH legacy "tags" AND enriched
|
|
61
|
+
# "character_tags". BUG-P (caught 2026-05-01 live demo): the
|
|
62
|
+
# bundled factory atlas uses character_tags exclusively, so
|
|
63
|
+
# _by_tag was empty across the board, breaking every tag-based
|
|
64
|
+
# role picker. Reading both fields makes the index actually
|
|
65
|
+
# populated for normal user atlases.
|
|
66
|
+
seen_tags = set()
|
|
67
|
+
for tag in dev.get("tags", []) or []:
|
|
68
|
+
tag_lower = str(tag).lower()
|
|
69
|
+
if tag_lower not in seen_tags:
|
|
70
|
+
seen_tags.add(tag_lower)
|
|
71
|
+
self._by_tag.setdefault(tag_lower, []).append(dev)
|
|
72
|
+
for tag in dev.get("character_tags", []) or []:
|
|
73
|
+
tag_lower = str(tag).lower()
|
|
74
|
+
if tag_lower not in seen_tags:
|
|
75
|
+
seen_tags.add(tag_lower)
|
|
76
|
+
self._by_tag.setdefault(tag_lower, []).append(dev)
|
|
63
77
|
|
|
64
78
|
# Genre index (primary + secondary)
|
|
65
79
|
for genre in dev.get("genres", {}).get("primary", []):
|
|
@@ -608,19 +608,25 @@ def demo_story(
|
|
|
608
608
|
primary_uname = primary_device.get("user_name") or ""
|
|
609
609
|
nonzero = _count_nonzero_macros(devices)
|
|
610
610
|
|
|
611
|
+
# BUG-E5: when user_name is empty AND primary device is a Group/Rack
|
|
612
|
+
# device, primary_cls reads as "InstrumentGroupDevice" /
|
|
613
|
+
# "AudioEffectGroupDevice" — useless prose like "InstrumentGroupDevice
|
|
614
|
+
# chosen as harmonic spine". Fall back to track name first, then class.
|
|
615
|
+
primary_label = primary_uname or t_name or primary_cls or "<unknown>"
|
|
616
|
+
|
|
611
617
|
if t_type == "ReturnTrack":
|
|
612
618
|
prod_decision = (
|
|
613
|
-
f"{
|
|
619
|
+
f"{primary_label} shared return — applies uniformly across all sends."
|
|
614
620
|
)
|
|
615
621
|
elif role == "harmonic-foundation":
|
|
616
622
|
prod_decision = (
|
|
617
|
-
f"{
|
|
623
|
+
f"{primary_label} chosen as harmonic spine; "
|
|
618
624
|
f"{nonzero} macro(s) committed to specific values, "
|
|
619
625
|
"suggesting deliberate timbral targeting."
|
|
620
626
|
)
|
|
621
627
|
elif role == "rhythmic-driver":
|
|
622
628
|
prod_decision = (
|
|
623
|
-
f"Drum rack '{
|
|
629
|
+
f"Drum rack '{primary_label}' provides rhythmic drive. "
|
|
624
630
|
f"{nonzero} non-default macro values indicate custom tuning."
|
|
625
631
|
)
|
|
626
632
|
elif role == "texture":
|
|
@@ -492,18 +492,33 @@ def _emit_execution_steps(
|
|
|
492
492
|
"device_index": device_index,
|
|
493
493
|
})
|
|
494
494
|
if fidelity != "structure-only" and macros:
|
|
495
|
+
# BUG-E2#1 (M4L path): producer-named macros on devices like
|
|
496
|
+
# PitchLoop89 ("Spectral Stretch") get raw "Macro N" labels here
|
|
497
|
+
# unless we cross-reference the preset sidecar — same as the rack
|
|
498
|
+
# path at line ~395. Without name resolution, set_device_parameter
|
|
499
|
+
# by name silently NOT-FOUNDs at execution time.
|
|
500
|
+
m4l_macro_names: dict[int, str] = {}
|
|
501
|
+
if uname and pack_name:
|
|
502
|
+
m4l_match = resolve_preset_for_device(pack_name, cls, uname)
|
|
503
|
+
m4l_macro_names = m4l_match.get("macro_names") or {}
|
|
495
504
|
macro_subset = (
|
|
496
505
|
_get_nonzero_macros(macros)
|
|
497
506
|
if fidelity == "exact"
|
|
498
507
|
else _top_k_macros_by_deviation(macros, k=5)
|
|
499
508
|
)
|
|
500
509
|
for m in macro_subset:
|
|
510
|
+
resolved_name = m4l_macro_names.get(m["index"]) or f"Macro {m['index'] + 1}"
|
|
511
|
+
source_tag = (
|
|
512
|
+
"als-parse+adg-parse"
|
|
513
|
+
if resolved_name != f"Macro {m['index'] + 1}"
|
|
514
|
+
else "als-parse"
|
|
515
|
+
)
|
|
501
516
|
steps.append({
|
|
502
517
|
"action": "set_device_parameter",
|
|
503
518
|
"device_index": device_index,
|
|
504
|
-
"parameter_name":
|
|
519
|
+
"parameter_name": resolved_name,
|
|
505
520
|
"value": round(_safe_float(m.get("value", "0")), 2),
|
|
506
|
-
"comment": f"M4L macro {m['index'] + 1} [SOURCE:
|
|
521
|
+
"comment": f"M4L macro {m['index'] + 1} [SOURCE: {source_tag}]",
|
|
507
522
|
})
|
|
508
523
|
return steps, warnings
|
|
509
524
|
|