livepilot 1.7.6 → 1.8.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.
@@ -0,0 +1,214 @@
1
+ """Offline audio perception tools for LivePilot v1.8.
2
+
3
+ 4 MCP tools wrapping the pure-function engine in _perception_engine.py.
4
+ These tools do NOT require an Ableton connection — they work on any local
5
+ audio file.
6
+
7
+ Tools:
8
+ analyze_loudness — integrated LUFS, true peak, LRA, streaming compliance
9
+ analyze_spectrum_offline — spectral centroid, rolloff, flatness, 5-band balance
10
+ compare_to_reference — loudness + spectral delta between mix and reference
11
+ read_audio_metadata — format, sample rate, tags, artwork flag
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from typing import Any, Optional
18
+
19
+ from ..server import mcp
20
+ from ._perception_engine import (
21
+ compute_loudness,
22
+ compute_spectral,
23
+ compare_to_reference as _compare,
24
+ read_audio_metadata as _read_metadata,
25
+ )
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Supported formats
30
+ # ---------------------------------------------------------------------------
31
+
32
+ _LOSSLESS_EXTS = {".wav", ".flac", ".ogg", ".aiff", ".aif"}
33
+ _LOSSY_EXTS = {".mp3", ".m4a"}
34
+ _MAX_FILE_SIZE = 500 * 1024 * 1024 # 500 MB
35
+
36
+
37
+ def _validate_audio(file_path: str, allow_mp3: bool = False) -> Optional[dict]:
38
+ """Validate that a file exists, has a supported extension, and is < 500 MB.
39
+
40
+ Returns None if valid, or an error dict if invalid.
41
+ """
42
+ if not os.path.exists(file_path):
43
+ return {"error": f"File not found: {file_path}", "code": "INVALID_PARAM"}
44
+
45
+ ext = os.path.splitext(file_path)[1].lower()
46
+ allowed = _LOSSLESS_EXTS | (_LOSSY_EXTS if allow_mp3 else set())
47
+ if ext not in allowed:
48
+ return {
49
+ "error": (
50
+ f"Unsupported format '{ext}'. "
51
+ f"Supported: {sorted(allowed)}"
52
+ ),
53
+ "code": "INVALID_PARAM",
54
+ }
55
+
56
+ size = os.path.getsize(file_path)
57
+ if size > _MAX_FILE_SIZE:
58
+ mb = size / (1024 * 1024)
59
+ return {
60
+ "error": f"File too large ({mb:.1f} MB). Maximum is 500 MB.",
61
+ "code": "INVALID_PARAM",
62
+ }
63
+
64
+ return None # valid
65
+
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # MCP Tools
69
+ # ---------------------------------------------------------------------------
70
+
71
+ @mcp.tool()
72
+ def analyze_loudness(
73
+ file_path: str,
74
+ detail: str = "summary",
75
+ ) -> dict[str, Any]:
76
+ """Analyze the integrated loudness of an audio file (offline — no Ableton needed).
77
+
78
+ Computes integrated LUFS (EBU R128), true peak, RMS, crest factor,
79
+ loudness range (LRA), and streaming platform compliance.
80
+
81
+ Args:
82
+ file_path: Absolute path to the audio file (.wav, .flac, .ogg, .aiff).
83
+ detail: "summary" (default) or "full" — "full" includes the short_term_lufs
84
+ array (up to 100 points, mean-pooled).
85
+
86
+ Returns:
87
+ On success: dict with integrated_lufs, true_peak_dbtp, rms_dbfs,
88
+ crest_factor_db, lra_lu, meets_streaming {spotify, apple, youtube, tidal},
89
+ and optionally short_term_lufs.
90
+ On error: {"error": ..., "code": ...}
91
+ """
92
+ err = _validate_audio(file_path, allow_mp3=False)
93
+ if err:
94
+ return err
95
+
96
+ if detail not in ("summary", "full"):
97
+ return {"error": "detail must be 'summary' or 'full'", "code": "INVALID_PARAM"}
98
+
99
+ try:
100
+ return compute_loudness(file_path, detail=detail)
101
+ except FileNotFoundError as exc:
102
+ return {"error": str(exc), "code": "INVALID_PARAM"}
103
+ except Exception as exc:
104
+ return {"error": f"Loudness analysis failed: {exc}", "code": "INTERNAL"}
105
+
106
+
107
+ @mcp.tool()
108
+ def analyze_spectrum_offline(
109
+ file_path: str,
110
+ n_fft: int = 2048,
111
+ hop_length: int = 512,
112
+ ) -> dict[str, Any]:
113
+ """Analyze the frequency spectrum of an audio file (offline — no Ableton needed).
114
+
115
+ Uses scipy STFT to compute spectral centroid, rolloff, flatness, bandwidth,
116
+ and 5-band energy balance (sub_60hz, low_250hz, mid_2khz, high_8khz, air_16khz).
117
+
118
+ Args:
119
+ file_path: Absolute path to the audio file (.wav, .flac, .ogg, .aiff).
120
+ n_fft: FFT window size (default 2048).
121
+ hop_length: Hop size in samples (default 512).
122
+
123
+ Returns:
124
+ On success: dict with centroid_hz, rolloff_hz, spectral_flatness,
125
+ bandwidth_hz, band_balance.
126
+ On error: {"error": ..., "code": ...}
127
+ """
128
+ err = _validate_audio(file_path, allow_mp3=False)
129
+ if err:
130
+ return err
131
+
132
+ if n_fft < 64 or n_fft > 65536:
133
+ return {"error": "n_fft must be between 64 and 65536", "code": "INVALID_PARAM"}
134
+ if hop_length < 1 or hop_length > n_fft:
135
+ return {"error": "hop_length must be between 1 and n_fft", "code": "INVALID_PARAM"}
136
+
137
+ try:
138
+ return compute_spectral(file_path, n_fft=n_fft, hop_length=hop_length)
139
+ except FileNotFoundError as exc:
140
+ return {"error": str(exc), "code": "INVALID_PARAM"}
141
+ except Exception as exc:
142
+ return {"error": f"Spectral analysis failed: {exc}", "code": "INTERNAL"}
143
+
144
+
145
+ @mcp.tool()
146
+ def compare_to_reference(
147
+ mix_path: str,
148
+ reference_path: str,
149
+ normalize: bool = True,
150
+ ) -> dict[str, Any]:
151
+ """Compare a mix to a reference track (offline — no Ableton needed).
152
+
153
+ Computes loudness delta (LUFS), spectral centroid delta, stereo width
154
+ comparison, per-band energy deltas, and actionable mixing suggestions.
155
+
156
+ When normalize=True (default), both files are LUFS-normalized to -14 LUFS
157
+ before spectral comparison so frequency differences aren't skewed by volume.
158
+
159
+ Args:
160
+ mix_path: Absolute path to the mix file (.wav, .flac, .ogg, .aiff).
161
+ reference_path: Absolute path to the reference file.
162
+ normalize: LUFS-normalize before spectral comparison (default True).
163
+
164
+ Returns:
165
+ On success: dict with loudness_delta_lufs, mix_lufs, reference_lufs,
166
+ centroid_delta_hz, stereo_width_mix, stereo_width_ref, band_deltas,
167
+ suggestions.
168
+ On error: {"error": ..., "code": ...}
169
+ """
170
+ err = _validate_audio(mix_path, allow_mp3=False)
171
+ if err:
172
+ return {"error": f"mix_path: {err['error']}", "code": err["code"]}
173
+
174
+ err = _validate_audio(reference_path, allow_mp3=False)
175
+ if err:
176
+ return {"error": f"reference_path: {err['error']}", "code": err["code"]}
177
+
178
+ try:
179
+ return _compare(mix_path, reference_path, normalize=normalize)
180
+ except FileNotFoundError as exc:
181
+ return {"error": str(exc), "code": "INVALID_PARAM"}
182
+ except Exception as exc:
183
+ return {"error": f"Reference comparison failed: {exc}", "code": "INTERNAL"}
184
+
185
+
186
+ @mcp.tool()
187
+ def read_audio_metadata(
188
+ file_path: str,
189
+ ) -> dict[str, Any]:
190
+ """Read metadata from an audio file (offline — no Ableton needed).
191
+
192
+ Uses mutagen for tag reading (title, artist, album, BPM, etc.) and
193
+ soundfile for format information. Falls back gracefully if mutagen
194
+ cannot parse the file.
195
+
196
+ Args:
197
+ file_path: Absolute path to the audio file (.wav, .flac, .ogg, .aiff,
198
+ .mp3, .m4a).
199
+
200
+ Returns:
201
+ On success: dict with format, duration, sample_rate, channels,
202
+ bitrate, tags, has_artwork, file_size.
203
+ On error: {"error": ..., "code": ...}
204
+ """
205
+ err = _validate_audio(file_path, allow_mp3=True)
206
+ if err:
207
+ return err
208
+
209
+ try:
210
+ return _read_metadata(file_path)
211
+ except FileNotFoundError as exc:
212
+ return {"error": str(exc), "code": "INVALID_PARAM"}
213
+ except Exception as exc:
214
+ return {"error": f"Metadata read failed: {exc}", "code": "INTERNAL"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.7.6",
3
+ "version": "1.8.0",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 155 tools, 16 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
+ "description": "Agentic production system for Ableton Live 12 — 168 tools, 17 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
6
6
  "author": "Pilot Studio",
7
7
  "license": "MIT",
8
8
  "type": "commonjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.7.6",
4
- "description": "Agentic production system for Ableton Live 12 — 155 tools, 16 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
3
+ "version": "1.8.0",
4
+ "description": "Agentic production system for Ableton Live 12 — 168 tools, 17 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": "Pilot Studio",
6
6
  "skills": [
7
7
  "skills/livepilot-core",
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: livepilot-core
3
- description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 155 tools across 16 domains. Device atlas (280+ devices), M4L analyzer (spectrum/RMS/key detection), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O. Use whenever working with Ableton Live through MCP tools.
3
+ description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 168 tools across 17 domains. Device atlas (280+ devices), M4L analyzer (spectrum/RMS/key detection), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O. Use whenever working with Ableton Live through MCP tools.
4
4
  ---
5
5
 
6
6
  # LivePilot Core — Ableton Live 12
7
7
 
8
- Agentic production system for Ableton Live 12. 155 tools across 16 domains, three layers:
8
+ Agentic production system for Ableton Live 12. 168 tools across 17 domains, three layers:
9
9
 
10
10
  - **Device Atlas** — A structured knowledge corpus of 280+ instruments, 139 drum kits, and 350+ impulse responses. Consult the atlas before loading any device. It contains real browser URIs, preset names, and sonic descriptions. Never guess a device name — look it up.
11
11
  - **M4L Analyzer** — Real-time audio analysis on the master bus (8-band spectrum, RMS/peak, key detection). Use it to verify mixing decisions, detect frequency problems, and find the key before writing harmonic content.
12
12
  - **Technique Memory** — Persistent storage for production decisions. Consult `memory_recall` before creative tasks to understand the user's taste. Save techniques when the user likes something. The memory shapes future decisions without constraining them.
13
13
 
14
- These layers sit on top of 155 deterministic tools across 16 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, and MIDI I/O.
14
+ These layers sit on top of 168 deterministic tools across 17 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, MIDI I/O, and perception.
15
15
 
16
16
  ## Golden Rules
17
17
 
@@ -32,7 +32,7 @@ These layers sit on top of 155 deterministic tools across 16 domains: transport,
32
32
  Not all tools respond instantly. Know the tiers and act accordingly.
33
33
 
34
34
  ### Instant (<1s) — Use freely, no warning needed
35
- All 155 core tools (transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, automation, theory, generative, harmony, midi_io) plus Layer A perception tools (spectral shape, timbral profile, mel spectrum, chroma, onsets, harmonic/percussive, novelty, momentary loudness). These are the reflex tools — call them anytime without hesitation.
35
+ All 168 core tools (transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, automation, theory, generative, harmony, midi_io, perception) plus Layer A perception tools (spectral shape, timbral profile, mel spectrum, chroma, onsets, harmonic/percussive, novelty, momentary loudness). These are the reflex tools — call them anytime without hesitation.
36
36
 
37
37
  ### Fast (1-5s) — Use freely, barely noticeable
38
38
  `analyze_loudness` · `analyze_dynamic_range` · `compare_loudness`
@@ -117,7 +117,7 @@ Never skip levels. The user's question determines the entry point, but always st
117
117
  - MIDI track with no instrument loaded
118
118
  - Notes programmed but clip not fired
119
119
 
120
- ## Tool Domains (155 total)
120
+ ## Tool Domains (168 total)
121
121
 
122
122
  ### Transport (12)
123
123
  `get_session_info` · `set_tempo` · `set_time_signature` · `start_playback` · `stop_playback` · `continue_playback` · `toggle_metronome` · `set_session_loop` · `undo` · `redo` · `get_recent_actions` · `get_session_diagnostics`
@@ -366,7 +366,7 @@ Deep production knowledge lives in `references/`. Consult these when making crea
366
366
 
367
367
  | File | What's inside | When to consult |
368
368
  |------|--------------|-----------------|
369
- | `references/overview.md` | All 155 tools mapped with params, units, ranges | Quick lookup for any tool |
369
+ | `references/overview.md` | All 168 tools mapped with params, units, ranges | Quick lookup for any tool |
370
370
  | `references/midi-recipes.md` | Drum patterns by genre, chord voicings, scales, hi-hat techniques, humanization, polymetrics | Programming MIDI notes, building beats |
371
371
  | `references/sound-design.md` | Stock instruments/effects, parameter recipes for bass/pad/lead/pluck, device chain patterns | Loading and configuring devices |
372
372
  | `references/mixing-patterns.md` | Gain staging, parallel compression, sidechain, EQ by instrument, bus processing, stereo width | Setting volumes, panning, adding effects |
@@ -1,6 +1,6 @@
1
- # LivePilot v1.7.6 — Architecture & Tool Reference
1
+ # LivePilot v1.8.0 — Architecture & Tool Reference
2
2
 
3
- Agentic production system for Ableton Live 12. 155 tools across 16 domains. Device atlas (280+ devices), spectral perception (M4L analyzer), 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.
3
+ Agentic production system for Ableton Live 12. 168 tools across 17 domains. Device atlas (280+ devices), spectral perception (M4L analyzer), 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.
4
4
 
5
5
  ## Architecture
6
6
 
@@ -32,7 +32,7 @@ A flat tool list lets the AI press buttons. LivePilot's three layers give it con
32
32
 
33
33
  This turns "set EQ band 3 to -4 dB" into "cut 400 Hz by 4 dB, then read the spectrum to confirm the mud is actually reduced."
34
34
 
35
- ## The 155 Tools — What Each One Does
35
+ ## The 168 Tools — What Each One Does
36
36
 
37
37
  ### Transport (12) — Playback, tempo, global state, diagnostics
38
38
 
@@ -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.7.6"
8
+ __version__ = "1.8.0"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from .server import LivePilotServer
@@ -34,7 +34,7 @@ class LivePilot(ControlSurface):
34
34
  ControlSurface.__init__(self, c_instance)
35
35
  self._server = LivePilotServer(self)
36
36
  self._server.start()
37
- self.log_message("LivePilot v1.7.6 initialized")
37
+ self.log_message("LivePilot v1.8.0 initialized")
38
38
  self.show_message("LivePilot: Listening on port 9878")
39
39
 
40
40
  def disconnect(self):
package/requirements.txt CHANGED
@@ -1,5 +1,11 @@
1
1
  # LivePilot MCP Server dependencies
2
+ numpy>=1.24.0
2
3
  fastmcp>=3.0.0,<4.0.0
3
4
  midiutil>=1.2.1
4
5
  pretty_midi>=0.2.10
5
6
  opycleid>=0.5.1
7
+ # v1.8 Perception Layer (offline analysis)
8
+ pyloudnorm>=0.1.0
9
+ soundfile>=0.12.0
10
+ scipy>=1.11.0
11
+ mutagen>=1.47.0