talkback-mcp 0.1.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.
Files changed (69) hide show
  1. package/dist/context-assembler.d.ts +7 -0
  2. package/dist/context-assembler.d.ts.map +1 -0
  3. package/dist/context-assembler.js +181 -0
  4. package/dist/context-assembler.js.map +1 -0
  5. package/dist/heuristics/dynamics.d.ts +3 -0
  6. package/dist/heuristics/dynamics.d.ts.map +1 -0
  7. package/dist/heuristics/dynamics.js +53 -0
  8. package/dist/heuristics/dynamics.js.map +1 -0
  9. package/dist/heuristics/frequency-buildup.d.ts +3 -0
  10. package/dist/heuristics/frequency-buildup.d.ts.map +1 -0
  11. package/dist/heuristics/frequency-buildup.js +69 -0
  12. package/dist/heuristics/frequency-buildup.js.map +1 -0
  13. package/dist/heuristics/headroom.d.ts +3 -0
  14. package/dist/heuristics/headroom.d.ts.map +1 -0
  15. package/dist/heuristics/headroom.js +54 -0
  16. package/dist/heuristics/headroom.js.map +1 -0
  17. package/dist/heuristics/index.d.ts +3 -0
  18. package/dist/heuristics/index.d.ts.map +1 -0
  19. package/dist/heuristics/index.js +22 -0
  20. package/dist/heuristics/index.js.map +1 -0
  21. package/dist/heuristics/routing.d.ts +3 -0
  22. package/dist/heuristics/routing.d.ts.map +1 -0
  23. package/dist/heuristics/routing.js +60 -0
  24. package/dist/heuristics/routing.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +50 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/mcp-server.d.ts +4 -0
  30. package/dist/mcp-server.d.ts.map +1 -0
  31. package/dist/mcp-server.js +437 -0
  32. package/dist/mcp-server.js.map +1 -0
  33. package/dist/param-maps/compressor.d.ts +3 -0
  34. package/dist/param-maps/compressor.d.ts.map +1 -0
  35. package/dist/param-maps/compressor.js +51 -0
  36. package/dist/param-maps/compressor.js.map +1 -0
  37. package/dist/param-maps/eq-eight.d.ts +3 -0
  38. package/dist/param-maps/eq-eight.d.ts.map +1 -0
  39. package/dist/param-maps/eq-eight.js +55 -0
  40. package/dist/param-maps/eq-eight.js.map +1 -0
  41. package/dist/param-maps/generic.d.ts +3 -0
  42. package/dist/param-maps/generic.d.ts.map +1 -0
  43. package/dist/param-maps/generic.js +11 -0
  44. package/dist/param-maps/generic.js.map +1 -0
  45. package/dist/param-maps/index.d.ts +14 -0
  46. package/dist/param-maps/index.d.ts.map +1 -0
  47. package/dist/param-maps/index.js +42 -0
  48. package/dist/param-maps/index.js.map +1 -0
  49. package/dist/plugin-library.d.ts +4 -0
  50. package/dist/plugin-library.d.ts.map +1 -0
  51. package/dist/plugin-library.js +63 -0
  52. package/dist/plugin-library.js.map +1 -0
  53. package/dist/spectral.d.ts +9 -0
  54. package/dist/spectral.d.ts.map +1 -0
  55. package/dist/spectral.js +63 -0
  56. package/dist/spectral.js.map +1 -0
  57. package/dist/stdio.d.ts +3 -0
  58. package/dist/stdio.d.ts.map +1 -0
  59. package/dist/stdio.js +32 -0
  60. package/dist/stdio.js.map +1 -0
  61. package/dist/types.d.ts +171 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +5 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/ws-bridge.d.ts +15 -0
  66. package/dist/ws-bridge.d.ts.map +1 -0
  67. package/dist/ws-bridge.js +139 -0
  68. package/dist/ws-bridge.js.map +1 -0
  69. package/package.json +51 -0
