@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
package/dist/teaching.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// ─── pianoai: Teaching Engine ────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Provides TeachingHook implementations that deliver teaching interjections
|
|
4
|
+
// during playback. The session engine calls these hooks at key moments.
|
|
5
|
+
//
|
|
6
|
+
// Implementations:
|
|
7
|
+
// - ConsoleTeachingHook: logs to console (development/CLI)
|
|
8
|
+
// - SilentTeachingHook: no-op (testing/benchmarks)
|
|
9
|
+
// - CallbackTeachingHook: routes to custom callbacks (MCP/voice/aside)
|
|
10
|
+
// - SingAlongHook: narrates note names/solfege/contour during playback
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
// ─── Console Hook (CLI / development) ───────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Logs teaching interjections to the console.
|
|
15
|
+
* Good for CLI mode and development.
|
|
16
|
+
*/
|
|
17
|
+
export function createConsoleTeachingHook() {
|
|
18
|
+
return {
|
|
19
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
20
|
+
const parts = [` [Measure ${measureNumber}]`];
|
|
21
|
+
if (dynamics)
|
|
22
|
+
parts.push(`(${dynamics})`);
|
|
23
|
+
if (teachingNote)
|
|
24
|
+
parts.push(`— ${teachingNote}`);
|
|
25
|
+
console.log(parts.join(" "));
|
|
26
|
+
},
|
|
27
|
+
async onKeyMoment(moment) {
|
|
28
|
+
console.log(` ★ ${moment}`);
|
|
29
|
+
},
|
|
30
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
31
|
+
console.log(`\n ✓ Finished "${songTitle}" — ${measuresPlayed} measures played.`);
|
|
32
|
+
},
|
|
33
|
+
async push(interjection) {
|
|
34
|
+
const prefix = interjection.priority === "high" ? "❗" :
|
|
35
|
+
interjection.priority === "med" ? "💡" : "ℹ️";
|
|
36
|
+
console.log(` ${prefix} ${interjection.text}`);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ─── Silent Hook (testing) ──────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* No-op hook — swallows all interjections.
|
|
43
|
+
* Use in tests where you don't want console noise.
|
|
44
|
+
*/
|
|
45
|
+
export function createSilentTeachingHook() {
|
|
46
|
+
return {
|
|
47
|
+
async onMeasureStart() { },
|
|
48
|
+
async onKeyMoment() { },
|
|
49
|
+
async onSongComplete() { },
|
|
50
|
+
async push() { },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Records all teaching events for test assertions.
|
|
55
|
+
* Use: `const hook = createRecordingTeachingHook(); ... hook.events`
|
|
56
|
+
*/
|
|
57
|
+
export function createRecordingTeachingHook() {
|
|
58
|
+
const events = [];
|
|
59
|
+
return {
|
|
60
|
+
events,
|
|
61
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
62
|
+
events.push({ type: "measure-start", measureNumber, teachingNote, dynamics });
|
|
63
|
+
},
|
|
64
|
+
async onKeyMoment(moment) {
|
|
65
|
+
events.push({ type: "key-moment", moment });
|
|
66
|
+
},
|
|
67
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
68
|
+
events.push({ type: "song-complete", measuresPlayed, songTitle });
|
|
69
|
+
},
|
|
70
|
+
async push(interjection) {
|
|
71
|
+
events.push({ type: "push", interjection });
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Routes teaching events to custom callbacks.
|
|
77
|
+
* Use this to wire to mcp-voice-soundboard, mcp-aside, or any other sink.
|
|
78
|
+
*/
|
|
79
|
+
export function createCallbackTeachingHook(callbacks) {
|
|
80
|
+
return {
|
|
81
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
82
|
+
await callbacks.onMeasureStart?.(measureNumber, teachingNote, dynamics);
|
|
83
|
+
},
|
|
84
|
+
async onKeyMoment(moment) {
|
|
85
|
+
await callbacks.onKeyMoment?.(moment);
|
|
86
|
+
},
|
|
87
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
88
|
+
await callbacks.onSongComplete?.(measuresPlayed, songTitle);
|
|
89
|
+
},
|
|
90
|
+
async push(interjection) {
|
|
91
|
+
await callbacks.onPush?.(interjection);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Voice teaching hook — produces VoiceDirective objects routed to a VoiceSink.
|
|
97
|
+
*
|
|
98
|
+
* The sink can be:
|
|
99
|
+
* - A real mcp-voice-soundboard call (via LLM tool routing)
|
|
100
|
+
* - A console.log wrapper (for CLI testing)
|
|
101
|
+
* - A recording array (for unit tests)
|
|
102
|
+
*
|
|
103
|
+
* Also records all directives for inspection.
|
|
104
|
+
*/
|
|
105
|
+
export function createVoiceTeachingHook(sink, options = {}) {
|
|
106
|
+
const { voice, speechSpeed = 1.0, speakTeachingNotes = true, speakKeyMoments = true, speakCompletion = true, blockOnKeyMoments = true, } = options;
|
|
107
|
+
const directives = [];
|
|
108
|
+
async function emit(directive) {
|
|
109
|
+
directives.push(directive);
|
|
110
|
+
await sink(directive);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
directives,
|
|
114
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
115
|
+
if (!speakTeachingNotes || !teachingNote)
|
|
116
|
+
return;
|
|
117
|
+
const dynamicsPart = dynamics ? ` Play ${dynamics}.` : "";
|
|
118
|
+
await emit({
|
|
119
|
+
text: `Measure ${measureNumber}.${dynamicsPart} ${teachingNote}`,
|
|
120
|
+
voice,
|
|
121
|
+
speed: speechSpeed,
|
|
122
|
+
blocking: false, // don't block on routine notes
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
async onKeyMoment(moment) {
|
|
126
|
+
if (!speakKeyMoments)
|
|
127
|
+
return;
|
|
128
|
+
await emit({
|
|
129
|
+
text: moment,
|
|
130
|
+
voice,
|
|
131
|
+
speed: speechSpeed,
|
|
132
|
+
blocking: blockOnKeyMoments,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
136
|
+
if (!speakCompletion)
|
|
137
|
+
return;
|
|
138
|
+
await emit({
|
|
139
|
+
text: `Great work! You finished ${songTitle}. ${measuresPlayed} measures played.`,
|
|
140
|
+
voice,
|
|
141
|
+
speed: speechSpeed,
|
|
142
|
+
blocking: false,
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
async push(interjection) {
|
|
146
|
+
const urgency = interjection.priority === "high" ? "Important: " : "";
|
|
147
|
+
await emit({
|
|
148
|
+
text: `${urgency}${interjection.text}`,
|
|
149
|
+
voice,
|
|
150
|
+
speed: speechSpeed,
|
|
151
|
+
blocking: interjection.priority === "high",
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Aside teaching hook — produces AsideDirective objects routed to an AsideSink.
|
|
158
|
+
*
|
|
159
|
+
* The sink can be:
|
|
160
|
+
* - A real mcp-aside push (via aside_push tool)
|
|
161
|
+
* - A recording array (for tests)
|
|
162
|
+
*
|
|
163
|
+
* Records all directives for inspection.
|
|
164
|
+
*/
|
|
165
|
+
export function createAsideTeachingHook(sink, options = {}) {
|
|
166
|
+
const { pushTeachingNotes = true, pushKeyMoments = true, pushCompletion = true, baseTags = ["piano-teacher"], } = options;
|
|
167
|
+
const directives = [];
|
|
168
|
+
async function emit(directive) {
|
|
169
|
+
directives.push(directive);
|
|
170
|
+
await sink(directive);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
directives,
|
|
174
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
175
|
+
if (!pushTeachingNotes || !teachingNote)
|
|
176
|
+
return;
|
|
177
|
+
const dynamicsPart = dynamics ? ` (${dynamics})` : "";
|
|
178
|
+
await emit({
|
|
179
|
+
text: `Measure ${measureNumber}${dynamicsPart}: ${teachingNote}`,
|
|
180
|
+
priority: "low",
|
|
181
|
+
reason: "measure-start",
|
|
182
|
+
source: `measure-${measureNumber}`,
|
|
183
|
+
tags: [...baseTags, "teaching-note"],
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
async onKeyMoment(moment) {
|
|
187
|
+
if (!pushKeyMoments)
|
|
188
|
+
return;
|
|
189
|
+
await emit({
|
|
190
|
+
text: moment,
|
|
191
|
+
priority: "med",
|
|
192
|
+
reason: "key-moment",
|
|
193
|
+
tags: [...baseTags, "key-moment"],
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
197
|
+
if (!pushCompletion)
|
|
198
|
+
return;
|
|
199
|
+
await emit({
|
|
200
|
+
text: `Finished "${songTitle}" — ${measuresPlayed} measures played.`,
|
|
201
|
+
priority: "low",
|
|
202
|
+
reason: "song-complete",
|
|
203
|
+
tags: [...baseTags, "completion"],
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
async push(interjection) {
|
|
207
|
+
await emit({
|
|
208
|
+
text: interjection.text,
|
|
209
|
+
priority: interjection.priority,
|
|
210
|
+
reason: interjection.reason,
|
|
211
|
+
source: interjection.source,
|
|
212
|
+
tags: [...baseTags, interjection.reason],
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// ─── Sing-Along Hook (note narration) ────────────────────────────────────────
|
|
218
|
+
import { measureToSingableText, } from "./note-parser.js";
|
|
219
|
+
/**
|
|
220
|
+
* Sing-along teaching hook — narrates note names/solfege/contour/syllables
|
|
221
|
+
* from the actual measure data, synchronized with playback.
|
|
222
|
+
*
|
|
223
|
+
* Requires the full SongEntry to look up measure note data by measure number.
|
|
224
|
+
* Emits blocking VoiceDirectives on onMeasureStart so the voice speaks BEFORE
|
|
225
|
+
* the piano plays each measure.
|
|
226
|
+
*
|
|
227
|
+
* Composable with other hooks via composeTeachingHooks.
|
|
228
|
+
*/
|
|
229
|
+
export function createSingAlongHook(sink, song, options = {}) {
|
|
230
|
+
const { mode = "note-names", hand = "right", voice, speechSpeed = 1.0, announceMeasureNumber = true, speakCompletion = true, } = options;
|
|
231
|
+
const directives = [];
|
|
232
|
+
// Pre-index measures by number for O(1) lookup
|
|
233
|
+
const measureMap = new Map();
|
|
234
|
+
for (const m of song.measures) {
|
|
235
|
+
measureMap.set(m.number, { rightHand: m.rightHand, leftHand: m.leftHand });
|
|
236
|
+
}
|
|
237
|
+
async function emit(directive) {
|
|
238
|
+
directives.push(directive);
|
|
239
|
+
await sink(directive);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
directives,
|
|
243
|
+
async onMeasureStart(measureNumber, _teachingNote, _dynamics) {
|
|
244
|
+
const measure = measureMap.get(measureNumber);
|
|
245
|
+
if (!measure)
|
|
246
|
+
return;
|
|
247
|
+
const singableText = measureToSingableText(measure, { mode, hand });
|
|
248
|
+
if (!singableText)
|
|
249
|
+
return;
|
|
250
|
+
const prefix = announceMeasureNumber ? `Measure ${measureNumber}: ` : "";
|
|
251
|
+
await emit({
|
|
252
|
+
text: `${prefix}${singableText}`,
|
|
253
|
+
voice,
|
|
254
|
+
speed: speechSpeed,
|
|
255
|
+
blocking: true, // speak BEFORE piano plays
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
async onKeyMoment(_moment) {
|
|
259
|
+
// Sing-along hook does not speak key moments
|
|
260
|
+
// (compose with a voice hook if you want both)
|
|
261
|
+
},
|
|
262
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
263
|
+
if (!speakCompletion)
|
|
264
|
+
return;
|
|
265
|
+
await emit({
|
|
266
|
+
text: `Finished singing along to ${songTitle}! ${measuresPlayed} measures.`,
|
|
267
|
+
voice,
|
|
268
|
+
speed: speechSpeed,
|
|
269
|
+
blocking: false,
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
async push(_interjection) {
|
|
273
|
+
// Sing-along hook does not speak push interjections
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// ─── Live Feedback Hook (voice + aside during singing) ───────────────────────
|
|
278
|
+
/** Encouragement phrases keyed by context. */
|
|
279
|
+
const ENCOURAGEMENTS = [
|
|
280
|
+
"Keep it up!",
|
|
281
|
+
"Sounding great!",
|
|
282
|
+
"Nice rhythm!",
|
|
283
|
+
"Beautiful phrasing!",
|
|
284
|
+
"You've got this!",
|
|
285
|
+
"Smooth playing!",
|
|
286
|
+
];
|
|
287
|
+
/** Dynamics-aware aside tips. */
|
|
288
|
+
const DYNAMICS_TIPS = {
|
|
289
|
+
pp: "Pianissimo — very soft, barely touching the keys.",
|
|
290
|
+
p: "Piano — play gently, light touch.",
|
|
291
|
+
mp: "Mezzo-piano — moderately soft, warm tone.",
|
|
292
|
+
mf: "Mezzo-forte — moderately loud, confident touch.",
|
|
293
|
+
f: "Forte — play strong, full sound.",
|
|
294
|
+
ff: "Fortissimo — very loud, powerful!",
|
|
295
|
+
crescendo: "Building intensity — lean into the crescendo.",
|
|
296
|
+
decrescendo: "Pulling back — ease off gradually.",
|
|
297
|
+
dim: "Diminuendo — getting softer, let the sound fade.",
|
|
298
|
+
};
|
|
299
|
+
/**
|
|
300
|
+
* Live feedback teaching hook — pushes context-aware encouragement and tips
|
|
301
|
+
* during singing/playback sessions via both voice and aside sinks.
|
|
302
|
+
*
|
|
303
|
+
* - Voice: periodic encouragement every N measures + key moment reactions
|
|
304
|
+
* - Aside: dynamics tips, fingering warnings, deeper contextual notes
|
|
305
|
+
*
|
|
306
|
+
* Composable with other hooks via composeTeachingHooks.
|
|
307
|
+
*/
|
|
308
|
+
export function createLiveFeedbackHook(voiceSink, asideSink, song, options = {}) {
|
|
309
|
+
const { voiceInterval = 4, encourageOnDynamics = true, warnOnDifficult = true, voice, speechSpeed = 1.0, } = options;
|
|
310
|
+
const voiceDirectives = [];
|
|
311
|
+
const asideDirectives = [];
|
|
312
|
+
let measureCount = 0;
|
|
313
|
+
let lastDynamics;
|
|
314
|
+
async function emitVoice(directive) {
|
|
315
|
+
voiceDirectives.push(directive);
|
|
316
|
+
await voiceSink(directive);
|
|
317
|
+
}
|
|
318
|
+
async function emitAside(directive) {
|
|
319
|
+
asideDirectives.push(directive);
|
|
320
|
+
await asideSink(directive);
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
voiceDirectives,
|
|
324
|
+
asideDirectives,
|
|
325
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
326
|
+
measureCount++;
|
|
327
|
+
// ── Dynamics change → aside tip ──
|
|
328
|
+
if (encourageOnDynamics && dynamics && dynamics !== lastDynamics) {
|
|
329
|
+
const tip = DYNAMICS_TIPS[dynamics.toLowerCase()] ??
|
|
330
|
+
`${dynamics} — adjust your touch accordingly.`;
|
|
331
|
+
await emitAside({
|
|
332
|
+
text: `Measure ${measureNumber}: ${tip}`,
|
|
333
|
+
priority: "low",
|
|
334
|
+
reason: "dynamics-change",
|
|
335
|
+
source: `measure-${measureNumber}`,
|
|
336
|
+
tags: ["piano-teacher", "live-feedback", "dynamics"],
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
lastDynamics = dynamics;
|
|
340
|
+
// ── Difficult passage warning → aside ──
|
|
341
|
+
if (warnOnDifficult && teachingNote) {
|
|
342
|
+
const lower = teachingNote.toLowerCase();
|
|
343
|
+
if (lower.includes("watch") || lower.includes("tricky") ||
|
|
344
|
+
lower.includes("careful") || lower.includes("difficult") ||
|
|
345
|
+
lower.includes("finger")) {
|
|
346
|
+
await emitAside({
|
|
347
|
+
text: `Heads up at measure ${measureNumber}: ${teachingNote}`,
|
|
348
|
+
priority: "med",
|
|
349
|
+
reason: "difficulty-warning",
|
|
350
|
+
source: `measure-${measureNumber}`,
|
|
351
|
+
tags: ["piano-teacher", "live-feedback", "warning"],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// ── Periodic voice encouragement ──
|
|
356
|
+
if (voiceInterval > 0 && measureCount % voiceInterval === 0) {
|
|
357
|
+
const phrase = ENCOURAGEMENTS[measureCount % ENCOURAGEMENTS.length];
|
|
358
|
+
await emitVoice({
|
|
359
|
+
text: phrase,
|
|
360
|
+
voice,
|
|
361
|
+
speed: speechSpeed,
|
|
362
|
+
blocking: false,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
async onKeyMoment(moment) {
|
|
367
|
+
await emitVoice({
|
|
368
|
+
text: `Watch for this: ${moment}`,
|
|
369
|
+
voice,
|
|
370
|
+
speed: speechSpeed,
|
|
371
|
+
blocking: false,
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
375
|
+
await emitVoice({
|
|
376
|
+
text: `Fantastic work on ${songTitle}! You played through all ${measuresPlayed} measures.`,
|
|
377
|
+
voice,
|
|
378
|
+
speed: speechSpeed,
|
|
379
|
+
blocking: false,
|
|
380
|
+
});
|
|
381
|
+
await emitAside({
|
|
382
|
+
text: `Session complete: ${songTitle} — ${measuresPlayed} measures played. Great practice session!`,
|
|
383
|
+
priority: "low",
|
|
384
|
+
reason: "session-complete",
|
|
385
|
+
tags: ["piano-teacher", "live-feedback", "completion"],
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
async push(interjection) {
|
|
389
|
+
// Route pushes to aside
|
|
390
|
+
await emitAside({
|
|
391
|
+
text: interjection.text,
|
|
392
|
+
priority: interjection.priority,
|
|
393
|
+
reason: interjection.reason,
|
|
394
|
+
source: interjection.source,
|
|
395
|
+
tags: ["piano-teacher", "live-feedback", interjection.reason],
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Compose multiple teaching hooks into one.
|
|
402
|
+
* Events are dispatched to all hooks in order (serially, not parallel).
|
|
403
|
+
*
|
|
404
|
+
* Example: combine a voice hook + aside hook + recording hook for full coverage.
|
|
405
|
+
*/
|
|
406
|
+
export function composeTeachingHooks(...hooks) {
|
|
407
|
+
return {
|
|
408
|
+
async onMeasureStart(measureNumber, teachingNote, dynamics) {
|
|
409
|
+
for (const h of hooks)
|
|
410
|
+
await h.onMeasureStart(measureNumber, teachingNote, dynamics);
|
|
411
|
+
},
|
|
412
|
+
async onKeyMoment(moment) {
|
|
413
|
+
for (const h of hooks)
|
|
414
|
+
await h.onKeyMoment(moment);
|
|
415
|
+
},
|
|
416
|
+
async onSongComplete(measuresPlayed, songTitle) {
|
|
417
|
+
for (const h of hooks)
|
|
418
|
+
await h.onSongComplete(measuresPlayed, songTitle);
|
|
419
|
+
},
|
|
420
|
+
async push(interjection) {
|
|
421
|
+
for (const h of hooks)
|
|
422
|
+
await h.push(interjection);
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
// ─── Key Moment Detector ────────────────────────────────────────────────────
|
|
427
|
+
/**
|
|
428
|
+
* Check if the current measure matches any key moments in the song.
|
|
429
|
+
* Key moments in ai-music-sheets reference bars like "Bar 1:", "Bars 3-4:", etc.
|
|
430
|
+
*/
|
|
431
|
+
export function detectKeyMoments(song, measureNumber) {
|
|
432
|
+
const matches = [];
|
|
433
|
+
for (const km of song.musicalLanguage.keyMoments) {
|
|
434
|
+
// Match patterns like "Bar 1:", "Bars 1-2:", "Bar 9:"
|
|
435
|
+
const singleMatch = km.match(/^Bars?\s+(\d+)\s*:/i);
|
|
436
|
+
const rangeMatch = km.match(/^Bars?\s+(\d+)\s*-\s*(\d+)\s*:/i);
|
|
437
|
+
if (rangeMatch) {
|
|
438
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
439
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
440
|
+
if (measureNumber >= start && measureNumber <= end) {
|
|
441
|
+
matches.push(km);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else if (singleMatch) {
|
|
445
|
+
const bar = parseInt(singleMatch[1], 10);
|
|
446
|
+
if (bar === measureNumber) {
|
|
447
|
+
matches.push(km);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return matches;
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=teaching.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teaching.js","sourceRoot":"","sources":["../src/teaching.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-sound.d.ts","sourceRoot":"","sources":["../src/test-sound.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node --import tsx
|
|
2
|
+
// Quick test: play middle C through the sample engine.
|
|
3
|
+
// Run: node --import tsx src/test-sound.ts
|
|
4
|
+
import { createSampleEngine } from "./sample-engine.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const piano = createSampleEngine({
|
|
7
|
+
samplesDir: "samples/AccurateSalamander",
|
|
8
|
+
});
|
|
9
|
+
console.error("Connecting (loading samples)...");
|
|
10
|
+
await piano.connect();
|
|
11
|
+
console.error("Playing C4 (middle C) at velocity 80...");
|
|
12
|
+
piano.noteOn(60, 80);
|
|
13
|
+
await sleep(2000);
|
|
14
|
+
piano.noteOff(60);
|
|
15
|
+
await sleep(1500); // let release ring
|
|
16
|
+
console.error("Playing C major chord (C4-E4-G4) at velocity 100...");
|
|
17
|
+
piano.noteOn(60, 100);
|
|
18
|
+
piano.noteOn(64, 95);
|
|
19
|
+
piano.noteOn(67, 90);
|
|
20
|
+
await sleep(3000);
|
|
21
|
+
piano.noteOff(60);
|
|
22
|
+
piano.noteOff(64);
|
|
23
|
+
piano.noteOff(67);
|
|
24
|
+
await sleep(1500);
|
|
25
|
+
console.error("Playing scale C4 to C5...");
|
|
26
|
+
const scale = [60, 62, 64, 65, 67, 69, 71, 72];
|
|
27
|
+
for (const note of scale) {
|
|
28
|
+
piano.noteOn(note, 70);
|
|
29
|
+
await sleep(350);
|
|
30
|
+
piano.noteOff(note);
|
|
31
|
+
await sleep(50);
|
|
32
|
+
}
|
|
33
|
+
await sleep(2000);
|
|
34
|
+
console.error("Done. Disconnecting...");
|
|
35
|
+
await piano.disconnect();
|
|
36
|
+
}
|
|
37
|
+
function sleep(ms) {
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
main().catch(console.error);
|
|
41
|
+
//# sourceMappingURL=test-sound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-sound.js","sourceRoot":"","sources":["../src/test-sound.ts"],"names":[],"mappings":";AACA,uDAAuD;AACvD,2CAA2C;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,kBAAkB,CAAC;QAC/B,UAAU,EAAE,4BAA4B;KACzC,CAAC,CAAC;IAEH,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;IAEtB,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACzD,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACrB,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClB,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB;IAEtC,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACrE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACtB,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACrB,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACrB,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClB,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAElB,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvB,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACjB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpB,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAElB,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IACxC,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;AAC3B,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
|