livepilot 1.16.0 → 1.16.1

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +75 -5
  2. package/README.md +11 -11
  3. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  4. package/m4l_device/livepilot_bridge.js +1 -1
  5. package/mcp_server/__init__.py +1 -1
  6. package/mcp_server/atlas/enrichments/audio_effects/pitch_hack.yaml +61 -0
  7. package/mcp_server/atlas/enrichments/audio_effects/pitchloop89.yaml +111 -0
  8. package/mcp_server/atlas/enrichments/audio_effects/re_enveloper.yaml +51 -0
  9. package/mcp_server/atlas/enrichments/audio_effects/snipper.yaml +36 -0
  10. package/mcp_server/atlas/enrichments/audio_effects/spectral_blur.yaml +64 -0
  11. package/mcp_server/atlas/enrichments/instruments/bell_tower.yaml +37 -0
  12. package/mcp_server/atlas/enrichments/instruments/granulator_iii.yaml +124 -0
  13. package/mcp_server/atlas/enrichments/instruments/harmonic_drone_generator.yaml +83 -0
  14. package/mcp_server/atlas/enrichments/instruments/impulse.yaml +47 -0
  15. package/mcp_server/atlas/enrichments/instruments/sting_iftah.yaml +44 -0
  16. package/mcp_server/atlas/enrichments/midi_effects/expressive_chords.yaml +38 -0
  17. package/mcp_server/atlas/enrichments/midi_effects/filler.yaml +32 -0
  18. package/mcp_server/atlas/enrichments/midi_effects/microtuner.yaml +83 -0
  19. package/mcp_server/atlas/enrichments/midi_effects/patterns_iftah.yaml +38 -0
  20. package/mcp_server/atlas/enrichments/midi_effects/phase_pattern.yaml +51 -0
  21. package/mcp_server/atlas/enrichments/midi_effects/polyrhythm.yaml +46 -0
  22. package/mcp_server/atlas/enrichments/midi_effects/retrigger.yaml +40 -0
  23. package/mcp_server/atlas/enrichments/midi_effects/slice_shuffler.yaml +39 -0
  24. package/mcp_server/atlas/enrichments/midi_effects/sq_sequencer.yaml +39 -0
  25. package/mcp_server/atlas/enrichments/midi_effects/stages.yaml +38 -0
  26. package/mcp_server/atlas/enrichments/utility/arrangement_looper.yaml +31 -0
  27. package/mcp_server/atlas/enrichments/utility/cv_clock_in.yaml +25 -0
  28. package/mcp_server/atlas/enrichments/utility/cv_clock_out.yaml +25 -0
  29. package/mcp_server/atlas/enrichments/utility/cv_envelope_follower.yaml +38 -0
  30. package/mcp_server/atlas/enrichments/utility/cv_in.yaml +26 -0
  31. package/mcp_server/atlas/enrichments/utility/cv_instrument.yaml +34 -0
  32. package/mcp_server/atlas/enrichments/utility/cv_lfo.yaml +38 -0
  33. package/mcp_server/atlas/enrichments/utility/cv_shaper.yaml +35 -0
  34. package/mcp_server/atlas/enrichments/utility/cv_triggers.yaml +26 -0
  35. package/mcp_server/atlas/enrichments/utility/cv_utility.yaml +37 -0
  36. package/mcp_server/atlas/enrichments/utility/performer.yaml +36 -0
  37. package/mcp_server/atlas/enrichments/utility/prearranger.yaml +36 -0
  38. package/mcp_server/atlas/enrichments/utility/rotating_rhythm_generator.yaml +35 -0
  39. package/mcp_server/atlas/enrichments/utility/surround_panner.yaml +40 -0
  40. package/mcp_server/atlas/enrichments/utility/variations.yaml +40 -0
  41. package/mcp_server/atlas/enrichments/utility/vector_map.yaml +36 -0
  42. package/mcp_server/sample_engine/tools.py +50 -4
  43. package/mcp_server/server.py +18 -6
  44. package/mcp_server/splice_client/client.py +90 -18
  45. package/mcp_server/splice_client/http_bridge.py +101 -28
  46. package/mcp_server/splice_client/models.py +12 -0
  47. package/mcp_server/tools/analyzer.py +150 -1
  48. package/package.json +2 -2
  49. package/remote_script/LivePilot/__init__.py +1 -1
  50. package/server.json +3 -3
@@ -212,6 +212,7 @@ async def get_master_spectrum(
212
212
  ctx: Context,
213
213
  window_ms: int = 0,
214
214
  samples: int = 0,
215
+ sub_detail: bool = False,
215
216
  ) -> dict:
