@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 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,oEAAoE;AACpE,qDAAqD;AACrD,yCAAyC;AACzC,iEAAiE;AACjE,gFAAgF;AAEhF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,mDAAmD;AACnD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;CACxF,CAAC,CAAC;AAEH,8BAA8B;AAC9B,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;CACpE,CAAC,CAAC;AAEH,sCAAsC;AACtC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;CAChF,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC;IACtC,iBAAiB;IACjB,cAAc;IACd,aAAa;CACd,CAAC,CAAC;AAKH,sDAAsD;AACtD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;SACzC,QAAQ,CAAC,2DAA2D,CAAC;IACxE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;SAChD,QAAQ,CAAC,gCAAgC,CAAC;IAC7C,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE;SAC1D,QAAQ,CAAC,eAAe,CAAC;IAC5B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;SAC7C,QAAQ,CAAC,uCAAuC,CAAC;IACpD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;SAC3C,QAAQ,CAAC,qCAAqC,CAAC;CACnD,CAAC,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { SongEntry } from "./songs/types.js";
|
|
2
|
+
import type { Session, SessionOptions, SessionState, ProgressCallback, ParseWarning, VmpkConnector, TeachingHook } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create a new practice session for a song.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createSession(song: SongEntry, connector: VmpkConnector, options?: SessionOptions): SessionController;
|
|
7
|
+
/**
|
|
8
|
+
* Session controller — the main runtime interface for a practice session.
|
|
9
|
+
*
|
|
10
|
+
* Holds the session state + connector + teaching hook.
|
|
11
|
+
* Provides play/pause/stop/skip methods.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SessionController {
|
|
14
|
+
readonly session: Session;
|
|
15
|
+
private readonly connector;
|
|
16
|
+
private readonly teachingHook;
|
|
17
|
+
private abortController;
|
|
18
|
+
private playableMeasures;
|
|
19
|
+
private playStartedAt;
|
|
20
|
+
private lastProgressMilestone;
|
|
21
|
+
private readonly onProgress?;
|
|
22
|
+
private readonly progressInterval;
|
|
23
|
+
/** Parse warnings collected during measure parsing (bad notes skipped). */
|
|
24
|
+
readonly parseWarnings: ParseWarning[];
|
|
25
|
+
constructor(session: Session, connector: VmpkConnector, teachingHook: TeachingHook, onProgress?: ProgressCallback, progressInterval?: number);
|
|
26
|
+
/**
|
|
27
|
+
* Re-parse all measures with current effective tempo.
|
|
28
|
+
* Uses safe parser — collects warnings instead of throwing.
|
|
29
|
+
*/
|
|
30
|
+
private reParseMeasures;
|
|
31
|
+
/**
|
|
32
|
+
* Effective tempo — base tempo (override or song default) * speed multiplier.
|
|
33
|
+
* This is the actual BPM used for playback timing.
|
|
34
|
+
*/
|
|
35
|
+
effectiveTempo(): number;
|
|
36
|
+
/**
|
|
37
|
+
* Base tempo — override or song default, without speed multiplier.
|
|
38
|
+
* Useful for display ("Playing at 60 BPM × 0.5 speed").
|
|
39
|
+
*/
|
|
40
|
+
baseTempo(): number;
|
|
41
|
+
/** Get current state. */
|
|
42
|
+
get state(): SessionState;
|
|
43
|
+
/** Get the current measure (1-based for display). */
|
|
44
|
+
get currentMeasureDisplay(): number;
|
|
45
|
+
/** Total measures in the song. */
|
|
46
|
+
get totalMeasures(): number;
|
|
47
|
+
/**
|
|
48
|
+
* Play the session from the current position.
|
|
49
|
+
*
|
|
50
|
+
* In "full" mode: plays all remaining measures.
|
|
51
|
+
* In "measure" mode: plays one measure and pauses.
|
|
52
|
+
* In "loop" mode: loops the specified range.
|
|
53
|
+
* In "hands" mode: plays RH, then LH, then both for each measure.
|
|
54
|
+
*/
|
|
55
|
+
play(): Promise<void>;
|
|
56
|
+
/** Pause playback. */
|
|
57
|
+
pause(): void;
|
|
58
|
+
/** Stop playback and reset to beginning. */
|
|
59
|
+
stop(): void;
|
|
60
|
+
/** Skip to next measure (in measure/hands mode). */
|
|
61
|
+
next(): void;
|
|
62
|
+
/** Go back to previous measure. */
|
|
63
|
+
prev(): void;
|
|
64
|
+
/** Jump to a specific measure (1-based). */
|
|
65
|
+
goTo(measureNumber: number): void;
|
|
66
|
+
/** Set tempo override (10–400 BPM). */
|
|
67
|
+
setTempo(bpm: number): void;
|
|
68
|
+
/**
|
|
69
|
+
* Set speed multiplier (0.5 = half speed, 1.0 = normal, 2.0 = double).
|
|
70
|
+
* Re-parses all measures with the new effective tempo.
|
|
71
|
+
*/
|
|
72
|
+
setSpeed(speed: number): void;
|
|
73
|
+
/** Get a summary of the current session state. */
|
|
74
|
+
summary(): string;
|
|
75
|
+
/** Build a PlaybackProgress snapshot. */
|
|
76
|
+
private buildProgress;
|
|
77
|
+
/**
|
|
78
|
+
* Emit a progress notification if we've crossed the next milestone.
|
|
79
|
+
* With progressInterval 0.1, fires at 10%, 20%, 30%, … 100%.
|
|
80
|
+
* With progressInterval 0, fires after every measure.
|
|
81
|
+
*/
|
|
82
|
+
private emitProgress;
|
|
83
|
+
/**
|
|
84
|
+
* Play a range of measures (inclusive, 0-based indices).
|
|
85
|
+
* Fires teaching hooks at measure boundaries and key moments.
|
|
86
|
+
*
|
|
87
|
+
* In "concurrent" syncMode: voice and piano play simultaneously (duet feel).
|
|
88
|
+
* In "before" syncMode: voice speaks before piano plays (lecture style).
|
|
89
|
+
* Key moments always fire before playback in both modes (instructional).
|
|
90
|
+
*/
|
|
91
|
+
private playRange;
|
|
92
|
+
/**
|
|
93
|
+
* Play a single measure — both hands in parallel.
|
|
94
|
+
*/
|
|
95
|
+
private playMeasure;
|
|
96
|
+
/**
|
|
97
|
+
* Play a sequence of beats serially.
|
|
98
|
+
*/
|
|
99
|
+
private playBeats;
|
|
100
|
+
/**
|
|
101
|
+
* Play hands separately then together (for "hands" mode).
|
|
102
|
+
* Respects syncMode: concurrent voice plays alongside the first hand pass.
|
|
103
|
+
*/
|
|
104
|
+
private playHandsSeparate;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,YAAY,EAIZ,gBAAgB,EAChB,YAAY,EAEZ,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAMpB;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,aAAa,EACxB,OAAO,GAAE,cAAmB,GAC3B,iBAAiB,CAgCnB;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;aAYV,OAAO,EAAE,OAAO;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAb/B,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,qBAAqB,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C,2EAA2E;IAC3E,QAAQ,CAAC,aAAa,EAAE,YAAY,EAAE,CAAM;gBAG1B,OAAO,EAAE,OAAO,EACf,SAAS,EAAE,aAAa,EACxB,YAAY,EAAE,YAAY,EAC3C,UAAU,CAAC,EAAE,gBAAgB,EAC7B,gBAAgB,CAAC,EAAE,MAAM;IAS3B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAQvB;;;OAGG;IACH,cAAc,IAAI,MAAM;IAKxB;;;OAGG;IACH,SAAS,IAAI,MAAM;IAInB,yBAAyB;IACzB,IAAI,KAAK,IAAI,YAAY,CAExB;IAED,qDAAqD;IACrD,IAAI,qBAAqB,IAAI,MAAM,CAElC;IAED,kCAAkC;IAClC,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsE3B,sBAAsB;IACtB,KAAK,IAAI,IAAI;IAOb,4CAA4C;IAC5C,IAAI,IAAI,IAAI;IAOZ,oDAAoD;IACpD,IAAI,IAAI,IAAI;IAMZ,mCAAmC;IACnC,IAAI,IAAI,IAAI;IAMZ,4CAA4C;IAC5C,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAOjC,uCAAuC;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQ3B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ7B,kDAAkD;IAClD,OAAO,IAAI,MAAM;IAcjB,yCAAyC;IACzC,OAAO,CAAC,aAAa;IAarB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAqBpB;;;;;;;OAOG;YACW,SAAS;IA+CvB;;OAEG;YACW,WAAW;IAWzB;;OAEG;YACW,SAAS;IAavB;;;OAGG;YACW,iBAAiB;CA2ChC"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// ─── pianoai: Session Engine ─────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Manages the playback session: load a song, parse measures, play through
|
|
4
|
+
// VMPK via the connector, track progress, handle pause/resume/stop.
|
|
5
|
+
//
|
|
6
|
+
// Teaching hooks fire at measure boundaries and key moments, allowing
|
|
7
|
+
// the AI teacher to speak, display tips, or push interjections.
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
import { safeParseMeasure } from "./note-parser.js";
|
|
10
|
+
import { createSilentTeachingHook, detectKeyMoments } from "./teaching.js";
|
|
11
|
+
let sessionCounter = 0;
|
|
12
|
+
/**
|
|
13
|
+
* Create a new practice session for a song.
|
|
14
|
+
*/
|
|
15
|
+
export function createSession(song, connector, options = {}) {
|
|
16
|
+
const speed = options.speed ?? 1.0;
|
|
17
|
+
if (speed <= 0 || speed > 4) {
|
|
18
|
+
throw new Error(`Speed must be between 0 (exclusive) and 4: got ${speed}`);
|
|
19
|
+
}
|
|
20
|
+
if (options.tempo !== undefined && (options.tempo < 10 || options.tempo > 400)) {
|
|
21
|
+
throw new Error(`Tempo must be between 10 and 400 BPM: got ${options.tempo}`);
|
|
22
|
+
}
|
|
23
|
+
const session = {
|
|
24
|
+
id: `session-${++sessionCounter}`,
|
|
25
|
+
song,
|
|
26
|
+
state: "loaded",
|
|
27
|
+
mode: options.mode ?? "full",
|
|
28
|
+
syncMode: options.syncMode ?? "concurrent",
|
|
29
|
+
currentMeasure: 0,
|
|
30
|
+
tempoOverride: options.tempo ?? null,
|
|
31
|
+
speed,
|
|
32
|
+
loopRange: options.loopRange ?? null,
|
|
33
|
+
startedAt: new Date(),
|
|
34
|
+
measuresPlayed: 0,
|
|
35
|
+
voiceEnabled: options.voice ?? true,
|
|
36
|
+
};
|
|
37
|
+
return new SessionController(session, connector, options.teachingHook ?? createSilentTeachingHook(), options.onProgress, options.progressInterval);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Session controller — the main runtime interface for a practice session.
|
|
41
|
+
*
|
|
42
|
+
* Holds the session state + connector + teaching hook.
|
|
43
|
+
* Provides play/pause/stop/skip methods.
|
|
44
|
+
*/
|
|
45
|
+
export class SessionController {
|
|
46
|
+
session;
|
|
47
|
+
connector;
|
|
48
|
+
teachingHook;
|
|
49
|
+
abortController = null;
|
|
50
|
+
playableMeasures = [];
|
|
51
|
+
playStartedAt = 0;
|
|
52
|
+
lastProgressMilestone = -1;
|
|
53
|
+
onProgress;
|
|
54
|
+
progressInterval;
|
|
55
|
+
/** Parse warnings collected during measure parsing (bad notes skipped). */
|
|
56
|
+
parseWarnings = [];
|
|
57
|
+
constructor(session, connector, teachingHook, onProgress, progressInterval) {
|
|
58
|
+
this.session = session;
|
|
59
|
+
this.connector = connector;
|
|
60
|
+
this.teachingHook = teachingHook;
|
|
61
|
+
this.onProgress = onProgress;
|
|
62
|
+
this.progressInterval = progressInterval ?? 0.1; // default: every 10%
|
|
63
|
+
// Pre-parse all measures — gracefully skip bad notes
|
|
64
|
+
this.reParseMeasures();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Re-parse all measures with current effective tempo.
|
|
68
|
+
* Uses safe parser — collects warnings instead of throwing.
|
|
69
|
+
*/
|
|
70
|
+
reParseMeasures() {
|
|
71
|
+
this.parseWarnings.length = 0; // clear previous warnings
|
|
72
|
+
const bpm = this.effectiveTempo();
|
|
73
|
+
this.playableMeasures = this.session.song.measures.map((m) => safeParseMeasure(m, bpm, this.parseWarnings));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Effective tempo — base tempo (override or song default) * speed multiplier.
|
|
77
|
+
* This is the actual BPM used for playback timing.
|
|
78
|
+
*/
|
|
79
|
+
effectiveTempo() {
|
|
80
|
+
const base = this.session.tempoOverride ?? this.session.song.tempo;
|
|
81
|
+
return base * this.session.speed;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Base tempo — override or song default, without speed multiplier.
|
|
85
|
+
* Useful for display ("Playing at 60 BPM × 0.5 speed").
|
|
86
|
+
*/
|
|
87
|
+
baseTempo() {
|
|
88
|
+
return this.session.tempoOverride ?? this.session.song.tempo;
|
|
89
|
+
}
|
|
90
|
+
/** Get current state. */
|
|
91
|
+
get state() {
|
|
92
|
+
return this.session.state;
|
|
93
|
+
}
|
|
94
|
+
/** Get the current measure (1-based for display). */
|
|
95
|
+
get currentMeasureDisplay() {
|
|
96
|
+
return this.session.currentMeasure + 1;
|
|
97
|
+
}
|
|
98
|
+
/** Total measures in the song. */
|
|
99
|
+
get totalMeasures() {
|
|
100
|
+
return this.session.song.measures.length;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Play the session from the current position.
|
|
104
|
+
*
|
|
105
|
+
* In "full" mode: plays all remaining measures.
|
|
106
|
+
* In "measure" mode: plays one measure and pauses.
|
|
107
|
+
* In "loop" mode: loops the specified range.
|
|
108
|
+
* In "hands" mode: plays RH, then LH, then both for each measure.
|
|
109
|
+
*/
|
|
110
|
+
async play() {
|
|
111
|
+
if (this.session.state === "playing")
|
|
112
|
+
return;
|
|
113
|
+
if (this.session.state === "finished") {
|
|
114
|
+
// Restart from beginning
|
|
115
|
+
this.session.currentMeasure = 0;
|
|
116
|
+
}
|
|
117
|
+
this.session.state = "playing";
|
|
118
|
+
this.abortController = new AbortController();
|
|
119
|
+
this.playStartedAt = Date.now();
|
|
120
|
+
this.lastProgressMilestone = -1;
|
|
121
|
+
const signal = this.abortController.signal;
|
|
122
|
+
try {
|
|
123
|
+
switch (this.session.mode) {
|
|
124
|
+
case "full":
|
|
125
|
+
await this.playRange(this.session.currentMeasure, this.totalMeasures - 1, signal);
|
|
126
|
+
break;
|
|
127
|
+
case "measure":
|
|
128
|
+
await this.playRange(this.session.currentMeasure, this.session.currentMeasure, signal);
|
|
129
|
+
this.session.state = "paused";
|
|
130
|
+
return;
|
|
131
|
+
case "loop": {
|
|
132
|
+
const [start, end] = this.session.loopRange ?? [1, this.totalMeasures];
|
|
133
|
+
const startIdx = start - 1; // convert to 0-based
|
|
134
|
+
const endIdx = end - 1;
|
|
135
|
+
// Loop forever until stopped
|
|
136
|
+
while (!signal.aborted) {
|
|
137
|
+
await this.playRange(startIdx, endIdx, signal);
|
|
138
|
+
this.session.currentMeasure = startIdx;
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "hands":
|
|
143
|
+
await this.playHandsSeparate(this.session.currentMeasure, signal);
|
|
144
|
+
this.session.state = "paused";
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!signal.aborted) {
|
|
148
|
+
this.session.state = "finished";
|
|
149
|
+
// Fire song-complete hook
|
|
150
|
+
await this.teachingHook.onSongComplete(this.session.measuresPlayed, this.session.song.title);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
if (signal.aborted) {
|
|
155
|
+
// Expected — user stopped playback
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Pause playback. */
|
|
162
|
+
pause() {
|
|
163
|
+
if (this.session.state !== "playing")
|
|
164
|
+
return;
|
|
165
|
+
this.abortController?.abort();
|
|
166
|
+
this.session.state = "paused";
|
|
167
|
+
this.connector.allNotesOff();
|
|
168
|
+
}
|
|
169
|
+
/** Stop playback and reset to beginning. */
|
|
170
|
+
stop() {
|
|
171
|
+
this.abortController?.abort();
|
|
172
|
+
this.session.state = "idle";
|
|
173
|
+
this.session.currentMeasure = 0;
|
|
174
|
+
this.connector.allNotesOff();
|
|
175
|
+
}
|
|
176
|
+
/** Skip to next measure (in measure/hands mode). */
|
|
177
|
+
next() {
|
|
178
|
+
if (this.session.currentMeasure < this.totalMeasures - 1) {
|
|
179
|
+
this.session.currentMeasure++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Go back to previous measure. */
|
|
183
|
+
prev() {
|
|
184
|
+
if (this.session.currentMeasure > 0) {
|
|
185
|
+
this.session.currentMeasure--;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Jump to a specific measure (1-based). */
|
|
189
|
+
goTo(measureNumber) {
|
|
190
|
+
const idx = measureNumber - 1;
|
|
191
|
+
if (idx >= 0 && idx < this.totalMeasures) {
|
|
192
|
+
this.session.currentMeasure = idx;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** Set tempo override (10–400 BPM). */
|
|
196
|
+
setTempo(bpm) {
|
|
197
|
+
if (bpm < 10 || bpm > 400) {
|
|
198
|
+
throw new Error(`Tempo must be between 10 and 400 BPM: got ${bpm}`);
|
|
199
|
+
}
|
|
200
|
+
this.session.tempoOverride = bpm;
|
|
201
|
+
this.reParseMeasures();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Set speed multiplier (0.5 = half speed, 1.0 = normal, 2.0 = double).
|
|
205
|
+
* Re-parses all measures with the new effective tempo.
|
|
206
|
+
*/
|
|
207
|
+
setSpeed(speed) {
|
|
208
|
+
if (speed <= 0 || speed > 4) {
|
|
209
|
+
throw new Error(`Speed must be between 0 (exclusive) and 4: got ${speed}`);
|
|
210
|
+
}
|
|
211
|
+
this.session.speed = speed;
|
|
212
|
+
this.reParseMeasures();
|
|
213
|
+
}
|
|
214
|
+
/** Get a summary of the current session state. */
|
|
215
|
+
summary() {
|
|
216
|
+
const s = this.session;
|
|
217
|
+
const speedStr = s.speed !== 1.0 ? ` × ${s.speed}x` : "";
|
|
218
|
+
const lines = [
|
|
219
|
+
`Session: ${s.id}`,
|
|
220
|
+
`Song: ${s.song.title} (${s.song.composer ?? "Traditional"})`,
|
|
221
|
+
`Genre: ${s.song.genre} | Key: ${s.song.key} | Tempo: ${this.baseTempo()} BPM${speedStr}`,
|
|
222
|
+
`Mode: ${s.mode} | State: ${s.state}`,
|
|
223
|
+
`Progress: measure ${this.currentMeasureDisplay} / ${this.totalMeasures}`,
|
|
224
|
+
`Measures played: ${s.measuresPlayed}`,
|
|
225
|
+
];
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
/** Build a PlaybackProgress snapshot. */
|
|
229
|
+
buildProgress() {
|
|
230
|
+
const current = this.session.measuresPlayed;
|
|
231
|
+
const total = this.totalMeasures;
|
|
232
|
+
const ratio = total > 0 ? current / total : 0;
|
|
233
|
+
return {
|
|
234
|
+
currentMeasure: this.currentMeasureDisplay,
|
|
235
|
+
totalMeasures: total,
|
|
236
|
+
ratio,
|
|
237
|
+
percent: `${Math.round(ratio * 100)}%`,
|
|
238
|
+
elapsedMs: Date.now() - this.playStartedAt,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Emit a progress notification if we've crossed the next milestone.
|
|
243
|
+
* With progressInterval 0.1, fires at 10%, 20%, 30%, … 100%.
|
|
244
|
+
* With progressInterval 0, fires after every measure.
|
|
245
|
+
*/
|
|
246
|
+
emitProgress() {
|
|
247
|
+
if (!this.onProgress)
|
|
248
|
+
return;
|
|
249
|
+
const progress = this.buildProgress();
|
|
250
|
+
if (this.progressInterval <= 0) {
|
|
251
|
+
// Fire after every measure
|
|
252
|
+
this.onProgress(progress);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Check if we've crossed the next milestone
|
|
256
|
+
const currentMilestone = Math.floor(progress.ratio / this.progressInterval);
|
|
257
|
+
if (currentMilestone > this.lastProgressMilestone) {
|
|
258
|
+
this.lastProgressMilestone = currentMilestone;
|
|
259
|
+
this.onProgress(progress);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// ─── Internal playback ──────────────────────────────────────────────────
|
|
263
|
+
/**
|
|
264
|
+
* Play a range of measures (inclusive, 0-based indices).
|
|
265
|
+
* Fires teaching hooks at measure boundaries and key moments.
|
|
266
|
+
*
|
|
267
|
+
* In "concurrent" syncMode: voice and piano play simultaneously (duet feel).
|
|
268
|
+
* In "before" syncMode: voice speaks before piano plays (lecture style).
|
|
269
|
+
* Key moments always fire before playback in both modes (instructional).
|
|
270
|
+
*/
|
|
271
|
+
async playRange(startIdx, endIdx, signal) {
|
|
272
|
+
const concurrent = this.session.syncMode === "concurrent";
|
|
273
|
+
for (let i = startIdx; i <= endIdx; i++) {
|
|
274
|
+
if (signal.aborted)
|
|
275
|
+
return;
|
|
276
|
+
this.session.currentMeasure = i;
|
|
277
|
+
const pm = this.playableMeasures[i];
|
|
278
|
+
const measureNum = i + 1; // 1-based for display/teaching
|
|
279
|
+
// ── Teaching: check for key moments (always before playback) ──
|
|
280
|
+
const keyMoments = detectKeyMoments(this.session.song, measureNum);
|
|
281
|
+
for (const km of keyMoments) {
|
|
282
|
+
if (signal.aborted)
|
|
283
|
+
return;
|
|
284
|
+
await this.teachingHook.onKeyMoment(km);
|
|
285
|
+
}
|
|
286
|
+
// ── Voice + piano: concurrent or sequential ──
|
|
287
|
+
if (concurrent) {
|
|
288
|
+
await Promise.all([
|
|
289
|
+
this.teachingHook.onMeasureStart(measureNum, pm.source.teachingNote, pm.source.dynamics),
|
|
290
|
+
this.playMeasure(pm, signal),
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
await this.teachingHook.onMeasureStart(measureNum, pm.source.teachingNote, pm.source.dynamics);
|
|
295
|
+
await this.playMeasure(pm, signal);
|
|
296
|
+
}
|
|
297
|
+
this.session.measuresPlayed++;
|
|
298
|
+
// ── Progress notification ──
|
|
299
|
+
this.emitProgress();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Play a single measure — both hands in parallel.
|
|
304
|
+
*/
|
|
305
|
+
async playMeasure(pm, signal) {
|
|
306
|
+
// Play both hands simultaneously
|
|
307
|
+
await Promise.all([
|
|
308
|
+
this.playBeats(pm.rightBeats, signal),
|
|
309
|
+
this.playBeats(pm.leftBeats, signal),
|
|
310
|
+
]);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Play a sequence of beats serially.
|
|
314
|
+
*/
|
|
315
|
+
async playBeats(beats, signal) {
|
|
316
|
+
for (const beat of beats) {
|
|
317
|
+
if (signal.aborted)
|
|
318
|
+
return;
|
|
319
|
+
// Play all notes in this beat simultaneously
|
|
320
|
+
const notePromises = beat.notes.map((n) => this.connector.playNote(n));
|
|
321
|
+
await Promise.all(notePromises);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Play hands separately then together (for "hands" mode).
|
|
326
|
+
* Respects syncMode: concurrent voice plays alongside the first hand pass.
|
|
327
|
+
*/
|
|
328
|
+
async playHandsSeparate(measureIdx, signal) {
|
|
329
|
+
const pm = this.playableMeasures[measureIdx];
|
|
330
|
+
const measureNum = measureIdx + 1;
|
|
331
|
+
const concurrent = this.session.syncMode === "concurrent";
|
|
332
|
+
// ── Teaching: check key moments (always before playback) ──
|
|
333
|
+
const keyMoments = detectKeyMoments(this.session.song, measureNum);
|
|
334
|
+
for (const km of keyMoments) {
|
|
335
|
+
if (signal.aborted)
|
|
336
|
+
return;
|
|
337
|
+
await this.teachingHook.onKeyMoment(km);
|
|
338
|
+
}
|
|
339
|
+
// ── Voice + first hand pass: concurrent or sequential ──
|
|
340
|
+
if (concurrent) {
|
|
341
|
+
await Promise.all([
|
|
342
|
+
this.teachingHook.onMeasureStart(measureNum, pm.source.teachingNote, pm.source.dynamics),
|
|
343
|
+
this.playBeats(pm.rightBeats, signal),
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
await this.teachingHook.onMeasureStart(measureNum, pm.source.teachingNote, pm.source.dynamics);
|
|
348
|
+
await this.playBeats(pm.rightBeats, signal);
|
|
349
|
+
}
|
|
350
|
+
if (signal.aborted)
|
|
351
|
+
return;
|
|
352
|
+
// Left hand alone
|
|
353
|
+
await this.playBeats(pm.leftBeats, signal);
|
|
354
|
+
if (signal.aborted)
|
|
355
|
+
return;
|
|
356
|
+
// Both together
|
|
357
|
+
await this.playMeasure(pm, signal);
|
|
358
|
+
this.session.measuresPlayed++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** A single SFZ region — one sample mapped to a key/velocity range. */
|
|
2
|
+
export interface SfzRegion {
|
|
3
|
+
/** Path to the WAV file (relative to SFZ file location). */
|
|
4
|
+
sample: string;
|
|
5
|
+
/** Lowest MIDI key this region responds to. */
|
|
6
|
+
lokey: number;
|
|
7
|
+
/** Highest MIDI key this region responds to. */
|
|
8
|
+
hikey: number;
|
|
9
|
+
/** Lowest velocity this region responds to. */
|
|
10
|
+
lovel: number;
|
|
11
|
+
/** Highest velocity this region responds to (127 if not specified). */
|
|
12
|
+
hivel: number;
|
|
13
|
+
/** The MIDI note at which the sample was recorded (for pitch shifting). */
|
|
14
|
+
pitchKeycenter: number;
|
|
15
|
+
/** Volume adjustment in dB. */
|
|
16
|
+
volume: number;
|
|
17
|
+
/** Tuning adjustment in cents. */
|
|
18
|
+
tune: number;
|
|
19
|
+
}
|
|
20
|
+
/** Parsed SFZ data. */
|
|
21
|
+
export interface SfzData {
|
|
22
|
+
regions: SfzRegion[];
|
|
23
|
+
/** Global amp_veltrack value (0-100). */
|
|
24
|
+
ampVeltrack: number;
|
|
25
|
+
/** Global release time in seconds. */
|
|
26
|
+
ampegRelease: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse an SFZ file and extract all region mappings.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseSfzFile(filePath: string): SfzData;
|
|
32
|
+
/**
|
|
33
|
+
* Parse SFZ content string.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseSfz(content: string): SfzData;
|
|
36
|
+
//# sourceMappingURL=sfz-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfz-parser.d.ts","sourceRoot":"","sources":["../src/sfz-parser.ts"],"names":[],"mappings":"AASA,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uBAAuB;AACvB,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGtD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA0DjD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// ─── SFZ Parser ─────────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Minimal SFZ parser that extracts region mappings from the
|
|
4
|
+
// Accurate-Salamander SFZ files. Not a general-purpose SFZ parser —
|
|
5
|
+
// only handles the opcodes we need for sample playback.
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
/**
|
|
9
|
+
* Parse an SFZ file and extract all region mappings.
|
|
10
|
+
*/
|
|
11
|
+
export function parseSfzFile(filePath) {
|
|
12
|
+
const content = readFileSync(filePath, "utf8");
|
|
13
|
+
return parseSfz(content);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse SFZ content string.
|
|
17
|
+
*/
|
|
18
|
+
export function parseSfz(content) {
|
|
19
|
+
const regions = [];
|
|
20
|
+
let ampVeltrack = 100;
|
|
21
|
+
let ampegRelease = 0.5;
|
|
22
|
+
// Track inherited values from <group> headers
|
|
23
|
+
let groupTune = 0;
|
|
24
|
+
// Strip comments
|
|
25
|
+
const lines = content
|
|
26
|
+
.split("\n")
|
|
27
|
+
.map(line => {
|
|
28
|
+
const commentIdx = line.indexOf("//");
|
|
29
|
+
return commentIdx >= 0 ? line.slice(0, commentIdx) : line;
|
|
30
|
+
})
|
|
31
|
+
.map(line => line.trim())
|
|
32
|
+
.filter(line => line.length > 0);
|
|
33
|
+
// Rejoin into a single string for header-based parsing
|
|
34
|
+
const joined = lines.join(" ");
|
|
35
|
+
// Extract global opcodes
|
|
36
|
+
const globalMatch = joined.match(/<global>\s*([\s\S]*?)(?=<(?:master|group|region|control)>|$)/);
|
|
37
|
+
if (globalMatch) {
|
|
38
|
+
const globalBlock = globalMatch[1];
|
|
39
|
+
const veltrackMatch = globalBlock.match(/amp_veltrack=([0-9.]+)/);
|
|
40
|
+
if (veltrackMatch)
|
|
41
|
+
ampVeltrack = parseFloat(veltrackMatch[1]);
|
|
42
|
+
const releaseMatch = globalBlock.match(/ampeg_release=([0-9.]+)/);
|
|
43
|
+
if (releaseMatch)
|
|
44
|
+
ampegRelease = parseFloat(releaseMatch[1]);
|
|
45
|
+
}
|
|
46
|
+
// Split by headers, tracking group-level tune
|
|
47
|
+
const tokens = joined.split(/(<\w+>)/);
|
|
48
|
+
let currentHeader = "";
|
|
49
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
50
|
+
const token = tokens[i].trim();
|
|
51
|
+
if (!token)
|
|
52
|
+
continue;
|
|
53
|
+
if (token.startsWith("<") && token.endsWith(">")) {
|
|
54
|
+
currentHeader = token;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (currentHeader === "<group>") {
|
|
58
|
+
// Extract group-level tune
|
|
59
|
+
const tuneMatch = token.match(/tune=([+-]?\d+)/);
|
|
60
|
+
if (tuneMatch)
|
|
61
|
+
groupTune = parseInt(tuneMatch[1], 10);
|
|
62
|
+
else
|
|
63
|
+
groupTune = 0;
|
|
64
|
+
}
|
|
65
|
+
if (currentHeader === "<region>") {
|
|
66
|
+
const region = parseRegion(token, groupTune);
|
|
67
|
+
if (region)
|
|
68
|
+
regions.push(region);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { regions, ampVeltrack, ampegRelease };
|
|
72
|
+
}
|
|
73
|
+
function parseRegion(opcodeStr, groupTune) {
|
|
74
|
+
const opcodes = new Map();
|
|
75
|
+
// Parse key=value pairs, handling paths with backslashes
|
|
76
|
+
const regex = /(\w+)=(\S+)/g;
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = regex.exec(opcodeStr)) !== null) {
|
|
79
|
+
opcodes.set(match[1], match[2]);
|
|
80
|
+
}
|
|
81
|
+
const sample = opcodes.get("sample");
|
|
82
|
+
if (!sample)
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
sample: sample.replace(/\\/g, "/"), // normalize path separators
|
|
86
|
+
lokey: parseInt(opcodes.get("lokey") ?? "0", 10),
|
|
87
|
+
hikey: parseInt(opcodes.get("hikey") ?? "127", 10),
|
|
88
|
+
lovel: parseInt(opcodes.get("lovel") ?? "1", 10),
|
|
89
|
+
hivel: parseInt(opcodes.get("hivel") ?? "127", 10),
|
|
90
|
+
pitchKeycenter: parseInt(opcodes.get("pitch_keycenter") ?? "60", 10),
|
|
91
|
+
volume: parseFloat(opcodes.get("volume") ?? "0"),
|
|
92
|
+
tune: groupTune + parseInt(opcodes.get("tune") ?? "0", 10),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sfz-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfz-parser.js","sourceRoot":"","sources":["../src/sfz-parser.ts"],"names":[],"mappings":"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"}
|
package/dist/smoke.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../src/smoke.ts"],"names":[],"mappings":""}
|