livepilot 1.26.1 → 1.26.2
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 +17 -0
- package/livepilot/.Codex-plugin/plugin.json +1 -1
- package/livepilot/.claude-plugin/plugin.json +1 -1
- package/livepilot/agents/livepilot-producer/AGENT.md +2 -2
- package/livepilot/commands/beat.md +3 -3
- package/livepilot/commands/evaluate.md +3 -1
- package/livepilot/commands/mix.md +2 -2
- package/livepilot/commands/sounddesign.md +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +6 -6
- package/livepilot/skills/livepilot-core/references/overview.md +1 -1
- package/livepilot/skills/livepilot-creative-director/SKILL.md +7 -0
- package/livepilot/skills/livepilot-creative-director/references/move-family-diversity-rule.md +1 -1
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-mix-engine/SKILL.md +9 -1
- package/livepilot/skills/livepilot-mixing/SKILL.md +12 -5
- package/livepilot/skills/livepilot-release/SKILL.md +2 -2
- package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +25 -3
- 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/composer/full/brief_builder.py +9 -0
- package/mcp_server/evaluation/feature_extractors.py +152 -8
- package/mcp_server/mix_engine/state_builder.py +19 -2
- package/mcp_server/mix_engine/tools.py +22 -0
- package/mcp_server/sound_design/tools.py +33 -0
- package/mcp_server/tools/_agent_os_engine/evaluation.py +7 -44
- package/mcp_server/tools/_agent_os_engine/models.py +2 -1
- package/mcp_server/tools/_conductor.py +5 -2
- package/mcp_server/tools/_evaluation_contracts.py +1 -1
- package/mcp_server/tools/_snapshot_normalizer.py +32 -3
- package/package.json +1 -1
- package/remote_script/LivePilot/__init__.py +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.26.2 — 2026-05-27
|
|
4
|
+
|
|
5
|
+
Patch release for Claude/Codex plugin instruction correctness and local install reliability.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Claude/Codex plugin commands now use `ensure_analyzer_on_master` instead of direct master-track analyzer loading, preserving the invariant that `LivePilot_Analyzer` measures the final post-master-chain signal.
|
|
10
|
+
- `/beat` now builds the master processing chain before ensuring the analyzer, preventing pre-effect spectral/RMS reads in fresh sessions.
|
|
11
|
+
- V2 semantic-move guidance now matches runtime behavior: `apply_semantic_move(mode="improve")` compiles an approval-ready plan and does not execute until the returned steps are run.
|
|
12
|
+
- Plugin device-loading guidance now routes through the Device Atlas first, then exact browser URI loading, with `find_and_load_device` reserved for simple built-in effects.
|
|
13
|
+
- Release checklist stale claims were corrected: removed the obsolete non-analyzer subtotal and updated the domain-list reminder from 45 to 56 domains.
|
|
14
|
+
- Producer-agent capability guidance now distinguishes stale/intermittent analyzer data (`measured_degraded`) from analyzer absence (`judgment_only`).
|
|
15
|
+
|
|
16
|
+
### Tests
|
|
17
|
+
|
|
18
|
+
- Added `tests/test_plugin_instruction_contracts.py` to prevent regressions in analyzer preflight guidance, semantic-move approval semantics, release-count claims, and core-skill enriched-device metadata coverage.
|
|
19
|
+
|
|
3
20
|
## v1.26.1 — 2026-05-24
|
|
4
21
|
|
|
5
22
|
Patch release for installer/release hygiene and live execution correctness.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.2",
|
|
4
4
|
"description": "Agentic production system for Ableton Live 12 \u2014 465 tools, 56 domains, 44 semantic moves, device atlas (5264 devices, 120 enriched, 7 indexes), Splice intelligence (gRPC + GraphQL describe-a-sound + preview + collections + presets), 9-band spectral perception auto-loaded via ensure_analyzer_on_master, Creative Director skill, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pilot Studio"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.2",
|
|
4
4
|
"description": "Agentic production system for Ableton Live 12 \u2014 465 tools, 56 domains, 44 semantic moves, device atlas (5264 devices, 120 enriched, 7 indexes), Splice intelligence (gRPC + GraphQL describe-a-sound + preview + collections + presets), 9-band spectral perception auto-loaded via ensure_analyzer_on_master, Creative Director skill, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pilot Studio"
|
|
@@ -206,7 +206,7 @@ After loading any instrument:
|
|
|
206
206
|
### Critical device loading rules:
|
|
207
207
|
|
|
208
208
|
- **NEVER load bare "Drum Rack"** — it's empty. Load a **kit preset**: `search_browser` path="Drums" name_filter="Kit" → `load_browser_item`
|
|
209
|
-
- **For synths, use `search_browser` → `load_browser_item`** with exact URI
|
|
209
|
+
- **For synths, consult `atlas_search` or `atlas_suggest` first, then use `search_browser` → `load_browser_item`** with exact URI
|
|
210
210
|
- **After loading any effect**, set key parameters to non-default values
|
|
211
211
|
|
|
212
212
|
## V2 Engine Intelligence
|
|
@@ -241,7 +241,7 @@ This replaces ad-hoc `get_session_info` + `get_track_info` calls for complex tas
|
|
|
241
241
|
|
|
242
242
|
Call `get_capability_state` to know what's trustworthy right now:
|
|
243
243
|
- `normal`: full analyzer + evaluation loop available
|
|
244
|
-
- `measured_degraded`:
|
|
244
|
+
- `measured_degraded`: analyzer data is stale or intermittent — refresh playback/cache before trusting spectral comparisons
|
|
245
245
|
- `judgment_only`: minimal evidence — be conservative
|
|
246
246
|
- `read_only`: can inspect but not mutate
|
|
247
247
|
|
|
@@ -11,8 +11,8 @@ If the user asks for a **fresh start** (new track, clean slate, start from scrat
|
|
|
11
11
|
|
|
12
12
|
1. **Read the session** — `get_session_info` to see what exists
|
|
13
13
|
2. **Delete all existing tracks** — loop through all tracks with `delete_track`, starting from the highest index down to 0 (deleting from the top prevents index shifts)
|
|
14
|
-
3. **
|
|
15
|
-
4. **
|
|
14
|
+
3. **Set up master chain** — load Glue Compressor + EQ Eight + Utility on master for bus processing
|
|
15
|
+
4. **Ensure analyzer last** — call `ensure_analyzer_on_master`. This enables real-time spectral analysis, RMS metering, and key detection, and prevents the analyzer from measuring pre-master-chain audio. If it returns `install_required`, call `install_m4l_device(source_path="<repo>/m4l_device/LivePilot_Analyzer.amxd")` and retry. If it warns that the analyzer is not last on master, tell the user and do not trust spectral reads until it is moved after all effects.
|
|
16
16
|
5. **Verify analyzer** — wait 2 seconds, then call `get_master_spectrum`. If it returns data, the bridge is connected. If it errors with "UDP bridge not connected", call `reconnect_bridge` to rebind.
|
|
17
17
|
|
|
18
18
|
If the user is adding to an **existing project**, skip step 0 — just call `get_session_info` and work with what's there.
|
|
@@ -24,7 +24,7 @@ Genre, tempo range, mood, reference tracks.
|
|
|
24
24
|
`set_tempo`, create tracks for drums/bass/harmony/melody with `create_midi_track`, name and color them.
|
|
25
25
|
|
|
26
26
|
## Step 3: Load instruments
|
|
27
|
-
|
|
27
|
+
Consult `atlas_search` or `atlas_suggest` first, then use `search_browser` to resolve the exact URI and `load_browser_item` to load it. Use `find_and_load_device` only for simple built-in effects named in the livepilot-core exception list.
|
|
28
28
|
|
|
29
29
|
## Step 4: Verify device health
|
|
30
30
|
After loading, run `get_device_info` on each loaded device. A `parameter_count` of 0 or 1 on AU/VST plugins means the plugin failed to initialize (dead plugin). If detected:
|
|
@@ -12,7 +12,9 @@ Run the universal evaluation loop on recent production changes.
|
|
|
12
12
|
- `read_only` — session disconnected
|
|
13
13
|
|
|
14
14
|
2. **Ensure analyzer** — if mode is `judgment_only`, try to get full perception:
|
|
15
|
-
- `
|
|
15
|
+
- `ensure_analyzer_on_master`
|
|
16
|
+
- If it returns `install_required`, call `install_m4l_device(source_path="<repo>/m4l_device/LivePilot_Analyzer.amxd")` and retry
|
|
17
|
+
- If it warns that the analyzer is not last on master, report that spectral evidence is untrusted until repaired
|
|
16
18
|
- Wait 2s, then `get_master_spectrum` to test the bridge
|
|
17
19
|
- If bridge disconnected: `reconnect_bridge`
|
|
18
20
|
- If still unavailable: proceed with `judgment_only` but tell the user
|
|
@@ -9,7 +9,7 @@ Help the user mix their session using the V2 orchestration pipeline.
|
|
|
9
9
|
|
|
10
10
|
1. **Session kernel** — `get_session_kernel(request_text=<user's request>, mode="improve")` for the full turn snapshot
|
|
11
11
|
2. **Route** — `route_request(<user's request>)` to get engine routes + semantic move recommendations
|
|
12
|
-
3. **Ensure analyzer** —
|
|
12
|
+
3. **Ensure analyzer** — call `ensure_analyzer_on_master` before trusting spectral reads. If it returns `install_required`, call `install_m4l_device(source_path="<repo>/m4l_device/LivePilot_Analyzer.amxd")` and retry. If it warns that the analyzer is not last on master, tell the user and treat spectral data as untrusted until it is repaired. If the bridge is disconnected, try `reconnect_bridge`.
|
|
13
13
|
|
|
14
14
|
## Analysis Phase
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ Help the user mix their session using the V2 orchestration pipeline.
|
|
|
29
29
|
|
|
30
30
|
## Execution Phase
|
|
31
31
|
|
|
32
|
-
11. **
|
|
32
|
+
11. **Compile for approval** — `apply_semantic_move(move_id, mode="improve")` returns the compiled plan without executing it. Present the concrete steps to the user. After approval, execute the returned steps individually and verify each write.
|
|
33
33
|
12. **Verify after EVERY change** — read `value_string` in response, call `get_track_meters(include_stereo=true)`, check no track went silent
|
|
34
34
|
13. **Capture + analyze** — `capture_audio` then `analyze_loudness` for LUFS/LRA, `analyze_spectrum_offline` for centroid/balance
|
|
35
35
|
14. **Evaluate** — `evaluate_mix_move` with before/after snapshots. If `keep_change` is false, `undo` immediately.
|
|
@@ -13,7 +13,7 @@ Guide the user through designing a sound using the V2 orchestration pipeline.
|
|
|
13
13
|
## Design Phase
|
|
14
14
|
|
|
15
15
|
3. **Ask about target** — what character? (warm pad, aggressive bass, shimmering lead, etc.)
|
|
16
|
-
4. **Choose instrument** — `
|
|
16
|
+
4. **Choose instrument** — consult `atlas_search` or `atlas_suggest` for candidate devices, then use `search_browser` to resolve the exact URI and `load_browser_item` to load
|
|
17
17
|
5. **Verify health** — `get_device_info` to confirm plugin initialized. Read `value_string` from `get_device_parameters` to understand actual units.
|
|
18
18
|
6. **Shape sound** — `set_device_parameter` or `batch_set_parameters`. **ALWAYS read `value_string` in response** to confirm Hz/dB/% values make sense.
|
|
19
19
|
7. **Verify after every change** — `get_track_meters(include_stereo=true)` — if stereo drops to 0, the effect killed the signal.
|
|
@@ -26,7 +26,7 @@ Guide the user through designing a sound using the V2 orchestration pipeline.
|
|
|
26
26
|
|
|
27
27
|
## Effects & Automation
|
|
28
28
|
|
|
29
|
-
11. **Add effects** —
|
|
29
|
+
11. **Add effects** — consult the atlas or relevant device-knowledge reference first. Load exact browser URIs with `load_browser_item`, or use `find_and_load_device` only for simple built-in effects named in the livepilot-core exception list. Verify health.
|
|
30
30
|
12. **Organic movement** — `apply_automation_shape(curve_type="perlin")` for filter/send drift
|
|
31
31
|
13. **Automation recipes** — `apply_automation_recipe` for breathing, vinyl_crackle, auto_pan. Verify after applying.
|
|
32
32
|
|
|
@@ -7,7 +7,7 @@ description: Core discipline for LivePilot — agentic production system for Abl
|
|
|
7
7
|
|
|
8
8
|
Agentic production system for Ableton Live 12. 465 tools across 56 domains, three layers:
|
|
9
9
|
|
|
10
|
-
- **Device Atlas** — 5264 devices indexed (
|
|
10
|
+
- **Device Atlas** — 5264 devices indexed (120 enriched with sonic intelligence, 683 drum kits). Consult `atlas_search` or `atlas_suggest` before loading any device. Never guess a device name.
|
|
11
11
|
- **M4L Analyzer** — Real-time audio analysis on the master bus (9-band spectrum sub_low → air, RMS/peak, key detection). Optional — all core tools work without it.
|
|
12
12
|
- **Technique Memory** — Persistent storage for production decisions. Consult `memory_recall` before creative tasks to understand user taste.
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ Agentic production system for Ableton Live 12. 465 tools across 56 domains, thre
|
|
|
18
18
|
3. **Use `undo` liberally** — mention it to users when doing destructive ops
|
|
19
19
|
4. **One operation at a time** — verify between steps
|
|
20
20
|
5. **Track indices are 0-based** — negative for return tracks (-1=A, -2=B), -1000 for master
|
|
21
|
-
6. **NEVER invent device/preset names** —
|
|
21
|
+
6. **NEVER invent device/preset names** — consult `atlas_search` or `atlas_suggest` first, then use `search_browser` and load the exact `uri` from results. Exception: `find_and_load_device` for built-in effects only ("Reverb", "Delay", "Compressor", "EQ Eight", "Saturator", "Utility")
|
|
22
22
|
7. **Color indices 0-69** — Ableton's fixed palette
|
|
23
23
|
8. **Volume 0.0-1.0, pan -1.0 to 1.0** — normalized, not dB
|
|
24
24
|
9. **Tempo range 20-999 BPM**
|
|
@@ -76,7 +76,7 @@ Report ALL errors to the user immediately. Common failure modes:
|
|
|
76
76
|
- **M4L bridge timeout** — device may be busy or removed → retry or skip analyzer features
|
|
77
77
|
- **Connection timeout** — Ableton unresponsive → check if session is heavy
|
|
78
78
|
- **Volume reset on scene fire** — Ableton restores mixer state when firing scenes. Always re-apply `set_track_volume`/`set_track_pan` after `fire_scene` if your mix settings differ from what was stored in the clips
|
|
79
|
-
- **M4L Analyzer not connected** — if `get_master_spectrum` errors with "Analyzer not detected",
|
|
79
|
+
- **M4L Analyzer not connected** — if `get_master_spectrum` errors with "Analyzer not detected", call `ensure_analyzer_on_master`. If it returns `install_required`, call `install_m4l_device(source_path="<repo>/m4l_device/LivePilot_Analyzer.amxd")` and retry. If it errors with "UDP bridge not connected", try `reconnect_bridge` first
|
|
80
80
|
- **Another client connected** — Remote Script only accepts one TCP client on port 9878. If you see this error, the MCP server is already connected. Use MCP tools instead of raw TCP
|
|
81
81
|
|
|
82
82
|
## Technique Memory
|
|
@@ -176,8 +176,8 @@ Use when the user has a concrete, specific request ("tighten the low end", "make
|
|
|
176
176
|
2. **`get_session_kernel`** — build the unified turn snapshot
|
|
177
177
|
3. **`propose_next_best_move`** — get ranked semantic move suggestions (taste-aware)
|
|
178
178
|
4. **`preview_semantic_move`** — see what a move will do before committing
|
|
179
|
-
5. **`apply_semantic_move`** — compile and execute the
|
|
180
|
-
6. **Evaluate** — use the appropriate evaluator
|
|
179
|
+
5. **`apply_semantic_move(mode="improve")`** — compile the move and return the concrete plan for approval; after approval, execute the returned steps individually
|
|
180
|
+
6. **Evaluate** — use the appropriate evaluator after the approved steps actually run
|
|
181
181
|
|
|
182
182
|
### Flow B — Exploratory (branch-native, for creative search)
|
|
183
183
|
Use when the user wants options, variants, or is stuck ("surprise me", "try some things", "I don't know what I want", "make it more like X"). **Flow B is also correct when `route_request` returns `workflow_mode="creative_search"`.**
|
|
@@ -198,7 +198,7 @@ Use when the user wants options, variants, or is stuck ("surprise me", "try some
|
|
|
198
198
|
**Rule of thumb**: if the user asked for a specific fix, Flow A. If they asked "what would you do?" or mentioned feel/vibe without parameters, Flow B.
|
|
199
199
|
|
|
200
200
|
### Semantic Moves
|
|
201
|
-
High-level musical intents that compile to deterministic tool sequences. 7 families (44 moves as of v1.26.
|
|
201
|
+
High-level musical intents that compile to deterministic tool sequences. 7 families (44 moves as of v1.26.2):
|
|
202
202
|
- **mix** — `tighten_low_end`, `widen_stereo`, `make_punchier`, `darken_without_losing_width`, `reduce_repetition_fatigue`, `make_kick_bass_lock`, `reduce_foreground_competition`
|
|
203
203
|
- **arrangement** — `refresh_repeated_section`, plus structural moves defined alongside mix
|
|
204
204
|
- **transition** — `create_buildup_tension`, `smooth_scene_handoff`, `increase_contrast_before_payoff`, `bridge_sections`, `increase_forward_motion`, `open_chorus`, `create_breakdown`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# LivePilot v1.26.
|
|
1
|
+
# LivePilot v1.26.2 — Architecture & Tool Reference
|
|
2
2
|
|
|
3
3
|
Agentic production system for Ableton Live 12. 465 tools across 56 domains. Device atlas (5264 devices, 120 enriched, 47 with aesthetic-tagged `signature_techniques`), spectral perception (M4L analyzer with 9-band FFT — sub_low / sub / low / low_mid / mid / high_mid / high / presence / air), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O, **LIVE Splice describe-a-sound + variations via captured GraphQL endpoints (v1.17)**, drum-rack pad-by-pad construction, live dead-device detection via meter sampling, role-aware Simpler defaults, session-record arrangement-automation workaround.
|
|
4
4
|
|
|
@@ -107,6 +107,12 @@ this skill. No → divergence path.
|
|
|
107
107
|
|
|
108
108
|
When triggered, these phases are REQUIRED in order. Skip none.
|
|
109
109
|
|
|
110
|
+
## Character-First Bias
|
|
111
|
+
|
|
112
|
+
For open-ended quality requests, treat timbre and spectral character as the main creative surface. Do not let the `mix` family win just because words like punch, clean, warm, dark, bright, or wide could be solved by volume/pan/send changes. Prefer `sound_design`, `device_creation`, `sample`, `arrangement`, or `transition` when analyzer evidence suggests a source, instrument, parameter, modulation, envelope, or structural decision would create more musical value.
|
|
113
|
+
|
|
114
|
+
The `mix` family is dominant only when the user asks for balance, loudness, headroom, masking, stereo translation, send levels, or an explicit mix pass. Otherwise use mix analysis as safety/evidence and keep it out of the main creative slot.
|
|
115
|
+
|
|
110
116
|
### Phase 1 — Ground
|
|
111
117
|
|
|
112
118
|
Read in parallel (all are fast). All of these are REQUIRED, not
|
|
@@ -119,6 +125,7 @@ advisory — skipping them is how pattern-repetition survives:
|
|
|
119
125
|
- `get_action_ledger_summary(limit=10)` — recent committed moves (repeat detection, see `references/anti-repetition-rules.md` for the recency threshold table). **v1.20 correction**: previous docs pointed at `memory_list`, which actually reads the persistent technique library (opt-in `memory_learn` writes) — a DIFFERENT store. The action ledger is the authoritative source; `apply_semantic_move` in explore mode populates it automatically.
|
|
120
126
|
- `get_last_move` — the single most recent committed move; populate the brief's `last_move_target` field so Phase 3 cannot repeat it
|
|
121
127
|
- `get_project_brain_summary` (or `build_project_brain` if absent) — track identity, accepted novelty band
|
|
128
|
+
- Analyzer character read when available: `get_master_spectrum`, `get_spectral_shape`, `get_onsets`, `get_novelty`, and `get_momentary_loudness` for evidence about brightness, flatness, motion, transient shape, and loudness safety. Use these to bias Phase 3 toward instrument/device/parameter decisions, not low-value level tweaks.
|
|
122
129
|
- `explain_song_identity` when the project has one
|
|
123
130
|
- `detect_stuckness` — cheap; its confidence drives escalation decisions (see §Anti-Repetition Protocol below)
|
|
124
131
|
- **Concept packet load (HARD filter when present):** if the user named an artist or genre, or if `project_brain` has a genre identity, retrieve the structured YAML packet from `livepilot-core/references/concepts/artists/<slug>.yaml` or `livepilot-core/references/concepts/genres/<slug>.yaml`. Fall back to the narrative .md entry only if no matching YAML exists. The packet's `avoid` list is a HARD filter on Phase 3 candidates. The packet's `reach_for` lists seed the candidate device pool. The packet's `key_techniques` list resolves to atlas `signature_techniques` or `sample-techniques.md` / `sound-design-deep.md` entries. If NO reference is named and `project_brain` has no genre identity, skip packet loading — do not infer. See `livepilot-core/references/concepts/_schema.md` for the full packet structure and loading rules.
|
package/livepilot/skills/livepilot-creative-director/references/move-family-diversity-rule.md
CHANGED
|
@@ -44,7 +44,7 @@ Never invent an eighth family at the director level.
|
|
|
44
44
|
|
|
45
45
|
**Discovery:** always call `list_semantic_moves(domain=<family>)` at
|
|
46
46
|
runtime to enumerate — do not hardcode move IDs. Families are stable;
|
|
47
|
-
the move catalog grows. As of v1.26.
|
|
47
|
+
the move catalog grows. As of v1.26.2 the runtime returns 44 moves
|
|
48
48
|
across all 7 domains.
|
|
49
49
|
|
|
50
50
|
**Why the director never invents an eighth `rhythmic` family:** the
|
|
@@ -173,4 +173,4 @@ routing is transparent.
|
|
|
173
173
|
- Link Audio (tempo-sync sharing between Live sets) — tracked as a
|
|
174
174
|
future Collaborative-tier feature.
|
|
175
175
|
- Stem Separation v2 — tracked as a future Collaborative-tier feature.
|
|
176
|
-
Neither is available in the 1.26.
|
|
176
|
+
Neither is available in the 1.26.2 release — still pending.
|
|
@@ -7,6 +7,14 @@ description: This skill should be used when the user asks to "analyze my mix", "
|
|
|
7
7
|
|
|
8
8
|
The mix engine runs an iterative critic loop: analyze, plan, execute, measure, evaluate, keep or undo. Every mix change is measured before and after. Nothing stays unless it scores better than the original.
|
|
9
9
|
|
|
10
|
+
## Character-First Default
|
|
11
|
+
|
|
12
|
+
Do not treat the full loop as the default for vague requests like "make it better", "more character", "more alive", "punchier", "warmer", or "more interesting". Those are usually sound-design or creative-direction requests. Start from analyzer character (`sonic_character`, `get_spectral_shape`, `get_novelty`, `get_onsets`, `get_mel_spectrum`) and prefer source, instrument, device-chain, envelope, filter, saturation, modulation, and transient-shape decisions before generic level changes.
|
|
13
|
+
|
|
14
|
+
Use `set_track_volume`, `set_track_pan`, and broad send-level balancing only when the user explicitly asks for balance/level/pan/send work, or when analyzer evidence shows a safety/translation problem such as clipping, headroom collapse, mono collapse, or a severe masking issue. Producers can adjust simple loudness by ear quickly; LivePilot's value is in hearing spectral character and choosing a smarter musical intervention.
|
|
15
|
+
|
|
16
|
+
For normal work, cap mix-engine action to one high-value move plus a short verdict. Enter the repeated full loop only for explicit requests like "deep mix pass", "mastering prep", "fix all mix issues", or an exact target such as LUFS/headroom/mono compatibility.
|
|
17
|
+
|
|
10
18
|
## The Mix Critic Loop
|
|
11
19
|
|
|
12
20
|
Follow these steps in order. Do not skip the evaluation step.
|
|
@@ -81,7 +89,7 @@ If the move scored above 0.7 and the user confirms satisfaction, call `memory_le
|
|
|
81
89
|
|
|
82
90
|
### Step 9 — Repeat
|
|
83
91
|
|
|
84
|
-
Return to Step 1 and re-analyze
|
|
92
|
+
Return to Step 1 and re-analyze only when the user requested a deep/full mix pass. Otherwise stop after the first measured high-value intervention and report the remaining optional issues as suggestions. Avoid spending a turn on small volume-balancing loops unless they are the requested task.
|
|
85
93
|
|
|
86
94
|
## Quick Mix Checks
|
|
87
95
|
|
|
@@ -7,6 +7,12 @@ description: This skill should be used when the user asks to "mix", "balance lev
|
|
|
7
7
|
|
|
8
8
|
Balance track levels, configure routing, apply mix effects, and analyze frequency content in Ableton Live.
|
|
9
9
|
|
|
10
|
+
## Default Value Filter
|
|
11
|
+
|
|
12
|
+
For broad musical requests, do not spend the turn on manual-feeling volume balancing. Levels, pan, and sends are useful when the user asks for them, when clipping/headroom/translation is objectively unsafe, or when a routing architecture is part of the style. Otherwise, treat meters as context and use analyzer character to make higher-value choices: source selection, filter/envelope shape, saturation, modulation, transient design, or a better device/preset.
|
|
13
|
+
|
|
14
|
+
When a request says "more punch", "more warmth", "more character", "less flat", "more alive", or similar, route through sound-design/creative-director first and use mix tools only as safety checks.
|
|
15
|
+
|
|
10
16
|
## Read Before Write
|
|
11
17
|
|
|
12
18
|
Always understand the current state before changing anything:
|
|
@@ -121,7 +127,7 @@ When the LivePilot Analyzer M4L device is on the master track:
|
|
|
121
127
|
- `get_master_rms` — true RMS and peak levels for loudness assessment
|
|
122
128
|
- `get_detected_key` — detect musical key from audio content
|
|
123
129
|
|
|
124
|
-
Use spectrum data to make informed EQ decisions. If the low_mid band is 6 dB hotter than everything else, there is mud to clean up. If the air band is absent, the mix may sound dull.
|
|
130
|
+
Use spectrum data to make informed EQ decisions. If the low_mid band is 6 dB hotter than everything else, there is mud to clean up. If the air band is absent, the mix may sound dull. When FluCoMa streams are active, prefer `get_spectral_shape`, `get_mel_spectrum`, `get_onsets`, and `get_novelty` for character decisions; those descriptors tell you whether the sound is bright/dark, flat/peaked, static/moving, or transient/soft in a way simple level meters cannot.
|
|
125
131
|
|
|
126
132
|
## Mix Engine — Critic-Driven Analysis
|
|
127
133
|
|
|
@@ -144,11 +150,12 @@ Use the mix engine when the user wants a critical evaluation of their mix, not j
|
|
|
144
150
|
|
|
145
151
|
Follow this progression — start fast, go deeper only when needed:
|
|
146
152
|
|
|
147
|
-
1. **Instant:** `get_master_spectrum` + `get_track_meters` — frequency balance +
|
|
148
|
-
2. **Fast
|
|
149
|
-
3. **
|
|
153
|
+
1. **Instant:** `get_master_spectrum` + `get_track_meters` — frequency balance + safety context.
|
|
154
|
+
2. **Fast character:** `get_spectral_shape` + `get_novelty` + `get_onsets` when available — decide whether the next move belongs to sound design, arrangement, or mix.
|
|
155
|
+
3. **Fast mix (1-5s):** `analyze_loudness` + `analyze_mix` — LUFS, true peak, and full mix analysis. For mastering prep or explicit mix critique.
|
|
156
|
+
4. **Slow (5-15s):** `compare_to_reference` + `analyze_spectrum_offline` — reference matching, offline spectral analysis. Ask the user first.
|
|
150
157
|
|
|
151
|
-
Never skip
|
|
158
|
+
Never skip safety context. Do not let safety context become a long volume-tweaking session unless the user asked for that.
|
|
152
159
|
|
|
153
160
|
## Reference
|
|
154
161
|
|
|
@@ -29,7 +29,7 @@ Run this checklist EVERY time the user says "update everything", "push", "releas
|
|
|
29
29
|
## 2. Tool Count (must ALL match)
|
|
30
30
|
|
|
31
31
|
Current: **465 tools across 56 domains**.
|
|
32
|
-
Spectral/analyzer (bridge-only): **38**.
|
|
32
|
+
Spectral/analyzer (bridge-only): **38**. The remaining tool surface works without the bridge or degrades gracefully. Backed by 32 bridge commands.
|
|
33
33
|
|
|
34
34
|
Verify: `grep -rc "@mcp.tool" mcp_server/tools/ | grep -v ":0" | awk -F: '{sum+=$2} END{print sum}'`
|
|
35
35
|
|
|
@@ -64,7 +64,7 @@ Files that reference tool count:
|
|
|
64
64
|
Current: **56 domains**: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine, song_brain, preview_studio, hook_hunter, stuckness_detector, wonder_mode, session_continuity, creative_constraints, device_forge, sample_engine, atlas, composer, experiment, musical_intelligence, semantic_moves, diagnostics, follow_actions, grooves, scales, take_lanes, miditool, synthesis_brain, creative_director, user_corpus, audit, grader.
|
|
65
65
|
|
|
66
66
|
- [ ] All files that mention domain count say "56 domains"
|
|
67
|
-
- [ ] Domain lists include ALL
|
|
67
|
+
- [ ] Domain lists include ALL 56 (especially newer domains — they're the most often omitted)
|
|
68
68
|
|
|
69
69
|
## 4. npm Registry
|
|
70
70
|
|
|
@@ -7,6 +7,28 @@ description: This skill should be used when the user asks to "design a sound", "
|
|
|
7
7
|
|
|
8
8
|
The sound design engine analyzes synth patches, identifies timbral weaknesses, and iteratively refines them through a measured critic loop. Every change is evaluated against the before state.
|
|
9
9
|
|
|
10
|
+
## Analyzer Character Is the Main Signal
|
|
11
|
+
|
|
12
|
+
For broad quality requests, this skill is the primary route. "More punch", "warmer", "darker", "brighter", "less flat", "more alive", "more texture", and "more character" should become source/device/parameter decisions before they become volume moves.
|
|
13
|
+
|
|
14
|
+
When the analyzer is available, read character, not just level:
|
|
15
|
+
|
|
16
|
+
- `get_master_spectrum` for the 9-band contour
|
|
17
|
+
- `get_spectral_shape` for centroid, flatness, crest, rolloff, and brightness/noise shape
|
|
18
|
+
- `get_mel_spectrum` when EQ or source choice needs perceptual detail
|
|
19
|
+
- `get_onsets` for transient/envelope decisions
|
|
20
|
+
- `get_novelty` for movement/staticness decisions
|
|
21
|
+
- `get_momentary_loudness` only for safety/headroom/loudness context
|
|
22
|
+
|
|
23
|
+
Translate those measurements into musical moves:
|
|
24
|
+
|
|
25
|
+
- Bright/harsh character → filter contour, softer source, de-harshing, saturation tone; do not merely lower volume.
|
|
26
|
+
- Dark/dull character → oscillator/filter opening, excitation, air-band source, tasteful saturation; do not merely raise volume.
|
|
27
|
+
- Static/low novelty → modulation, envelope drift, LFO, generative device, granular/vector source.
|
|
28
|
+
- Weak punch → envelope/transient shaping, source layering, attack/release work; volume push is last.
|
|
29
|
+
- Flat/noisy spectrum → source substitution, subtractive filtering, simpler spectral role.
|
|
30
|
+
- Weak weight → instrument/register/source decision before master or track gain.
|
|
31
|
+
|
|
10
32
|
## Atlas-first reflex (v1.23.x+, MANDATORY before any creative move)
|
|
11
33
|
|
|
12
34
|
Before producing ANY creative response, query the user's atlas overlays. The corpus contains 337 entries across 3 namespaces, plus 3,917 parameter-level JSON sidecars — far richer than anything inferable from training data alone.
|
|
@@ -97,7 +119,7 @@ Move vocabulary:
|
|
|
97
119
|
### Step 4 — Capture Before
|
|
98
120
|
|
|
99
121
|
1. Call `get_device_parameters(track_index, device_index)` — save current parameter state
|
|
100
|
-
2. Call `get_master_spectrum` — save spectral snapshot (if analyzer available)
|
|
122
|
+
2. Call `get_master_spectrum` plus the relevant character streams above — save spectral snapshot (if analyzer available)
|
|
101
123
|
|
|
102
124
|
### Step 5 — Execute
|
|
103
125
|
|
|
@@ -116,7 +138,7 @@ Execute one move at a time. Verify before continuing.
|
|
|
116
138
|
Repeat the same measurements:
|
|
117
139
|
|
|
118
140
|
1. Call `get_device_parameters(track_index, device_index)` — confirm the change took effect
|
|
119
|
-
2. Call `get_master_spectrum` — save post-change spectral snapshot
|
|
141
|
+
2. Call `get_master_spectrum` plus the same character streams used before — save post-change spectral snapshot
|
|
120
142
|
|
|
121
143
|
### Step 7 — Evaluate
|
|
122
144
|
|
|
@@ -135,7 +157,7 @@ If `keep_change` is `true`, report the improvement. If score > 0.7, consider cal
|
|
|
135
157
|
|
|
136
158
|
### Step 9 — Repeat
|
|
137
159
|
|
|
138
|
-
Return to Step 2
|
|
160
|
+
Return to Step 2 only when the user asked for a deep refinement pass. In normal mode, stop after one meaningful character-improving move and summarize what changed plus the next optional direction. Avoid long loops of small parameter nudges.
|
|
139
161
|
|
|
140
162
|
## Working with Opaque Plugins
|
|
141
163
|
|
|
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.26.
|
|
37
|
+
var VERSION = "1.26.2";
|
|
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.26.
|
|
2
|
+
__version__ = "1.26.2"
|
|
@@ -47,6 +47,15 @@ _DESIGN_TARGETS = (
|
|
|
47
47
|
"moves to schedule at chosen phrase boundaries. For niche style references "
|
|
48
48
|
"in research_hooks, run WebSearch to ground your form choices in the "
|
|
49
49
|
"actual conventions of that subgenre.\n\n"
|
|
50
|
+
"CHARACTER-FIRST NORMAL MODE:\n"
|
|
51
|
+
"Do not spend full mode on long level-balancing loops. Producers can adjust "
|
|
52
|
+
"simple volume by ear; your high-value job is to choose instruments, sources, "
|
|
53
|
+
"device chains, macro states, envelopes, filters, saturation, modulation, "
|
|
54
|
+
"and structural reveals that fit the requested character. Use analyzer/mix "
|
|
55
|
+
"feedback as evidence and safety, but prefer timbral/source decisions over "
|
|
56
|
+
"`set_track_volume`, `set_track_pan`, or broad send tweaking unless the "
|
|
57
|
+
"brief explicitly asks for mix balance, loudness, headroom, stereo translation, "
|
|
58
|
+
"or masking repair.\n\n"
|
|
50
59
|
"INSTRUMENT SELECTION (v1.25 hybrid knowledge surface — MANDATED FOUR-SOURCE SEARCH):\n"
|
|
51
60
|
"The brief's `atlas_anchors` is ONE source. Before committing any role pick "
|
|
52
61
|
"you MUST also query the other three sources below. Factory-atlas-only picks "
|
|
@@ -9,7 +9,7 @@ All returned values are clamped to 0.0-1.0 for consistent scoring.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import math
|
|
12
|
-
from typing import Optional
|
|
12
|
+
from typing import Any, Optional
|
|
13
13
|
|
|
14
14
|
from ..tools._evaluation_contracts import MEASURABLE_DIMENSIONS
|
|
15
15
|
|
|
@@ -19,6 +19,42 @@ def _clamp(value: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
|
19
19
|
return max(lo, min(hi, value))
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _number(value: Any) -> Optional[float]:
|
|
23
|
+
"""Best-effort numeric coercion for analyzer payloads."""
|
|
24
|
+
if isinstance(value, bool):
|
|
25
|
+
return float(value)
|
|
26
|
+
if isinstance(value, (int, float)):
|
|
27
|
+
return float(value)
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _nested_number(payload: Any, *keys: str) -> Optional[float]:
|
|
32
|
+
"""Read a numeric value from a dict payload using candidate keys."""
|
|
33
|
+
if isinstance(payload, dict):
|
|
34
|
+
for key in keys:
|
|
35
|
+
value = _number(payload.get(key))
|
|
36
|
+
if value is not None:
|
|
37
|
+
return value
|
|
38
|
+
return _number(payload)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _centroid_to_unit(centroid: float) -> float:
|
|
42
|
+
"""Map centroid-like values to 0..1.
|
|
43
|
+
|
|
44
|
+
FluCoMa deployments may report centroid in Hz or normalized units.
|
|
45
|
+
Values <= 1 are treated as normalized. Larger values are mapped across
|
|
46
|
+
a practical musical range where 150 Hz is very dark and 8 kHz is bright.
|
|
47
|
+
"""
|
|
48
|
+
if centroid <= 1.0:
|
|
49
|
+
return _clamp(centroid)
|
|
50
|
+
return _clamp((centroid - 150.0) / (8000.0 - 150.0))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _lufs_to_unit(lufs: float) -> float:
|
|
54
|
+
"""Map momentary LUFS to a rough 0..1 energy proxy."""
|
|
55
|
+
return _clamp((lufs + 60.0) / 60.0)
|
|
56
|
+
|
|
57
|
+
|
|
22
58
|
def extract_dimension_value(
|
|
23
59
|
snapshot: dict,
|
|
24
60
|
dimension: str,
|
|
@@ -37,31 +73,49 @@ def extract_dimension_value(
|
|
|
37
73
|
if not snapshot or not isinstance(snapshot, dict):
|
|
38
74
|
return None
|
|
39
75
|
|
|
40
|
-
bands = snapshot.get("spectrum")
|
|
41
|
-
|
|
42
|
-
|
|
76
|
+
bands = snapshot.get("spectrum") or {}
|
|
77
|
+
spectral_shape = snapshot.get("spectral_shape") or {}
|
|
78
|
+
onset = snapshot.get("onset") or {}
|
|
79
|
+
novelty = snapshot.get("novelty") or {}
|
|
80
|
+
loudness = snapshot.get("loudness") or {}
|
|
43
81
|
|
|
44
82
|
rms = snapshot.get("rms")
|
|
45
83
|
peak = snapshot.get("peak")
|
|
46
84
|
|
|
47
85
|
if dimension == "brightness":
|
|
86
|
+
centroid = _nested_number(spectral_shape, "centroid", "centroid_hz")
|
|
87
|
+
if centroid is not None:
|
|
88
|
+
return _centroid_to_unit(centroid)
|
|
89
|
+
if not bands:
|
|
90
|
+
return None
|
|
48
91
|
high = bands.get("high", 0)
|
|
49
92
|
presence = bands.get("presence", 0)
|
|
50
93
|
return _clamp((high + presence) / 2.0)
|
|
51
94
|
|
|
52
95
|
elif dimension == "warmth":
|
|
96
|
+
if not bands:
|
|
97
|
+
return None
|
|
53
98
|
return _clamp(bands.get("low_mid", 0))
|
|
54
99
|
|
|
55
100
|
elif dimension == "weight":
|
|
56
|
-
|
|
101
|
+
if not bands:
|
|
102
|
+
return None
|
|
103
|
+
sub = bands.get("sub_low", bands.get("sub", 0))
|
|
57
104
|
low = bands.get("low", 0)
|
|
58
105
|
return _clamp((sub + low) / 2.0)
|
|
59
106
|
|
|
60
107
|
elif dimension == "clarity":
|
|
108
|
+
if not bands:
|
|
109
|
+
return None
|
|
61
110
|
low_mid = bands.get("low_mid", 0)
|
|
62
111
|
return _clamp(1.0 - low_mid)
|
|
63
112
|
|
|
64
113
|
elif dimension == "density":
|
|
114
|
+
flatness = _nested_number(spectral_shape, "flatness", "spectral_flatness")
|
|
115
|
+
if flatness is not None:
|
|
116
|
+
return _clamp(flatness)
|
|
117
|
+
if not bands:
|
|
118
|
+
return None
|
|
65
119
|
vals = [max(v, 1e-10) for v in bands.values()
|
|
66
120
|
if isinstance(v, (int, float))]
|
|
67
121
|
if not vals:
|
|
@@ -71,14 +125,104 @@ def extract_dimension_value(
|
|
|
71
125
|
return _clamp(geo_mean / max(arith_mean, 1e-10))
|
|
72
126
|
|
|
73
127
|
elif dimension == "energy":
|
|
74
|
-
|
|
128
|
+
rms_value = _number(rms)
|
|
129
|
+
if rms_value is not None:
|
|
130
|
+
return _clamp(rms_value)
|
|
131
|
+
lufs = _nested_number(loudness, "momentary_lufs", "lufs", "integrated_lufs")
|
|
132
|
+
if lufs is not None:
|
|
133
|
+
return _lufs_to_unit(lufs)
|
|
134
|
+
return None
|
|
75
135
|
|
|
76
136
|
elif dimension == "punch":
|
|
77
|
-
|
|
78
|
-
|
|
137
|
+
rms_value = _number(rms)
|
|
138
|
+
peak_value = _number(peak)
|
|
139
|
+
if rms_value and peak_value and rms_value > 0:
|
|
140
|
+
crest_db = 20.0 * math.log10(max(peak_value / rms_value, 1.0))
|
|
79
141
|
return _clamp(crest_db / 20.0)
|
|
142
|
+
onset_strength = _nested_number(onset, "strength", "onset")
|
|
143
|
+
if onset_strength is not None:
|
|
144
|
+
return _clamp(onset_strength)
|
|
145
|
+
spectral_crest = _nested_number(spectral_shape, "crest")
|
|
146
|
+
if spectral_crest is not None:
|
|
147
|
+
return _clamp(spectral_crest)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
elif dimension == "novelty":
|
|
151
|
+
novelty_score = _nested_number(novelty, "score", "novelty", "value")
|
|
152
|
+
return _clamp(novelty_score) if novelty_score is not None else None
|
|
153
|
+
|
|
154
|
+
elif dimension == "motion":
|
|
155
|
+
novelty_score = _nested_number(novelty, "score", "novelty", "value")
|
|
156
|
+
onset_strength = _nested_number(onset, "strength", "onset")
|
|
157
|
+
vals = [v for v in (novelty_score, onset_strength) if v is not None]
|
|
158
|
+
if vals:
|
|
159
|
+
return _clamp(sum(vals) / len(vals))
|
|
80
160
|
return None
|
|
81
161
|
|
|
82
162
|
else:
|
|
83
163
|
# Unmeasurable dimension
|
|
84
164
|
return None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _label_low_mid_high(value: float, low: str, mid: str, high: str) -> str:
|
|
168
|
+
if value < 0.33:
|
|
169
|
+
return low
|
|
170
|
+
if value > 0.67:
|
|
171
|
+
return high
|
|
172
|
+
return mid
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def extract_character_profile(snapshot: dict) -> dict:
|
|
176
|
+
"""Summarize analyzer data as a production-oriented character profile.
|
|
177
|
+
|
|
178
|
+
This is intentionally descriptive, not prescriptive. Engines can attach
|
|
179
|
+
the profile to their analysis response so the agent chooses sound-source,
|
|
180
|
+
device, and parameter moves before reaching for generic level changes.
|
|
181
|
+
"""
|
|
182
|
+
if not snapshot or not isinstance(snapshot, dict):
|
|
183
|
+
return {"available": False, "values": {}, "labels": {}, "biases": []}
|
|
184
|
+
|
|
185
|
+
dimensions = (
|
|
186
|
+
"brightness", "warmth", "weight", "clarity", "density",
|
|
187
|
+
"energy", "punch", "motion", "novelty",
|
|
188
|
+
)
|
|
189
|
+
values = {
|
|
190
|
+
dim: round(val, 4)
|
|
191
|
+
for dim in dimensions
|
|
192
|
+
if (val := extract_dimension_value(snapshot, dim)) is not None
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
labels: dict[str, str] = {}
|
|
196
|
+
if "brightness" in values:
|
|
197
|
+
labels["brightness"] = _label_low_mid_high(values["brightness"], "dark", "balanced", "bright")
|
|
198
|
+
if "warmth" in values:
|
|
199
|
+
labels["warmth"] = _label_low_mid_high(values["warmth"], "lean", "warm", "thick")
|
|
200
|
+
if "weight" in values:
|
|
201
|
+
labels["weight"] = _label_low_mid_high(values["weight"], "light", "grounded", "heavy")
|
|
202
|
+
if "density" in values:
|
|
203
|
+
labels["density"] = _label_low_mid_high(values["density"], "peaked", "shaped", "flat/noisy")
|
|
204
|
+
if "punch" in values:
|
|
205
|
+
labels["punch"] = _label_low_mid_high(values["punch"], "soft", "defined", "spiky")
|
|
206
|
+
if "motion" in values:
|
|
207
|
+
labels["motion"] = _label_low_mid_high(values["motion"], "static", "moving", "busy")
|
|
208
|
+
|
|
209
|
+
biases: list[str] = []
|
|
210
|
+
if values.get("brightness", 0.5) > 0.72:
|
|
211
|
+
biases.append("prefer filter tone, source choice, or de-harshing over lowering track volume")
|
|
212
|
+
if values.get("brightness", 0.5) < 0.28:
|
|
213
|
+
biases.append("prefer oscillator/filter opening, excitation, or air-band source choice over level boosts")
|
|
214
|
+
if values.get("motion", 0.5) < 0.25:
|
|
215
|
+
biases.append("prefer modulation, envelope drift, or evolving devices before static mix moves")
|
|
216
|
+
if values.get("punch", 0.5) < 0.25:
|
|
217
|
+
biases.append("prefer envelope/transient shaping or source layering before pushing volume")
|
|
218
|
+
if values.get("density", 0.0) > 0.75:
|
|
219
|
+
biases.append("prefer subtractive filtering or simpler source selection when the spectrum is flat/noisy")
|
|
220
|
+
if values.get("weight", 0.5) < 0.25:
|
|
221
|
+
biases.append("prefer instrument/register/source changes for low-end weight before master gain")
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"available": bool(values),
|
|
225
|
+
"values": values,
|
|
226
|
+
"labels": labels,
|
|
227
|
+
"biases": biases,
|
|
228
|
+
}
|
|
@@ -68,6 +68,19 @@ def _name_signals_non_anchor(track_name: str) -> bool:
|
|
|
68
68
|
# Frequency bands where masking is most problematic.
|
|
69
69
|
_MASKING_BANDS = ("sub", "low", "low_mid", "mid", "high_mid", "presence", "high")
|
|
70
70
|
|
|
71
|
+
_MASKING_ROLE_ALIASES = {
|
|
72
|
+
"sub_bass": "bass",
|
|
73
|
+
"hihat": "percussion",
|
|
74
|
+
"hat": "percussion",
|
|
75
|
+
"clap": "percussion",
|
|
76
|
+
"snare": "percussion",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _masking_role(role: str) -> str:
|
|
81
|
+
"""Normalize detailed track roles into the collision-rule vocabulary."""
|
|
82
|
+
return _MASKING_ROLE_ALIASES.get(role, role)
|
|
83
|
+
|
|
71
84
|
|
|
72
85
|
# ── Balance ─────────────────────────────────────────────────────────
|
|
73
86
|
|
|
@@ -164,7 +177,7 @@ def build_masking_map(
|
|
|
164
177
|
# Build role->indices mapping
|
|
165
178
|
role_to_indices: dict[str, list[int]] = {}
|
|
166
179
|
for idx, role in track_roles.items():
|
|
167
|
-
role_to_indices.setdefault(role, []).append(idx)
|
|
180
|
+
role_to_indices.setdefault(_masking_role(role), []).append(idx)
|
|
168
181
|
|
|
169
182
|
# Known problematic role pairs and their collision bands
|
|
170
183
|
collision_rules: list[tuple[str, str, str, float]] = [
|
|
@@ -267,7 +280,11 @@ def build_mix_state(
|
|
|
267
280
|
role_hints = role_hints or {}
|
|
268
281
|
|
|
269
282
|
balance = build_balance_state(track_infos, role_hints)
|
|
270
|
-
|
|
283
|
+
inferred_roles = {
|
|
284
|
+
track.track_index: role_hints.get(track.track_index, track.role)
|
|
285
|
+
for track in balance.track_states
|
|
286
|
+
}
|
|
287
|
+
masking = build_masking_map(spectrum, inferred_roles)
|
|
271
288
|
|
|
272
289
|
# Extract peak from spectrum if available
|
|
273
290
|
peak = None
|
|
@@ -11,6 +11,7 @@ from fastmcp import Context
|
|
|
11
11
|
from ..server import mcp
|
|
12
12
|
from ..tools._evaluation_contracts import EvaluationRequest
|
|
13
13
|
from ..tools._snapshot_normalizer import normalize_sonic_snapshot
|
|
14
|
+
from ..evaluation.feature_extractors import extract_character_profile
|
|
14
15
|
from ..evaluation.fabric import evaluate_sonic_move
|
|
15
16
|
from .state_builder import build_mix_state
|
|
16
17
|
from .critics import run_all_mix_critics
|
|
@@ -56,6 +57,18 @@ def _fetch_mix_data(ctx: Context) -> dict:
|
|
|
56
57
|
rms_snap = spectral.get("rms")
|
|
57
58
|
if rms_snap:
|
|
58
59
|
rms_data = rms_snap["value"] if isinstance(rms_snap["value"], dict) else rms_snap["value"]
|
|
60
|
+
if spectrum is not None:
|
|
61
|
+
spectrum["rms"] = rms_data.get("rms") if isinstance(rms_data, dict) else rms_data
|
|
62
|
+
peak_snap = spectral.get("peak")
|
|
63
|
+
if peak_snap and spectrum is not None:
|
|
64
|
+
spectrum["peak"] = peak_snap["value"]
|
|
65
|
+
|
|
66
|
+
for key in ("spectral_shape", "mel_bands", "chroma", "onset", "novelty", "loudness"):
|
|
67
|
+
snap = spectral.get(key)
|
|
68
|
+
if snap:
|
|
69
|
+
if spectrum is None:
|
|
70
|
+
spectrum = {}
|
|
71
|
+
spectrum[key] = snap["value"]
|
|
59
72
|
except Exception as exc:
|
|
60
73
|
logger.debug("_fetch_mix_data failed: %s", exc)
|
|
61
74
|
|
|
@@ -86,9 +99,12 @@ def analyze_mix(ctx: Context) -> dict:
|
|
|
86
99
|
)
|
|
87
100
|
issues = run_all_mix_critics(mix_state)
|
|
88
101
|
moves = plan_mix_moves(issues, mix_state)
|
|
102
|
+
sonic_snapshot = normalize_sonic_snapshot(data["spectrum"], source="mix_engine")
|
|
103
|
+
sonic_character = extract_character_profile(sonic_snapshot or {})
|
|
89
104
|
|
|
90
105
|
return {
|
|
91
106
|
"mix_state": mix_state.to_dict(),
|
|
107
|
+
"sonic_character": sonic_character,
|
|
92
108
|
"issues": [i.to_dict() for i in issues],
|
|
93
109
|
"suggested_moves": [m.to_dict() for m in moves],
|
|
94
110
|
"issue_count": len(issues),
|
|
@@ -110,8 +126,10 @@ def get_mix_issues(ctx: Context) -> dict:
|
|
|
110
126
|
rms_data=data["rms_data"],
|
|
111
127
|
)
|
|
112
128
|
issues = run_all_mix_critics(mix_state)
|
|
129
|
+
sonic_snapshot = normalize_sonic_snapshot(data["spectrum"], source="mix_engine")
|
|
113
130
|
|
|
114
131
|
return {
|
|
132
|
+
"sonic_character": extract_character_profile(sonic_snapshot or {}),
|
|
115
133
|
"issues": [i.to_dict() for i in issues],
|
|
116
134
|
"issue_count": len(issues),
|
|
117
135
|
}
|
|
@@ -133,8 +151,10 @@ def plan_mix_move(ctx: Context) -> dict:
|
|
|
133
151
|
)
|
|
134
152
|
issues = run_all_mix_critics(mix_state)
|
|
135
153
|
moves = plan_mix_moves(issues, mix_state)
|
|
154
|
+
sonic_snapshot = normalize_sonic_snapshot(data["spectrum"], source="mix_engine")
|
|
136
155
|
|
|
137
156
|
return {
|
|
157
|
+
"sonic_character": extract_character_profile(sonic_snapshot or {}),
|
|
138
158
|
"moves": [m.to_dict() for m in moves],
|
|
139
159
|
"move_count": len(moves),
|
|
140
160
|
"issue_count": len(issues),
|
|
@@ -212,10 +232,12 @@ def get_mix_summary(ctx: Context) -> dict:
|
|
|
212
232
|
rms_data=data["rms_data"],
|
|
213
233
|
)
|
|
214
234
|
issues = run_all_mix_critics(mix_state)
|
|
235
|
+
sonic_snapshot = normalize_sonic_snapshot(data["spectrum"], source="mix_engine")
|
|
215
236
|
|
|
216
237
|
return {
|
|
217
238
|
"track_count": len(mix_state.balance.track_states),
|
|
218
239
|
"issue_count": len(issues),
|
|
240
|
+
"sonic_character": extract_character_profile(sonic_snapshot or {}),
|
|
219
241
|
"dynamics": mix_state.dynamics.to_dict(),
|
|
220
242
|
"stereo": mix_state.stereo.to_dict(),
|
|
221
243
|
"depth": mix_state.depth.to_dict(),
|
|
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|
|
9
9
|
from fastmcp import Context
|
|
10
10
|
|
|
11
11
|
from ..server import mcp
|
|
12
|
+
from ..evaluation.feature_extractors import extract_character_profile
|
|
13
|
+
from ..tools._snapshot_normalizer import normalize_sonic_snapshot
|
|
12
14
|
from .models import (
|
|
13
15
|
LayerStrategy,
|
|
14
16
|
PatchBlock,
|
|
@@ -189,9 +191,36 @@ def _fetch_sound_design_data(ctx: Context, track_index: int) -> dict:
|
|
|
189
191
|
# Get devices from track_info response (already included by Remote Script)
|
|
190
192
|
devices: list[dict] = track_info.get("devices", [])
|
|
191
193
|
|
|
194
|
+
sonic = None
|
|
195
|
+
try:
|
|
196
|
+
spectral = ctx.lifespan_context.get("spectral")
|
|
197
|
+
if spectral and spectral.is_connected:
|
|
198
|
+
sonic = {}
|
|
199
|
+
spec_data = spectral.get("spectrum")
|
|
200
|
+
if spec_data:
|
|
201
|
+
sonic["bands"] = spec_data["value"]
|
|
202
|
+
rms_snap = spectral.get("rms")
|
|
203
|
+
if rms_snap:
|
|
204
|
+
sonic["rms"] = rms_snap["value"]
|
|
205
|
+
peak_snap = spectral.get("peak")
|
|
206
|
+
if peak_snap:
|
|
207
|
+
sonic["peak"] = peak_snap["value"]
|
|
208
|
+
key_snap = spectral.get("key")
|
|
209
|
+
if key_snap:
|
|
210
|
+
sonic["detected_key"] = key_snap["value"]
|
|
211
|
+
for key in ("spectral_shape", "mel_bands", "chroma", "onset", "novelty", "loudness"):
|
|
212
|
+
snap = spectral.get(key)
|
|
213
|
+
if snap:
|
|
214
|
+
sonic[key] = snap["value"]
|
|
215
|
+
if not sonic:
|
|
216
|
+
sonic = None
|
|
217
|
+
except Exception:
|
|
218
|
+
sonic = None
|
|
219
|
+
|
|
192
220
|
return {
|
|
193
221
|
"track_info": track_info,
|
|
194
222
|
"devices": devices,
|
|
223
|
+
"sonic_snapshot": normalize_sonic_snapshot(sonic, source="sound_design"),
|
|
195
224
|
}
|
|
196
225
|
|
|
197
226
|
|
|
@@ -262,9 +291,11 @@ def analyze_sound_design(ctx: Context, track_index: int) -> dict:
|
|
|
262
291
|
issues, data["track_info"].get("name", "")
|
|
263
292
|
)
|
|
264
293
|
moves = plan_sound_design_moves(issues, state)
|
|
294
|
+
sonic_character = extract_character_profile(data.get("sonic_snapshot") or {})
|
|
265
295
|
|
|
266
296
|
return {
|
|
267
297
|
"state": state.to_dict(),
|
|
298
|
+
"sonic_character": sonic_character,
|
|
268
299
|
"issues": [i.to_dict() for i in issues],
|
|
269
300
|
"suggested_moves": [m.to_dict() for m in moves],
|
|
270
301
|
"issue_count": len(issues),
|
|
@@ -295,6 +326,7 @@ def get_sound_design_issues(ctx: Context, track_index: int) -> dict:
|
|
|
295
326
|
)
|
|
296
327
|
|
|
297
328
|
return {
|
|
329
|
+
"sonic_character": extract_character_profile(data.get("sonic_snapshot") or {}),
|
|
298
330
|
"issues": [i.to_dict() for i in issues],
|
|
299
331
|
"issue_count": len(issues),
|
|
300
332
|
}
|
|
@@ -330,6 +362,7 @@ def plan_sound_design_move(ctx: Context, track_index: int) -> dict:
|
|
|
330
362
|
moves = plan_sound_design_moves(issues, state)
|
|
331
363
|
|
|
332
364
|
result: dict = {
|
|
365
|
+
"sonic_character": extract_character_profile(data.get("sonic_snapshot") or {}),
|
|
333
366
|
"moves": [m.to_dict() for m in moves],
|
|
334
367
|
"move_count": len(moves),
|
|
335
368
|
"issue_count": len(issues),
|
|
@@ -11,6 +11,10 @@ import re
|
|
|
11
11
|
from dataclasses import asdict, dataclass, field
|
|
12
12
|
from typing import Any, Optional
|
|
13
13
|
|
|
14
|
+
from ...evaluation.feature_extractors import (
|
|
15
|
+
extract_dimension_value as _shared_extract_dimension_value,
|
|
16
|
+
)
|
|
17
|
+
from .._snapshot_normalizer import normalize_sonic_snapshot
|
|
14
18
|
from .models import QUALITY_DIMENSIONS, GoalVector, WorldModel, _clamp
|
|
15
19
|
from .taste import compute_taste_fit
|
|
16
20
|
|
|
@@ -29,50 +33,10 @@ def _extract_dimension_value(
|
|
|
29
33
|
"""
|
|
30
34
|
if not sonic:
|
|
31
35
|
return None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# Finding 2 fix: tolerate either shape so raw analyzer output works.
|
|
35
|
-
bands = sonic.get("spectrum") or sonic.get("bands")
|
|
36
|
-
if not bands:
|
|
37
|
-
return None
|
|
38
|
-
rms = sonic.get("rms")
|
|
39
|
-
peak = sonic.get("peak")
|
|
40
|
-
|
|
41
|
-
if dimension == "brightness":
|
|
42
|
-
high = bands.get("high", 0)
|
|
43
|
-
presence = bands.get("presence", 0)
|
|
44
|
-
return _clamp((high + presence) / 2.0)
|
|
45
|
-
elif dimension == "warmth":
|
|
46
|
-
return _clamp(bands.get("low_mid", 0))
|
|
47
|
-
elif dimension == "weight":
|
|
48
|
-
sub = bands.get("sub", 0)
|
|
49
|
-
low = bands.get("low", 0)
|
|
50
|
-
return _clamp((sub + low) / 2.0)
|
|
51
|
-
elif dimension == "clarity":
|
|
52
|
-
low_mid = bands.get("low_mid", 0)
|
|
53
|
-
return _clamp(1.0 - low_mid)
|
|
54
|
-
elif dimension == "density":
|
|
55
|
-
# Spectral flatness: geometric mean / arithmetic mean of band values.
|
|
56
|
-
# Higher = more evenly distributed energy (noise-like).
|
|
57
|
-
# Lower = more tonal (energy concentrated in few bands).
|
|
58
|
-
vals = [max(v, 1e-10) for v in bands.values() if isinstance(v, (int, float))]
|
|
59
|
-
if not vals:
|
|
60
|
-
return None
|
|
61
|
-
geo_mean = math.exp(sum(math.log(v) for v in vals) / len(vals))
|
|
62
|
-
arith_mean = sum(vals) / len(vals)
|
|
63
|
-
return _clamp(geo_mean / max(arith_mean, 1e-10))
|
|
64
|
-
elif dimension == "energy":
|
|
65
|
-
return _clamp(rms) if rms is not None else None
|
|
66
|
-
elif dimension == "punch":
|
|
67
|
-
if rms and peak and rms > 0:
|
|
68
|
-
crest_db = 20.0 * math.log10(max(peak / rms, 1.0))
|
|
69
|
-
# Normalize: 0 dB = 0.0, 20 dB = 1.0
|
|
70
|
-
return _clamp(crest_db / 20.0)
|
|
71
|
-
return None
|
|
72
|
-
else:
|
|
73
|
-
# Unmeasurable in Phase 1 (width, depth, motion, contrast,
|
|
74
|
-
# groove, tension, novelty, polish, emotion, cohesion)
|
|
36
|
+
normalized = normalize_sonic_snapshot(sonic, source="agent_os")
|
|
37
|
+
if normalized is None:
|
|
75
38
|
return None
|
|
39
|
+
return _shared_extract_dimension_value(normalized, dimension)
|
|
76
40
|
|
|
77
41
|
def compute_evaluation_score(
|
|
78
42
|
goal: GoalVector,
|
|
@@ -203,4 +167,3 @@ def compute_evaluation_score(
|
|
|
203
167
|
# I5: hint for the agent to track consecutive undos
|
|
204
168
|
"consecutive_undo_hint": not keep_change,
|
|
205
169
|
}
|
|
206
|
-
|
|
@@ -33,6 +33,8 @@ MEASURABLE_PROXIES: dict[str, str] = {
|
|
|
33
33
|
"density": "spectral flatness (geometric/arithmetic mean ratio)",
|
|
34
34
|
"energy": "RMS level",
|
|
35
35
|
"punch": "crest factor in dB (20*log10(peak/rms))",
|
|
36
|
+
"motion": "spectral novelty + onset strength",
|
|
37
|
+
"novelty": "FluCoMa novelty score",
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
VALID_MODES = frozenset({"observe", "improve", "explore", "finish", "diagnose"})
|
|
@@ -129,4 +131,3 @@ class TechniqueCard:
|
|
|
129
131
|
"verification": self.verification,
|
|
130
132
|
"evidence": self.evidence,
|
|
131
133
|
}
|
|
132
|
-
|
|
@@ -70,7 +70,7 @@ class ConductorPlan:
|
|
|
70
70
|
_ROUTING_PATTERNS: list[tuple[str, str, str, str, list[str]]] = [
|
|
71
71
|
# Mix requests
|
|
72
72
|
(r"clean|mud|muddy|low.?mid|eq|equaliz", "mix_engine", "mix", "analyze_mix", ["plan_mix_move", "evaluate_mix_move"]),
|
|
73
|
-
(r"
|
|
73
|
+
(r"dynamics|compress|crest|over.?compress|flat.?dynamics", "mix_engine", "mix", "analyze_mix", ["plan_mix_move"]),
|
|
74
74
|
(r"wide|wider|width|stereo|narrow|mono.?compat", "mix_engine", "mix", "analyze_mix", ["plan_mix_move"]),
|
|
75
75
|
(r"glue|cohes|bus.?comp|mix.?bus", "mix_engine", "mix", "analyze_mix", ["plan_mix_move"]),
|
|
76
76
|
(r"balance|level|volume.?balanc|gain.?stag", "mix_engine", "mix", "analyze_mix", ["plan_mix_move"]),
|
|
@@ -87,6 +87,7 @@ _ROUTING_PATTERNS: list[tuple[str, str, str, str, list[str]]] = [
|
|
|
87
87
|
|
|
88
88
|
# Sound design requests
|
|
89
89
|
(r"synth|patch|oscillat|timbre|timbral|wavetable|operator", "sound_design", "sound_design", "analyze_sound_design", ["plan_sound_design_move"]),
|
|
90
|
+
(r"punch|punchy|hit.?harder|snap|attack|transient", "sound_design", "sound_design", "analyze_sound_design", ["plan_sound_design_move"]),
|
|
90
91
|
(r"haunted|lush|aggressive|warm.?pad|fat.?bass|bright.?lead", "sound_design", "sound_design", "analyze_sound_design", ["plan_sound_design_move"]),
|
|
91
92
|
(r"modulation|lfo|movement|evolv|texture", "sound_design", "sound_design", "get_patch_model", ["analyze_sound_design"]),
|
|
92
93
|
(r"layer|sub.?layer|transient.?layer|body", "sound_design", "sound_design", "analyze_sound_design", ["plan_sound_design_move"]),
|
|
@@ -254,7 +255,7 @@ def classify_request(request: str) -> ConductorPlan:
|
|
|
254
255
|
|
|
255
256
|
# Determine capability requirements
|
|
256
257
|
caps = ["session_access"]
|
|
257
|
-
if any(r.engine
|
|
258
|
+
if any(r.engine in ("mix_engine", "sound_design") for r in routes):
|
|
258
259
|
caps.append("analyzer")
|
|
259
260
|
if any(r.engine in ("reference_engine",) for r in routes):
|
|
260
261
|
caps.append("offline_perception")
|
|
@@ -267,6 +268,8 @@ def classify_request(request: str) -> ConductorPlan:
|
|
|
267
268
|
notes.append("Multi-engine task — start with get_session_kernel for shared state")
|
|
268
269
|
if any(r.engine == "mix_engine" for r in routes):
|
|
269
270
|
notes.append("Mix engine works best with analyzer data — check get_capability_state")
|
|
271
|
+
if any(r.engine == "sound_design" for r in routes):
|
|
272
|
+
notes.append("Sound design should use analyzer character before level or pan changes")
|
|
270
273
|
|
|
271
274
|
# V2: Search semantic moves for matching intents
|
|
272
275
|
semantic_moves = _find_matching_semantic_moves(lower)
|
|
@@ -22,7 +22,7 @@ from typing import Optional
|
|
|
22
22
|
# must report confidence=0.0 for that dimension.
|
|
23
23
|
MEASURABLE_DIMENSIONS: frozenset[str] = frozenset({
|
|
24
24
|
"brightness", "warmth", "weight", "clarity",
|
|
25
|
-
"density", "energy", "punch",
|
|
25
|
+
"density", "energy", "punch", "motion", "novelty",
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
# All valid quality dimensions (measurable + unmeasurable).
|
|
@@ -13,6 +13,18 @@ import time
|
|
|
13
13
|
from typing import Optional
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
_RICH_ANALYZER_KEYS = (
|
|
17
|
+
"spectral_shape",
|
|
18
|
+
"mel_bands",
|
|
19
|
+
"chroma",
|
|
20
|
+
"onset",
|
|
21
|
+
"onsets",
|
|
22
|
+
"novelty",
|
|
23
|
+
"loudness",
|
|
24
|
+
"sub_detail",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
def normalize_sonic_snapshot(
|
|
17
29
|
raw: Optional[dict],
|
|
18
30
|
source: str = "unknown",
|
|
@@ -20,6 +32,9 @@ def normalize_sonic_snapshot(
|
|
|
20
32
|
"""Normalize a raw analyzer/perception output into canonical snapshot form.
|
|
21
33
|
|
|
22
34
|
Accepts both {"bands": {...}} and {"spectrum": {...}} shapes.
|
|
35
|
+
Rich analyzer streams are preserved when present so evaluators can
|
|
36
|
+
reason from FluCoMa character descriptors instead of collapsing every
|
|
37
|
+
decision down to the 9-band spectrum.
|
|
23
38
|
Returns None if input is empty or None.
|
|
24
39
|
|
|
25
40
|
Canonical form:
|
|
@@ -28,6 +43,12 @@ def normalize_sonic_snapshot(
|
|
|
28
43
|
"rms": float or None,
|
|
29
44
|
"peak": float or None,
|
|
30
45
|
"detected_key": str or None,
|
|
46
|
+
"spectral_shape": dict or None,
|
|
47
|
+
"mel_bands": list or None,
|
|
48
|
+
"chroma": dict/list or None,
|
|
49
|
+
"onset": dict or None,
|
|
50
|
+
"novelty": dict or None,
|
|
51
|
+
"loudness": dict or None,
|
|
31
52
|
"source": str,
|
|
32
53
|
"normalized_at_ms": int,
|
|
33
54
|
}
|
|
@@ -35,11 +56,12 @@ def normalize_sonic_snapshot(
|
|
|
35
56
|
if not raw or not isinstance(raw, dict):
|
|
36
57
|
return None
|
|
37
58
|
|
|
38
|
-
bands = raw.get("spectrum") or raw.get("bands")
|
|
39
|
-
|
|
59
|
+
bands = raw.get("spectrum") or raw.get("bands") or {}
|
|
60
|
+
has_rich_analyzer_data = any(raw.get(k) is not None for k in _RICH_ANALYZER_KEYS)
|
|
61
|
+
if not bands and not has_rich_analyzer_data:
|
|
40
62
|
return None
|
|
41
63
|
|
|
42
|
-
|
|
64
|
+
normalized = {
|
|
43
65
|
"spectrum": bands,
|
|
44
66
|
"rms": raw.get("rms"),
|
|
45
67
|
"peak": raw.get("peak"),
|
|
@@ -47,3 +69,10 @@ def normalize_sonic_snapshot(
|
|
|
47
69
|
"source": source,
|
|
48
70
|
"normalized_at_ms": int(time.time() * 1000),
|
|
49
71
|
}
|
|
72
|
+
|
|
73
|
+
for key in _RICH_ANALYZER_KEYS:
|
|
74
|
+
if key in raw and raw.get(key) is not None:
|
|
75
|
+
out_key = "onset" if key == "onsets" else key
|
|
76
|
+
normalized[out_key] = raw.get(key)
|
|
77
|
+
|
|
78
|
+
return normalized
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.2",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
5
|
"description": "Agentic production system for Ableton Live 12 — 465 tools, 56 domains, 44 semantic moves. Device atlas (5264 devices, 120 enriched, 7 indexes), Splice intelligence (gRPC + GraphQL describe-a-sound + preview + collections + presets), 9-band spectral perception auto-loaded via ensure_analyzer_on_master, Creative Director skill, technique memory, 12 creative intelligence engines",
|
|
6
6
|
"author": "Pilot Studio",
|
|
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
|
|
|
5
5
|
when this script is selected in Preferences > Link, Tempo & MIDI.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.26.
|
|
8
|
+
__version__ = "1.26.2"
|
|
9
9
|
|
|
10
10
|
from _Framework.ControlSurface import ControlSurface
|
|
11
11
|
from . import router
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/dreamrec/LivePilot",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.26.
|
|
9
|
+
"version": "1.26.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.26.
|
|
14
|
+
"version": "1.26.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|