216
217
  """Get 8-band frequency analysis of the master bus.
217
218
 
@@ -236,6 +237,14 @@ async def get_master_spectrum(
236
237
  The sampled bands are also returned as `bands_min`, `bands_max` and
237
238
  `bands_std` so callers can see variance within the window — useful
238
239
  for detecting transient-heavy content vs. sustained material.
240
+
241
+ BUG-2026-04-22#15 fix — sub-band resolution:
242
+ Pass `sub_detail=True` to attach a `sub_detail` dict with three
243
+ finer buckets: `sub_deep` (20-45 Hz), `sub_mid` (45-60 Hz),
244
+ `sub_high` (60-80 Hz). Derived from the FluCoMa mel spectrum
245
+ (40 bands) rather than the 8-band cache, so it requires FluCoMa
246
+ to be active. When FluCoMa is unavailable, sub_detail is omitted
247
+ with a `sub_detail_warning` field explaining why.
239
248
  """
240
249
  cache = _get_spectral(ctx)
241
250
  _require_analyzer(cache)
@@ -291,6 +300,8 @@ async def get_master_spectrum(
291
300
  key_data = cache.get("key")
292
301
  if key_data:
293
302
  result["detected_key"] = key_data["value"]
303
+ if sub_detail:
304
+ _attach_sub_detail(cache, result)
294
305
  return result
295
306
 
296
307
  # Legacy instantaneous path
@@ -303,10 +314,55 @@ async def get_master_spectrum(
303
314
  key_data = cache.get("key")
304
315
  if key_data:
305
316
  result["detected_key"] = key_data["value"]
317
+ if sub_detail:
318
+ _attach_sub_detail(cache, result)
306
319
 
307
320
  return result
308
321
 
309
322
 
323
+ def _attach_sub_detail(cache, result: dict) -> None:
324
+ """Compute finer sub-band breakdown (20-45, 45-60, 60-80 Hz) from
325
+ FluCoMa's 40-band mel spectrum and attach to the result dict.
326
+
327
+ Mel band edges are perceptual, not linear Hz — we map approximately:
328
+ with a standard 40-band mel filterbank from 0-20kHz, the first
329
+ ~4 bands cover 0-80 Hz and are distributed roughly:
330
+ band 0: ~0-25 Hz
331
+ band 1: ~25-45 Hz
332
+ band 2: ~45-65 Hz
333
+ band 3: ~65-90 Hz
334
+ We use these mappings as approximations; exact cutoffs depend on
335
+ FluCoMa's filterbank config but this is tight enough for mixing
336
+ decisions (the question is "is energy in the 30 Hz or 60 Hz range?").
337
+ """
338
+ mel_snap = cache.get("mel_bands")
339
+ if not mel_snap or not mel_snap.get("value"):
340
+ result["sub_detail_warning"] = (
341
+ "FluCoMa mel spectrum not available — sub_detail requires "
342
+ "FluCoMa active on the M4L analyzer. Use check_flucoma to diagnose."
343
+ )
344
+ return
345
+ mel_bands = mel_snap["value"]
346
+ if not isinstance(mel_bands, list) or len(mel_bands) < 4:
347
+ result["sub_detail_warning"] = (
348
+ f"Mel spectrum has {len(mel_bands) if isinstance(mel_bands, list) else 0} "
349
+ "bands — need at least 4 for sub_detail decomposition."
350
+ )
351
+ return
352
+
353
+ def _mean(indices):
354
+ vals = [float(mel_bands[i]) for i in indices if i < len(mel_bands)]
355
+ return round(sum(vals) / len(vals), 4) if vals else 0.0
356
+
357
+ result["sub_detail"] = {
358
+ "sub_deep": _mean([0, 1]), # ~0-45 Hz (kick fundamental)
359
+ "sub_mid": _mean([2]), # ~45-60 Hz (808 body / kick upper)
360
+ "sub_high": _mean([3]), # ~60-80 Hz (bass guitar low, sub-bass crossover)
361
+ "age_ms": mel_snap.get("age_ms"),
362
+ "source": "flucoma_mel_40",
363
+ }
364
+
365
+
310
366
  def _sanitize_pitch(pitch: Optional[dict]) -> Optional[dict]:
311
367
  """Validate a pitch reading from the M4L analyzer (BUG-F1).
312
368
 
@@ -751,7 +807,10 @@ async def add_drum_rack_pad(
751
807
  "method": "native_12_4",
752
808
  }
753
809
  """
754
- from .._analyzer_engine.sample import _simpler_post_load_hygiene # noqa
810
+ # _simpler_post_load_hygiene is already imported at module scope
811
+ # (line 29). Do not re-import inline — the earlier inline form used
812
+ # the wrong relative path (..; should've been .) and crashed at
813
+ # runtime with "No module named 'mcp_server._analyzer_engine'".
755
814
  ableton = ctx.lifespan_context["ableton"]
756
815
  caps = _live_caps(ctx)
757
816
  if not caps.has_replace_sample_native:
@@ -1534,6 +1593,96 @@ async def verify_device_health(
1534
1593
  }
1535
1594
 
1536
1595
 
1596
+ @mcp.tool()
1597
+ async def verify_all_devices_health(
1598
+ ctx: Context,
1599
+ test_midi_note: int = 60,
1600
+ test_velocity: int = 100,
1601
+ test_duration_ms: int = 250,
1602
+ threshold: float = 0.005,
1603
+ skip_audio_tracks: bool = True,
1604
+ skip_empty_tracks: bool = True,
1605
+ ) -> dict:
1606
+ """Run verify_device_health across every eligible track in one call.
1607
+
1608
+ Session-wide silent-track detector. Useful right after opening a
1609
+ project to surface dead plugins before mixing. Serial execution —
1610
+ firing notes in parallel would make the meter readings ambiguous.
1611
+
1612
+ skip_audio_tracks: audio tracks have no MIDI input, skip them (default True)
1613
+ skip_empty_tracks: tracks without any instrument also skip (default True)
1614
+
1615
+ Returns: {
1616
+ "ok": bool,
1617
+ "tracks_tested": int,
1618
+ "alive": [track_index...],
1619
+ "dead": [{track_index, track_name, peak_level}...],
1620
+ "skipped": [{track_index, reason}...],
1621
+ }
1622
+ """
1623
+ ableton = ctx.lifespan_context["ableton"]
1624
+ try:
1625
+ session = ableton.send_command("get_session_info", {})
1626
+ except Exception as exc:
1627
+ return {"ok": False, "error": f"get_session_info failed: {exc}"}
1628
+ if not isinstance(session, dict):
1629
+ return {"ok": False, "error": "Unexpected get_session_info response"}
1630
+
1631
+ tracks = session.get("tracks", []) or []
1632
+ alive: list = []
1633
+ dead: list = []
1634
+ skipped: list = []
1635
+
1636
+ for t in tracks:
1637
+ tid = t.get("index")
1638
+ tname = t.get("name", f"Track {tid}")
1639
+ if tid is None:
1640
+ continue
1641
+ is_audio = bool(t.get("is_audio_track") or t.get("type") == "audio")
1642
+ if skip_audio_tracks and is_audio:
1643
+ skipped.append({"track_index": tid, "track_name": tname,
1644
+ "reason": "audio_track_no_midi_input"})
1645
+ continue
1646
+ devices = t.get("devices") or []
1647
+ if skip_empty_tracks and not devices:
1648
+ skipped.append({"track_index": tid, "track_name": tname,
1649
+ "reason": "no_devices_on_track"})
1650
+ continue
1651
+
1652
+ # Run the per-track health check.
1653
+ result = await verify_device_health(
1654
+ ctx, track_index=tid,
1655
+ test_midi_note=test_midi_note,
1656
+ test_velocity=test_velocity,
1657
+ test_duration_ms=test_duration_ms,
1658
+ threshold=threshold,
1659
+ )
1660
+ if not result.get("ok"):
1661
+ skipped.append({"track_index": tid, "track_name": tname,
1662
+ "reason": result.get("error", "health_check_failed")})
1663
+ continue
1664
+ if result.get("alive"):
1665
+ alive.append(tid)
1666
+ else:
1667
+ dead.append({
1668
+ "track_index": tid,
1669
+ "track_name": tname,
1670
+ "peak_level": result.get("peak_level", 0),
1671
+ })
1672
+
1673
+ return {
1674
+ "ok": True,
1675
+ "tracks_tested": len(alive) + len(dead),
1676
+ "alive": alive,
1677
+ "dead": dead,
1678
+ "skipped": skipped,
1679
+ "summary": (
1680
+ f"{len(alive)} alive, {len(dead)} dead, "
1681
+ f"{len(skipped)} skipped out of {len(tracks)} total tracks"
1682
+ ),
1683
+ }
1684
+
1685
+
1537
1686
  @mcp.tool()
1538
1687
  def get_momentary_loudness(ctx: Context) -> dict:
1539
1688
  """Get EBU R128 momentary LUFS + true peak from FluCoMa.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.16.0",
3
+ "version": "1.16.1",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 421 tools, 52 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
5
+ "description": "Agentic production system for Ableton Live 12 — 422 tools, 52 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
6
6
  "author": "Pilot Studio",
7
7
  "license": "BSL-1.1",
8
8
  "type": "commonjs",
@@ -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.16.0"
8
+ __version__ = "1.16.1"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from . import router
package/server.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.dreamrec/livepilot",
4
- "description": "421-tool agentic MCP production system for Ableton Live 12 — device atlas, sample engine, composer",
4
+ "description": "422-tool agentic MCP production system for Ableton Live 12 — device atlas, sample engine, composer",
5
5
  "repository": {
6
6
  "url": "https://github.com/dreamrec/LivePilot",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.16.0",
9
+ "version": "1.16.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "livepilot",
14
- "version": "1.16.0",
14
+ "version": "1.16.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }