@williamzujkowski/live-coding-music-mcp 3.0.0 → 4.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 (53) hide show
  1. package/README.md +212 -780
  2. package/dist/AudioAnalyzer.js +1294 -91
  3. package/dist/PatternStore.js +276 -23
  4. package/dist/StrudelController.js +388 -56
  5. package/dist/server/resources.js +27 -20
  6. package/dist/server/server.js +391 -83
  7. package/dist/server/tools/ai.js +129 -152
  8. package/dist/server/tools/analysis.js +170 -91
  9. package/dist/server/tools/capture.js +128 -55
  10. package/dist/server/tools/compose.js +28 -42
  11. package/dist/server/tools/diagnostics.js +23 -53
  12. package/dist/server/tools/editor.js +63 -72
  13. package/dist/server/tools/generate.js +45 -167
  14. package/dist/server/tools/history.js +35 -66
  15. package/dist/server/tools/playback.js +7 -22
  16. package/dist/server/tools/session.js +41 -47
  17. package/dist/server/tools/storage.js +173 -50
  18. package/dist/server/tools/transform.js +136 -199
  19. package/dist/server/tools/types.js +209 -14
  20. package/dist/services/AudioCaptureService.js +302 -33
  21. package/dist/services/AudioExportService.js +318 -0
  22. package/dist/services/BrowserOnlyFunctions.js +149 -0
  23. package/dist/services/EventDensityProbe.js +324 -0
  24. package/dist/services/GeminiService.js +221 -36
  25. package/dist/services/IsolatedEngineRunner.js +308 -0
  26. package/dist/services/IsolatedStrudelEngine.js +135 -0
  27. package/dist/services/LocalPatternEngine.js +16 -0
  28. package/dist/services/MIDIExportService.js +765 -71
  29. package/dist/services/MIDIImportService.js +474 -0
  30. package/dist/services/MusicTheory.js +85 -13
  31. package/dist/services/PatternGenerator.js +168 -85
  32. package/dist/services/PatternSandbox.js +359 -0
  33. package/dist/services/SessionManager.js +338 -65
  34. package/dist/services/StrudelEngine.js +218 -12
  35. package/dist/services/StrudelEngineHelpers.js +24 -8
  36. package/dist/services/StyleRegistry.js +204 -0
  37. package/dist/services/Transferable.js +114 -0
  38. package/dist/services/ai/AiTransport.js +56 -0
  39. package/dist/services/ai/AudioMeasurements.js +155 -0
  40. package/dist/services/ai/CliTransport.js +256 -0
  41. package/dist/services/engineChild.js +83 -0
  42. package/dist/services/engineChildPath.js +28 -0
  43. package/dist/utils/CategorisedError.js +59 -0
  44. package/dist/utils/ErrorRecovery.js +130 -110
  45. package/dist/utils/InputValidator.js +55 -7
  46. package/dist/utils/PatternValidator.js +136 -17
  47. package/dist/utils/SafePath.js +112 -0
  48. package/dist/utils/ServerConfig.js +248 -0
  49. package/dist/utils/TableLookup.js +41 -0
  50. package/dist/utils/Tempo.js +143 -0
  51. package/package.json +17 -5
  52. package/dist/services/__mocks__/StrudelEngine.js +0 -157
  53. package/dist/types/index.js +0 -1
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Decides whether a pattern query is safe to materialize (#360).
3
+ *
4
+ * ## What was wrong with the old guard
5
+ *
6
+ * `queryEvents` sampled the first 1e-4 of the requested range, extrapolated,
7
+ * and refused if the projection exceeded the cap. Measured, that guard has
8
+ * two independent holes:
9
+ *
10
+ * s("bd*200000") -> refused correctly
11
+ * s("~ bd*200000") -> OUT OF HEAP. One leading rest, and the probe
12
+ * sees nothing, projects nothing, refuses nothing.
13
+ * s("[bd*99999]*99999") -> OUT OF HEAP. 1e-4 of a 10^10-event cycle is
14
+ * still 10^6 events; the probe dies before it
15
+ * can refuse.
16
+ *
17
+ * The second is the cheaper attack: it needs no density in the probe
18
+ * window at all, just ordinary musical notation.
19
+ *
20
+ * ## What this does instead
21
+ *
22
+ * Probes at several offsets spread across the range — so a rest at the
23
+ * start cannot hide anything — with the span grown geometrically from
24
+ * very small, so a dense pattern is caught while the sample is still
25
+ * tiny.
26
+ *
27
+ * ## The rule that makes it safe to extrapolate
28
+ *
29
+ * Measured, and it is the part that is easy to get wrong:
30
+ *
31
+ * s("bd").slow(64) at span 1e-12 -> 1 hap
32
+ * stack(s("bd*4"), s("hh*8")) at span 1e-12 -> 2 haps
33
+ *
34
+ * Naively projecting the first gives 10^12 events and refuses one of the
35
+ * most ordinary patterns there is. Events legitimately begin at t=0, and
36
+ * a window narrower than the true inter-onset interval ALWAYS
37
+ * over-projects. So a projection is only taken once the sample looks
38
+ * like a sample: at least `minDistinctOnsets` DISTINCT onset times
39
+ * inside the window. Distinct times, not hap count — a stack of N layers
40
+ * all starting together is one moment observed N times, and says nothing
41
+ * about density.
42
+ *
43
+ * (The old `PROBE_FRACTION = 1e-4` avoided this by arithmetic accident:
44
+ * one stray hap projects to 10^4, which happens to sit under the 50,000
45
+ * cap. Any smaller constant would have begun refusing real patterns.)
46
+ *
47
+ * ## Residual
48
+ *
49
+ * Sampling cannot be sound against arbitrary non-uniform density. Three
50
+ * shapes get past this, all of them contained since #307 as an error
51
+ * envelope and a respawned child rather than a dead server:
52
+ *
53
+ * - density confined to a gap between every window, at every rung;
54
+ * - a burst so dense that growing a window into it from an adjacent
55
+ * rest is itself fatal, since growth is what discovers it;
56
+ * - clustered events sitting exactly on a slice boundary, which the
57
+ * phase offset deliberately steps away from.
58
+ *
59
+ * The last is the price of decorrelating from the cycle, and it is worth
60
+ * paying: without the offset, EVERY window on a multi-cycle range shares
61
+ * one phase, and a leading rest hides a pattern from all of them.
62
+ */
63
+ /**
64
+ * Windows spread across the range. This is the guard's resolution: a
65
+ * dense region narrower than 1/OFFSETS of the requested range, sitting
66
+ * between two windows, is not seen.
67
+ *
68
+ * Six was tried and rejected — it misses density confined to the last
69
+ * EIGHTH, which is an ordinary thing for a pattern to do. Coverage is
70
+ * worth more here than the ~15ms it costs, because this runs on
71
+ * query_pattern_events, a diagnostic, and not on the write path.
72
+ */
73
+ export const DEFAULT_OFFSETS = 8;
74
+ export const DEFAULT_MIN_DISTINCT_ONSETS = 16;
75
+ /**
76
+ * Onsets a window should hold before its density is believed.
77
+ *
78
+ * Small samples are unreliable in both directions and the error is not
79
+ * academic: measured, a 1e-3 window at t=0 of `s("bd*40000")` holds 80
80
+ * onsets where the cycle average is 40, so extrapolating from it
81
+ * projects 80,000 against a 50,000 cap and refuses a pattern that is
82
+ * comfortably legal. Growing the window until it holds a real sample
83
+ * costs a few thousand haps and removes the guesswork; it is a much
84
+ * better answer than a fudge factor on the threshold.
85
+ */
86
+ export const DEFAULT_SAMPLE_TARGET = 2000;
87
+ /**
88
+ * How far above the cap a SMALL sample must project before it is
89
+ * believed without growing the window.
90
+ *
91
+ * This is the escape hatch for the pathological case: at 10^10 events
92
+ * per cycle there is no growing the window — the next step up would
93
+ * materialize more than the heap holds. A sample projecting ten times
94
+ * the cap is not a borderline call, and waiting for a bigger one is how
95
+ * the old guard died.
96
+ */
97
+ export const OBVIOUSLY_HUGE_MULTIPLE = 10;
98
+ /**
99
+ * Three rungs, not five. Every rung costs a query at every offset, and a
100
+ * `queryArc` call has ~1.8 ms of fixed overhead regardless of how little
101
+ * it returns — so the ladder's length, not its contents, is what the
102
+ * ordinary sparse pattern pays for.
103
+ *
104
+ * The first rung has to be tiny or the probe is the thing that runs out
105
+ * of heap: 1e-4 of a 10^10-event cycle is a million events, which is how
106
+ * the guard this replaced died. From there, three orders of magnitude a
107
+ * step reaches a usable sample for anything the cap cares about.
108
+ */
109
+ export const DEFAULT_LADDER = [1e-8, 1e-6, 1e-4, 1e-2];
110
+ /**
111
+ * Fractional part of the golden ratio, used to offset each window within
112
+ * its own slice.
113
+ *
114
+ * Evenly spaced windows are not enough, and the failure is not subtle.
115
+ * `query_pattern_events` accepts up to 16 cycles; with 8 windows across
116
+ * 16 cycles every window starts on a whole cycle — the SAME PHASE, eight
117
+ * times. So a pattern with a leading rest hides from all of them:
118
+ *
119
+ * s("~ bd*200000") over 0..1 -> refused
120
+ * s("~ bd*200000") over 0..4 -> OUT OF HEAP
121
+ * s("~ bd*200000") over 0..16 -> OUT OF HEAP
122
+ *
123
+ * Musical patterns repeat per cycle, so any window placement that is
124
+ * commensurate with the cycle samples one phase repeatedly and calls it
125
+ * coverage. An irrational step decorrelates the phases while keeping one
126
+ * window per slice, which pure golden-ratio placement would not — that
127
+ * leaves whole slices empty.
128
+ */
129
+ const GOLDEN_FRACTION = 0.618033988749895;
130
+ /**
131
+ * Hard ceiling on a probe window, in cycles, whatever the range.
132
+ *
133
+ * Ladder rungs are fractions of the requested range, and the range may be
134
+ * 16 cycles — so the top rung would be 0.16 cycles wide, and a window
135
+ * that starts in a rest and grows across the boundary into a dense region
136
+ * materializes in proportion to how far it reaches. Capping the absolute
137
+ * width bounds that exposure by 16x on the widest ranges without
138
+ * changing any verdict: density is per cycle, so a smaller window
139
+ * measures it just as well.
140
+ */
141
+ const MAX_PROBE_CYCLES = 0.01;
142
+ /**
143
+ * Decides whether querying `[start, end)` is safe.
144
+ *
145
+ * @param queryArc - The pattern's own query function
146
+ * @param start - Range start, in cycles
147
+ * @param end - Range end, in cycles
148
+ * @param options - Cap and probe shape
149
+ * @returns A refusal with its evidence, or permission to proceed
150
+ */
151
+ export function probeEventDensity(queryArc, start, end, options) {
152
+ const span = end - start;
153
+ const offsets = options.offsets ?? DEFAULT_OFFSETS;
154
+ const minDistinct = options.minDistinctOnsets ?? DEFAULT_MIN_DISTINCT_ONSETS;
155
+ const sampleTarget = options.sampleTarget ?? DEFAULT_SAMPLE_TARGET;
156
+ const ladder = options.ladder ?? DEFAULT_LADDER;
157
+ const cap = options.maxEvents;
158
+ let queries = 0;
159
+ let observedOnsets = 0;
160
+ /** Onsets and window width accumulated across every BELIEVABLE sample. */
161
+ let sampledOnsets = 0;
162
+ let sampledSpan = 0;
163
+ /**
164
+ * Every onset seen in a decision sample, believable or not.
165
+ *
166
+ * Windows are disjoint, so this is a LOWER BOUND on the events inside
167
+ * the requested range — evidence rather than arithmetic. It is the only
168
+ * thing that catches clustered events: eight chords of ten thousand
169
+ * layers hold 80,000 events and describe no density at all, so the
170
+ * distinctness gate rejects every sample and the projection has nothing
171
+ * to work with. Counting still works. (Found by cross-model review
172
+ * after my first attempt refused to accumulate unbelievable samples and
173
+ * let exactly this through.)
174
+ */
175
+ let countedFloor = 0;
176
+ /** How many windows contributed anything. Used to sanity-check an empty result. */
177
+ let windowsWithOnsets = 0;
178
+ if (span <= 0 || offsets < 1) {
179
+ return { kind: 'proceed', queries, observedOnsets, windowsWithOnsets: 0 };
180
+ }
181
+ const step = span / offsets;
182
+ for (let i = 0; i < offsets; i++) {
183
+ // Where in this slice the window sits. Irrational in the cycle, so
184
+ // eight windows sample eight different phases rather than the same
185
+ // one eight times — see GOLDEN_FRACTION.
186
+ const phase = (i * GOLDEN_FRACTION) % 1;
187
+ for (let rung = 0; rung < ladder.length; rung++) {
188
+ const fraction = ladder[rung];
189
+ const probeSpan = Math.min(span * fraction, MAX_PROBE_CYCLES);
190
+ if (probeSpan <= 0)
191
+ continue;
192
+ // Slid within its own slice, and never so far that the window runs
193
+ // past the slice's end — which would put the last one past `end`.
194
+ const windowStart = start + i * step + phase * (step - probeSpan);
195
+ // Whether there is anywhere left to grow. The largest sample this
196
+ // offset can produce still has to be USED — discarding it because
197
+ // it fell short of the target is how a pattern at 120,000 events
198
+ // per cycle walked straight past the guard.
199
+ const canGrow = rung + 1 < ladder.length;
200
+ const windowEnd = windowStart + probeSpan;
201
+ const haps = queryArc(windowStart, windowEnd);
202
+ queries++;
203
+ // Onsets only. A note held across the window began somewhere else
204
+ // and is not evidence of density here.
205
+ const onsets = [];
206
+ for (const hap of haps) {
207
+ const begin = hap.whole?.begin.valueOf();
208
+ if (begin === undefined)
209
+ continue;
210
+ if (begin >= windowStart && begin < windowEnd)
211
+ onsets.push(begin);
212
+ }
213
+ observedOnsets = Math.max(observedOnsets, onsets.length);
214
+ // Counted, not projected. Every one of these onsets is inside the
215
+ // requested range, so seeing cap-many in a single window proves the
216
+ // range holds at least that many — no extrapolation, and no
217
+ // distinctness gate to pass.
218
+ //
219
+ // This is the hole cross-model review (agy) found: a pattern with
220
+ // thousands of layers starting together clears the sample target
221
+ // while failing the distinctness gate, and fell out of the ladder
222
+ // without a verdict. Simultaneous events say nothing about DENSITY,
223
+ // but they are still events, and there can be too many of them.
224
+ if (onsets.length >= cap) {
225
+ return {
226
+ kind: 'refuse',
227
+ projected: onsets.length,
228
+ sampleCount: onsets.length,
229
+ spanFraction: probeSpan / span,
230
+ };
231
+ }
232
+ // From the actual window width, not the nominal rung: the two
233
+ // differ once MAX_PROBE_CYCLES bites, and using the rung would
234
+ // under-project by the same factor.
235
+ const projected = Math.round((onsets.length * span) / probeSpan);
236
+ const distinct = new Set(onsets).size;
237
+ // Enough separate moments for the count to describe a rate rather
238
+ // than a chord. See minDistinctOnsets.
239
+ const believable = distinct >= minDistinct;
240
+ // Obviously huge: refuse on a small sample rather than grow the
241
+ // window into the heap. Only this shortcut acts on one window;
242
+ // everything nearer the cap is decided in aggregate below.
243
+ if (believable && projected > cap * OBVIOUSLY_HUGE_MULTIPLE) {
244
+ return {
245
+ kind: 'refuse',
246
+ projected,
247
+ sampleCount: onsets.length,
248
+ // The width actually queried, which is not the rung once
249
+ // MAX_PROBE_CYCLES clamps it.
250
+ spanFraction: probeSpan / span,
251
+ };
252
+ }
253
+ // A sample big enough to trust — or the biggest this offset will
254
+ // ever produce. Either way, record it and move to the next offset.
255
+ if (onsets.length >= sampleTarget || !canGrow) {
256
+ countedFloor += onsets.length;
257
+ if (onsets.length > 0)
258
+ windowsWithOnsets++;
259
+ if (countedFloor > cap) {
260
+ return {
261
+ kind: 'refuse',
262
+ projected: countedFloor,
263
+ sampleCount: countedFloor,
264
+ spanFraction: probeSpan / span,
265
+ };
266
+ }
267
+ // EVERY window that reaches here counts, distinct or not.
268
+ //
269
+ // The distinctness gate exists to stop a rate being extrapolated
270
+ // from a handful of stacked events — `s("bd").slow(64)` returns
271
+ // one hap at any span, and projecting from it refused ordinary
272
+ // patterns (#360 rule 1). It was never meant to discard a window
273
+ // that is *full*: a window holding `sampleTarget` events is
274
+ // evidence of density whatever their onset spelling.
275
+ //
276
+ // Discarding it let a pattern through that the probe exists to
277
+ // refuse. Measured, 150,000 onset instants x 250 simultaneous
278
+ // layers — 37,500,000 events in one cycle:
279
+ //
280
+ // before: {"kind":"proceed","observedOnsets":3750,
281
+ // "windowsWithOnsets":8}
282
+ //
283
+ // Every one of the eight windows saw the density and none of
284
+ // them was allowed to say so: each cleared `sampleTarget` (so
285
+ // the ladder stopped) but failed `minDistinct` (so it was not
286
+ // recorded), and `countedFloor` stayed under the cap because
287
+ // eight small windows hold few events in absolute terms (#460).
288
+ //
289
+ // Unconditional, and it has to say so. The fix for that wrote
290
+ // `if (believable || windowIsFull || !canGrow)` — inside a
291
+ // branch already guarded by `onsets.length >= sampleTarget ||
292
+ // !canGrow`, which implies it. The condition was always true,
293
+ // so `believable` had no effect here and the code described a
294
+ // gate it did not have. Reaching this line means the window is
295
+ // full or maximal; both count, so there is nothing to test
296
+ // (#464). `believable` still gates the obviously-huge shortcut
297
+ // above, which is the place the distinctness rule belongs.
298
+ sampledOnsets += onsets.length;
299
+ sampledSpan += probeSpan;
300
+ break;
301
+ }
302
+ }
303
+ }
304
+ // Averaged across every window, not taken from the densest one.
305
+ //
306
+ // The max is right for the obvious cases and wrong for the borderline:
307
+ // measured, a window at t=0 of `s("bd*40000")` sees twice the cycle's
308
+ // average density, so projecting from it gives 80,000 against a 50,000
309
+ // cap and refuses a pattern holding 40,001 events. Averaging over
310
+ // windows spread across the range is what makes the near-cap verdict
311
+ // trustworthy; the shortcut above still handles anything wildly over.
312
+ if (sampledSpan > 0) {
313
+ const projected = Math.round((sampledOnsets * span) / sampledSpan);
314
+ if (projected > cap) {
315
+ return {
316
+ kind: 'refuse',
317
+ projected,
318
+ sampleCount: sampledOnsets,
319
+ spanFraction: sampledSpan / span,
320
+ };
321
+ }
322
+ }
323
+ return { kind: 'proceed', queries, observedOnsets, windowsWithOnsets };
324
+ }