@mcptoolshop/ai-jam-sessions 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 +177 -0
- package/README.fr.md +178 -0
- package/README.hi.md +175 -0
- package/README.it.md +177 -0
- package/README.ja.md +174 -0
- package/README.md +174 -0
- package/README.pt-BR.md +177 -0
- package/README.zh.md +173 -0
- package/dist/audio-engine.d.ts +12 -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/chord-detect.d.ts +14 -0
- package/dist/chord-detect.d.ts.map +1 -0
- package/dist/chord-detect.js +90 -0
- package/dist/chord-detect.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +805 -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/journal.d.ts +41 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +149 -0
- package/dist/journal.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 +1256 -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 +319 -0
- package/dist/note-parser.js.map +1 -0
- package/dist/piano-roll.d.ts +28 -0
- package/dist/piano-roll.d.ts.map +1 -0
- package/dist/piano-roll.js +321 -0
- package/dist/piano-roll.js.map +1 -0
- package/dist/piano-voices.d.ts +90 -0
- package/dist/piano-voices.d.ts.map +1 -0
- package/dist/piano-voices.js +416 -0
- package/dist/piano-voices.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 +79 -0
- package/dist/songs/config/schema.d.ts.map +1 -0
- package/dist/songs/config/schema.js +56 -0
- package/dist/songs/config/schema.js.map +1 -0
- package/dist/songs/index.d.ts +18 -0
- package/dist/songs/index.d.ts.map +1 -0
- package/dist/songs/index.js +22 -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 +338 -0
- package/dist/songs/jam.js.map +1 -0
- package/dist/songs/library.d.ts +47 -0
- package/dist/songs/library.d.ts.map +1 -0
- package/dist/songs/library.js +156 -0
- package/dist/songs/library.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 +197 -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 +29 -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 +28 -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/docs/fur-elise-m1-8.svg +290 -0
- package/logo.png +0 -0
- package/package.json +71 -0
- package/songs/library/blues/blues-in-the-night.json +14 -0
- package/songs/library/blues/blues-in-the-night.mid +0 -0
- package/songs/library/blues/born-under-a-bad-sign.json +13 -0
- package/songs/library/blues/born-under-a-bad-sign.mid +0 -0
- package/songs/library/blues/crossroad-blues.json +14 -0
- package/songs/library/blues/crossroad-blues.mid +0 -0
- package/songs/library/blues/everyday-i-have-the-blues.json +13 -0
- package/songs/library/blues/everyday-i-have-the-blues.mid +0 -0
- package/songs/library/blues/hoochie-coochie-man.json +14 -0
- package/songs/library/blues/hoochie-coochie-man.mid +0 -0
- package/songs/library/blues/red-house.json +14 -0
- package/songs/library/blues/red-house.mid +0 -0
- package/songs/library/blues/st-louis-blues.json +14 -0
- package/songs/library/blues/st-louis-blues.mid +0 -0
- package/songs/library/blues/stormy-monday.json +13 -0
- package/songs/library/blues/stormy-monday.mid +0 -0
- package/songs/library/blues/sweet-home-chicago.json +14 -0
- package/songs/library/blues/sweet-home-chicago.mid +0 -0
- package/songs/library/blues/the-thrill-is-gone.json +17 -0
- package/songs/library/blues/the-thrill-is-gone.mid +0 -0
- package/songs/library/classical/bach-prelude-c-major-bwv846.json +37 -0
- package/songs/library/classical/bach-prelude-c-major-bwv846.mid +0 -0
- package/songs/library/classical/chopin-nocturne-op9-no2.json +36 -0
- package/songs/library/classical/chopin-nocturne-op9-no2.mid +0 -0
- package/songs/library/classical/chopin-prelude-e-minor.json +35 -0
- package/songs/library/classical/chopin-prelude-e-minor.mid +0 -0
- package/songs/library/classical/clair-de-lune.json +37 -0
- package/songs/library/classical/clair-de-lune.mid +0 -0
- package/songs/library/classical/debussy-arabesque-no1.json +35 -0
- package/songs/library/classical/debussy-arabesque-no1.mid +0 -0
- package/songs/library/classical/fur-elise.json +35 -0
- package/songs/library/classical/fur-elise.mid +0 -0
- package/songs/library/classical/mozart-k545-mvt1.json +35 -0
- package/songs/library/classical/mozart-k545-mvt1.mid +0 -0
- package/songs/library/classical/pathetique-mvt2.json +36 -0
- package/songs/library/classical/pathetique-mvt2.mid +0 -0
- package/songs/library/classical/satie-gymnopedie-no1.json +36 -0
- package/songs/library/classical/satie-gymnopedie-no1.mid +0 -0
- package/songs/library/classical/schumann-traumerei.json +36 -0
- package/songs/library/classical/schumann-traumerei.mid +0 -0
- package/songs/library/film/cinema-paradiso.json +14 -0
- package/songs/library/film/cinema-paradiso.mid +0 -0
- package/songs/library/film/comptine-dun-autre-ete.json +17 -0
- package/songs/library/film/comptine-dun-autre-ete.mid +0 -0
- package/songs/library/film/forrest-gump.json +14 -0
- package/songs/library/film/forrest-gump.mid +0 -0
- package/songs/library/film/hedwigs-theme.json +14 -0
- package/songs/library/film/hedwigs-theme.mid +0 -0
- package/songs/library/film/mia-and-sebastians-theme.json +14 -0
- package/songs/library/film/mia-and-sebastians-theme.mid +0 -0
- package/songs/library/film/moon-river.json +14 -0
- package/songs/library/film/moon-river.mid +0 -0
- package/songs/library/film/my-heart-will-go-on.json +14 -0
- package/songs/library/film/my-heart-will-go-on.mid +0 -0
- package/songs/library/film/nuvole-bianche.json +14 -0
- package/songs/library/film/nuvole-bianche.mid +0 -0
- package/songs/library/film/pink-panther.json +14 -0
- package/songs/library/film/pink-panther.mid +0 -0
- package/songs/library/film/schindlers-list-theme.json +14 -0
- package/songs/library/film/schindlers-list-theme.mid +0 -0
- package/songs/library/folk/amazing-grace.json +14 -0
- package/songs/library/folk/amazing-grace.mid +0 -0
- package/songs/library/folk/auld-lang-syne.json +14 -0
- package/songs/library/folk/auld-lang-syne.mid +0 -0
- package/songs/library/folk/danny-boy.json +14 -0
- package/songs/library/folk/danny-boy.mid +0 -0
- package/songs/library/folk/greensleeves.json +17 -0
- package/songs/library/folk/greensleeves.mid +0 -0
- package/songs/library/folk/house-of-the-rising-sun.json +14 -0
- package/songs/library/folk/house-of-the-rising-sun.mid +0 -0
- package/songs/library/folk/sakura-sakura.json +14 -0
- package/songs/library/folk/sakura-sakura.mid +0 -0
- package/songs/library/folk/scarborough-fair.json +14 -0
- package/songs/library/folk/scarborough-fair.mid +0 -0
- package/songs/library/folk/shenandoah.json +14 -0
- package/songs/library/folk/shenandoah.mid +0 -0
- package/songs/library/folk/simple-gifts.json +14 -0
- package/songs/library/folk/simple-gifts.mid +0 -0
- package/songs/library/folk/the-water-is-wide.json +14 -0
- package/songs/library/folk/the-water-is-wide.mid +0 -0
- package/songs/library/jazz/all-the-things-you-are.json +13 -0
- package/songs/library/jazz/all-the-things-you-are.mid +0 -0
- package/songs/library/jazz/autumn-leaves.json +17 -0
- package/songs/library/jazz/autumn-leaves.mid +0 -0
- package/songs/library/jazz/blue-bossa.json +14 -0
- package/songs/library/jazz/blue-bossa.mid +0 -0
- package/songs/library/jazz/fly-me-to-the-moon.json +14 -0
- package/songs/library/jazz/fly-me-to-the-moon.mid +0 -0
- package/songs/library/jazz/georgia-on-my-mind.json +14 -0
- package/songs/library/jazz/georgia-on-my-mind.mid +0 -0
- package/songs/library/jazz/misty.json +14 -0
- package/songs/library/jazz/misty.mid +0 -0
- package/songs/library/jazz/my-funny-valentine.json +14 -0
- package/songs/library/jazz/my-funny-valentine.mid +0 -0
- package/songs/library/jazz/round-midnight.json +14 -0
- package/songs/library/jazz/round-midnight.mid +0 -0
- package/songs/library/jazz/summertime.json +14 -0
- package/songs/library/jazz/summertime.mid +0 -0
- package/songs/library/jazz/take-the-a-train.json +14 -0
- package/songs/library/jazz/take-the-a-train.mid +0 -0
- package/songs/library/latin/agua-de-beber.json +14 -0
- package/songs/library/latin/agua-de-beber.mid +0 -0
- package/songs/library/latin/besame-mucho.json +14 -0
- package/songs/library/latin/besame-mucho.mid +0 -0
- package/songs/library/latin/black-orpheus.json +14 -0
- package/songs/library/latin/black-orpheus.mid +0 -0
- package/songs/library/latin/corcovado.json +14 -0
- package/songs/library/latin/corcovado.mid +0 -0
- package/songs/library/latin/desafinado.json +14 -0
- package/songs/library/latin/desafinado.mid +0 -0
- package/songs/library/latin/el-condor-pasa.json +14 -0
- package/songs/library/latin/el-condor-pasa.mid +0 -0
- package/songs/library/latin/girl-from-ipanema.json +17 -0
- package/songs/library/latin/girl-from-ipanema.mid +0 -0
- package/songs/library/latin/mas-que-nada.json +14 -0
- package/songs/library/latin/mas-que-nada.mid +0 -0
- package/songs/library/latin/perfidia.json +14 -0
- package/songs/library/latin/perfidia.mid +0 -0
- package/songs/library/latin/wave.json +14 -0
- package/songs/library/latin/wave.mid +0 -0
- package/songs/library/new-age/divenire.json +14 -0
- package/songs/library/new-age/divenire.mid +0 -0
- package/songs/library/new-age/experience.json +14 -0
- package/songs/library/new-age/experience.mid +0 -0
- package/songs/library/new-age/kiss-the-rain.json +14 -0
- package/songs/library/new-age/kiss-the-rain.mid +0 -0
- package/songs/library/new-age/may-be.json +13 -0
- package/songs/library/new-age/may-be.mid +0 -0
- package/songs/library/new-age/metamorphosis-two.json +14 -0
- package/songs/library/new-age/metamorphosis-two.mid +0 -0
- package/songs/library/new-age/nuvole-bianche-na.json +14 -0
- package/songs/library/new-age/nuvole-bianche-na.mid +0 -0
- package/songs/library/new-age/opening-glassworks.json +14 -0
- package/songs/library/new-age/opening-glassworks.mid +0 -0
- package/songs/library/new-age/river-flows-in-you.json +17 -0
- package/songs/library/new-age/river-flows-in-you.mid +0 -0
- package/songs/library/new-age/una-mattina.json +14 -0
- package/songs/library/new-age/una-mattina.mid +0 -0
- package/songs/library/new-age/watermark.json +14 -0
- package/songs/library/new-age/watermark.mid +0 -0
- package/songs/library/pop/a-thousand-years.json +14 -0
- package/songs/library/pop/a-thousand-years.mid +0 -0
- package/songs/library/pop/all-of-me.json +14 -0
- package/songs/library/pop/all-of-me.mid +0 -0
- package/songs/library/pop/bohemian-rhapsody.json +14 -0
- package/songs/library/pop/bohemian-rhapsody.mid +0 -0
- package/songs/library/pop/clocks.json +14 -0
- package/songs/library/pop/clocks.mid +0 -0
- package/songs/library/pop/imagine.json +17 -0
- package/songs/library/pop/imagine.mid +0 -0
- package/songs/library/pop/let-it-be.json +14 -0
- package/songs/library/pop/let-it-be.mid +0 -0
- package/songs/library/pop/piano-man.json +14 -0
- package/songs/library/pop/piano-man.mid +0 -0
- package/songs/library/pop/someone-like-you.json +14 -0
- package/songs/library/pop/someone-like-you.mid +0 -0
- package/songs/library/pop/someone-you-loved.json +14 -0
- package/songs/library/pop/someone-you-loved.mid +0 -0
- package/songs/library/pop/viva-la-vida.json +14 -0
- package/songs/library/pop/viva-la-vida.mid +0 -0
- package/songs/library/ragtime/bethena.json +14 -0
- package/songs/library/ragtime/bethena.mid +0 -0
- package/songs/library/ragtime/elite-syncopations.json +13 -0
- package/songs/library/ragtime/elite-syncopations.mid +0 -0
- package/songs/library/ragtime/gladiolus-rag.json +13 -0
- package/songs/library/ragtime/gladiolus-rag.mid +0 -0
- package/songs/library/ragtime/maple-leaf-rag.json +13 -0
- package/songs/library/ragtime/maple-leaf-rag.mid +0 -0
- package/songs/library/ragtime/peacherine-rag.json +13 -0
- package/songs/library/ragtime/peacherine-rag.mid +0 -0
- package/songs/library/ragtime/pineapple-rag.json +13 -0
- package/songs/library/ragtime/pineapple-rag.mid +0 -0
- package/songs/library/ragtime/solace.json +14 -0
- package/songs/library/ragtime/solace.mid +0 -0
- package/songs/library/ragtime/the-easy-winners.json +13 -0
- package/songs/library/ragtime/the-easy-winners.mid +0 -0
- package/songs/library/ragtime/the-entertainer.json +17 -0
- package/songs/library/ragtime/the-entertainer.mid +0 -0
- package/songs/library/ragtime/weeping-willow.json +14 -0
- package/songs/library/ragtime/weeping-willow.mid +0 -0
- package/songs/library/rnb/fallin.json +17 -0
- package/songs/library/rnb/fallin.mid +0 -0
- package/songs/library/rnb/halo.json +14 -0
- package/songs/library/rnb/halo.mid +0 -0
- package/songs/library/rnb/i-will-always-love-you.json +14 -0
- package/songs/library/rnb/i-will-always-love-you.mid +0 -0
- package/songs/library/rnb/if-i-aint-got-you.json +17 -0
- package/songs/library/rnb/if-i-aint-got-you.mid +0 -0
- package/songs/library/rnb/isnt-she-lovely.json +17 -0
- package/songs/library/rnb/isnt-she-lovely.mid +0 -0
- package/songs/library/rnb/killing-me-softly.json +14 -0
- package/songs/library/rnb/killing-me-softly.mid +0 -0
- package/songs/library/rnb/no-one.json +14 -0
- package/songs/library/rnb/no-one.mid +0 -0
- package/songs/library/rnb/ordinary-people.json +14 -0
- package/songs/library/rnb/ordinary-people.mid +0 -0
- package/songs/library/rnb/ribbon-in-the-sky.json +14 -0
- package/songs/library/rnb/ribbon-in-the-sky.mid +0 -0
- package/songs/library/rnb/superstition.json +17 -0
- package/songs/library/rnb/superstition.mid +0 -0
- package/songs/library/rock/baba-oriley.json +14 -0
- package/songs/library/rock/baba-oriley.mid +0 -0
- package/songs/library/rock/bennie-and-the-jets.json +14 -0
- package/songs/library/rock/bennie-and-the-jets.mid +0 -0
- package/songs/library/rock/dont-stop-believin.json +14 -0
- package/songs/library/rock/dont-stop-believin.mid +0 -0
- package/songs/library/rock/dream-on.json +14 -0
- package/songs/library/rock/dream-on.mid +0 -0
- package/songs/library/rock/layla-unplugged.json +14 -0
- package/songs/library/rock/layla-unplugged.mid +0 -0
- package/songs/library/rock/november-rain.json +15 -0
- package/songs/library/rock/november-rain.mid +0 -0
- package/songs/library/rock/rocket-man.json +14 -0
- package/songs/library/rock/rocket-man.mid +0 -0
- package/songs/library/rock/stairway-to-heaven.json +14 -0
- package/songs/library/rock/stairway-to-heaven.mid +0 -0
- package/songs/library/rock/tiny-dancer.json +14 -0
- package/songs/library/rock/tiny-dancer.mid +0 -0
- package/songs/library/rock/your-song.json +17 -0
- package/songs/library/rock/your-song.mid +0 -0
- package/songs/library/soul/a-change-is-gonna-come.json +14 -0
- package/songs/library/soul/a-change-is-gonna-come.mid +0 -0
- package/songs/library/soul/aint-no-sunshine.json +14 -0
- package/songs/library/soul/aint-no-sunshine.mid +0 -0
- package/songs/library/soul/dock-of-the-bay.json +13 -0
- package/songs/library/soul/dock-of-the-bay.mid +0 -0
- package/songs/library/soul/i-got-you.json +14 -0
- package/songs/library/soul/i-got-you.mid +0 -0
- package/songs/library/soul/lean-on-me.json +17 -0
- package/songs/library/soul/lean-on-me.mid +0 -0
- package/songs/library/soul/lets-stay-together.json +14 -0
- package/songs/library/soul/lets-stay-together.mid +0 -0
- package/songs/library/soul/my-girl.json +14 -0
- package/songs/library/soul/my-girl.mid +0 -0
- package/songs/library/soul/respect.json +14 -0
- package/songs/library/soul/respect.mid +0 -0
- package/songs/library/soul/stand-by-me.json +14 -0
- package/songs/library/soul/stand-by-me.mid +0 -0
- package/songs/library/soul/whats-going-on.json +14 -0
- package/songs/library/soul/whats-going-on.mid +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// ─── MIDI → SongEntry Converter ──────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Parses a standard MIDI file and merges it with a human-authored SongConfig
|
|
4
|
+
// to produce a complete SongEntry ready for the registry.
|
|
5
|
+
//
|
|
6
|
+
// The MIDI provides: notes, timing, duration, structure.
|
|
7
|
+
// The config provides: metadata, musical language, teaching notes, fingering.
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
import { parseMidi } from "midi-file";
|
|
10
|
+
import { ticksPerMeasure, computeTotalMeasures, sliceIntoMeasures, resolveTimeSignature, } from "./measures.js";
|
|
11
|
+
import { separateHands, formatHand, DEFAULT_SPLIT_POINT, } from "./hands.js";
|
|
12
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
13
|
+
const DEFAULT_TEMPO = 120;
|
|
14
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Convert a MIDI buffer + human-authored config into a complete SongEntry.
|
|
17
|
+
*/
|
|
18
|
+
export function midiToSongEntry(midiBuffer, config) {
|
|
19
|
+
const midi = parseMidi(midiBuffer);
|
|
20
|
+
const tpb = midi.header.ticksPerBeat ?? 480;
|
|
21
|
+
const splitPoint = config.splitPoint ?? DEFAULT_SPLIT_POINT;
|
|
22
|
+
// 1. Collect tempo + time signature events across all tracks
|
|
23
|
+
const tempoEvents = extractTempoEvents(midi);
|
|
24
|
+
const timeSigEvents = extractTimeSigEvents(midi);
|
|
25
|
+
// 2. Resolve all notes to absolute ticks + durations
|
|
26
|
+
const notes = resolveNotes(midi);
|
|
27
|
+
// 3. Determine effective tempo and time signature
|
|
28
|
+
const effectiveTempo = config.tempo ?? tempoFromEvents(tempoEvents);
|
|
29
|
+
const effectiveTimeSig = resolveTimeSignature(timeSigEvents, config.timeSignature);
|
|
30
|
+
// 4. Compute ticks per measure
|
|
31
|
+
const tpm = ticksPerMeasure(tpb, effectiveTimeSig.numerator, effectiveTimeSig.denominator);
|
|
32
|
+
// 5. Determine total measures
|
|
33
|
+
const totalMeasures = computeTotalMeasures(notes, tpm);
|
|
34
|
+
// 6. Slice notes into measure buckets
|
|
35
|
+
const buckets = sliceIntoMeasures(notes, totalMeasures, tpm);
|
|
36
|
+
// 7. Build measure objects with hand separation + formatting
|
|
37
|
+
const measures = buildMeasures(buckets, tpb, splitPoint, config);
|
|
38
|
+
// 8. Compute duration in seconds
|
|
39
|
+
const lastNoteTick = notes.length > 0
|
|
40
|
+
? Math.max(...notes.map(n => n.startTick + n.durationTicks))
|
|
41
|
+
: 0;
|
|
42
|
+
const durationSeconds = ticksToSeconds(lastNoteTick, tempoEvents, tpb);
|
|
43
|
+
return {
|
|
44
|
+
id: config.id,
|
|
45
|
+
title: config.title,
|
|
46
|
+
genre: config.genre,
|
|
47
|
+
composer: config.composer,
|
|
48
|
+
arranger: config.arranger,
|
|
49
|
+
difficulty: config.difficulty,
|
|
50
|
+
key: config.key,
|
|
51
|
+
tempo: effectiveTempo,
|
|
52
|
+
timeSignature: `${effectiveTimeSig.numerator}/${effectiveTimeSig.denominator}`,
|
|
53
|
+
durationSeconds: Math.round(durationSeconds),
|
|
54
|
+
musicalLanguage: config.musicalLanguage ?? {
|
|
55
|
+
description: "",
|
|
56
|
+
structure: "",
|
|
57
|
+
keyMoments: [],
|
|
58
|
+
teachingGoals: [],
|
|
59
|
+
styleTips: [],
|
|
60
|
+
},
|
|
61
|
+
measures,
|
|
62
|
+
tags: config.tags,
|
|
63
|
+
source: config.source,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Re-export for barrel convenience
|
|
67
|
+
export { midiNoteToScientific } from "./hands.js";
|
|
68
|
+
// ─── Internal: Extract Events ────────────────────────────────────────────────
|
|
69
|
+
function extractTempoEvents(midi) {
|
|
70
|
+
const events = [];
|
|
71
|
+
for (const track of midi.tracks) {
|
|
72
|
+
let tick = 0;
|
|
73
|
+
for (const event of track) {
|
|
74
|
+
tick += event.deltaTime;
|
|
75
|
+
if (event.type === "setTempo") {
|
|
76
|
+
events.push({ tick, microsecondsPerBeat: event.microsecondsPerBeat });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
events.sort((a, b) => a.tick - b.tick);
|
|
81
|
+
return events;
|
|
82
|
+
}
|
|
83
|
+
function extractTimeSigEvents(midi) {
|
|
84
|
+
const events = [];
|
|
85
|
+
for (const track of midi.tracks) {
|
|
86
|
+
let tick = 0;
|
|
87
|
+
for (const event of track) {
|
|
88
|
+
tick += event.deltaTime;
|
|
89
|
+
if (event.type === "timeSignature") {
|
|
90
|
+
events.push({
|
|
91
|
+
tick,
|
|
92
|
+
numerator: event.numerator,
|
|
93
|
+
denominator: event.denominator,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
events.sort((a, b) => a.tick - b.tick);
|
|
99
|
+
return events;
|
|
100
|
+
}
|
|
101
|
+
/** Get the initial tempo from MIDI events. */
|
|
102
|
+
function tempoFromEvents(events) {
|
|
103
|
+
if (events.length === 0)
|
|
104
|
+
return DEFAULT_TEMPO;
|
|
105
|
+
return Math.round(60_000_000 / events[0].microsecondsPerBeat);
|
|
106
|
+
}
|
|
107
|
+
// ─── Internal: Resolve Notes ─────────────────────────────────────────────────
|
|
108
|
+
/** Flatten all tracks into resolved notes with absolute tick positions. */
|
|
109
|
+
function resolveNotes(midi) {
|
|
110
|
+
const notes = [];
|
|
111
|
+
for (const track of midi.tracks) {
|
|
112
|
+
let tick = 0;
|
|
113
|
+
const pending = new Map();
|
|
114
|
+
for (const event of track) {
|
|
115
|
+
tick += event.deltaTime;
|
|
116
|
+
if (event.type === "noteOn" && event.velocity > 0) {
|
|
117
|
+
const key = `${event.channel}:${event.noteNumber}`;
|
|
118
|
+
pending.set(key, {
|
|
119
|
+
startTick: tick,
|
|
120
|
+
velocity: event.velocity,
|
|
121
|
+
channel: event.channel,
|
|
122
|
+
noteNumber: event.noteNumber,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else if (event.type === "noteOff" ||
|
|
126
|
+
(event.type === "noteOn" && event.velocity === 0)) {
|
|
127
|
+
const key = `${event.channel}:${event.noteNumber}`;
|
|
128
|
+
const start = pending.get(key);
|
|
129
|
+
if (start) {
|
|
130
|
+
notes.push({
|
|
131
|
+
noteNumber: start.noteNumber,
|
|
132
|
+
startTick: start.startTick,
|
|
133
|
+
durationTicks: tick - start.startTick,
|
|
134
|
+
velocity: start.velocity,
|
|
135
|
+
channel: start.channel,
|
|
136
|
+
});
|
|
137
|
+
pending.delete(key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
notes.sort((a, b) => a.startTick - b.startTick || a.noteNumber - b.noteNumber);
|
|
143
|
+
return notes;
|
|
144
|
+
}
|
|
145
|
+
// ─── Internal: Build Measures ────────────────────────────────────────────────
|
|
146
|
+
/** Build Measure objects from measure buckets. */
|
|
147
|
+
function buildMeasures(buckets, ticksPerBeat, splitPoint, config) {
|
|
148
|
+
const overrides = new Map();
|
|
149
|
+
if (config.measureOverrides) {
|
|
150
|
+
for (const ov of config.measureOverrides) {
|
|
151
|
+
overrides.set(ov.measure, ov);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return buckets.map(bucket => {
|
|
155
|
+
const { rightHand, leftHand } = separateHands(bucket.notes, splitPoint);
|
|
156
|
+
const measure = {
|
|
157
|
+
number: bucket.number,
|
|
158
|
+
rightHand: formatHand(rightHand, ticksPerBeat),
|
|
159
|
+
leftHand: formatHand(leftHand, ticksPerBeat),
|
|
160
|
+
};
|
|
161
|
+
const ov = overrides.get(bucket.number);
|
|
162
|
+
if (ov?.fingering)
|
|
163
|
+
measure.fingering = ov.fingering;
|
|
164
|
+
if (ov?.teachingNote)
|
|
165
|
+
measure.teachingNote = ov.teachingNote;
|
|
166
|
+
if (ov?.dynamics)
|
|
167
|
+
measure.dynamics = ov.dynamics;
|
|
168
|
+
if (ov?.tempoOverride)
|
|
169
|
+
measure.tempoOverride = ov.tempoOverride;
|
|
170
|
+
return measure;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
// ─── Internal: Tick-to-Time Conversion ───────────────────────────────────────
|
|
174
|
+
/** Convert a tick position to seconds, respecting tempo changes. */
|
|
175
|
+
function ticksToSeconds(targetTick, tempoEvents, ticksPerBeat) {
|
|
176
|
+
let seconds = 0;
|
|
177
|
+
let currentTick = 0;
|
|
178
|
+
let microsecondsPerBeat = tempoEvents.length > 0
|
|
179
|
+
? tempoEvents[0].microsecondsPerBeat
|
|
180
|
+
: 500_000; // 120 BPM default
|
|
181
|
+
for (const event of tempoEvents) {
|
|
182
|
+
if (event.tick >= targetTick)
|
|
183
|
+
break;
|
|
184
|
+
if (event.tick > currentTick) {
|
|
185
|
+
const deltaTicks = event.tick - currentTick;
|
|
186
|
+
seconds += (deltaTicks / ticksPerBeat) * (microsecondsPerBeat / 1_000_000);
|
|
187
|
+
currentTick = event.tick;
|
|
188
|
+
}
|
|
189
|
+
microsecondsPerBeat = event.microsecondsPerBeat;
|
|
190
|
+
}
|
|
191
|
+
if (currentTick < targetTick) {
|
|
192
|
+
const deltaTicks = targetTick - currentTick;
|
|
193
|
+
seconds += (deltaTicks / ticksPerBeat) * (microsecondsPerBeat / 1_000_000);
|
|
194
|
+
}
|
|
195
|
+
return seconds;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/songs/midi/ingest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ResolvedNote, TimeSigEvent } from "./types.js";
|
|
2
|
+
/** A bucket of notes belonging to a single measure. */
|
|
3
|
+
export interface MeasureBucket {
|
|
4
|
+
/** 1-based measure number. */
|
|
5
|
+
number: number;
|
|
6
|
+
/** Absolute tick where this measure starts. */
|
|
7
|
+
startTick: number;
|
|
8
|
+
/** Absolute tick where this measure ends. */
|
|
9
|
+
endTick: number;
|
|
10
|
+
/** Notes whose startTick falls within this measure. */
|
|
11
|
+
notes: ResolvedNote[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Compute ticks per measure for a given time signature.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ticksPerMeasure(ticksPerBeat: number, numerator: number, denominator: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Determine total number of measures needed to contain all notes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function computeTotalMeasures(notes: ResolvedNote[], tpm: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Slice notes into measure buckets.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sliceIntoMeasures(notes: ResolvedNote[], totalMeasures: number, tpm: number): MeasureBucket[];
|
|
25
|
+
/**
|
|
26
|
+
* Parse a time signature string like "4/4" or "3/4".
|
|
27
|
+
* Returns { numerator, denominator } or defaults to 4/4 on invalid input.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseTimeSignature(timeSig?: string): {
|
|
30
|
+
numerator: number;
|
|
31
|
+
denominator: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Get effective time signature from MIDI events or config string.
|
|
35
|
+
* Config string takes priority over MIDI events.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTimeSignature(events: TimeSigEvent[], configTimeSig?: string): {
|
|
38
|
+
numerator: number;
|
|
39
|
+
denominator: number;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=measures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measures.d.ts","sourceRoot":"","sources":["../../../src/songs/midi/measures.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI7D,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EAAE,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EAAE,EACrB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,GACV,aAAa,EAAE,CAgBjB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAO5C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EAAE,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAM5C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// ─── Measure Slicing ─────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Slices a flat array of resolved notes into measure buckets based on
|
|
4
|
+
// time signature and ticks-per-beat.
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
7
|
+
/**
|
|
8
|
+
* Compute ticks per measure for a given time signature.
|
|
9
|
+
*/
|
|
10
|
+
export function ticksPerMeasure(ticksPerBeat, numerator, denominator) {
|
|
11
|
+
return ticksPerBeat * numerator * (4 / denominator);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Determine total number of measures needed to contain all notes.
|
|
15
|
+
*/
|
|
16
|
+
export function computeTotalMeasures(notes, tpm) {
|
|
17
|
+
if (notes.length === 0)
|
|
18
|
+
return 1;
|
|
19
|
+
const lastNoteTick = Math.max(...notes.map(n => n.startTick + n.durationTicks));
|
|
20
|
+
return Math.max(1, Math.ceil(lastNoteTick / tpm));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Slice notes into measure buckets.
|
|
24
|
+
*/
|
|
25
|
+
export function sliceIntoMeasures(notes, totalMeasures, tpm) {
|
|
26
|
+
const buckets = [];
|
|
27
|
+
for (let m = 0; m < totalMeasures; m++) {
|
|
28
|
+
const startTick = m * tpm;
|
|
29
|
+
const endTick = (m + 1) * tpm;
|
|
30
|
+
buckets.push({
|
|
31
|
+
number: m + 1,
|
|
32
|
+
startTick,
|
|
33
|
+
endTick,
|
|
34
|
+
notes: notes.filter(n => n.startTick >= startTick && n.startTick < endTick),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return buckets;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a time signature string like "4/4" or "3/4".
|
|
41
|
+
* Returns { numerator, denominator } or defaults to 4/4 on invalid input.
|
|
42
|
+
*/
|
|
43
|
+
export function parseTimeSignature(timeSig) {
|
|
44
|
+
if (!timeSig)
|
|
45
|
+
return { numerator: 4, denominator: 4 };
|
|
46
|
+
const parts = timeSig.split("/").map(Number);
|
|
47
|
+
if (parts.length === 2 && parts[0] > 0 && parts[1] > 0) {
|
|
48
|
+
return { numerator: parts[0], denominator: parts[1] };
|
|
49
|
+
}
|
|
50
|
+
return { numerator: 4, denominator: 4 };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get effective time signature from MIDI events or config string.
|
|
54
|
+
* Config string takes priority over MIDI events.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveTimeSignature(events, configTimeSig) {
|
|
57
|
+
if (configTimeSig)
|
|
58
|
+
return parseTimeSignature(configTimeSig);
|
|
59
|
+
if (events.length > 0) {
|
|
60
|
+
return { numerator: events[0].numerator, denominator: events[0].denominator };
|
|
61
|
+
}
|
|
62
|
+
return { numerator: 4, denominator: 4 };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=measures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measures.js","sourceRoot":"","sources":["../../../src/songs/midi/measures.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,sEAAsE;AACtE,qCAAqC;AACrC,gFAAgF;AAkBhF,gFAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,SAAiB,EACjB,WAAmB;IAEnB,OAAO,YAAY,GAAG,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAqB,EACrB,GAAW;IAEX,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAqB,EACrB,aAAqB,EACrB,GAAW;IAEX,MAAM,OAAO,GAAoB,EAAE,CAAC;IAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1B,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAE9B,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,CAAC,GAAG,CAAC;YACb,SAAS;YACT,OAAO;YACP,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC;SAC5E,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAgB;IAEhB,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IACtD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAsB,EACtB,aAAsB;IAEtB,IAAI,aAAa;QAAE,OAAO,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAC5D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A resolved note with absolute timing from MIDI. */
|
|
2
|
+
export interface ResolvedNote {
|
|
3
|
+
/** MIDI note number 0-127. */
|
|
4
|
+
noteNumber: number;
|
|
5
|
+
/** Start time in ticks from the beginning. */
|
|
6
|
+
startTick: number;
|
|
7
|
+
/** Duration in ticks. */
|
|
8
|
+
durationTicks: number;
|
|
9
|
+
/** Velocity 0-127. */
|
|
10
|
+
velocity: number;
|
|
11
|
+
/** MIDI channel. */
|
|
12
|
+
channel: number;
|
|
13
|
+
}
|
|
14
|
+
/** A tempo change event with absolute tick position. */
|
|
15
|
+
export interface IngestTempoEvent {
|
|
16
|
+
tick: number;
|
|
17
|
+
microsecondsPerBeat: number;
|
|
18
|
+
}
|
|
19
|
+
/** A time signature event with absolute tick position. */
|
|
20
|
+
export interface TimeSigEvent {
|
|
21
|
+
tick: number;
|
|
22
|
+
numerator: number;
|
|
23
|
+
denominator: number;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/songs/midi/types.ts"],"names":[],"mappings":"AAOA,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// ─── MIDI Ingest Types ──────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Tick-based types for the MIDI-to-SongEntry conversion pipeline.
|
|
4
|
+
// These are separate from src/midi/types.ts which uses seconds-based timing
|
|
5
|
+
// for real-time playback.
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/songs/midi/types.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,kEAAkE;AAClE,4EAA4E;AAC5E,0BAA0B;AAC1B,gFAAgF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SongEntry, Genre, Difficulty, SearchOptions, RegistryStats } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Register a song in the global registry.
|
|
4
|
+
* Throws if the ID is already taken or validation fails.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerSong(song: SongEntry): void;
|
|
7
|
+
/**
|
|
8
|
+
* Register multiple songs at once.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerSongs(entries: SongEntry[]): void;
|
|
11
|
+
/**
|
|
12
|
+
* Validate a single song entry. Returns an array of error messages (empty = valid).
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateSong(song: SongEntry): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Validate the entire registry. Throws on first invalid song.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateRegistry(): void;
|
|
19
|
+
/** Get a song by its unique ID. */
|
|
20
|
+
export declare function getSong(id: string): SongEntry | undefined;
|
|
21
|
+
/** Get all registered songs. */
|
|
22
|
+
export declare function getAllSongs(): SongEntry[];
|
|
23
|
+
/** Get all songs in a specific genre. */
|
|
24
|
+
export declare function getSongsByGenre(genre: Genre): SongEntry[];
|
|
25
|
+
/** Get all songs at a specific difficulty level. */
|
|
26
|
+
export declare function getSongsByDifficulty(difficulty: Difficulty): SongEntry[];
|
|
27
|
+
/**
|
|
28
|
+
* Search songs with flexible filtering.
|
|
29
|
+
* The `query` field does a case-insensitive match against title, composer, tags,
|
|
30
|
+
* and musicalLanguage.description.
|
|
31
|
+
*/
|
|
32
|
+
export declare function searchSongs(options: SearchOptions): SongEntry[];
|
|
33
|
+
/** Get summary statistics about the registry. */
|
|
34
|
+
export declare function getStats(): RegistryStats;
|
|
35
|
+
/** Clear all registered songs. Used when reloading or in tests. */
|
|
36
|
+
export declare function clearRegistry(): void;
|
|
37
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/songs/registry.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EACT,KAAK,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACd,MAAM,YAAY,CAAC;AASpB;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAWlD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAIxD;AAID;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,EAAE,CAuFtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAID,mCAAmC;AACnC,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEzD;AAED,gCAAgC;AAChC,wBAAgB,WAAW,IAAI,SAAS,EAAE,CAEzC;AAED,yCAAyC;AACzC,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,EAAE,CAEzD;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,CAExE;AAID;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,EAAE,CAsC/D;AAID,iDAAiD;AACjD,wBAAgB,QAAQ,IAAI,aAAa,CAsBxC;AAID,mEAAmE;AACnE,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// ─── Song Registry ──────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for all songs loaded at runtime.
|
|
4
|
+
// Songs are loaded from JSON files (builtin + user directories).
|
|
5
|
+
// The registry provides validation, search, filtering, and stats.
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
import { GENRES, DIFFICULTIES } from "./types.js";
|
|
8
|
+
// ─── Internal state ─────────────────────────────────────────────────────────
|
|
9
|
+
const songs = new Map();
|
|
10
|
+
// ─── Registration ───────────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Register a song in the global registry.
|
|
13
|
+
* Throws if the ID is already taken or validation fails.
|
|
14
|
+
*/
|
|
15
|
+
export function registerSong(song) {
|
|
16
|
+
const errors = validateSong(song);
|
|
17
|
+
if (errors.length > 0) {
|
|
18
|
+
throw new Error(`Invalid song "${song.id}":\n - ${errors.join("\n - ")}`);
|
|
19
|
+
}
|
|
20
|
+
if (songs.has(song.id)) {
|
|
21
|
+
throw new Error(`Duplicate song ID: "${song.id}"`);
|
|
22
|
+
}
|
|
23
|
+
songs.set(song.id, song);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register multiple songs at once.
|
|
27
|
+
*/
|
|
28
|
+
export function registerSongs(entries) {
|
|
29
|
+
for (const song of entries) {
|
|
30
|
+
registerSong(song);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
34
|
+
/**
|
|
35
|
+
* Validate a single song entry. Returns an array of error messages (empty = valid).
|
|
36
|
+
*/
|
|
37
|
+
export function validateSong(song) {
|
|
38
|
+
const errors = [];
|
|
39
|
+
if (!song.id || typeof song.id !== "string") {
|
|
40
|
+
errors.push("id is required and must be a non-empty string");
|
|
41
|
+
}
|
|
42
|
+
else if (!/^[a-z0-9-]+$/.test(song.id)) {
|
|
43
|
+
errors.push("id must be kebab-case (lowercase alphanumeric + hyphens)");
|
|
44
|
+
}
|
|
45
|
+
if (!song.title || typeof song.title !== "string") {
|
|
46
|
+
errors.push("title is required");
|
|
47
|
+
}
|
|
48
|
+
if (!GENRES.includes(song.genre)) {
|
|
49
|
+
errors.push(`genre must be one of: ${GENRES.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
if (!DIFFICULTIES.includes(song.difficulty)) {
|
|
52
|
+
errors.push(`difficulty must be one of: ${DIFFICULTIES.join(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
if (!song.key || typeof song.key !== "string") {
|
|
55
|
+
errors.push("key signature is required");
|
|
56
|
+
}
|
|
57
|
+
if (typeof song.tempo !== "number" || song.tempo < 20 || song.tempo > 300) {
|
|
58
|
+
errors.push("tempo must be a number between 20 and 300 BPM");
|
|
59
|
+
}
|
|
60
|
+
if (!song.timeSignature || !/^\d+\/\d+$/.test(song.timeSignature)) {
|
|
61
|
+
errors.push('timeSignature must be in "N/N" format');
|
|
62
|
+
}
|
|
63
|
+
if (typeof song.durationSeconds !== "number" ||
|
|
64
|
+
song.durationSeconds <= 0) {
|
|
65
|
+
errors.push("durationSeconds must be a positive number");
|
|
66
|
+
}
|
|
67
|
+
// Musical language layer
|
|
68
|
+
if (!song.musicalLanguage) {
|
|
69
|
+
errors.push("musicalLanguage is required");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (!song.musicalLanguage.description) {
|
|
73
|
+
errors.push("musicalLanguage.description is required");
|
|
74
|
+
}
|
|
75
|
+
if (!song.musicalLanguage.structure) {
|
|
76
|
+
errors.push("musicalLanguage.structure is required");
|
|
77
|
+
}
|
|
78
|
+
if (!Array.isArray(song.musicalLanguage.keyMoments) ||
|
|
79
|
+
song.musicalLanguage.keyMoments.length === 0) {
|
|
80
|
+
errors.push("musicalLanguage.keyMoments must have at least one entry");
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(song.musicalLanguage.teachingGoals) ||
|
|
83
|
+
song.musicalLanguage.teachingGoals.length === 0) {
|
|
84
|
+
errors.push("musicalLanguage.teachingGoals must have at least one entry");
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(song.musicalLanguage.styleTips)) {
|
|
87
|
+
errors.push("musicalLanguage.styleTips must be an array");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Measures
|
|
91
|
+
if (!Array.isArray(song.measures) || song.measures.length === 0) {
|
|
92
|
+
errors.push("measures must be a non-empty array");
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
for (let i = 0; i < song.measures.length; i++) {
|
|
96
|
+
const m = song.measures[i];
|
|
97
|
+
if (typeof m.number !== "number" || m.number !== i + 1) {
|
|
98
|
+
errors.push(`measure[${i}].number should be ${i + 1}, got ${m.number}`);
|
|
99
|
+
}
|
|
100
|
+
if (!m.rightHand && !m.leftHand) {
|
|
101
|
+
errors.push(`measure[${i}] must have at least rightHand or leftHand`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!Array.isArray(song.tags)) {
|
|
106
|
+
errors.push("tags must be an array");
|
|
107
|
+
}
|
|
108
|
+
return errors;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate the entire registry. Throws on first invalid song.
|
|
112
|
+
*/
|
|
113
|
+
export function validateRegistry() {
|
|
114
|
+
const allSongs = getAllSongs();
|
|
115
|
+
if (allSongs.length === 0) {
|
|
116
|
+
throw new Error("Registry is empty — no songs registered");
|
|
117
|
+
}
|
|
118
|
+
console.error(`Registry valid: ${allSongs.length} songs across ${new Set(allSongs.map((s) => s.genre)).size} genres.`);
|
|
119
|
+
}
|
|
120
|
+
// ─── Lookup ─────────────────────────────────────────────────────────────────
|
|
121
|
+
/** Get a song by its unique ID. */
|
|
122
|
+
export function getSong(id) {
|
|
123
|
+
return songs.get(id);
|
|
124
|
+
}
|
|
125
|
+
/** Get all registered songs. */
|
|
126
|
+
export function getAllSongs() {
|
|
127
|
+
return [...songs.values()];
|
|
128
|
+
}
|
|
129
|
+
/** Get all songs in a specific genre. */
|
|
130
|
+
export function getSongsByGenre(genre) {
|
|
131
|
+
return getAllSongs().filter((s) => s.genre === genre);
|
|
132
|
+
}
|
|
133
|
+
/** Get all songs at a specific difficulty level. */
|
|
134
|
+
export function getSongsByDifficulty(difficulty) {
|
|
135
|
+
return getAllSongs().filter((s) => s.difficulty === difficulty);
|
|
136
|
+
}
|
|
137
|
+
// ─── Search ─────────────────────────────────────────────────────────────────
|
|
138
|
+
/**
|
|
139
|
+
* Search songs with flexible filtering.
|
|
140
|
+
* The `query` field does a case-insensitive match against title, composer, tags,
|
|
141
|
+
* and musicalLanguage.description.
|
|
142
|
+
*/
|
|
143
|
+
export function searchSongs(options) {
|
|
144
|
+
let results = getAllSongs();
|
|
145
|
+
if (options.genre) {
|
|
146
|
+
results = results.filter((s) => s.genre === options.genre);
|
|
147
|
+
}
|
|
148
|
+
if (options.difficulty) {
|
|
149
|
+
results = results.filter((s) => s.difficulty === options.difficulty);
|
|
150
|
+
}
|
|
151
|
+
if (options.tags && options.tags.length > 0) {
|
|
152
|
+
const wanted = new Set(options.tags.map((t) => t.toLowerCase()));
|
|
153
|
+
results = results.filter((s) => s.tags.some((t) => wanted.has(t.toLowerCase())));
|
|
154
|
+
}
|
|
155
|
+
if (options.maxDuration !== undefined) {
|
|
156
|
+
results = results.filter((s) => s.durationSeconds <= options.maxDuration);
|
|
157
|
+
}
|
|
158
|
+
if (options.minDuration !== undefined) {
|
|
159
|
+
results = results.filter((s) => s.durationSeconds >= options.minDuration);
|
|
160
|
+
}
|
|
161
|
+
if (options.query) {
|
|
162
|
+
const q = options.query.toLowerCase();
|
|
163
|
+
results = results.filter((s) => s.title.toLowerCase().includes(q) ||
|
|
164
|
+
s.composer?.toLowerCase().includes(q) ||
|
|
165
|
+
s.tags.some((t) => t.toLowerCase().includes(q)) ||
|
|
166
|
+
s.musicalLanguage.description.toLowerCase().includes(q));
|
|
167
|
+
}
|
|
168
|
+
return results;
|
|
169
|
+
}
|
|
170
|
+
// ─── Stats ──────────────────────────────────────────────────────────────────
|
|
171
|
+
/** Get summary statistics about the registry. */
|
|
172
|
+
export function getStats() {
|
|
173
|
+
const allSongs = getAllSongs();
|
|
174
|
+
const byGenre = {};
|
|
175
|
+
for (const g of GENRES)
|
|
176
|
+
byGenre[g] = 0;
|
|
177
|
+
for (const s of allSongs)
|
|
178
|
+
byGenre[s.genre]++;
|
|
179
|
+
const byDifficulty = {};
|
|
180
|
+
for (const d of DIFFICULTIES)
|
|
181
|
+
byDifficulty[d] = 0;
|
|
182
|
+
for (const s of allSongs)
|
|
183
|
+
byDifficulty[s.difficulty]++;
|
|
184
|
+
const totalMeasures = allSongs.reduce((sum, s) => sum + s.measures.length, 0);
|
|
185
|
+
return {
|
|
186
|
+
totalSongs: allSongs.length,
|
|
187
|
+
byGenre,
|
|
188
|
+
byDifficulty,
|
|
189
|
+
totalMeasures,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// ─── Reset ──────────────────────────────────────────────────────────────────
|
|
193
|
+
/** Clear all registered songs. Used when reloading or in tests. */
|
|
194
|
+
export function clearRegistry() {
|
|
195
|
+
songs.clear();
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/songs/registry.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,0DAA0D;AAC1D,iEAAiE;AACjE,kEAAkE;AAClE,gFAAgF;AAShF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAElD,+EAA+E;AAE/E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;AAE3C,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAe;IAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,CAAC,EAAE,WAAW,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAC3D,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,OAAoB;IAChD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,YAAY,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAe;IAC1C,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;SAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAc,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,yBAAyB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAwB,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,8BAA8B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;QAC1E,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAClE,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACvD,CAAC;IAED,IACE,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ;QACxC,IAAI,CAAC,eAAe,IAAI,CAAC,EACzB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC3D,CAAC;IAED,yBAAyB;IACzB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;QACzD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QACvD,CAAC;QACD,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC;YAC/C,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAC5C,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;QACzE,CAAC;QACD,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC;YAClD,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,EAC/C,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,WAAW;IACX,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACpD,CAAC;SAAM,CAAC;QACN,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC3B,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvD,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,sBAAsB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1E,CAAC;YACD,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,4CAA4C,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,iBAAiB,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;AACzH,CAAC;AAED,+EAA+E;AAE/E,mCAAmC;AACnC,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACvB,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,eAAe,CAAC,KAAY;IAC1C,OAAO,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AACxD,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,OAAO,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;AAClE,CAAC;AAED,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAsB;IAChD,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;IAE5B,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACjE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAChD,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,OAAO,CAAC,WAAY,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,OAAO,CAAC,WAAY,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACtC,OAAO,GAAG,OAAO,CAAC,MAAM,CACtB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACjC,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACrC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC/C,CAAC,CAAC,eAAe,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC1D,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+EAA+E;AAE/E,iDAAiD;AACjD,MAAM,UAAU,QAAQ;IACtB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAE/B,MAAM,OAAO,GAAG,EAA2B,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;IAE7C,MAAM,YAAY,GAAG,EAAgC,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,YAAY;QAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC;IAEvD,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CACnC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,EACnC,CAAC,CACF,CAAC;IAEF,OAAO;QACL,UAAU,EAAE,QAAQ,CAAC,MAAM;QAC3B,OAAO;QACP,YAAY;QACZ,aAAa;KACd,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E,mEAAmE;AACnE,MAAM,UAAU,aAAa;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|