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.
- package/CHANGELOG.md +31 -0
- package/README.md +5 -5
- package/bin/livepilot.js +135 -0
- package/m4l_device/livepilot_bridge.js +117 -3
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/m4l_bridge.py +81 -0
- package/mcp_server/server.py +1 -0
- package/mcp_server/tools/_perception_engine.py +459 -0
- package/mcp_server/tools/analyzer.py +186 -2
- package/mcp_server/tools/perception.py +214 -0
- package/package.json +2 -2
- package/plugin/plugin.json +2 -2
- package/plugin/skills/livepilot-core/SKILL.md +6 -6
- package/plugin/skills/livepilot-core/references/overview.md +3 -3
- package/remote_script/LivePilot/__init__.py +2 -2
- package/requirements.txt +6 -0
|
@@ -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.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
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",
|
package/plugin/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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.
|
|
1
|
+
# LivePilot v1.8.0 — Architecture & Tool Reference
|
|
2
2
|
|
|
3
|
-
Agentic production system for Ableton Live 12.
|
|
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
|
|
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.
|
|
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.
|
|
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