@@ -0,0 +1,7 @@
1
+ import type { SemanticSession, SessionCache } from "./types.js";
2
+ export declare function rawVolumeToDb(raw: number): string;
3
+ export declare function rawPanToHuman(raw: number): string;
4
+ export declare function rawSendToPercent(raw: number): string;
5
+ export declare function rawMeterToDb(left: number, right: number): string;
6
+ export declare function assembleContext(cache: SessionCache): SemanticSession | null;
7
+ //# sourceMappingURL=context-assembler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-assembler.d.ts","sourceRoot":"","sources":["../src/context-assembler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,eAAe,EAGf,YAAY,EACb,MAAM,YAAY,CAAC;AAOpB,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhE;AAsJD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,GAAG,IAAI,CA6C3E"}
@@ -0,0 +1,181 @@
1
+ import { lookupParamMap, translateParameter } from "./param-maps/index.js";
2
+ // ============================================================
3
+ // Volume / pan / meter conversions
4
+ // ============================================================
5
+ export function rawVolumeToDb(raw) {
6
+ if (raw <= 0)
7
+ return "-inf dB";
8
+ const db = 20 * Math.log10(raw / 0.85);
9
+ if (db <= -70)
10
+ return "-inf dB";
11
+ const rounded = Math.round(db * 10) / 10;
12
+ const sign = rounded > 0 ? "+" : "";
13
+ return `${sign}${rounded}dB`;
14
+ }
15
+ export function rawPanToHuman(raw) {
16
+ if (Math.abs(raw) < 0.01)
17
+ return "center";
18
+ const pct = Math.round(Math.abs(raw) * 100);
19
+ const side = raw < 0 ? "left" : "right";
20
+ if (pct >= 99)
21
+ return `hard ${side}`;
22
+ return `${pct}% ${side}`;
23
+ }
24
+ export function rawSendToPercent(raw) {
25
+ return `${Math.round(raw * 100)}%`;
26
+ }
27
+ export function rawMeterToDb(left, right) {
28
+ const peak = Math.max(left, right);
29
+ if (peak <= 0)
30
+ return "silent";
31
+ const db = 20 * Math.log10(peak);
32
+ if (db <= -60)
33
+ return "silent";
34
+ const rounded = Math.round(db * 10) / 10;
35
+ return `${rounded}dB`;
36
+ }
37
+ // ============================================================
38
+ // Device assembly
39
+ // ============================================================
40
+ function assembleDevice(device) {
41
+ const paramMap = lookupParamMap(device.className);
42
+ const isThirdParty = device.type === "PluginDevice";
43
+ const parameters = {};
44
+ const observations = [];
45
+ for (const param of device.parameters) {
46
+ parameters[param.name] = translateParameter(device.className, param.name, param.value, paramMap);
47
+ }
48
+ // Generate observations from param map advisors
49
+ if (paramMap) {
50
+ for (const param of device.parameters) {
51
+ const mapEntry = paramMap.parameters[param.name];
52
+ if (mapEntry?.observe) {
53
+ const observation = mapEntry.observe(param.value, parameters[param.name]);
54
+ if (observation)
55
+ observations.push(observation);
56
+ }
57
+ }
58
+ }
59
+ return {
60
+ id: device.id,
61
+ name: device.name,
62
+ active: device.isActive,
63
+ isThirdParty,
64
+ parameters,
65
+ observations,
66
+ };
67
+ }
68
+ // ============================================================
69
+ // Track assembly
70
+ // ============================================================
71
+ function assembleTrack(track, returnTrackNames, trackNameMap) {
72
+ const sends = {};
73
+ if (Array.isArray(track.sends)) {
74
+ for (const send of track.sends) {
75
+ const returnName = returnTrackNames.get(send.returnTrackId) ?? send.returnTrackId;
76
+ sends[returnName] = rawSendToPercent(send.amount);
77
+ }
78
+ }
79
+ // Handle both full device objects and lightweight device name arrays
80
+ const devices = Array.isArray(track.devices)
81
+ ? track.devices.map(assembleDevice)
82
+ : [];
83
+ // If we only have deviceNames (lightweight snapshot), show them as simple entries
84
+ const deviceNameList = track.deviceNames ?? [];
85
+ if (devices.length === 0 && deviceNameList.length > 0) {
86
+ for (const dn of deviceNameList) {
87
+ devices.push({
88
+ id: dn,
89
+ name: dn,
90
+ active: true,
91
+ isThirdParty: false,
92
+ parameters: {},
93
+ observations: [],
94
+ });
95
+ }
96
+ }
97
+ // Resolve group name from ID
98
+ const groupName = track.groupId
99
+ ? trackNameMap.get(track.groupId) ?? track.groupId
100
+ : null;
101
+ // Output level from meters
102
+ const meterLeft = track.outputMeterLeft ?? 0;
103
+ const meterRight = track.outputMeterRight ?? 0;
104
+ return {
105
+ id: track.id,
106
+ name: track.name,
107
+ type: track.type,
108
+ volume: rawVolumeToDb(track.volume),
109
+ panning: rawPanToHuman(track.panning),
110
+ muted: track.mute,
111
+ soloed: track.solo,
112
+ armed: track.arm ?? false,
113
+ monitoring: track.monitoring ?? "off",
114
+ group: groupName,
115
+ outputRouting: track.outputRouting,
116
+ outputLevel: rawMeterToDb(meterLeft, meterRight),
117
+ sends,
118
+ devices,
119
+ clipNames: track.clipNames ?? [],
120
+ };
121
+ }
122
+ // ============================================================
123
+ // Session assembly
124
+ // ============================================================
125
+ function generateSessionSummary(tracks, returnTracks, masterTrack) {
126
+ const parts = [];
127
+ const groupCount = tracks.filter((t) => t.type === "group").length;
128
+ parts.push(`${tracks.length} tracks${groupCount > 0 ? ` (${groupCount} groups)` : ""}, ${returnTracks.length} returns.`);
129
+ // Master output level
130
+ if (masterTrack.outputLevel !== "silent") {
131
+ parts.push(`Master output: ${masterTrack.outputLevel}.`);
132
+ }
133
+ // Note any devices on master
134
+ const masterDevices = masterTrack.devices.filter((d) => d.active);
135
+ if (masterDevices.length > 0) {
136
+ parts.push(`Master chain: ${masterDevices.map((d) => d.name).join(" → ")}.`);
137
+ }
138
+ // Collect observations across all tracks
139
+ for (const track of [...tracks, ...returnTracks, masterTrack]) {
140
+ for (const device of track.devices) {
141
+ for (const obs of device.observations) {
142
+ parts.push(`[${track.name}] ${obs}`);
143
+ }
144
+ }
145
+ }
146
+ return parts.join(" ");
147
+ }
148
+ export function assembleContext(cache) {
149
+ if (!cache.raw)
150
+ return null;
151
+ const { tempo, signature, tracks, returnTracks, masterTrack } = cache.raw;
152
+ const sampleRate = cache.raw.sampleRate ?? 44100;
153
+ // Build return track name map for send labels
154
+ const returnTrackNames = new Map();
155
+ for (const rt of returnTracks) {
156
+ returnTrackNames.set(rt.id, rt.name);
157
+ }
158
+ // Build track name map for group resolution
159
+ const trackNameMap = new Map();
160
+ for (const t of tracks) {
161
+ trackNameMap.set(t.id, t.name);
162
+ }
163
+ const semanticTracks = tracks.map((t) => assembleTrack(t, returnTrackNames, trackNameMap));
164
+ const semanticReturns = returnTracks.map((t) => assembleTrack(t, returnTrackNames, trackNameMap));
165
+ const semanticMaster = assembleTrack(masterTrack, returnTrackNames, trackNameMap);
166
+ const groupCount = tracks.filter((t) => t.isGroupTrack).length;
167
+ return {
168
+ tempo: `${tempo} BPM`,
169
+ signature: `${signature.numerator}/${signature.denominator}`,
170
+ sampleRate: `${sampleRate} Hz`,
171
+ trackCount: tracks.length,
172
+ groupCount,
173
+ returnTrackCount: returnTracks.length,
174
+ m4lConnected: cache.isConnected,
175
+ tracks: semanticTracks,
176
+ returnTracks: semanticReturns,
177
+ masterTrack: semanticMaster,
178
+ sessionSummary: generateSessionSummary(semanticTracks, semanticReturns, semanticMaster),
179
+ };
180
+ }
181
+ //# sourceMappingURL=context-assembler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-assembler.js","sourceRoot":"","sources":["../src/context-assembler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SemanticTrack, HeuristicFinding } from "../types.js";
2
+ export declare function checkDynamics(tracks: SemanticTrack[]): HeuristicFinding[];
3
+ //# sourceMappingURL=dynamics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamics.d.ts","sourceRoot":"","sources":["../../src/heuristics/dynamics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASnE,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,gBAAgB,EAAE,CAmDzE"}
@@ -0,0 +1,53 @@
1
+ const TRANSIENT_KEYWORDS = ["kick", "snare", "808", "drum", "perc", "hat", "clap", "rim"];
2
+ function isTransientTrack(track) {
3
+ const lower = track.name.toLowerCase();
4
+ return TRANSIENT_KEYWORDS.some((kw) => lower.includes(kw));
5
+ }
6
+ export function checkDynamics(tracks) {
7
+ const findings = [];
8
+ for (const track of tracks) {
9
+ for (const device of track.devices) {
10
+ if (!device.active)
11
+ continue;
12
+ // Slow attack on transient-heavy tracks
13
+ if (device.name === "Compressor" &&
14
+ isTransientTrack(track)) {
15
+ const attack = device.parameters["Attack"];
16
+ if (attack) {
17
+ const ms = parseFloat(attack);
18
+ if (ms > 50) {
19
+ findings.push({
20
+ id: "slow_attack_on_transient_track",
21
+ severity: "warning",
22
+ track: track.name,
23
+ device: device.name,
24
+ message: `Compressor attack is ${attack} — transients passing through uncompressed on a percussive track`,
25
+ suggestion: `Try 10-20ms for tighter transient control`,
26
+ });
27
+ }
28
+ }
29
+ }
30
+ // High ratio without matching fast attack
31
+ if (device.name === "Compressor") {
32
+ const ratio = device.parameters["Ratio"];
33
+ const attack = device.parameters["Attack"];
34
+ if (ratio && attack) {
35
+ const ratioVal = parseFloat(ratio);
36
+ const attackMs = parseFloat(attack);
37
+ if (ratioVal > 10 && attackMs > 30) {
38
+ findings.push({
39
+ id: "high_ratio_slow_attack",
40
+ severity: "info",
41
+ track: track.name,
42
+ device: device.name,
43
+ message: `High ratio (${ratio}) with slow attack (${attack}) — not catching transients despite aggressive ratio`,
44
+ suggestion: `Either lower the ratio or speed up the attack`,
45
+ });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return findings;
52
+ }
53
+ //# sourceMappingURL=dynamics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamics.js","sourceRoot":"","sources":["../../src/heuristics/dynamics.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAE1F,SAAS,gBAAgB,CAAC,KAAoB;IAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACvC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAuB;IACnD,MAAM,QAAQ,GAAuB,EAAE,CAAC;IAExC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,SAAS;YAE7B,wCAAwC;YACxC,IACE,MAAM,CAAC,IAAI,KAAK,YAAY;gBAC5B,gBAAgB,CAAC,KAAK,CAAC,EACvB,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;gBAC3C,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;oBAC9B,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;wBACZ,QAAQ,CAAC,IAAI,CAAC;4BACZ,EAAE,EAAE,gCAAgC;4BACpC,QAAQ,EAAE,SAAS;4BACnB,KAAK,EAAE,KAAK,CAAC,IAAI;4BACjB,MAAM,EAAE,MAAM,CAAC,IAAI;4BACnB,OAAO,EAAE,wBAAwB,MAAM,kEAAkE;4BACzG,UAAU,EAAE,2CAA2C;yBACxD,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YAED,0CAA0C;YAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBACjC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACzC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;gBAC3C,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;oBACpB,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;oBACnC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;oBACpC,IAAI,QAAQ,GAAG,EAAE,IAAI,QAAQ,GAAG,EAAE,EAAE,CAAC;wBACnC,QAAQ,CAAC,IAAI,CAAC;4BACZ,EAAE,EAAE,wBAAwB;4BAC5B,QAAQ,EAAE,MAAM;4BAChB,KAAK,EAAE,KAAK,CAAC,IAAI;4BACjB,MAAM,EAAE,MAAM,CAAC,IAAI;4BACnB,OAAO,EAAE,eAAe,KAAK,uBAAuB,MAAM,sDAAsD;4BAChH,UAAU,EAAE,+CAA+C;yBAC5D,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SemanticTrack, HeuristicFinding } from "../types.js";
2
+ export declare function checkFrequencyBuildup(tracks: SemanticTrack[]): HeuristicFinding[];
3
+ //# sourceMappingURL=frequency-buildup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frequency-buildup.d.ts","sourceRoot":"","sources":["../../src/heuristics/frequency-buildup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASnE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EAAE,GACtB,gBAAgB,EAAE,CAiEpB"}
@@ -0,0 +1,69 @@
1
+ const BASS_KEYWORDS = ["bass", "808", "sub", "kick", "low"];
2
+ function isBassTrack(track) {
3
+ const lower = track.name.toLowerCase();
4
+ return BASS_KEYWORDS.some((kw) => lower.includes(kw));
5
+ }
6
+ export function checkFrequencyBuildup(tracks) {
7
+ const findings = [];
8
+ // Check for non-bass tracks missing a high-pass
9
+ for (const track of tracks) {
10
+ if (isBassTrack(track) || track.type === "group")
11
+ continue;
12
+ const hasHighPass = track.devices.some((d) => {
13
+ if (d.name !== "EQ Eight" && d.name !== "Eq8")
14
+ return false;
15
+ // Check if band 1 is configured as a high-pass (heuristic: low frequency + active)
16
+ // This is a simplification — full implementation checks filter type parameter
17
+ return d.active;
18
+ });
19
+ // Only flag if track has devices at all (empty tracks aren't a concern)
20
+ if (!hasHighPass && track.devices.length > 0) {
21
+ findings.push({
22
+ id: "no_high_pass",
23
+ severity: "info",
24
+ track: track.name,
25
+ device: null,
26
+ message: `No EQ with high-pass detected — sub-bass accumulation possible`,
27
+ suggestion: `Consider adding a high-pass filter around 30-80Hz to clean up low-end`,
28
+ });
29
+ }
30
+ }
31
+ // Check for multiple tracks boosting low-mids
32
+ // This requires EQ Eight gain analysis across tracks
33
+ let lowMidBoostCount = 0;
34
+ const lowMidBoostTracks = [];
35
+ for (const track of tracks) {
36
+ for (const device of track.devices) {
37
+ if (!device.active)
38
+ continue;
39
+ if (device.name !== "EQ Eight" && device.name !== "Eq8")
40
+ continue;
41
+ // Check bands in the 150-500Hz range for boosts
42
+ for (const [paramName, value] of Object.entries(device.parameters)) {
43
+ if (!paramName.includes("Gain"))
44
+ continue;
45
+ const db = parseFloat(value);
46
+ if (db > 3) {
47
+ // Check if the corresponding frequency is in the low-mid range
48
+ // This is a simplified check — full implementation correlates gain with frequency
49
+ lowMidBoostCount++;
50
+ if (!lowMidBoostTracks.includes(track.name)) {
51
+ lowMidBoostTracks.push(track.name);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ if (lowMidBoostTracks.length >= 3) {
58
+ findings.push({
59
+ id: "low_mid_buildup",
60
+ severity: "warning",
61
+ track: null,
62
+ device: null,
63
+ message: `${lowMidBoostTracks.length} tracks have EQ boosts in the low-mid range (${lowMidBoostTracks.join(", ")}) — likely causing muddiness`,
64
+ suggestion: `Review these tracks for competing boosts in the 150-500Hz range. Consider cutting on some tracks rather than boosting on others.`,
65
+ });
66
+ }
67
+ return findings;
68
+ }
69
+ //# sourceMappingURL=frequency-buildup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frequency-buildup.js","sourceRoot":"","sources":["../../src/heuristics/frequency-buildup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SemanticTrack, HeuristicFinding } from "../types.js";
2
+ export declare function checkHeadroom(tracks: SemanticTrack[], masterTrack: SemanticTrack): HeuristicFinding[];
3
+ //# sourceMappingURL=headroom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headroom.d.ts","sourceRoot":"","sources":["../../src/heuristics/headroom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EAAE,EACvB,WAAW,EAAE,aAAa,GACzB,gBAAgB,EAAE,CA0DpB"}
@@ -0,0 +1,54 @@
1
+ export function checkHeadroom(tracks, masterTrack) {
2
+ const findings = [];
3
+ // Master volume above 0dB
4
+ const masterDb = parseFloat(masterTrack.volume);
5
+ if (!isNaN(masterDb) && masterDb > 0) {
6
+ findings.push({
7
+ id: "master_above_unity",
8
+ severity: "issue",
9
+ track: "Master",
10
+ device: null,
11
+ message: `Master track volume is ${masterTrack.volume} — no headroom`,
12
+ suggestion: `Bring master fader to 0dB or below. Gain should come from individual tracks.`,
13
+ });
14
+ }
15
+ // Count tracks near or above 0dB
16
+ const hotTracks = tracks.filter((t) => {
17
+ const db = parseFloat(t.volume);
18
+ return !isNaN(db) && db >= -1;
19
+ });
20
+ if (hotTracks.length > 5) {
21
+ findings.push({
22
+ id: "many_hot_tracks",
23
+ severity: "warning",
24
+ track: null,
25
+ device: null,
26
+ message: `${hotTracks.length} tracks are near or above 0dB — gain staging may need attention`,
27
+ suggestion: `Consider pulling track volumes down collectively and using makeup gain or bus volume to compensate.`,
28
+ });
29
+ }
30
+ // Master limiter working hard (check for limiter on master with low threshold)
31
+ for (const device of masterTrack.devices) {
32
+ if (!device.active)
33
+ continue;
34
+ if (device.name.toLowerCase().includes("limiter") ||
35
+ device.name.toLowerCase().includes("glue compressor")) {
36
+ const threshold = device.parameters["Threshold"];
37
+ if (threshold) {
38
+ const threshDb = parseFloat(threshold);
39
+ if (!isNaN(threshDb) && threshDb < -6) {
40
+ findings.push({
41
+ id: "master_limiter_overloaded",
42
+ severity: "warning",
43
+ track: "Master",
44
+ device: device.name,
45
+ message: `Master ${device.name} threshold at ${threshold} — likely doing heavy gain reduction`,
46
+ suggestion: `Mix may be hitting the master too hot. Pull back track/bus levels to reduce limiter workload.`,
47
+ });
48
+ }
49
+ }
50
+ }
51
+ }
52
+ return findings;
53
+ }
54
+ //# sourceMappingURL=headroom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headroom.js","sourceRoot":"","sources":["../../src/heuristics/headroom.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAC3B,MAAuB,EACvB,WAA0B;IAE1B,MAAM,QAAQ,GAAuB,EAAE,CAAC;IAExC,0BAA0B;IAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,oBAAoB;YACxB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,0BAA0B,WAAW,CAAC,MAAM,gBAAgB;YACrE,UAAU,EAAE,8EAA8E;SAC3F,CAAC,CAAC;IACL,CAAC;IAED,iCAAiC;IACjC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACpC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,iBAAiB;YACrB,QAAQ,EAAE,SAAS;YACnB,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,GAAG,SAAS,CAAC,MAAM,iEAAiE;YAC7F,UAAU,EAAE,qGAAqG;SAClH,CAAC,CAAC;IACL,CAAC;IAED,+EAA+E;IAC/E,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,SAAS;QAC7B,IACE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EACrD,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YACjD,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;gBACvC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;oBACtC,QAAQ,CAAC,IAAI,CAAC;wBACZ,EAAE,EAAE,2BAA2B;wBAC/B,QAAQ,EAAE,SAAS;wBACnB,KAAK,EAAE,QAAQ;wBACf,MAAM,EAAE,MAAM,CAAC,IAAI;wBACnB,OAAO,EAAE,UAAU,MAAM,CAAC,IAAI,iBAAiB,SAAS,sCAAsC;wBAC9F,UAAU,EAAE,+FAA+F;qBAC5G,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SemanticSession, HeuristicFinding } from "../types.js";
2
+ export declare function runHeuristics(session: SemanticSession): HeuristicFinding[];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/heuristics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAMrB,wBAAgB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,gBAAgB,EAAE,CAmB1E"}
@@ -0,0 +1,22 @@
1
+ import { checkDynamics } from "./dynamics.js";
2
+ import { checkFrequencyBuildup } from "./frequency-buildup.js";
3
+ import { checkHeadroom } from "./headroom.js";
4
+ import { checkRouting } from "./routing.js";
5
+ export function runHeuristics(session) {
6
+ const allTracks = [
7
+ ...session.tracks,
8
+ ...session.returnTracks,
9
+ session.masterTrack,
10
+ ];
11
+ const findings = [
12
+ ...checkDynamics(session.tracks),
13
+ ...checkFrequencyBuildup(session.tracks),
14
+ ...checkHeadroom(session.tracks, session.masterTrack),
15
+ ...checkRouting(session.tracks, session.returnTracks),
16
+ ];
17
+ // Sort by severity: issue > warning > info
18
+ const severityOrder = { issue: 0, warning: 1, info: 2 };
19
+ findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
20
+ return findings;
21
+ }
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/heuristics/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,UAAU,aAAa,CAAC,OAAwB;IACpD,MAAM,SAAS,GAAoB;QACjC,GAAG,OAAO,CAAC,MAAM;QACjB,GAAG,OAAO,CAAC,YAAY;QACvB,OAAO,CAAC,WAAW;KACpB,CAAC;IAEF,MAAM,QAAQ,GAAuB;QACnC,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC;QAChC,GAAG,qBAAqB,CAAC,OAAO,CAAC,MAAM,CAAC;QACxC,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC;QACrD,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC;KACtD,CAAC;IAEF,2CAA2C;IAC3C,MAAM,aAAa,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACxD,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE/E,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SemanticTrack, HeuristicFinding } from "../types.js";
2
+ export declare function checkRouting(tracks: SemanticTrack[], returnTracks: SemanticTrack[]): HeuristicFinding[];
3
+ //# sourceMappingURL=routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/heuristics/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EAAE,EACvB,YAAY,EAAE,aAAa,EAAE,GAC5B,gBAAgB,EAAE,CAoEpB"}
@@ -0,0 +1,60 @@
1
+ export function checkRouting(tracks, returnTracks) {
2
+ const findings = [];
3
+ // Check for many tracks routing directly to master with no bus structure
4
+ const directToMaster = tracks.filter((t) => t.type !== "group" &&
5
+ (t.outputRouting.toLowerCase() === "master" ||
6
+ t.outputRouting === ""));
7
+ if (directToMaster.length > 12 && tracks.some((t) => t.type !== "group")) {
8
+ const groupCount = tracks.filter((t) => t.type === "group").length;
9
+ if (groupCount === 0) {
10
+ findings.push({
11
+ id: "no_bus_structure",
12
+ severity: "info",
13
+ track: null,
14
+ device: null,
15
+ message: `${directToMaster.length} tracks routing directly to master with no group buses — session could benefit from bus structure`,
16
+ suggestion: `Consider creating group tracks (e.g., Drums Bus, Music Bus, Vocal Bus) for better mix management and processing.`,
17
+ });
18
+ }
19
+ }
20
+ // Check for duplicate reverb settings across tracks
21
+ const reverbDevices = [];
22
+ for (const track of tracks) {
23
+ for (const device of track.devices) {
24
+ if (!device.active)
25
+ continue;
26
+ if (device.name.toLowerCase().includes("reverb") ||
27
+ device.name === "Reverb") {
28
+ // Create a rough fingerprint of the reverb settings
29
+ const fingerprint = Object.entries(device.parameters)
30
+ .sort(([a], [b]) => a.localeCompare(b))
31
+ .map(([k, v]) => `${k}:${v}`)
32
+ .join("|");
33
+ reverbDevices.push({ track: track.name, params: fingerprint });
34
+ }
35
+ }
36
+ }
37
+ if (reverbDevices.length >= 4) {
38
+ // Check if many have similar settings
39
+ const fingerprints = new Map();
40
+ for (const rd of reverbDevices) {
41
+ const existing = fingerprints.get(rd.params) ?? [];
42
+ existing.push(rd.track);
43
+ fingerprints.set(rd.params, existing);
44
+ }
45
+ for (const [, trackNames] of fingerprints) {
46
+ if (trackNames.length >= 3) {
47
+ findings.push({
48
+ id: "duplicate_reverbs",
49
+ severity: "info",
50
+ track: null,
51
+ device: null,
52
+ message: `${trackNames.length} tracks have similar reverb settings (${trackNames.join(", ")}) — consider consolidating to a return track`,
53
+ suggestion: `Move the reverb to a return track and use sends. This gives a more cohesive space and saves CPU.`,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ return findings;
59
+ }
60
+ //# sourceMappingURL=routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/heuristics/routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import "dotenv/config";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ import "dotenv/config";
2
+ import express from "express";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { WsBridge } from "./ws-bridge.js";
5
+ import { createMcpServer } from "./mcp-server.js";
6
+ const WS_PORT = parseInt(process.env.WS_PORT ?? "8765", 10);
7
+ const MCP_PORT = parseInt(process.env.MCP_PORT ?? "3000", 10);
8
+ // ============================================================
9
+ // Start WebSocket bridge
10
+ // ============================================================
11
+ const bridge = new WsBridge();
12
+ await bridge.start(WS_PORT);
13
+ bridge.on("connected", () => {
14
+ console.log("[main] M4L bridge connected — session data will flow");
15
+ });
16
+ bridge.on("disconnected", () => {
17
+ console.log("[main] M4L bridge disconnected");
18
+ });
19
+ bridge.on("session_updated", () => {
20
+ const trackCount = bridge.cache.raw?.tracks.length ?? 0;
21
+ console.log(`[main] Session updated — ${trackCount} tracks`);
22
+ });
23
+ // ============================================================
24
+ // Start MCP server with streamable HTTP transport
25
+ // ============================================================
26
+ const mcpServer = createMcpServer(bridge);
27
+ const app = express();
28
+ app.post("/mcp", async (req, res) => {
29
+ const transport = new StreamableHTTPServerTransport({
30
+ sessionIdGenerator: undefined, // stateless for now
31
+ });
32
+ await mcpServer.connect(transport);
33
+ await transport.handleRequest(req, res);
34
+ });
35
+ // Health check
36
+ app.get("/health", (_req, res) => {
37
+ res.json({
38
+ status: "ok",
39
+ m4lConnected: bridge.cache.isConnected,
40
+ lastUpdated: bridge.cache.lastUpdated,
41
+ trackCount: bridge.cache.raw?.tracks.length ?? 0,
42
+ });
43
+ });
44
+ app.listen(MCP_PORT, () => {
45
+ console.log(`[main] AbletonMCP server running`);
46
+ console.log(`[main] MCP endpoint: http://localhost:${MCP_PORT}/mcp`);
47
+ console.log(`[main] WebSocket bridge: ws://localhost:${WS_PORT}`);
48
+ console.log(`[main] Health check: http://localhost:${MCP_PORT}/health`);
49
+ });
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAC5D,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAE9D,+DAA+D;AAC/D,yBAAyB;AACzB,+DAA+D;AAE/D,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;AAC9B,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAE5B,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;IAC1B,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;IAC7B,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAChC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,4BAA4B,UAAU,SAAS,CAAC,CAAC;AAC/D,CAAC,CAAC,CAAC;AAEH,+DAA+D;AAC/D,kDAAkD;AAClD,+DAA+D;AAE/D,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;AAC1C,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AAEtB,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IAClC,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;QAClD,kBAAkB,EAAE,SAAS,EAAE,oBAAoB;KACpD,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC,CAAC,CAAC;AAEH,eAAe;AACf,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAC/B,GAAG,CAAC,IAAI,CAAC;QACP,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW;QACtC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW;QACrC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC;KACjD,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,MAAM,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,6CAA6C,OAAO,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,SAAS,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { WsBridge } from "./ws-bridge.js";
3
+ export declare function createMcpServer(bridge: WsBridge): McpServer;
4
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA+C/C,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CA2e3D"}