@mcptoolshop/ai_jam_session 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.
- package/LICENSE +21 -0
- package/README.es.md +212 -0
- package/README.fr.md +212 -0
- package/README.hi.md +212 -0
- package/README.it.md +212 -0
- package/README.ja.md +214 -0
- package/README.md +214 -0
- package/README.pt-BR.md +212 -0
- package/dist/audio-engine.d.ts +9 -0
- package/dist/audio-engine.d.ts.map +1 -0
- package/dist/audio-engine.js +422 -0
- package/dist/audio-engine.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +551 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +903 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/midi/parser.d.ts +16 -0
- package/dist/midi/parser.d.ts.map +1 -0
- package/dist/midi/parser.js +192 -0
- package/dist/midi/parser.js.map +1 -0
- package/dist/midi/types.d.ts +44 -0
- package/dist/midi/types.d.ts.map +1 -0
- package/dist/midi/types.js +8 -0
- package/dist/midi/types.js.map +1 -0
- package/dist/note-parser.d.ts +105 -0
- package/dist/note-parser.d.ts.map +1 -0
- package/dist/note-parser.js +278 -0
- package/dist/note-parser.js.map +1 -0
- package/dist/playback/controls.d.ts +124 -0
- package/dist/playback/controls.d.ts.map +1 -0
- package/dist/playback/controls.js +252 -0
- package/dist/playback/controls.js.map +1 -0
- package/dist/playback/midi-engine.d.ts +68 -0
- package/dist/playback/midi-engine.d.ts.map +1 -0
- package/dist/playback/midi-engine.js +227 -0
- package/dist/playback/midi-engine.js.map +1 -0
- package/dist/playback/position.d.ts +95 -0
- package/dist/playback/position.d.ts.map +1 -0
- package/dist/playback/position.js +223 -0
- package/dist/playback/position.js.map +1 -0
- package/dist/playback/timing.d.ts +31 -0
- package/dist/playback/timing.d.ts.map +1 -0
- package/dist/playback/timing.js +57 -0
- package/dist/playback/timing.js.map +1 -0
- package/dist/sample-engine.d.ts +17 -0
- package/dist/sample-engine.d.ts.map +1 -0
- package/dist/sample-engine.js +428 -0
- package/dist/sample-engine.js.map +1 -0
- package/dist/schemas.d.ts +40 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +42 -0
- package/dist/schemas.js.map +1 -0
- package/dist/session.d.ts +106 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +361 -0
- package/dist/session.js.map +1 -0
- package/dist/sfz-parser.d.ts +36 -0
- package/dist/sfz-parser.d.ts.map +1 -0
- package/dist/sfz-parser.js +95 -0
- package/dist/sfz-parser.js.map +1 -0
- package/dist/smoke.d.ts +2 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +512 -0
- package/dist/smoke.js.map +1 -0
- package/dist/songs/config/loader.d.ts +14 -0
- package/dist/songs/config/loader.d.ts.map +1 -0
- package/dist/songs/config/loader.js +53 -0
- package/dist/songs/config/loader.js.map +1 -0
- package/dist/songs/config/schema.d.ts +70 -0
- package/dist/songs/config/schema.d.ts.map +1 -0
- package/dist/songs/config/schema.js +53 -0
- package/dist/songs/config/schema.js.map +1 -0
- package/dist/songs/index.d.ts +16 -0
- package/dist/songs/index.d.ts.map +1 -0
- package/dist/songs/index.js +20 -0
- package/dist/songs/index.js.map +1 -0
- package/dist/songs/jam.d.ts +48 -0
- package/dist/songs/jam.d.ts.map +1 -0
- package/dist/songs/jam.js +324 -0
- package/dist/songs/jam.js.map +1 -0
- package/dist/songs/loader.d.ts +27 -0
- package/dist/songs/loader.d.ts.map +1 -0
- package/dist/songs/loader.js +90 -0
- package/dist/songs/loader.js.map +1 -0
- package/dist/songs/midi/hands.d.ts +46 -0
- package/dist/songs/midi/hands.d.ts.map +1 -0
- package/dist/songs/midi/hands.js +134 -0
- package/dist/songs/midi/hands.js.map +1 -0
- package/dist/songs/midi/ingest.d.ts +8 -0
- package/dist/songs/midi/ingest.d.ts.map +1 -0
- package/dist/songs/midi/ingest.js +191 -0
- package/dist/songs/midi/ingest.js.map +1 -0
- package/dist/songs/midi/measures.d.ts +41 -0
- package/dist/songs/midi/measures.d.ts.map +1 -0
- package/dist/songs/midi/measures.js +64 -0
- package/dist/songs/midi/measures.js.map +1 -0
- package/dist/songs/midi/types.d.ts +25 -0
- package/dist/songs/midi/types.d.ts.map +1 -0
- package/dist/songs/midi/types.js +8 -0
- package/dist/songs/midi/types.js.map +1 -0
- package/dist/songs/registry.d.ts +37 -0
- package/dist/songs/registry.d.ts.map +1 -0
- package/dist/songs/registry.js +197 -0
- package/dist/songs/registry.js.map +1 -0
- package/dist/songs/types.d.ts +99 -0
- package/dist/songs/types.d.ts.map +1 -0
- package/dist/songs/types.js +27 -0
- package/dist/songs/types.js.map +1 -0
- package/dist/teaching/live-midi-feedback.d.ts +36 -0
- package/dist/teaching/live-midi-feedback.d.ts.map +1 -0
- package/dist/teaching/live-midi-feedback.js +259 -0
- package/dist/teaching/live-midi-feedback.js.map +1 -0
- package/dist/teaching/midi-feedback.d.ts +33 -0
- package/dist/teaching/midi-feedback.d.ts.map +1 -0
- package/dist/teaching/midi-feedback.js +208 -0
- package/dist/teaching/midi-feedback.js.map +1 -0
- package/dist/teaching/sing-on-midi.d.ts +77 -0
- package/dist/teaching/sing-on-midi.d.ts.map +1 -0
- package/dist/teaching/sing-on-midi.js +186 -0
- package/dist/teaching/sing-on-midi.js.map +1 -0
- package/dist/teaching.d.ts +148 -0
- package/dist/teaching.d.ts.map +1 -0
- package/dist/teaching.js +453 -0
- package/dist/teaching.js.map +1 -0
- package/dist/test-sound.d.ts +3 -0
- package/dist/test-sound.d.ts.map +1 -0
- package/dist/test-sound.js +41 -0
- package/dist/test-sound.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/vmpk.d.ts +23 -0
- package/dist/vmpk.d.ts.map +1 -0
- package/dist/vmpk.js +236 -0
- package/dist/vmpk.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +70 -0
- package/songs/builtin/a-change-is-gonna-come.json +95 -0
- package/songs/builtin/a-thousand-years.json +93 -0
- package/songs/builtin/aint-no-sunshine.json +98 -0
- package/songs/builtin/all-blues.json +92 -0
- package/songs/builtin/autumn-leaves.json +100 -0
- package/songs/builtin/baba-oriley.json +91 -0
- package/songs/builtin/bach-invention-no1.json +70 -0
- package/songs/builtin/bach-prelude-c-major-bwv846.json +1282 -0
- package/songs/builtin/basic-12-bar-blues.json +119 -0
- package/songs/builtin/beethoven-waldstein-mvt1.json +7766 -0
- package/songs/builtin/bennie-and-the-jets.json +92 -0
- package/songs/builtin/besame-mucho.json +93 -0
- package/songs/builtin/black-orpheus.json +92 -0
- package/songs/builtin/blue-bossa.json +94 -0
- package/songs/builtin/blues-in-g.json +92 -0
- package/songs/builtin/bohemian-rhapsody-intro.json +94 -0
- package/songs/builtin/boogie-woogie-basics.json +93 -0
- package/songs/builtin/bossa-nova-basic.json +95 -0
- package/songs/builtin/chopin-nocturne-op9-no2.json +70 -0
- package/songs/builtin/cinema-paradiso.json +94 -0
- package/songs/builtin/clair-de-lune.json +11511 -0
- package/songs/builtin/clocks.json +91 -0
- package/songs/builtin/crystal-stream.json +70 -0
- package/songs/builtin/desafinado.json +93 -0
- package/songs/builtin/dont-stop-believin.json +91 -0
- package/songs/builtin/dream-on.json +100 -0
- package/songs/builtin/easy-winners.json +70 -0
- package/songs/builtin/el-condor-pasa.json +93 -0
- package/songs/builtin/elite-syncopations.json +70 -0
- package/songs/builtin/evening-calm.json +70 -0
- package/songs/builtin/everyday-blues.json +93 -0
- package/songs/builtin/fly-me-to-the-moon.json +91 -0
- package/songs/builtin/forrest-gump-suite.json +93 -0
- package/songs/builtin/fur-elise.json +20094 -0
- package/songs/builtin/georgia-on-my-mind.json +93 -0
- package/songs/builtin/girl-from-ipanema.json +92 -0
- package/songs/builtin/gladiolus-rag.json +70 -0
- package/songs/builtin/great-balls-of-fire.json +92 -0
- package/songs/builtin/guantanamera.json +92 -0
- package/songs/builtin/hallelujah.json +92 -0
- package/songs/builtin/hedwigs-theme.json +93 -0
- package/songs/builtin/hotel-california.json +92 -0
- package/songs/builtin/imagine.json +92 -0
- package/songs/builtin/just-the-two-of-us.json +92 -0
- package/songs/builtin/la-bamba.json +92 -0
- package/songs/builtin/layla-piano-coda.json +93 -0
- package/songs/builtin/lean-on-me.json +91 -0
- package/songs/builtin/let-it-be.json +101 -0
- package/songs/builtin/lets-stay-together.json +93 -0
- package/songs/builtin/magnetic-rag.json +70 -0
- package/songs/builtin/maple-leaf-rag.json +99 -0
- package/songs/builtin/mia-and-sebastians-theme.json +93 -0
- package/songs/builtin/minor-blues-in-a.json +94 -0
- package/songs/builtin/misty.json +94 -0
- package/songs/builtin/moon-river.json +93 -0
- package/songs/builtin/moonlight-sonata-mvt1.json +101 -0
- package/songs/builtin/morning-light.json +70 -0
- package/songs/builtin/mountain-dawn.json +70 -0
- package/songs/builtin/mozart-k545-mvt1.json +2956 -0
- package/songs/builtin/my-girl.json +92 -0
- package/songs/builtin/night-train.json +92 -0
- package/songs/builtin/november-rain.json +93 -0
- package/songs/builtin/ocean-waves.json +70 -0
- package/songs/builtin/over-the-rainbow.json +93 -0
- package/songs/builtin/oye-como-va.json +93 -0
- package/songs/builtin/peacherine-rag.json +70 -0
- package/songs/builtin/piano-man.json +92 -0
- package/songs/builtin/pineapple-rag.json +70 -0
- package/songs/builtin/pink-panther.json +94 -0
- package/songs/builtin/ragtime-dance.json +70 -0
- package/songs/builtin/river-flows-in-you.json +102 -0
- package/songs/builtin/rocket-man.json +92 -0
- package/songs/builtin/satie-gymnopedie-no1.json +70 -0
- package/songs/builtin/satin-doll.json +93 -0
- package/songs/builtin/schindlers-list.json +96 -0
- package/songs/builtin/schumann-traumerei.json +70 -0
- package/songs/builtin/sitting-on-the-dock.json +91 -0
- package/songs/builtin/slow-blues-in-bb.json +98 -0
- package/songs/builtin/snowfall.json +70 -0
- package/songs/builtin/so-what.json +92 -0
- package/songs/builtin/solace.json +70 -0
- package/songs/builtin/someone-like-you.json +92 -0
- package/songs/builtin/spirited-away.json +94 -0
- package/songs/builtin/st-louis-blues.json +93 -0
- package/songs/builtin/stairway-intro.json +93 -0
- package/songs/builtin/starlight-waltz.json +70 -0
- package/songs/builtin/stay-with-me.json +93 -0
- package/songs/builtin/stormy-monday.json +94 -0
- package/songs/builtin/superstition.json +93 -0
- package/songs/builtin/sweet-home-chicago.json +93 -0
- package/songs/builtin/take-five.json +92 -0
- package/songs/builtin/take-the-a-train.json +93 -0
- package/songs/builtin/the-entertainer.json +98 -0
- package/songs/builtin/the-godfather-waltz.json +93 -0
- package/songs/builtin/thrill-is-gone.json +94 -0
- package/songs/builtin/twilight-garden.json +70 -0
- package/songs/builtin/watermark.json +70 -0
- package/songs/builtin/wave.json +93 -0
- package/songs/builtin/whats-going-on.json +93 -0
- package/songs/builtin/yesterday.json +92 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MidiNoteEvent } from "../midi/types.js";
|
|
2
|
+
/** A scheduled event with wall-clock time in milliseconds. */
|
|
3
|
+
export interface ScheduledEvent extends MidiNoteEvent {
|
|
4
|
+
/** When to fire noteOn, in ms from playback start. */
|
|
5
|
+
scheduledOnMs: number;
|
|
6
|
+
/** When to fire noteOff, in ms from playback start. */
|
|
7
|
+
scheduledOffMs: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Calculate scheduled wall-clock times for all events.
|
|
11
|
+
*
|
|
12
|
+
* @param events Sorted note events with absolute time in seconds.
|
|
13
|
+
* @param speed Speed multiplier (1.0 = normal).
|
|
14
|
+
* @returns Events with scheduledOnMs and scheduledOffMs.
|
|
15
|
+
*/
|
|
16
|
+
export declare function calculateSchedule(events: readonly MidiNoteEvent[], speed?: number): ScheduledEvent[];
|
|
17
|
+
/**
|
|
18
|
+
* Get the total playback duration in milliseconds at a given speed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function totalDurationMs(events: readonly MidiNoteEvent[], speed?: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Group events into time clusters (events within `thresholdMs` of each other).
|
|
23
|
+
* Useful for chord detection or batch scheduling.
|
|
24
|
+
*/
|
|
25
|
+
export declare function clusterEvents(events: readonly MidiNoteEvent[], thresholdMs?: number): MidiNoteEvent[][];
|
|
26
|
+
/**
|
|
27
|
+
* Slice events to a time range (in seconds, at speed 1.0).
|
|
28
|
+
* Returns events where time >= startSec and time < endSec.
|
|
29
|
+
*/
|
|
30
|
+
export declare function sliceEventsByTime(events: readonly MidiNoteEvent[], startSec: number, endSec: number): MidiNoteEvent[];
|
|
31
|
+
//# sourceMappingURL=timing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../src/playback/timing.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,8DAA8D;AAC9D,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,sDAAsD;IACtD,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,KAAK,GAAE,MAAY,GAClB,cAAc,EAAE,CAMlB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,KAAK,GAAE,MAAY,GAClB,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,WAAW,GAAE,MAAU,GACtB,aAAa,EAAE,EAAE,CAkBnB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,aAAa,EAAE,CAEjB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// ─── Timing & Scheduling Utilities ──────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Helpers for MIDI playback timing calculations.
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Calculate scheduled wall-clock times for all events.
|
|
7
|
+
*
|
|
8
|
+
* @param events Sorted note events with absolute time in seconds.
|
|
9
|
+
* @param speed Speed multiplier (1.0 = normal).
|
|
10
|
+
* @returns Events with scheduledOnMs and scheduledOffMs.
|
|
11
|
+
*/
|
|
12
|
+
export function calculateSchedule(events, speed = 1.0) {
|
|
13
|
+
return events.map((e) => ({
|
|
14
|
+
...e,
|
|
15
|
+
scheduledOnMs: (e.time / speed) * 1000,
|
|
16
|
+
scheduledOffMs: ((e.time + e.duration) / speed) * 1000,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the total playback duration in milliseconds at a given speed.
|
|
21
|
+
*/
|
|
22
|
+
export function totalDurationMs(events, speed = 1.0) {
|
|
23
|
+
if (events.length === 0)
|
|
24
|
+
return 0;
|
|
25
|
+
const last = events[events.length - 1];
|
|
26
|
+
return ((last.time + last.duration) / speed) * 1000;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Group events into time clusters (events within `thresholdMs` of each other).
|
|
30
|
+
* Useful for chord detection or batch scheduling.
|
|
31
|
+
*/
|
|
32
|
+
export function clusterEvents(events, thresholdMs = 5) {
|
|
33
|
+
if (events.length === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const thresholdSec = thresholdMs / 1000;
|
|
36
|
+
const clusters = [];
|
|
37
|
+
let current = [events[0]];
|
|
38
|
+
for (let i = 1; i < events.length; i++) {
|
|
39
|
+
if (events[i].time - events[i - 1].time <= thresholdSec) {
|
|
40
|
+
current.push(events[i]);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
clusters.push(current);
|
|
44
|
+
current = [events[i]];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
clusters.push(current);
|
|
48
|
+
return clusters;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Slice events to a time range (in seconds, at speed 1.0).
|
|
52
|
+
* Returns events where time >= startSec and time < endSec.
|
|
53
|
+
*/
|
|
54
|
+
export function sliceEventsByTime(events, startSec, endSec) {
|
|
55
|
+
return events.filter((e) => e.time >= startSec && e.time < endSec);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/playback/timing.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,iDAAiD;AACjD,gFAAgF;AAYhF;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAgC,EAChC,QAAgB,GAAG;IAEnB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxB,GAAG,CAAC;QACJ,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,IAAI;QACtC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI;KACvD,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAgC,EAChC,QAAgB,GAAG;IAEnB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAgC,EAChC,cAAsB,CAAC;IAEvB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,YAAY,GAAG,WAAW,GAAG,IAAI,CAAC;IACxC,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,IAAI,OAAO,GAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,YAAY,EAAE,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEvB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAgC,EAChC,QAAgB,EAChB,MAAc;IAEd,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,QAAQ,IAAI,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { VmpkConnector } from "./types.js";
|
|
2
|
+
export interface SampleEngineOptions {
|
|
3
|
+
/** Path to the AccurateSalamander directory (contains sfz_minimum/, 48khz24bit/). */
|
|
4
|
+
samplesDir: string;
|
|
5
|
+
/** SFZ profile to use. Default: "sfz_minimum". */
|
|
6
|
+
sfzProfile?: "sfz_minimum" | "sfz_daw" | "sfz_live";
|
|
7
|
+
/** Maximum simultaneous voices. Default: 48. */
|
|
8
|
+
maxPolyphony?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create a sample-based piano engine using Accurate-Salamander samples.
|
|
12
|
+
*
|
|
13
|
+
* Implements VmpkConnector so it's a drop-in replacement for the old
|
|
14
|
+
* oscillator engine.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createSampleEngine(options: SampleEngineOptions): VmpkConnector;
|
|
17
|
+
//# sourceMappingURL=sample-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sample-engine.d.ts","sourceRoot":"","sources":["../src/sample-engine.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,YAAY,CAAC;AAKtE,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;IACpD,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAwFD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa,CAmY9E"}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// ─── pianoai: Sample-Based Piano Engine ─────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Plays real piano samples from the Accurate-Salamander Grand Piano library.
|
|
4
|
+
// 480 WAV samples (48kHz/24-bit), 16 velocity layers, 88 keys.
|
|
5
|
+
//
|
|
6
|
+
// This replaces the oscillator-based engine with actual recorded sound.
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// const piano = createSampleEngine({ samplesDir: "samples/AccurateSalamander" });
|
|
10
|
+
// await piano.connect(); // loads SFZ + WAV files (~1.6GB)
|
|
11
|
+
// piano.noteOn(60, 100); // middle C, forte — real piano sound
|
|
12
|
+
// piano.noteOff(60);
|
|
13
|
+
// await piano.disconnect();
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { parseSfzFile } from "./sfz-parser.js";
|
|
18
|
+
function buildRegionMap(regions) {
|
|
19
|
+
const map = new Map();
|
|
20
|
+
for (const r of regions) {
|
|
21
|
+
for (let key = r.lokey; key <= r.hikey; key++) {
|
|
22
|
+
let slots = map.get(key);
|
|
23
|
+
if (!slots) {
|
|
24
|
+
slots = [];
|
|
25
|
+
map.set(key, slots);
|
|
26
|
+
}
|
|
27
|
+
slots.push({ lovel: r.lovel, hivel: r.hivel, region: r });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Sort by lovel for binary search
|
|
31
|
+
for (const [, slots] of map) {
|
|
32
|
+
slots.sort((a, b) => a.lovel - b.lovel);
|
|
33
|
+
}
|
|
34
|
+
return map;
|
|
35
|
+
}
|
|
36
|
+
/** Find the matching region for a given MIDI note + velocity. */
|
|
37
|
+
function findRegion(regionMap, note, velocity) {
|
|
38
|
+
const slots = regionMap.get(note);
|
|
39
|
+
if (!slots || slots.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
for (const slot of slots) {
|
|
42
|
+
if (velocity >= slot.lovel && velocity <= slot.hivel)
|
|
43
|
+
return slot.region;
|
|
44
|
+
}
|
|
45
|
+
// Fallback: closest velocity
|
|
46
|
+
return slots[slots.length - 1].region;
|
|
47
|
+
}
|
|
48
|
+
// ─── Audio Helpers ──────────────────────────────────────────────────────────
|
|
49
|
+
/** Stereo pan: bass left, treble right (player perspective). */
|
|
50
|
+
function noteToPan(note) {
|
|
51
|
+
return Math.max(-0.7, Math.min(0.7, ((note - 21) / 87) * 1.4 - 0.7));
|
|
52
|
+
}
|
|
53
|
+
/** Convert dB to linear gain. */
|
|
54
|
+
function dbToGain(db) {
|
|
55
|
+
return Math.pow(10, db / 20);
|
|
56
|
+
}
|
|
57
|
+
/** Compute playback rate for pitch shifting: cents from sample's pitch center. */
|
|
58
|
+
function centsToRate(cents) {
|
|
59
|
+
return Math.pow(2, cents / 1200);
|
|
60
|
+
}
|
|
61
|
+
// ─── Lazy Import ────────────────────────────────────────────────────────────
|
|
62
|
+
let _AudioContext = null;
|
|
63
|
+
async function loadAudioContext() {
|
|
64
|
+
if (!_AudioContext) {
|
|
65
|
+
const mod = await import("node-web-audio-api");
|
|
66
|
+
_AudioContext = mod.AudioContext;
|
|
67
|
+
}
|
|
68
|
+
return _AudioContext;
|
|
69
|
+
}
|
|
70
|
+
// ─── Engine ─────────────────────────────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Create a sample-based piano engine using Accurate-Salamander samples.
|
|
73
|
+
*
|
|
74
|
+
* Implements VmpkConnector so it's a drop-in replacement for the old
|
|
75
|
+
* oscillator engine.
|
|
76
|
+
*/
|
|
77
|
+
export function createSampleEngine(options) {
|
|
78
|
+
const { samplesDir, sfzProfile = "sfz_minimum", maxPolyphony = 48, } = options;
|
|
79
|
+
let ctx = null;
|
|
80
|
+
let currentStatus = "disconnected";
|
|
81
|
+
let compressor = null;
|
|
82
|
+
let master = null;
|
|
83
|
+
// Sample data
|
|
84
|
+
let sfzData = null;
|
|
85
|
+
let regionMap = null;
|
|
86
|
+
const audioBuffers = new Map(); // sample path → AudioBuffer
|
|
87
|
+
// Voice management
|
|
88
|
+
const activeVoices = new Map();
|
|
89
|
+
const voiceOrder = [];
|
|
90
|
+
// ── WAV Parsing ──
|
|
91
|
+
/**
|
|
92
|
+
* Parse a WAV file into an AudioBuffer manually.
|
|
93
|
+
* Handles 16-bit and 24-bit PCM (the Accurate-Salamander set is 24-bit/48kHz).
|
|
94
|
+
* This avoids needing the async decodeAudioData call for 480+ files.
|
|
95
|
+
*/
|
|
96
|
+
function parseWavToAudioBuffer(filePath) {
|
|
97
|
+
const fileData = readFileSync(filePath);
|
|
98
|
+
const view = new DataView(fileData.buffer, fileData.byteOffset, fileData.byteLength);
|
|
99
|
+
// ── Find 'fmt ' chunk ──
|
|
100
|
+
let offset = 12; // skip RIFF header (4 RIFF + 4 size + 4 WAVE)
|
|
101
|
+
let fmtOffset = -1;
|
|
102
|
+
let dataOffset = -1;
|
|
103
|
+
let dataSize = 0;
|
|
104
|
+
while (offset < view.byteLength - 8) {
|
|
105
|
+
const chunkId = String.fromCharCode(view.getUint8(offset)) +
|
|
106
|
+
String.fromCharCode(view.getUint8(offset + 1)) +
|
|
107
|
+
String.fromCharCode(view.getUint8(offset + 2)) +
|
|
108
|
+
String.fromCharCode(view.getUint8(offset + 3));
|
|
109
|
+
const chunkSize = view.getUint32(offset + 4, true);
|
|
110
|
+
if (chunkId === "fmt ") {
|
|
111
|
+
fmtOffset = offset + 8;
|
|
112
|
+
}
|
|
113
|
+
else if (chunkId === "data") {
|
|
114
|
+
dataOffset = offset + 8;
|
|
115
|
+
dataSize = chunkSize;
|
|
116
|
+
}
|
|
117
|
+
if (fmtOffset >= 0 && dataOffset >= 0)
|
|
118
|
+
break;
|
|
119
|
+
// Next chunk (aligned to 2 bytes)
|
|
120
|
+
offset += 8 + chunkSize + (chunkSize % 2);
|
|
121
|
+
}
|
|
122
|
+
if (fmtOffset < 0)
|
|
123
|
+
throw new Error(`No 'fmt ' chunk in ${filePath}`);
|
|
124
|
+
if (dataOffset < 0)
|
|
125
|
+
throw new Error(`No 'data' chunk in ${filePath}`);
|
|
126
|
+
// ── Parse format ──
|
|
127
|
+
let audioFormat = view.getUint16(fmtOffset, true);
|
|
128
|
+
const numChannels = view.getUint16(fmtOffset + 2, true);
|
|
129
|
+
const sampleRate = view.getUint32(fmtOffset + 4, true);
|
|
130
|
+
let bitsPerSample = view.getUint16(fmtOffset + 14, true);
|
|
131
|
+
// WAVE_FORMAT_EXTENSIBLE (0xFFFE / 65534): real format is in SubFormat GUID
|
|
132
|
+
if (audioFormat === 0xFFFE) {
|
|
133
|
+
// cbSize at offset 16 (should be 22), wValidBitsPerSample at offset 18
|
|
134
|
+
const validBits = view.getUint16(fmtOffset + 18, true);
|
|
135
|
+
if (validBits > 0)
|
|
136
|
+
bitsPerSample = validBits;
|
|
137
|
+
// SubFormat GUID starts at offset 24, first 2 bytes = actual format tag
|
|
138
|
+
audioFormat = view.getUint16(fmtOffset + 24, true);
|
|
139
|
+
}
|
|
140
|
+
if (audioFormat !== 1 && audioFormat !== 3) {
|
|
141
|
+
throw new Error(`Unsupported WAV format ${audioFormat} (need PCM=1 or IEEE_FLOAT=3) in ${filePath}`);
|
|
142
|
+
}
|
|
143
|
+
const isFloat = audioFormat === 3;
|
|
144
|
+
const bytesPerSample = bitsPerSample / 8;
|
|
145
|
+
const numFrames = Math.floor(dataSize / (numChannels * bytesPerSample));
|
|
146
|
+
// ── Create AudioBuffer ──
|
|
147
|
+
const audioBuffer = ctx.createBuffer(numChannels, numFrames, sampleRate);
|
|
148
|
+
// ── Decode PCM data into Float32 channel arrays ──
|
|
149
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
150
|
+
const channelData = new Float32Array(numFrames);
|
|
151
|
+
for (let i = 0; i < numFrames; i++) {
|
|
152
|
+
const sampleOffset = dataOffset + (i * numChannels + ch) * bytesPerSample;
|
|
153
|
+
let sample;
|
|
154
|
+
if (isFloat && bitsPerSample === 32) {
|
|
155
|
+
// 32-bit IEEE float — already -1..1
|
|
156
|
+
sample = view.getFloat32(sampleOffset, true);
|
|
157
|
+
}
|
|
158
|
+
else if (isFloat && bitsPerSample === 64) {
|
|
159
|
+
// 64-bit IEEE float
|
|
160
|
+
sample = view.getFloat64(sampleOffset, true);
|
|
161
|
+
}
|
|
162
|
+
else if (bitsPerSample === 24) {
|
|
163
|
+
// 24-bit signed little-endian → float
|
|
164
|
+
const b0 = view.getUint8(sampleOffset);
|
|
165
|
+
const b1 = view.getUint8(sampleOffset + 1);
|
|
166
|
+
const b2 = view.getUint8(sampleOffset + 2);
|
|
167
|
+
const raw = b0 | (b1 << 8) | (b2 << 16);
|
|
168
|
+
// Sign extend from 24-bit
|
|
169
|
+
sample = (raw > 0x7FFFFF ? raw - 0x1000000 : raw) / 8388608; // 2^23
|
|
170
|
+
}
|
|
171
|
+
else if (bitsPerSample === 16) {
|
|
172
|
+
// 16-bit signed little-endian → float
|
|
173
|
+
sample = view.getInt16(sampleOffset, true) / 32768; // 2^15
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
throw new Error(`Unsupported bit depth ${bitsPerSample} in ${filePath}`);
|
|
177
|
+
}
|
|
178
|
+
channelData[i] = sample;
|
|
179
|
+
}
|
|
180
|
+
audioBuffer.copyToChannel(channelData, ch);
|
|
181
|
+
}
|
|
182
|
+
return audioBuffer;
|
|
183
|
+
}
|
|
184
|
+
// ── Sample Loading ──
|
|
185
|
+
/** Load all unique sample files referenced by the SFZ regions. */
|
|
186
|
+
function loadSamples() {
|
|
187
|
+
if (!sfzData)
|
|
188
|
+
return;
|
|
189
|
+
const sfzDir = join(samplesDir, sfzProfile);
|
|
190
|
+
const uniqueSamples = new Set();
|
|
191
|
+
for (const r of sfzData.regions) {
|
|
192
|
+
uniqueSamples.add(r.sample);
|
|
193
|
+
}
|
|
194
|
+
console.error(`Loading ${uniqueSamples.size} piano samples...`);
|
|
195
|
+
const startTime = Date.now();
|
|
196
|
+
let loaded = 0;
|
|
197
|
+
for (const samplePath of uniqueSamples) {
|
|
198
|
+
// Resolve relative path from SFZ file location
|
|
199
|
+
const fullPath = join(sfzDir, samplePath);
|
|
200
|
+
try {
|
|
201
|
+
const audioBuffer = parseWavToAudioBuffer(fullPath);
|
|
202
|
+
audioBuffers.set(samplePath, audioBuffer);
|
|
203
|
+
loaded++;
|
|
204
|
+
if (loaded % 50 === 0) {
|
|
205
|
+
console.error(` ${loaded}/${uniqueSamples.size} samples loaded`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
console.error(` SKIP ${samplePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
213
|
+
console.error(`Loaded ${loaded}/${uniqueSamples.size} samples in ${elapsed}s`);
|
|
214
|
+
}
|
|
215
|
+
// ── Voice Management ──
|
|
216
|
+
function stealOldest() {
|
|
217
|
+
if (voiceOrder.length === 0)
|
|
218
|
+
return;
|
|
219
|
+
const oldestNote = voiceOrder.shift();
|
|
220
|
+
const voice = activeVoices.get(oldestNote);
|
|
221
|
+
if (voice) {
|
|
222
|
+
killVoice(voice);
|
|
223
|
+
activeVoices.delete(oldestNote);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function removeFromOrder(note) {
|
|
227
|
+
const idx = voiceOrder.indexOf(note);
|
|
228
|
+
if (idx >= 0)
|
|
229
|
+
voiceOrder.splice(idx, 1);
|
|
230
|
+
}
|
|
231
|
+
function killVoice(voice) {
|
|
232
|
+
if (voice.cleanupTimer) {
|
|
233
|
+
clearTimeout(voice.cleanupTimer);
|
|
234
|
+
voice.cleanupTimer = null;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
voice.source.stop();
|
|
238
|
+
}
|
|
239
|
+
catch { /* already stopped */ }
|
|
240
|
+
try {
|
|
241
|
+
voice.source.disconnect();
|
|
242
|
+
}
|
|
243
|
+
catch { /* ok */ }
|
|
244
|
+
try {
|
|
245
|
+
voice.gain.disconnect();
|
|
246
|
+
}
|
|
247
|
+
catch { /* ok */ }
|
|
248
|
+
try {
|
|
249
|
+
voice.panner.disconnect();
|
|
250
|
+
}
|
|
251
|
+
catch { /* ok */ }
|
|
252
|
+
}
|
|
253
|
+
function releaseVoice(voice) {
|
|
254
|
+
if (voice.released)
|
|
255
|
+
return;
|
|
256
|
+
voice.released = true;
|
|
257
|
+
const now = ctx.currentTime;
|
|
258
|
+
const releaseTime = sfzData?.ampegRelease ?? 1.0;
|
|
259
|
+
// Fade out over release time
|
|
260
|
+
voice.gain.gain.cancelScheduledValues(now);
|
|
261
|
+
voice.gain.gain.setValueAtTime(voice.gain.gain.value, now);
|
|
262
|
+
voice.gain.gain.exponentialRampToValueAtTime(0.001, now + releaseTime);
|
|
263
|
+
// Cleanup after release
|
|
264
|
+
voice.cleanupTimer = setTimeout(() => killVoice(voice), (releaseTime + 0.1) * 1000);
|
|
265
|
+
}
|
|
266
|
+
// ── VmpkConnector Implementation ──
|
|
267
|
+
return {
|
|
268
|
+
async connect() {
|
|
269
|
+
if (currentStatus === "connected")
|
|
270
|
+
return;
|
|
271
|
+
currentStatus = "connecting";
|
|
272
|
+
try {
|
|
273
|
+
// 1. Create audio context
|
|
274
|
+
const AC = await loadAudioContext();
|
|
275
|
+
ctx = new AC({ sampleRate: 48000, latencyHint: "playback" });
|
|
276
|
+
// 2. Master chain: compressor → gain → speakers
|
|
277
|
+
compressor = ctx.createDynamicsCompressor();
|
|
278
|
+
compressor.threshold.value = -12;
|
|
279
|
+
compressor.knee.value = 10;
|
|
280
|
+
compressor.ratio.value = 4;
|
|
281
|
+
compressor.attack.value = 0.003;
|
|
282
|
+
compressor.release.value = 0.25;
|
|
283
|
+
master = ctx.createGain();
|
|
284
|
+
master.gain.value = 0.85;
|
|
285
|
+
compressor.connect(master);
|
|
286
|
+
master.connect(ctx.destination);
|
|
287
|
+
// 3. Parse SFZ
|
|
288
|
+
const sfzFile = join(samplesDir, sfzProfile, sfzProfile === "sfz_minimum"
|
|
289
|
+
? "Accurate-SalamanderGrandPiano_flat.Recommended.sfz"
|
|
290
|
+
: sfzProfile === "sfz_daw"
|
|
291
|
+
? "Accurate-SalamanderGrandPiano_flat.Recommended.sfz"
|
|
292
|
+
: "Accurate-SalamanderGrandPiano_flat.Recommended.sfz");
|
|
293
|
+
sfzData = parseSfzFile(sfzFile);
|
|
294
|
+
regionMap = buildRegionMap(sfzData.regions);
|
|
295
|
+
// 4. Load all WAV samples
|
|
296
|
+
loadSamples();
|
|
297
|
+
currentStatus = "connected";
|
|
298
|
+
console.error(`Piano engine connected (sample-based, ${audioBuffers.size} samples)`);
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
currentStatus = "error";
|
|
302
|
+
throw new Error(`Failed to start sample engine: ${err instanceof Error ? err.message : String(err)}`);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
async disconnect() {
|
|
306
|
+
for (const [, voice] of activeVoices) {
|
|
307
|
+
try {
|
|
308
|
+
killVoice(voice);
|
|
309
|
+
}
|
|
310
|
+
catch { /* ok */ }
|
|
311
|
+
}
|
|
312
|
+
activeVoices.clear();
|
|
313
|
+
voiceOrder.length = 0;
|
|
314
|
+
audioBuffers.clear();
|
|
315
|
+
sfzData = null;
|
|
316
|
+
regionMap = null;
|
|
317
|
+
if (ctx) {
|
|
318
|
+
try {
|
|
319
|
+
await ctx.close();
|
|
320
|
+
}
|
|
321
|
+
catch { /* ok */ }
|
|
322
|
+
ctx = null;
|
|
323
|
+
compressor = null;
|
|
324
|
+
master = null;
|
|
325
|
+
}
|
|
326
|
+
currentStatus = "disconnected";
|
|
327
|
+
},
|
|
328
|
+
status() {
|
|
329
|
+
return currentStatus;
|
|
330
|
+
},
|
|
331
|
+
listPorts() {
|
|
332
|
+
return ["Accurate-Salamander Grand Piano"];
|
|
333
|
+
},
|
|
334
|
+
noteOn(note, velocity, channel) {
|
|
335
|
+
if (!ctx || currentStatus !== "connected" || !regionMap)
|
|
336
|
+
return;
|
|
337
|
+
// Clamp
|
|
338
|
+
velocity = Math.max(1, Math.min(127, velocity));
|
|
339
|
+
note = Math.max(21, Math.min(108, note));
|
|
340
|
+
// Kill existing voice on same note (retrigger)
|
|
341
|
+
const existing = activeVoices.get(note);
|
|
342
|
+
if (existing) {
|
|
343
|
+
killVoice(existing);
|
|
344
|
+
activeVoices.delete(note);
|
|
345
|
+
removeFromOrder(note);
|
|
346
|
+
}
|
|
347
|
+
// Voice stealing
|
|
348
|
+
while (activeVoices.size >= maxPolyphony) {
|
|
349
|
+
stealOldest();
|
|
350
|
+
}
|
|
351
|
+
// Find the right sample
|
|
352
|
+
const region = findRegion(regionMap, note, velocity);
|
|
353
|
+
if (!region)
|
|
354
|
+
return;
|
|
355
|
+
const audioBuffer = audioBuffers.get(region.sample);
|
|
356
|
+
if (!audioBuffer)
|
|
357
|
+
return;
|
|
358
|
+
const now = ctx.currentTime;
|
|
359
|
+
// Create buffer source
|
|
360
|
+
const source = ctx.createBufferSource();
|
|
361
|
+
source.buffer = audioBuffer;
|
|
362
|
+
// Pitch shift: difference between target note and sample's recorded pitch
|
|
363
|
+
const semitoneDiff = note - region.pitchKeycenter;
|
|
364
|
+
const tuneCents = region.tune; // additional fine tuning from SFZ
|
|
365
|
+
const totalCents = semitoneDiff * 100 + tuneCents;
|
|
366
|
+
source.playbackRate.value = centsToRate(totalCents);
|
|
367
|
+
// Volume: SFZ volume offset + velocity scaling
|
|
368
|
+
const velocity01 = velocity / 127;
|
|
369
|
+
const velTrack = (sfzData?.ampVeltrack ?? 97) / 100;
|
|
370
|
+
// SFZ velocity tracking: gain = (1 - velTrack) + velTrack * velocity01
|
|
371
|
+
const velGain = (1 - velTrack) + velTrack * velocity01;
|
|
372
|
+
const volumeGain = dbToGain(region.volume);
|
|
373
|
+
const gain = ctx.createGain();
|
|
374
|
+
gain.gain.value = velGain * volumeGain * 0.5; // 0.5 = headroom factor
|
|
375
|
+
// Stereo position
|
|
376
|
+
const panner = ctx.createStereoPanner();
|
|
377
|
+
panner.pan.value = noteToPan(note);
|
|
378
|
+
// Connect: source → gain → panner → compressor → master → speakers
|
|
379
|
+
source.connect(gain);
|
|
380
|
+
gain.connect(panner);
|
|
381
|
+
panner.connect(compressor);
|
|
382
|
+
source.start(now);
|
|
383
|
+
const voice = {
|
|
384
|
+
note,
|
|
385
|
+
source,
|
|
386
|
+
gain,
|
|
387
|
+
panner,
|
|
388
|
+
released: false,
|
|
389
|
+
cleanupTimer: null,
|
|
390
|
+
};
|
|
391
|
+
activeVoices.set(note, voice);
|
|
392
|
+
voiceOrder.push(note);
|
|
393
|
+
},
|
|
394
|
+
noteOff(note, channel) {
|
|
395
|
+
if (!ctx || currentStatus !== "connected")
|
|
396
|
+
return;
|
|
397
|
+
const voice = activeVoices.get(note);
|
|
398
|
+
if (voice) {
|
|
399
|
+
releaseVoice(voice);
|
|
400
|
+
activeVoices.delete(note);
|
|
401
|
+
removeFromOrder(note);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
allNotesOff(channel) {
|
|
405
|
+
if (!ctx)
|
|
406
|
+
return;
|
|
407
|
+
for (const [, voice] of activeVoices) {
|
|
408
|
+
killVoice(voice);
|
|
409
|
+
}
|
|
410
|
+
activeVoices.clear();
|
|
411
|
+
voiceOrder.length = 0;
|
|
412
|
+
},
|
|
413
|
+
async playNote(midiNote) {
|
|
414
|
+
if (midiNote.note < 0) {
|
|
415
|
+
await sleep(midiNote.durationMs);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
this.noteOn(midiNote.note, midiNote.velocity, midiNote.channel);
|
|
419
|
+
await sleep(midiNote.durationMs);
|
|
420
|
+
this.noteOff(midiNote.note, midiNote.channel);
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
425
|
+
function sleep(ms) {
|
|
426
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=sample-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sample-engine.js","sourceRoot":"","sources":["../src/sample-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Play a song from the built-in library by ID. */
|
|
3
|
+
export declare const LibraryPlaySchema: z.ZodObject<{
|
|
4
|
+
songId: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
/** Play a local MIDI file. */
|
|
7
|
+
export declare const FilePlaySchema: z.ZodObject<{
|
|
8
|
+
midiPath: z.ZodString;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
/** Play a remote MIDI file by URL. */
|
|
11
|
+
export declare const UrlPlaySchema: z.ZodObject<{
|
|
12
|
+
midiUrl: z.ZodString;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
/**
|
|
15
|
+
* Unified play source — one of: library song, local file, or remote URL.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PlaySourceSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
18
|
+
songId: z.ZodString;
|
|
19
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
20
|
+
midiPath: z.ZodString;
|
|
21
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
22
|
+
midiUrl: z.ZodString;
|
|
23
|
+
}, z.core.$strip>]>;
|
|
24
|
+
/** Infer the TypeScript type. */
|
|
25
|
+
export type PlaySource = z.infer<typeof PlaySourceSchema>;
|
|
26
|
+
/** Shared playback options (apply to all sources). */
|
|
27
|
+
export declare const PlaybackOptionsSchema: z.ZodObject<{
|
|
28
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
tempo: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
31
|
+
full: "full";
|
|
32
|
+
measure: "measure";
|
|
33
|
+
hands: "hands";
|
|
34
|
+
loop: "loop";
|
|
35
|
+
}>>;
|
|
36
|
+
startMeasure: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
endMeasure: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
export type PlaybackOptions = z.infer<typeof PlaybackOptionsSchema>;
|
|
40
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,mDAAmD;AACnD,eAAO,MAAM,iBAAiB;;iBAE5B,CAAC;AAEH,8BAA8B;AAC9B,eAAO,MAAM,cAAc;;iBAEzB,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,aAAa;;iBAExB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;;mBAI3B,CAAC;AAEH,iCAAiC;AACjC,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,sDAAsD;AACtD,eAAO,MAAM,qBAAqB;;;;;;;;;;;iBAWhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ─── Playback Schemas ───────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Zod schemas for play_song input. Supports three playback sources:
|
|
4
|
+
// 1. songId — play from the built-in song library
|
|
5
|
+
// 2. midiPath — play a local .mid file
|
|
6
|
+
// 3. midiUrl — play a remote .mid file (downloaded on demand)
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
/** Play a song from the built-in library by ID. */
|
|
10
|
+
export const LibraryPlaySchema = z.object({
|
|
11
|
+
songId: z.string().describe("Song ID from the built-in library (e.g. 'autumn-leaves')"),
|
|
12
|
+
});
|
|
13
|
+
/** Play a local MIDI file. */
|
|
14
|
+
export const FilePlaySchema = z.object({
|
|
15
|
+
midiPath: z.string().describe("Absolute path to a local .mid file"),
|
|
16
|
+
});
|
|
17
|
+
/** Play a remote MIDI file by URL. */
|
|
18
|
+
export const UrlPlaySchema = z.object({
|
|
19
|
+
midiUrl: z.string().url().describe("URL to a .mid file (downloaded on demand)"),
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Unified play source — one of: library song, local file, or remote URL.
|
|
23
|
+
*/
|
|
24
|
+
export const PlaySourceSchema = z.union([
|
|
25
|
+
LibraryPlaySchema,
|
|
26
|
+
FilePlaySchema,
|
|
27
|
+
UrlPlaySchema,
|
|
28
|
+
]);
|
|
29
|
+
/** Shared playback options (apply to all sources). */
|
|
30
|
+
export const PlaybackOptionsSchema = z.object({
|
|
31
|
+
speed: z.number().min(0.1).max(4).optional()
|
|
32
|
+
.describe("Speed multiplier (0.5 = half, 1.0 = normal, 2.0 = double)"),
|
|
33
|
+
tempo: z.number().int().min(10).max(400).optional()
|
|
34
|
+
.describe("Override tempo in BPM (10-400)"),
|
|
35
|
+
mode: z.enum(["full", "measure", "hands", "loop"]).optional()
|
|
36
|
+
.describe("Playback mode"),
|
|
37
|
+
startMeasure: z.number().int().min(1).optional()
|
|
38
|
+
.describe("Start measure for loop mode (1-based)"),
|
|
39
|
+
endMeasure: z.number().int().min(1).optional()
|
|
40
|
+
.describe("End measure for loop mode (1-based)"),
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=schemas.js.map
|