@ugfoundation/swaralipi-js 0.3.4 → 0.5.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/README.md +8 -8
- package/dist/{batch-Y3HKAL3B.js → batch-UETOBPHT.js} +4 -4
- package/dist/{batch-Y3HKAL3B.js.map → batch-UETOBPHT.js.map} +1 -1
- package/dist/{chunk-AIGSKUD3.js → chunk-C6TRCMPL.js} +24 -5
- package/dist/chunk-C6TRCMPL.js.map +1 -0
- package/dist/{chunk-KPKD53NA.js → chunk-ID44OP2N.js} +3 -3
- package/dist/{chunk-KPKD53NA.js.map → chunk-ID44OP2N.js.map} +1 -1
- package/dist/{chunk-IG2CIUTP.js → chunk-UD2K4UIC.js} +3 -3
- package/dist/{chunk-IG2CIUTP.js.map → chunk-UD2K4UIC.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/player/index.cjs +58 -15
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +2 -2
- package/dist/player/index.d.ts +2 -2
- package/dist/player/index.js +39 -15
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-dXV5cUQI.d.ts → schedule-BaYIJf8y.d.ts} +14 -4
- package/dist/{schedule-C2WN7YOK.d.cts → schedule-bBvy18MO.d.cts} +14 -4
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +1 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +27 -13
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +1 -1
- package/dist/viewer/index.d.ts +1 -1
- package/dist/viewer/index.js +14 -12
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AIGSKUD3.js.map +0 -1
package/dist/player/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-D3wyNRqM.cjs';
|
|
2
2
|
import { T as TaalId } from '../taals-CjzBuZ35.cjs';
|
|
3
|
-
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-
|
|
4
|
-
export { b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, d as INSTRUMENTS, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as drumPattern, m as instrumentSampleUrl, n as nearestTablaBpm, o as normalizeBase, p as probeUrl, q as quantizeLoopEnd, s as santoorUrls, r as subsetUrls, t as tablaBpms, u as tablaUrl } from '../schedule-
|
|
3
|
+
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-bBvy18MO.cjs';
|
|
4
|
+
export { b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, d as INSTRUMENTS, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as drumPattern, m as instrumentSampleUrl, n as nearestTablaBpm, o as normalizeBase, p as probeUrl, q as quantizeLoopEnd, s as santoorUrls, r as subsetUrls, t as tablaBpms, u as tablaUrl } from '../schedule-bBvy18MO.cjs';
|
|
5
5
|
import 'zod';
|
|
6
6
|
import 'tone';
|
|
7
7
|
|
package/dist/player/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-D3wyNRqM.js';
|
|
2
2
|
import { T as TaalId } from '../taals-CjzBuZ35.js';
|
|
3
|
-
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-
|
|
4
|
-
export { b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, d as INSTRUMENTS, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as drumPattern, m as instrumentSampleUrl, n as nearestTablaBpm, o as normalizeBase, p as probeUrl, q as quantizeLoopEnd, s as santoorUrls, r as subsetUrls, t as tablaBpms, u as tablaUrl } from '../schedule-
|
|
3
|
+
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-BaYIJf8y.js';
|
|
4
|
+
export { b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, d as INSTRUMENTS, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as drumPattern, m as instrumentSampleUrl, n as nearestTablaBpm, o as normalizeBase, p as probeUrl, q as quantizeLoopEnd, s as santoorUrls, r as subsetUrls, t as tablaBpms, u as tablaUrl } from '../schedule-BaYIJf8y.js';
|
|
5
5
|
import 'zod';
|
|
6
6
|
import 'tone';
|
|
7
7
|
|
package/dist/player/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, buildSchedule, quantizeLoopEnd, midiToSampleName, createSampler, tablaUrl, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic } from '../chunk-
|
|
2
|
-
export { DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, drumPattern, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl } from '../chunk-
|
|
1
|
+
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, buildSchedule, quantizeLoopEnd, midiToSampleName, createSampler, tablaUrl, tablaShiftSemitones, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic } from '../chunk-C6TRCMPL.js';
|
|
2
|
+
export { DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, drumPattern, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl } from '../chunk-C6TRCMPL.js';
|
|
3
3
|
import { TAALS } from '../chunk-6ETPFHZ6.js';
|
|
4
4
|
|
|
5
5
|
// src/player/player.ts
|
|
@@ -17,6 +17,13 @@ function midiRange(schedule) {
|
|
|
17
17
|
}
|
|
18
18
|
return lo <= hi ? { lo, hi } : void 0;
|
|
19
19
|
}
|
|
20
|
+
async function loadedOrWarn(Tone) {
|
|
21
|
+
try {
|
|
22
|
+
await Tone.loaded();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.warn("[swaralipi] some audio samples failed to load; continuing without them", err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
var SwaralipiPlayer = class {
|
|
21
28
|
#opts;
|
|
22
29
|
/** Per-song setBpm tweak: beats meta.bpm but is cleared by the next load(). */
|
|
@@ -35,6 +42,7 @@ var SwaralipiPlayer = class {
|
|
|
35
42
|
#tanpura;
|
|
36
43
|
#tanpuraKey = "";
|
|
37
44
|
#metronome;
|
|
45
|
+
/** The looping tabla — a `GrainPlayer` when the tonic needs it retuned. */
|
|
38
46
|
#tabla;
|
|
39
47
|
#drums;
|
|
40
48
|
#scheduleIds = [];
|
|
@@ -387,13 +395,14 @@ var SwaralipiPlayer = class {
|
|
|
387
395
|
const melody = this.#buildMelody(Tone, this.#effectiveInstrument(), base, range);
|
|
388
396
|
this.#buildRhythm(Tone, transport, base, spb);
|
|
389
397
|
this.#buildTanpura(Tone, transport, base, spb);
|
|
390
|
-
await Tone
|
|
398
|
+
await loadedOrWarn(Tone);
|
|
391
399
|
if (this.#tabla?.buffer?.loaded) {
|
|
392
400
|
this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
|
|
393
401
|
}
|
|
394
402
|
const draw = Tone.getDraw();
|
|
403
|
+
const shift = this.#opts.rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
395
404
|
for (const ev of schedule.events) {
|
|
396
|
-
const at = ev.beat * spb;
|
|
405
|
+
const at = ev.beat * spb + shift;
|
|
397
406
|
if (ev.midi !== null) {
|
|
398
407
|
const name = midiToSampleName(ev.midi);
|
|
399
408
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
@@ -407,10 +416,10 @@ var SwaralipiPlayer = class {
|
|
|
407
416
|
draw.schedule(() => this.#emitHighlight(ev.path), time);
|
|
408
417
|
}, at);
|
|
409
418
|
}
|
|
410
|
-
const endAt = schedule.totalBeats * spb + spb;
|
|
419
|
+
const endAt = schedule.totalBeats * spb + shift + spb;
|
|
411
420
|
transport.scheduleOnce((time) => {
|
|
412
421
|
draw.schedule(() => this.#emitHighlight(null), time);
|
|
413
|
-
}, schedule.totalBeats * spb);
|
|
422
|
+
}, schedule.totalBeats * spb + shift);
|
|
414
423
|
transport.scheduleOnce(() => this.stop(), endAt);
|
|
415
424
|
}
|
|
416
425
|
/** Taal mode: loop one avartan forever (tabla loop / drum sequence via
|
|
@@ -430,7 +439,7 @@ var SwaralipiPlayer = class {
|
|
|
430
439
|
transport.bpm.value = bpm;
|
|
431
440
|
const spb = 60 / bpm;
|
|
432
441
|
this.#buildRhythm(Tone, transport, base, spb);
|
|
433
|
-
await Tone
|
|
442
|
+
await loadedOrWarn(Tone);
|
|
434
443
|
if (this.#tabla?.buffer?.loaded) {
|
|
435
444
|
this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
|
|
436
445
|
}
|
|
@@ -470,14 +479,21 @@ var SwaralipiPlayer = class {
|
|
|
470
479
|
const wantsTabla = this.#opts.rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0;
|
|
471
480
|
if (wantsTabla) {
|
|
472
481
|
const url = tablaUrl(base, taalId, nearestTablaBpm(this.bpm, tablaBpms(taalId)), this.#variation());
|
|
473
|
-
|
|
482
|
+
const shift = tablaShiftSemitones(this.#tonicSemitone());
|
|
483
|
+
this.#tabla = (shift === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
484
|
+
url,
|
|
485
|
+
loop: true,
|
|
486
|
+
detune: shift * 100,
|
|
487
|
+
grainSize: TABLA_GRAIN_SEC,
|
|
488
|
+
overlap: TABLA_GRAIN_OVERLAP_SEC
|
|
489
|
+
})).connect(master);
|
|
474
490
|
this.#tabla.sync().start(0);
|
|
475
491
|
return;
|
|
476
492
|
}
|
|
477
493
|
if (this.#opts.rhythm === "drums") {
|
|
478
494
|
this.#drums ??= createDrumKit(Tone).connect(master);
|
|
479
495
|
const drums = this.#drums;
|
|
480
|
-
const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : { numBeats: 4, sam: 0 }
|
|
496
|
+
const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };
|
|
481
497
|
const pattern = drumPattern(taal);
|
|
482
498
|
let beat2 = 0;
|
|
483
499
|
const id2 = transport.scheduleRepeat((time) => {
|
|
@@ -491,7 +507,7 @@ var SwaralipiPlayer = class {
|
|
|
491
507
|
if (this.#opts.rhythm === "none" || base === void 0) return;
|
|
492
508
|
this.#metronome ??= createMetronome(Tone, base).connect(master);
|
|
493
509
|
const metro = this.#metronome;
|
|
494
|
-
const cycle = this.#taal?.numBeats ??
|
|
510
|
+
const cycle = this.#taal?.numBeats ?? this.#doc?.meta.taal?.numBeats ?? 4;
|
|
495
511
|
let beat = 0;
|
|
496
512
|
const id = transport.scheduleRepeat((time) => {
|
|
497
513
|
metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
|
|
@@ -575,7 +591,8 @@ async function exportAudio(doc, options = {}) {
|
|
|
575
591
|
const spb = 60 / bpm;
|
|
576
592
|
const id = options.instrument ?? "piano";
|
|
577
593
|
const def = pickDef(id, options);
|
|
578
|
-
const
|
|
594
|
+
const shift = rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
595
|
+
const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
|
|
579
596
|
const rendered = await Tone.Offline(
|
|
580
597
|
async ({ transport }) => {
|
|
581
598
|
transport.bpm.value = bpm;
|
|
@@ -584,7 +601,7 @@ async function exportAudio(doc, options = {}) {
|
|
|
584
601
|
const sampler = createSampler(Tone, def, base, range).connect(master);
|
|
585
602
|
for (const ev of schedule.events) {
|
|
586
603
|
if (ev.midi === null) continue;
|
|
587
|
-
const at = ev.beat * spb;
|
|
604
|
+
const at = ev.beat * spb + shift;
|
|
588
605
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
589
606
|
if (ev.kan !== null) {
|
|
590
607
|
const name2 = midiToSampleName(ev.kan);
|
|
@@ -597,7 +614,7 @@ async function exportAudio(doc, options = {}) {
|
|
|
597
614
|
let tabla;
|
|
598
615
|
if (rhythm === "drums" && base) {
|
|
599
616
|
const drums = createDrumKit(Tone).connect(master);
|
|
600
|
-
const taal = taalId !== void 0 ? TAALS[taalId] : { numBeats: 4, sam: 0 };
|
|
617
|
+
const taal = taalId !== void 0 ? TAALS[taalId] : doc.meta.taal ?? { numBeats: 4, sam: 0 };
|
|
601
618
|
const pattern = drumPattern(taal);
|
|
602
619
|
let beat = 0;
|
|
603
620
|
transport.scheduleRepeat((time) => {
|
|
@@ -608,11 +625,18 @@ async function exportAudio(doc, options = {}) {
|
|
|
608
625
|
} else if (rhythm !== "none" && base) {
|
|
609
626
|
if (usesTabla && taalId) {
|
|
610
627
|
const url = tablaUrl(base, taalId, bpm, variation);
|
|
611
|
-
|
|
628
|
+
const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));
|
|
629
|
+
tabla = (semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
630
|
+
url,
|
|
631
|
+
loop: true,
|
|
632
|
+
detune: semis * 100,
|
|
633
|
+
grainSize: TABLA_GRAIN_SEC,
|
|
634
|
+
overlap: TABLA_GRAIN_OVERLAP_SEC
|
|
635
|
+
})).connect(master);
|
|
612
636
|
tabla.sync().start(0);
|
|
613
637
|
} else {
|
|
614
638
|
const metro = createMetronome(Tone, base).connect(master);
|
|
615
|
-
const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : 4;
|
|
639
|
+
const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : doc.meta.taal?.numBeats ?? 4;
|
|
616
640
|
let beat = 0;
|
|
617
641
|
transport.scheduleRepeat((time) => {
|
|
618
642
|
metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
|
package/dist/player/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/player/player.ts","../../src/player/export.ts","../../src/player/midi.ts"],"names":["beat","id","midiRange","name","off","safeName"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `SwaralipiPlayer` — the stateful audio facade over Tone.js.\n *\n * Tone is **dynamically imported** on first `play()` so that importing\n * `@ugfoundation/swaralipi-js/player` in Node/SSR (or just to reach the pure\n * helpers) never pulls in the audio engine or touches `AudioContext`. The\n * pure cores (pitch.ts, schedule.ts, instruments.ts) carry the logic; this\n * class only wires them onto the Tone Transport and drives the viewer's\n * `highlight()`.\n *\n * Model: build a beat-timed `Schedule`, lay each note onto the Transport in\n * seconds (`beat × 60/bpm`), loop a tabla (or click a metronome) underneath,\n * drone a tanpura, and fire highlights on Tone's draw thread. Any setting\n * change while playing re-arranges from the top (documented v1 behavior — no\n * mid-stream tempo ramp or seek).\n */\nimport type { Gain, Players, Player, Sampler } from 'tone';\nimport type { Swaralipi } from '../spec/index.js';\n// Taal type-only from schema.ts (not the barrel) — keeps the player zod-free,\n// same reason instruments.ts imports TAALS from the taals subpath.\nimport type { Taal } from '../spec/schema.js';\n// TAALS from the taals subpath, not the barrel — see the note in instruments.ts\n// (the barrel pulls zod, which would defeat the player's bare-load guarantee).\nimport { TAALS, type TaalId } from '../spec/taals.js';\nimport { midiToSampleName, parseTonic } from './pitch.js';\nimport { buildSchedule, type Schedule } from './schedule.js';\nimport {\n DEFAULT_SOUNDS_BASE,\n INSTRUMENTS,\n SAMPLED_TAALS,\n TANPURA_PCS,\n createDrumKit,\n createMetronome,\n createSampler,\n createTanpura,\n drumPattern,\n instrumentSampleUrl,\n nearestTablaBpm,\n normalizeBase,\n probeUrl,\n quantizeLoopEnd,\n tablaBpms,\n tablaUrl,\n type InstrumentDef,\n type InstrumentId,\n type ToneModule,\n} from './instruments.js';\n\n/** Seconds the accompaniment fades over when playback is stopped. */\nconst STOP_FADE_SEC = 0.5;\n\nexport type PlayerState = 'idle' | 'loading' | 'ready' | 'playing' | 'paused';\nexport type RhythmMode = 'tabla' | 'drums' | 'metronome' | 'none';\n\nexport interface PlayerOptions {\n /** Base URL for the project's samples (santoor/tabla/tanpura/metronome).\n * Defaults to `DEFAULT_SOUNDS_BASE` (the project's CORS-enabled CDN), so the\n * player works with zero config; override to self-host. */\n soundsBaseUrl?: string;\n /** Grand-piano sample host. Default: the Salamander CDN (see SALAMANDER_BASE).\n * Set this to self-host the piano under your own URL. */\n pianoBaseUrl?: string;\n /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */\n tonic?: string;\n /** Tempo in matra/min. Default 90. Snapped to the 15-grid while tabla plays. */\n bpm?: number;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Active melody instrument id (a built-in or a `customInstruments` key).\n * Default `piano` (CDN-hosted, zero-config). Built-ins: piano, santoor,\n * harmonium, violin, flute. */\n instrument?: InstrumentId;\n /** Instrument ids the UI may expose. Default: all built-ins. Narrow it to\n * e.g. `['santoor','harmonium']` to limit the picker. */\n instruments?: InstrumentId[];\n /** Extra melody instruments, keyed by id — e.g. a self-hosted sitar/sarod.\n * (True sampled sitar/sarod/sarangi/esraj have no free web set; supply your\n * own Tone.Sampler `urls` + `baseUrl` here and they slot into the picker.) */\n customInstruments?: Record<string, InstrumentDef>;\n /** Rhythm accompaniment. Default `tabla` (→ metronome for unsampled taals).\n * `drums` sequences a Western drumset (kick/snare/hihat) to the taal's\n * sam/tali/khaali structure — a genuine \"drum atlas\"-style translation,\n * not a loop; works for every taal (no per-taal sample requirement). */\n rhythm?: RhythmMode;\n /** Play the tanpura drone. Default true. */\n tanpura?: boolean;\n /** Stop playback after this many seconds (a policy-neutral preview cap).\n * Default `Infinity` (unlimited). The library only enforces the number it is\n * handed; who is capped / the value / the upsell is the consumer's policy. */\n previewLimitSec?: number;\n /** Called when the preview cap stops playback (so the consumer can show an\n * upsell). Not called on a normal stop()/end. */\n onLimit?: () => void;\n /** Called with the active note's `part.row.note` path, or null when idle. */\n onHighlight?: (path: string | null) => void;\n /** Taal mode only (`loadTaal`): called on the draw thread once per matra with\n * the beat index within the cycle (0-based, `< numBeats`), so a consumer can\n * highlight the active theka bol / drum-staff note. Never fired in doc mode. */\n onBeat?: (beatInCycle: number) => void;\n /** Called on every state transition. */\n onState?: (state: PlayerState) => void;\n}\n\n/** Minimal shape of the viewer element the player drives (avoids a hard\n * dependency on the viewer module). `highlight` returns the highlighted node\n * (or null) so bindSheet can optionally scroll it into view. */\ninterface HighlightTarget {\n highlight(path: string): HTMLElement | null;\n clearHighlights(): void;\n}\n\n/** MIDI span (incl. kan graces) of a schedule's notes, for sample subsetting;\n * undefined when there are no pitched events. */\nfunction midiRange(schedule: Schedule): { lo: number; hi: number } | undefined {\n let lo = Infinity;\n let hi = -Infinity;\n for (const ev of schedule.events) {\n for (const m of [ev.midi, ev.kan]) {\n if (m !== null) {\n if (m < lo) lo = m;\n if (m > hi) hi = m;\n }\n }\n }\n return lo <= hi ? { lo, hi } : undefined;\n}\n\nexport interface BindSheetOptions {\n /** Keep the active note visible while playing by calling `scrollIntoView`\n * on it. `true` → `'center'` (keeps the note's lyric, rendered just below it,\n * on screen too); or pass an explicit block position (`'nearest'`, `'start'`,\n * …). Default off so it never fights a host that manages its own scrolling.\n * Works through the shadow boundary, so it scrolls the page, not just the\n * sheet. */\n autoScroll?: boolean | ScrollLogicalPosition;\n}\n\n/** The common melody-voice call the scheduler needs — both melody instruments\n * are `Tone.Sampler`s, but this keeps the call site decoupled from the class. */\ninterface MelodyVoice {\n triggerAttackRelease(note: string, duration: number, time: number): unknown;\n}\n\nexport class SwaralipiPlayer {\n #opts: Required<\n Omit<\n PlayerOptions,\n | 'soundsBaseUrl'\n | 'pianoBaseUrl'\n | 'customInstruments'\n | 'onHighlight'\n | 'onBeat'\n | 'onState'\n | 'onLimit'\n | 'tonic'\n | 'bpm'\n >\n > & {\n soundsBaseUrl?: string;\n pianoBaseUrl?: string;\n customInstruments?: Record<string, InstrumentDef>;\n tonic?: string;\n /** Pinned constructor tempo: overrides meta.bpm, survives load(). */\n bpm?: number;\n onHighlight?: (path: string | null) => void;\n onBeat?: (beatInCycle: number) => void;\n onState?: (state: PlayerState) => void;\n onLimit?: () => void;\n };\n /** Per-song setBpm tweak: beats meta.bpm but is cleared by the next load(). */\n #bpmTweak: number | undefined;\n #doc: Swaralipi | undefined;\n /** Taal mode (`loadTaal`): loops one avartan of this taal's theka with no\n * melody. Mutually exclusive with `#doc`. */\n #taal: Taal | undefined;\n #state: PlayerState = 'idle';\n\n #tone: ToneModule | undefined;\n /** Master bus everything routes through, so stop() can fade it out. */\n #master: Gain | undefined;\n /** Cached melody samplers keyed by instrument id, with the MIDI range each\n * was loaded for (rebuilt when a song needs a wider range). */\n #samplers = new Map<string, { sampler: Sampler; lo: number; hi: number }>();\n #tanpura: Players | undefined;\n #tanpuraKey = '';\n #metronome: Player | undefined;\n #tabla: Player | undefined;\n #drums: Players | undefined;\n #scheduleIds: number[] = [];\n #fadeTimer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(options: PlayerOptions = {}) {\n this.#opts = {\n soundsBaseUrl: options.soundsBaseUrl ?? DEFAULT_SOUNDS_BASE,\n pianoBaseUrl: options.pianoBaseUrl,\n customInstruments: options.customInstruments,\n tonic: options.tonic,\n bpm: options.bpm,\n baseOctave: options.baseOctave ?? 4,\n instrument: options.instrument ?? 'piano',\n instruments: options.instruments ?? Object.keys(INSTRUMENTS),\n rhythm: options.rhythm ?? 'tabla',\n tanpura: options.tanpura ?? true,\n previewLimitSec: options.previewLimitSec ?? Infinity,\n onHighlight: options.onHighlight,\n onBeat: options.onBeat,\n onState: options.onState,\n onLimit: options.onLimit,\n };\n }\n\n /* ----------------------------------------------------------------- */\n /* Public getters */\n /* ----------------------------------------------------------------- */\n\n get state(): PlayerState {\n return this.#state;\n }\n get bpm(): number {\n return this.#bpmTweak ?? this.#opts.bpm ?? this.#doc?.meta.bpm ?? 90;\n }\n /** The preview cap in seconds (`Infinity` = unlimited). */\n get previewLimitSec(): number {\n return this.#opts.previewLimitSec;\n }\n get instrument(): InstrumentId {\n return this.#effectiveInstrument();\n }\n get instruments(): InstrumentId[] {\n return [...this.#opts.instruments];\n }\n /** The taal currently driving the rhythm (auto from the doc, or an override). */\n get taalId(): TaalId | undefined {\n return this.#taalId();\n }\n /** Whether the effective rhythm will actually be a sampled tabla. */\n get usesTabla(): boolean {\n const id = this.#taalId();\n return this.#opts.rhythm === 'tabla' && id !== undefined && SAMPLED_TAALS.has(id) && !!this.#opts.soundsBaseUrl;\n }\n\n /* ----------------------------------------------------------------- */\n /* Loading + transport control */\n /* ----------------------------------------------------------------- */\n\n /** Set the document to play. Stops any current playback. */\n load(doc: Swaralipi): void {\n this.#halt();\n this.#doc = doc;\n this.#taal = undefined;\n this.#taalOverride = undefined;\n this.#bpmTweak = undefined; // per-song tweak does not carry across songs\n this.#setState('ready');\n }\n\n /**\n * Taal mode: loop one avartan of a taal's theka (rhythm only, no melody) —\n * for a standalone theka player. `play()` loops indefinitely (tabla loop /\n * drum sequence) and fires `onBeat(beatInCycle)` once per matra on the draw\n * thread so the consumer can highlight the active bol / drum-staff note. The\n * taal's `id` should be a sampled taal (see `SAMPLED_TAALS`) for tabla audio;\n * `variation` picks a tabla loop variation. Stops any current playback.\n */\n loadTaal(taal: Taal): void {\n this.#halt();\n this.#taal = taal;\n this.#doc = undefined;\n this.#taalOverride = undefined;\n this.#bpmTweak = undefined;\n this.#setState('ready');\n }\n\n /** Start (or resume) playback. Must be triggered from a user gesture so the\n * browser permits `AudioContext` resume. */\n async play(): Promise<void> {\n if ((this.#doc === undefined && this.#taal === undefined) || this.#state === 'playing') return;\n const Tone = await this.#ensureTone();\n await Tone.start();\n\n if (this.#state === 'paused') {\n Tone.getTransport().start();\n this.#setState('playing');\n return;\n }\n\n // Fresh start — abort any in-flight stop fade and clear a left-running\n // transport before re-arranging.\n this.#cancelFade();\n Tone.getTransport().stop();\n this.#setState('loading');\n await this.#arrange(Tone);\n Tone.getTransport().start();\n this.#setState('playing');\n\n // Preview cap: stop at the limit and notify (policy is the consumer's).\n const limit = this.#opts.previewLimitSec;\n if (Number.isFinite(limit)) {\n Tone.getTransport().scheduleOnce(() => {\n // getDraw defers to the visual frame so stop() runs off the audio thread.\n Tone.getDraw().schedule(() => {\n if (this.#state === 'playing') {\n this.stop();\n this.#opts.onLimit?.();\n }\n }, this.#tone!.now());\n }, limit);\n }\n }\n\n /** Pause; `play()` resumes from the same position. */\n pause(): void {\n if (this.#tone === undefined || this.#state !== 'playing') return;\n this.#tone.getTransport().pause();\n this.#setState('paused');\n }\n\n /**\n * Stop and rewind to the start. When sounding, the accompaniment (tabla /\n * metronome / tanpura, which otherwise loop) is gently faded out over\n * STOP_FADE_SEC before the transport is halted, so the rhythm doesn't keep\n * playing or cut abruptly; otherwise it halts immediately.\n */\n stop(): void {\n if (this.#tone === undefined || this.#state === 'idle') {\n this.#halt();\n return;\n }\n if ((this.#state === 'playing' || this.#state === 'paused') && this.#master !== undefined) {\n const now = this.#tone.now();\n this.#master.gain.cancelScheduledValues(now);\n this.#master.gain.rampTo(0, STOP_FADE_SEC, now);\n this.#emitHighlight(null);\n this.#setState(this.#doc || this.#taal ? 'ready' : 'idle');\n this.#cancelFadeTimer();\n this.#fadeTimer = setTimeout(() => this.#halt(), STOP_FADE_SEC * 1000 + 60);\n return;\n }\n this.#halt();\n }\n\n /** Immediate, synchronous teardown: stop+rewind the transport, kill the\n * looping accompaniment, restore the master gain for the next play. */\n #halt(): void {\n this.#cancelFadeTimer();\n if (this.#tone !== undefined) {\n const transport = this.#tone.getTransport();\n transport.stop();\n transport.cancel(0);\n transport.position = 0;\n }\n // Dispose the looping tabla outright — a synced loop can outlive a bare\n // transport.stop(); #arrange recreates it on the next play.\n this.#tabla?.dispose();\n this.#tabla = undefined;\n this.#restoreMasterGain();\n this.#scheduleIds = [];\n this.#emitHighlight(null);\n if (this.#state !== 'idle') this.#setState(this.#doc || this.#taal ? 'ready' : 'idle');\n }\n\n #cancelFadeTimer(): void {\n if (this.#fadeTimer !== undefined) {\n clearTimeout(this.#fadeTimer);\n this.#fadeTimer = undefined;\n }\n }\n\n /** Abort an in-flight stop-fade (timer + gain ramp) without touching the\n * transport — used when play() interrupts a fade. */\n #cancelFade(): void {\n this.#cancelFadeTimer();\n this.#restoreMasterGain();\n }\n\n #restoreMasterGain(): void {\n if (this.#master !== undefined && this.#tone !== undefined) {\n this.#master.gain.cancelScheduledValues(this.#tone.now());\n this.#master.gain.value = 1;\n }\n }\n\n /* ----------------------------------------------------------------- */\n /* Settings (re-arrange from the top if mid-playback) */\n /* ----------------------------------------------------------------- */\n\n setTonic(tonic: string): void {\n this.#opts.tonic = tonic;\n this.#reapply();\n }\n setBpm(bpm: number): void {\n this.#bpmTweak = bpm;\n this.#reapply();\n }\n /** Update the preview cap (`Infinity` lifts it). Takes effect on next play. */\n setPreviewLimit(sec: number): void {\n this.#opts.previewLimitSec = sec;\n }\n setInstrument(instrument: InstrumentId): void {\n this.#opts.instrument = instrument;\n this.#reapply();\n }\n setRhythm(rhythm: RhythmMode): void {\n this.#opts.rhythm = rhythm;\n this.#reapply();\n }\n setTanpura(on: boolean): void {\n this.#opts.tanpura = on;\n this.#reapply();\n }\n /** Override the taal (null restores auto-detection from `meta.taal`). */\n setTaal(id: TaalId | null): void {\n this.#taalOverride = id ?? undefined;\n this.#taalAuto = id === null;\n this.#reapply();\n }\n\n /** Wire highlight output to a `<swaralipi-sheet>` (or any highlight target).\n * Pass `{ autoScroll }` to keep the active note in view while playing. */\n bindSheet(target: HighlightTarget, opts: BindSheetOptions = {}): void {\n // 'center' so the lyric directly below the note stays on screen too\n // ('nearest' can park the note at the bottom edge, hiding its lyric).\n const block: ScrollLogicalPosition | undefined =\n opts.autoScroll === true ? 'center' : opts.autoScroll || undefined;\n this.#opts.onHighlight = (path) => {\n target.clearHighlights();\n if (path === null) return;\n const el = target.highlight(path);\n if (block !== undefined && el !== null) {\n el.scrollIntoView({ block, inline: 'nearest', behavior: 'smooth' });\n }\n };\n }\n\n /**\n * Check whether an instrument's samples are reachable (its CDN is up / the\n * local files exist), via a one-byte ranged fetch of a representative sample.\n * A pass means Tone can actually load it. `false` for unknown ids, local\n * instruments with no `soundsBaseUrl`, or any network failure/timeout.\n */\n async checkInstrument(id: string, timeoutMs?: number): Promise<boolean> {\n const def = this.#resolveDef(id);\n if (def === undefined) return false;\n const url = instrumentSampleUrl(def, this.#opts.soundsBaseUrl);\n if (url === undefined) return false;\n return probeUrl(url, timeoutMs);\n }\n\n /**\n * Probe several instruments at once (default: the exposed `instruments`),\n * returning `{ id: available }`. Use it to disable picker options whose CDN\n * or files are down. Probes run concurrently; a check never throws.\n */\n async checkInstruments(\n ids: string[] = this.#opts.instruments,\n timeoutMs?: number,\n ): Promise<Record<string, boolean>> {\n const entries = await Promise.all(\n ids.map(async (id) => [id, await this.checkInstrument(id, timeoutMs)] as const),\n );\n return Object.fromEntries(entries);\n }\n\n /** Release all audio resources. The instance is unusable afterward. */\n dispose(): void {\n this.#halt();\n for (const { sampler } of this.#samplers.values()) sampler.dispose();\n this.#samplers.clear();\n this.#tanpura?.dispose();\n this.#metronome?.dispose();\n this.#tabla?.dispose();\n this.#drums?.dispose();\n this.#master?.dispose();\n this.#tanpura = this.#metronome = this.#tabla = this.#drums = this.#master = undefined;\n }\n\n /* ----------------------------------------------------------------- */\n /* Internals */\n /* ----------------------------------------------------------------- */\n\n #taalOverride: TaalId | undefined;\n #taalAuto = true;\n\n #setState(state: PlayerState): void {\n this.#state = state;\n this.#opts.onState?.(state);\n }\n\n #emitHighlight(path: string | null): void {\n this.#opts.onHighlight?.(path);\n }\n\n /** The tabla variation to play: taal-mode taal's, the doc's own (auto mode),\n * else none — an explicit setTaal override plays the taal's default variation. */\n #variation(): string | undefined {\n if (this.#taal !== undefined) return this.#taal.variation;\n return this.#taalAuto ? this.#doc?.meta.taal?.variation : undefined;\n }\n\n /** Resolve the active taal id: taal mode's taal, an explicit override, else\n * the doc's — but only when it names a sampled taal. */\n #taalId(): TaalId | undefined {\n const id = this.#taal?.id ?? (!this.#taalAuto && this.#taalOverride !== undefined ? this.#taalOverride : this.#doc?.meta.taal?.id);\n return id !== undefined && id in TAALS ? (id as TaalId) : undefined;\n }\n\n /** Resolve an instrument id to its def (custom overrides built-ins; the\n * `pianoBaseUrl` shortcut self-hosts the built-in piano). */\n #resolveDef(id: string): InstrumentDef | undefined {\n const custom = this.#opts.customInstruments?.[id];\n if (custom) return custom;\n const def = INSTRUMENTS[id];\n if (def && id === 'piano' && this.#opts.pianoBaseUrl) {\n return { ...def, baseUrl: normalizeBase(this.#opts.pianoBaseUrl) };\n }\n return def;\n }\n\n /** A CDN-backed instrument that needs no `soundsBaseUrl` — used when the\n * chosen one is local-only and no samples host is set, or is unknown. */\n #fallbackInstrument(): string {\n const exposed = this.#opts.instruments.find((id) => {\n const d = this.#resolveDef(id);\n return d !== undefined && d.local !== true;\n });\n return exposed ?? 'harmonium';\n }\n\n /** The instrument actually used: the chosen one, unless it's local-only with\n * no `soundsBaseUrl` (or unknown), in which case a CDN fallback. */\n #effectiveInstrument(): string {\n const id = this.#opts.instrument;\n const def = this.#resolveDef(id);\n if (def === undefined) return this.#fallbackInstrument();\n if (def.local === true && !this.#opts.soundsBaseUrl) return this.#fallbackInstrument();\n return id;\n }\n\n /** Effective tempo: snapped to the taal's available tabla tempos while a\n * tabla actually plays (so it never snaps to a bpm that taal lacks). */\n #effectiveBpm(): number {\n const id = this.#taalId();\n return this.usesTabla && id ? nearestTablaBpm(this.bpm, tablaBpms(id)) : this.bpm;\n }\n\n async #ensureTone(): Promise<ToneModule> {\n this.#tone ??= await import('tone');\n return this.#tone;\n }\n\n /** Hard stop → rebuild → restart, only while playing; otherwise just store.\n * Uses #halt (not the stop() fade) so a setting change restarts instantly. */\n #reapply(): void {\n if (this.#state !== 'playing') return;\n this.#halt();\n void this.play();\n }\n\n /** The master bus everything routes through (created once, → destination). */\n #ensureMaster(Tone: ToneModule): Gain {\n this.#master ??= new Tone.Gain(1).toDestination();\n return this.#master;\n }\n\n /** Build the melody instrument(s)/rhythm/drone for the current settings and\n * wait for their buffers, then schedule everything on the Transport. */\n async #arrange(Tone: ToneModule): Promise<void> {\n if (this.#taal !== undefined) return this.#arrangeTaal(Tone);\n if (this.#doc === undefined) return;\n const base = this.#opts.soundsBaseUrl;\n const transport = Tone.getTransport();\n transport.cancel(0);\n transport.position = 0;\n this.#scheduleIds = [];\n\n // Fresh master bus at unity gain (a prior stop-fade may have lowered it).\n this.#ensureMaster(Tone);\n this.#restoreMasterGain();\n\n const bpm = this.#effectiveBpm();\n transport.bpm.value = bpm;\n const spb = 60 / bpm;\n\n // Build the (pure) schedule first so the melody sampler loads only the\n // samples covering this song's pitch range — fewer files to fetch.\n const schedule: Schedule = buildSchedule(this.#doc, {\n tonic: this.#opts.tonic,\n baseOctave: this.#opts.baseOctave,\n });\n const range = midiRange(schedule);\n\n // --- melody instrument ---\n const melody = this.#buildMelody(Tone, this.#effectiveInstrument(), base, range);\n\n // --- rhythm ---\n this.#buildRhythm(Tone, transport, base, spb);\n\n // --- tanpura drone ---\n this.#buildTanpura(Tone, transport, base, spb);\n\n // wait for the (now-minimal) sample set to load before scheduling\n await Tone.loaded();\n\n // Snap the tabla loop to a whole number of beats now its buffer length is\n // known, so a mistrimmed loop phase-locks instead of drifting (no-op for a\n // correctly trimmed file).\n if (this.#tabla?.buffer?.loaded) {\n this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);\n }\n\n // --- notes + highlights ---\n const draw = Tone.getDraw();\n\n for (const ev of schedule.events) {\n const at = ev.beat * spb;\n if (ev.midi !== null) {\n const name = midiToSampleName(ev.midi);\n const dur = Math.max(ev.durationBeats * spb, 0.05);\n if (ev.kan !== null) {\n const kanName = midiToSampleName(ev.kan);\n transport.scheduleOnce((time) => melody.triggerAttackRelease(kanName, spb * 0.4, time), Math.max(at - spb * 0.12, 0));\n }\n transport.scheduleOnce((time) => melody.triggerAttackRelease(name, dur, time), at);\n }\n transport.scheduleOnce((time) => {\n draw.schedule(() => this.#emitHighlight(ev.path), time);\n }, at);\n }\n\n // clear highlight + auto-stop at the end\n const endAt = schedule.totalBeats * spb + spb;\n transport.scheduleOnce((time) => {\n draw.schedule(() => this.#emitHighlight(null), time);\n }, schedule.totalBeats * spb);\n transport.scheduleOnce(() => this.stop(), endAt);\n }\n\n /** Taal mode: loop one avartan forever (tabla loop / drum sequence via\n * #buildRhythm), firing onBeat + a `String(beatInCycle)` highlight each matra\n * on the draw thread. No melody, no tanpura, no auto-stop — it plays until\n * stop(). */\n async #arrangeTaal(Tone: ToneModule): Promise<void> {\n if (this.#taal === undefined) return;\n const base = this.#opts.soundsBaseUrl;\n const transport = Tone.getTransport();\n transport.cancel(0);\n transport.position = 0;\n this.#scheduleIds = [];\n\n this.#ensureMaster(Tone);\n this.#restoreMasterGain();\n\n const bpm = this.#effectiveBpm();\n transport.bpm.value = bpm;\n const spb = 60 / bpm;\n\n this.#buildRhythm(Tone, transport, base, spb);\n await Tone.loaded();\n if (this.#tabla?.buffer?.loaded) {\n this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);\n }\n\n const draw = Tone.getDraw();\n const n = Math.max(1, this.#taal.numBeats);\n let beat = 0;\n const id = transport.scheduleRepeat((time) => {\n const b = beat % n;\n draw.schedule(() => {\n this.#emitHighlight(String(b));\n this.#opts.onBeat?.(b);\n }, time);\n beat += 1;\n }, spb, 0);\n this.#scheduleIds.push(id);\n }\n\n /** Build (or reuse) the melody sampler for `id`, loading only the samples\n * covering `range`. A cached sampler is reused while it still covers the\n * range; otherwise it's rebuilt (so changing songs/tonic widens it lazily). */\n #buildMelody(\n Tone: ToneModule,\n id: string,\n base: string | undefined,\n range: { lo: number; hi: number } | undefined,\n ): MelodyVoice {\n const def = this.#resolveDef(id) ?? INSTRUMENTS.harmonium!;\n const master = this.#ensureMaster(Tone);\n const cached = this.#samplers.get(id);\n if (cached && (range === undefined || (cached.lo <= range.lo && cached.hi >= range.hi))) {\n return cached.sampler;\n }\n cached?.sampler.dispose();\n const sampler = createSampler(Tone, def, base, range).connect(master);\n this.#samplers.set(id, { sampler, lo: range?.lo ?? -Infinity, hi: range?.hi ?? Infinity });\n return sampler;\n }\n\n #buildRhythm(Tone: ToneModule, transport: ReturnType<ToneModule['getTransport']>, base: string | undefined, spb: number): void {\n // dispose a stale tabla loop so it doesn't double up after a re-arrange\n this.#tabla?.dispose();\n this.#tabla = undefined;\n\n const master = this.#ensureMaster(Tone);\n const taalId = this.#taalId();\n const wantsTabla = this.#opts.rhythm === 'tabla' && taalId !== undefined && SAMPLED_TAALS.has(taalId) && base !== undefined;\n\n if (wantsTabla) {\n const url = tablaUrl(base!, taalId!, nearestTablaBpm(this.bpm, tablaBpms(taalId!)), this.#variation());\n this.#tabla = new Tone.Player({ url, loop: true }).connect(master);\n this.#tabla.sync().start(0);\n return;\n }\n\n if (this.#opts.rhythm === 'drums') {\n this.#drums ??= createDrumKit(Tone).connect(master);\n const drums = this.#drums;\n const taal = this.#taal ?? (taalId !== undefined ? TAALS[taalId] : { numBeats: 4, sam: 0 });\n const pattern = drumPattern(taal);\n let beat = 0;\n const id = transport.scheduleRepeat((time) => {\n const voice = pattern[beat % pattern.length]!;\n if (drums.has(voice)) drums.player(voice).start(time);\n beat += 1;\n }, spb, 0);\n this.#scheduleIds.push(id);\n return;\n }\n\n if (this.#opts.rhythm === 'none' || base === undefined) return;\n\n // metronome fallback: a click each beat, louder on sam (cycle start)\n this.#metronome ??= createMetronome(Tone, base).connect(master);\n const metro = this.#metronome;\n const cycle = this.#taal?.numBeats ?? (taalId !== undefined ? TAALS[taalId].numBeats : 4);\n let beat = 0;\n const id = transport.scheduleRepeat((time) => {\n metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);\n metro.start(time);\n beat += 1;\n }, spb, 0);\n this.#scheduleIds.push(id);\n }\n\n #buildTanpura(Tone: ToneModule, transport: ReturnType<ToneModule['getTransport']>, base: string | undefined, spb: number): void {\n if (!this.#opts.tanpura || base === undefined) return;\n\n // Pa–Sa–Sa–Sa drone, one pluck every two beats, cycling.\n const tonicPc = ((this.#tonicSemitone() % 12) + 12) % 12;\n const sa = TANPURA_PCS[tonicPc]!;\n const pa = TANPURA_PCS[(tonicPc + 7) % 12]!;\n\n // Load only the two pitch classes the drone uses (not all 12); rebuild if\n // the tonic changed.\n const key = `${sa},${pa}`;\n if (this.#tanpura === undefined || this.#tanpuraKey !== key) {\n this.#tanpura?.dispose();\n this.#tanpura = createTanpura(Tone, base, [sa, pa]).connect(this.#ensureMaster(Tone));\n this.#tanpuraKey = key;\n }\n const players = this.#tanpura;\n players.volume.value = -14;\n\n const pattern = [pa, sa, sa, sa];\n let i = 0;\n const id = transport.scheduleRepeat((time) => {\n const stem = pattern[i % pattern.length]!;\n if (players.has(stem)) players.player(stem).start(time);\n i += 1;\n }, spb * 2, 0);\n this.#scheduleIds.push(id);\n }\n\n #tonicSemitone(): number {\n return parseTonic(this.#opts.tonic ?? this.#doc?.meta.tonic);\n }\n}\n","/**\n * Offline audio export — render a swaralipi doc to a downloadable WAV or MP3\n * **entirely in the browser** (no server): `Tone.Offline` renders the same\n * melody / tabla / tanpura arrangement the live player builds into an\n * OfflineAudioContext (faster than real time), then a pure encoder writes the\n * PCM out.\n *\n * Like the player, Tone is **dynamically imported** (so importing this module\n * in Node/SSR never touches `AudioContext`) and the MP3 path dynamically\n * imports lamejs (an *optional* peer dep — WAV needs no dependency at all).\n * The actual render needs a browser (Node has no OfflineAudioContext); the two\n * encoders below are pure and unit-testable on their own.\n *\n * ponytail: the melody/rhythm/tanpura scheduling mirrors player.ts `#arrange`\n * (minus highlights / caching / fades) — keep the two in sync.\n */\nimport type { Player } from 'tone';\nimport type { Swaralipi } from '../spec/index.js';\nimport { TAALS, type TaalId } from '../spec/taals.js';\nimport { midiToSampleName, parseTonic } from './pitch.js';\nimport { buildSchedule, type Schedule } from './schedule.js';\nimport type { RhythmMode } from './player.js';\nimport {\n DEFAULT_SOUNDS_BASE,\n INSTRUMENTS,\n SAMPLED_TAALS,\n TANPURA_PCS,\n createDrumKit,\n createMetronome,\n createSampler,\n createTanpura,\n drumPattern,\n nearestTablaBpm,\n normalizeBase,\n quantizeLoopEnd,\n tablaBpms,\n tablaUrl,\n type InstrumentDef,\n type InstrumentId,\n type ToneModule,\n} from './instruments.js';\n\nexport type AudioFormat = 'wav' | 'mp3';\n\nexport interface AudioExportOptions {\n /** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */\n soundsBaseUrl?: string;\n /** Self-host the grand piano (see the player's `pianoBaseUrl`). */\n pianoBaseUrl?: string;\n /** Extra melody instruments, keyed by id (see the player's option). */\n customInstruments?: Record<string, InstrumentDef>;\n /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */\n tonic?: string;\n /** Tempo in matra/min. Default: `meta.bpm`, else 90. Snapped to the tabla grid\n * when a sampled tabla actually plays. */\n bpm?: number;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Melody instrument id. Default `piano`. */\n instrument?: InstrumentId;\n /** Include the notation (melody) voice. Default true. */\n melody?: boolean;\n /** Rhythm accompaniment: `tabla` (→ metronome for unsampled taals), `drums`\n * (Western drumset following sam/tali/khaali), `metronome`, or `none`.\n * Default `none` — the base export is notation only. */\n rhythm?: RhythmMode;\n /** Taal for the rhythm: an explicit id, or `auto` (from `meta.taal`). Default\n * `auto`. Only used when `rhythm` isn't `none`. */\n taal?: TaalId | 'auto';\n /** Include the tanpura drone. Default false. */\n tanpura?: boolean;\n /** Output format. Default `wav` (lossless, zero-dependency). `mp3` needs the\n * optional peer dep `@breezystack/lamejs`. */\n format?: AudioFormat;\n /** MP3 bitrate in kbps. Default 192. */\n mp3Kbps?: number;\n /** Output channels. Default 1 (the content is monophonic and centred, so mono\n * halves the file with no audible loss); set 2 for stereo. */\n channels?: 1 | 2;\n /** Sample rate. Default 44100. */\n sampleRate?: number;\n /** Fade the last N seconds so a looped tabla/tanpura doesn't hard-cut at the\n * render boundary. Default 0.4. */\n fadeOutSec?: number;\n}\n\nexport interface AudioExportResult {\n /** The encoded audio, ready to download or upload. */\n blob: Blob;\n /** Suggested `<title>.<ext>` download filename. */\n filename: string;\n mimeType: string;\n /** Rendered length in seconds. */\n durationSec: number;\n}\n\n/** MIDI span (incl. kan graces) of a schedule, for sample subsetting. */\nfunction midiRange(schedule: Schedule): { lo: number; hi: number } | undefined {\n let lo = Infinity;\n let hi = -Infinity;\n for (const ev of schedule.events) {\n for (const m of [ev.midi, ev.kan]) {\n if (m !== null) {\n if (m < lo) lo = m;\n if (m > hi) hi = m;\n }\n }\n }\n return lo <= hi ? { lo, hi } : undefined;\n}\n\n/** Resolve the melody instrument def (mirrors the player's #resolveDef): a\n * custom override, the piano self-host shortcut, a CDN fallback for a local\n * instrument with no host, else the requested built-in. */\nfunction pickDef(id: string, o: AudioExportOptions): InstrumentDef {\n const def = o.customInstruments?.[id] ?? INSTRUMENTS[id];\n if (def === undefined) return INSTRUMENTS.harmonium!; // unknown id → CDN fallback\n if (id === 'piano' && o.pianoBaseUrl) return { ...def, baseUrl: normalizeBase(o.pianoBaseUrl) };\n if (def.local === true && !o.soundsBaseUrl) return INSTRUMENTS.harmonium!; // local needs a host\n return def;\n}\n\n/** The active taal id (explicit override, else the doc's), or undefined for an\n * unknown/absent taal (→ metronome falls back to a 4-beat cycle). */\nfunction resolveTaal(doc: Swaralipi, taal: AudioExportOptions['taal']): TaalId | undefined {\n const id = taal !== undefined && taal !== 'auto' ? taal : doc.meta.taal?.id;\n return id !== undefined && id in TAALS ? (id as TaalId) : undefined;\n}\n\n/** Strip filesystem-hostile characters (keeps Bengali/Unicode letters). */\nfunction safeName(title: string | undefined): string {\n return (title ?? '').replace(/[\\\\/:*?\"<>|]+/g, '').trim() || 'swaralipi';\n}\n\n/**\n * Render a swaralipi doc to an audio Blob in the browser. Runs in an\n * OfflineAudioContext — no user gesture and no server needed. Throws in an\n * environment without Web Audio (Node/SSR) or, for `format: 'mp3'`, when the\n * optional `@breezystack/lamejs` peer dependency is not installed.\n */\nexport async function exportAudio(\n doc: Swaralipi,\n options: AudioExportOptions = {},\n): Promise<AudioExportResult> {\n const Tone: ToneModule = await import('tone');\n\n const base = options.soundsBaseUrl ?? DEFAULT_SOUNDS_BASE;\n const format = options.format ?? 'wav';\n const channels = options.channels ?? 1;\n const sampleRate = options.sampleRate ?? 44100;\n const fadeOutSec = options.fadeOutSec ?? 0.4;\n const rhythm: RhythmMode = options.rhythm ?? 'none';\n const withMelody = options.melody ?? true;\n const withTanpura = options.tanpura ?? false;\n\n const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });\n const range = midiRange(schedule);\n\n const taalId = resolveTaal(doc, options.taal);\n const variation = options.taal === undefined || options.taal === 'auto' ? doc.meta.taal?.variation : undefined;\n const usesTabla = rhythm === 'tabla' && taalId !== undefined && SAMPLED_TAALS.has(taalId) && !!base;\n\n const reqBpm = options.bpm ?? doc.meta.bpm ?? 90;\n const bpm = usesTabla && taalId ? nearestTablaBpm(reqBpm, tablaBpms(taalId)) : reqBpm;\n const spb = 60 / bpm;\n\n const id = options.instrument ?? 'piano';\n const def = pickDef(id, options);\n const durationSec = Math.max(schedule.totalBeats * spb + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);\n\n const rendered = await Tone.Offline(\n async ({ transport }) => {\n transport.bpm.value = bpm;\n const master = new Tone.Gain(1).toDestination();\n\n // --- melody (the notation, sounded) ---\n if (withMelody) {\n const sampler = createSampler(Tone, def, base, range).connect(master);\n for (const ev of schedule.events) {\n if (ev.midi === null) continue;\n const at = ev.beat * spb;\n const dur = Math.max(ev.durationBeats * spb, 0.05);\n if (ev.kan !== null) {\n const name = midiToSampleName(ev.kan);\n transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(at - spb * 0.12, 0));\n }\n const name = midiToSampleName(ev.midi);\n transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, dur, t), at);\n }\n }\n\n // --- rhythm (looped tabla, drumset, or a metronome click) ---\n let tabla: Player | undefined;\n if (rhythm === 'drums' && base) {\n const drums = createDrumKit(Tone).connect(master);\n const taal = taalId !== undefined ? TAALS[taalId] : { numBeats: 4, sam: 0 };\n const pattern = drumPattern(taal);\n let beat = 0;\n transport.scheduleRepeat((time) => {\n const voice = pattern[beat % pattern.length]!;\n if (drums.has(voice)) drums.player(voice).start(time);\n beat += 1;\n }, spb, 0);\n } else if (rhythm !== 'none' && base) {\n if (usesTabla && taalId) {\n const url = tablaUrl(base, taalId, bpm, variation);\n tabla = new Tone.Player({ url, loop: true }).connect(master);\n tabla.sync().start(0);\n } else {\n const metro = createMetronome(Tone, base).connect(master);\n const cycle = taalId !== undefined ? TAALS[taalId].numBeats : 4;\n let beat = 0;\n transport.scheduleRepeat((time) => {\n metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);\n metro.start(time);\n beat += 1;\n }, spb, 0);\n }\n }\n\n // --- tanpura drone (Pa–Sa–Sa–Sa, one pluck every two beats) ---\n if (withTanpura && base) {\n const tonicPc = ((parseTonic(options.tonic ?? doc.meta.tonic) % 12) + 12) % 12;\n const sa = TANPURA_PCS[tonicPc]!;\n const pa = TANPURA_PCS[(tonicPc + 7) % 12]!;\n const players = createTanpura(Tone, base, [sa, pa]).connect(master);\n players.volume.value = -14;\n const pattern = [pa, sa, sa, sa];\n let i = 0;\n transport.scheduleRepeat((time) => {\n const stem = pattern[i % pattern.length]!;\n if (players.has(stem)) players.player(stem).start(time);\n i += 1;\n }, spb * 2, 0);\n }\n\n // Buffers must be loaded before the (one-shot) render, or they arrive too\n // late to be heard — the async callback is awaited, so this is the fix.\n await Tone.loaded();\n // Snap a mistrimmed tabla loop to whole beats now its length is known.\n if (tabla?.buffer?.loaded) tabla.loopEnd = quantizeLoopEnd(tabla.buffer.duration, spb);\n\n // Soften the tail so looped accompaniment doesn't click off at the edge.\n if (fadeOutSec > 0) {\n master.gain.setValueAtTime(1, Math.max(durationSec - fadeOutSec, 0));\n master.gain.linearRampToValueAtTime(0, durationSec);\n }\n transport.start(0);\n },\n durationSec,\n channels,\n sampleRate,\n );\n\n const chans: Float32Array[] = [];\n for (let c = 0; c < rendered.numberOfChannels; c++) chans.push(rendered.getChannelData(c));\n\n const bytes =\n format === 'mp3'\n ? await encodeMp3(chans, rendered.sampleRate, options.mp3Kbps)\n : encodeWav(chans, rendered.sampleRate);\n const mimeType = format === 'mp3' ? 'audio/mpeg' : 'audio/wav';\n return {\n blob: new Blob([bytes as BlobPart], { type: mimeType }),\n filename: `${safeName(doc.meta.title)}.${format}`,\n mimeType,\n durationSec: rendered.duration,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Pure encoders (browser-free, unit-testable) */\n/* ------------------------------------------------------------------ */\n\n/** Clamp a float sample to a 16-bit signed integer. */\nfunction toInt16(f: number): number {\n const s = f < -1 ? -1 : f > 1 ? 1 : f;\n return s < 0 ? s * 0x8000 : s * 0x7fff;\n}\n\n/**\n * Encode planar float channels to a 16-bit PCM WAV (RIFF). Pure — no Web Audio\n * — so it runs (and tests) in Node. 16-bit PCM is lossless CD quality and plays\n * everywhere; float/24-bit skipped (add if mastering-grade output is needed).\n */\nexport function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array {\n const numCh = channels.length;\n const frames = channels[0]?.length ?? 0;\n const bytesPerSample = 2;\n const blockAlign = numCh * bytesPerSample;\n const dataSize = frames * blockAlign;\n const buf = new ArrayBuffer(44 + dataSize);\n const view = new DataView(buf);\n const str = (off: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i));\n };\n\n str(0, 'RIFF');\n view.setUint32(4, 36 + dataSize, true);\n str(8, 'WAVE');\n str(12, 'fmt ');\n view.setUint32(16, 16, true); // PCM fmt chunk size\n view.setUint16(20, 1, true); // format = PCM\n view.setUint16(22, numCh, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * blockAlign, true); // byte rate\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bytesPerSample * 8, true); // bits per sample\n str(36, 'data');\n view.setUint32(40, dataSize, true);\n\n let off = 44;\n for (let f = 0; f < frames; f++) {\n for (let c = 0; c < numCh; c++) {\n view.setInt16(off, toInt16(channels[c]![f]!), true);\n off += 2;\n }\n }\n return new Uint8Array(buf);\n}\n\n/** Convert a float channel to the Int16 samples lamejs expects. */\nfunction floatToInt16(f: Float32Array): Int16Array {\n const out = new Int16Array(f.length);\n for (let i = 0; i < f.length; i++) out[i] = toInt16(f[i]!);\n return out;\n}\n\nfunction concatBytes(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const out = new Uint8Array(total);\n let off = 0;\n for (const c of chunks) {\n out.set(c, off);\n off += c.length;\n }\n return out;\n}\n\n/** Load the optional lamejs encoder, tolerating named/default export shapes. */\nasync function loadLame(): Promise<{ new (channels: number, sampleRate: number, kbps: number): Mp3EncoderLike }> {\n try {\n const mod = (await import('@breezystack/lamejs')) as unknown as {\n Mp3Encoder?: new (c: number, s: number, k: number) => Mp3EncoderLike;\n default?: { Mp3Encoder: new (c: number, s: number, k: number) => Mp3EncoderLike };\n };\n const Mp3Encoder = mod.Mp3Encoder ?? mod.default?.Mp3Encoder;\n if (!Mp3Encoder) throw new Error('no Mp3Encoder export');\n return Mp3Encoder;\n } catch {\n throw new Error(\n 'MP3 export needs the optional peer dependency \"@breezystack/lamejs\" — ' +\n 'install it, or export as WAV (format: \"wav\").',\n );\n }\n}\n\ninterface Mp3EncoderLike {\n encodeBuffer(left: Int16Array, right?: Int16Array): Int8Array;\n flush(): Int8Array;\n}\n\n/**\n * Encode planar float channels to MP3 via lamejs. lamejs is **dynamically\n * imported** and an *optional* peer dependency (LGPL-3.0) — WAV needs no\n * dependency, so only reach for this when a compressed file is wanted. Samples\n * are fed in 1152-frame blocks (a multiple of 576, lamejs' preferred size).\n */\nexport async function encodeMp3(\n channels: Float32Array[],\n sampleRate: number,\n kbps = 192,\n): Promise<Uint8Array> {\n const Mp3Encoder = await loadLame();\n const numCh = Math.min(channels.length, 2); // lamejs is mono or stereo\n const enc = new Mp3Encoder(numCh, sampleRate, kbps);\n const left = floatToInt16(channels[0] ?? new Float32Array());\n const right = numCh > 1 ? floatToInt16(channels[1]!) : undefined;\n const block = 1152;\n const out: Uint8Array[] = [];\n for (let i = 0; i < left.length; i += block) {\n const l = left.subarray(i, i + block);\n const chunk = right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l);\n if (chunk.length > 0) out.push(new Uint8Array(chunk));\n }\n const tail = enc.flush();\n if (tail.length > 0) out.push(new Uint8Array(tail));\n return concatBytes(out);\n}\n","/**\n * MIDI export — write a swaralipi doc to a downloadable Standard MIDI File.\n *\n * Unlike the audio export this needs **no Tone and no AudioContext**: MIDI is\n * just note events, and `buildSchedule` already gives us (midi, beat,\n * durationBeats) per note. So the whole thing is pure bytes — it runs (and\n * tests) in plain Node, and carries no dependency (the SMF format is trivial),\n * which keeps it clear of any redistribution/license question.\n *\n * Scope = the **melody voice**, plus — when `drums` is on — the Western-drumset\n * translation of the taal (the same `drumPattern` the player sequences) written\n * as a **separate GM channel-10 track**. Tabla is prerecorded audio loops with\n * no per-stroke note data, so there's nothing to turn into MIDI notes; tanpura\n * is a fixed background drone. A `.mid` here is the notation line (+ optional\n * drums) — the user opens it in any DAW and assigns instruments.\n */\nimport type { Swaralipi } from '../spec/index.js';\nimport { TAALS } from '../spec/taals.js';\nimport { buildSchedule, type Schedule } from './schedule.js';\nimport { drumPattern, type DrumVoice } from './instruments.js';\n\n/** Pulses per quarter note. One matra (beat) == one quarter note. */\nconst PPQ = 480;\n\nexport interface MidiExportOptions {\n /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */\n tonic?: string;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Tempo in matra/min written as the file's tempo. Default `meta.bpm`, else 90. */\n bpm?: number;\n /** Track name meta-event. Default the song title. */\n name?: string;\n /** Also export the taal's drumset pattern as a separate GM channel-10 track. */\n drums?: boolean;\n}\n\nexport interface MidiExportResult {\n blob: Blob;\n /** Suggested `<title>.mid` filename. */\n filename: string;\n mimeType: string;\n}\n\n/** Variable-length quantity (7 bits/byte, high bit = continue). */\nfunction vlq(n: number): number[] {\n const out = [n & 0x7f];\n n >>>= 7;\n while (n > 0) {\n out.unshift((n & 0x7f) | 0x80);\n n >>>= 7;\n }\n return out;\n}\n\n/** UTF-8 bytes of a string (Bengali titles survive; most DAWs read UTF-8). */\nfunction utf8(s: string): number[] {\n return [...new TextEncoder().encode(s)];\n}\n\nfunction u32be(n: number): number[] {\n return [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];\n}\n\ninterface RawEvent {\n tick: number;\n on: boolean;\n note: number;\n vel: number;\n ch: number;\n}\n\n/** General MIDI channel-10 (index 9) percussion note per drumset voice. */\nconst DRUM_NOTE: Record<DrumVoice, number> = {\n kick: 36,\n snare: 38,\n hihat: 42, // closed hi-hat\n tom1: 48,\n tom2: 45,\n tom3: 41,\n};\n\n/** Serialise sorted raw events into an MTrk chunk (bytes, incl. header). */\nfunction trackChunk(raw: RawEvent[], head: number[]): number[] {\n // Time order; at equal ticks release before attacking so a repeated pitch\n // doesn't leave a hung note.\n raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));\n const track = [...head];\n let last = 0;\n for (const e of raw) {\n const status = (e.on ? 0x90 : 0x80) | (e.ch & 0x0f);\n track.push(...vlq(e.tick - last), status, e.note & 0x7f, e.vel & 0x7f);\n last = e.tick;\n }\n track.push(...vlq(0), 0xff, 0x2f, 0x00); // end of track\n return [0x4d, 0x54, 0x72, 0x6b, ...u32be(track.length), ...track];\n}\n\n/**\n * Encode a schedule to a Standard MIDI File. Melody rides channel 0; when\n * `drums` is given, its per-beat voices ride a second GM channel-10 track\n * (the file becomes format 1). Pure — no Tone, no browser — directly testable.\n */\nexport function encodeMidi(\n schedule: Schedule,\n opts: { bpm: number; name?: string; drums?: DrumVoice[] },\n): Uint8Array {\n const melody: RawEvent[] = [];\n for (const ev of schedule.events) {\n if (ev.midi === null) continue;\n const start = Math.round(ev.beat * PPQ);\n const end = Math.max(Math.round((ev.beat + ev.durationBeats) * PPQ), start + 1);\n if (ev.kan !== null) {\n const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);\n melody.push({ tick: kOn, on: true, note: ev.kan, vel: 70, ch: 0 });\n melody.push({ tick: start, on: false, note: ev.kan, vel: 0, ch: 0 }); // grace releases at the main attack\n }\n melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });\n melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });\n }\n\n // Tempo lives on the first track alongside the name.\n const usPerBeat = Math.round(60_000_000 / opts.bpm);\n const head: number[] = [];\n if (opts.name) head.push(...vlq(0), 0xff, 0x03, ...vlq(utf8(opts.name).length), ...utf8(opts.name));\n head.push(...vlq(0), 0xff, 0x51, 0x03, (usPerBeat >> 16) & 0xff, (usPerBeat >> 8) & 0xff, usPerBeat & 0xff);\n\n const chunks = [trackChunk(melody, head)];\n\n if (opts.drums && opts.drums.length > 0) {\n const drum: RawEvent[] = [];\n const totalBeats = Math.ceil(schedule.totalBeats);\n for (let beat = 0; beat < totalBeats; beat++) {\n const note = DRUM_NOTE[opts.drums[beat % opts.drums.length]!];\n const t = beat * PPQ;\n drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });\n drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 }); // one-shot: short gate\n }\n chunks.push(trackChunk(drum, [...vlq(0), 0xff, 0x03, ...vlq(5), 0x44, 0x72, 0x75, 0x6d, 0x73])); // \"Drums\"\n }\n\n const format = chunks.length > 1 ? 1 : 0;\n const header = [\n 0x4d, 0x54, 0x68, 0x64, ...u32be(6),\n 0x00, format, (chunks.length >> 8) & 0xff, chunks.length & 0xff,\n (PPQ >> 8) & 0xff, PPQ & 0xff,\n ];\n return new Uint8Array([...header, ...chunks.flat()]);\n}\n\n/** Strip filesystem-hostile characters (keeps Bengali/Unicode letters). */\nfunction safeName(title: string | undefined): string {\n return (title ?? '').replace(/[\\\\/:*?\"<>|]+/g, '').trim() || 'swaralipi';\n}\n\n/**\n * Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs\n * beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.\n */\nexport function exportMidi(doc: Swaralipi, options: MidiExportOptions = {}): MidiExportResult {\n const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });\n const bpm = options.bpm ?? doc.meta.bpm ?? 90;\n const taalId = doc.meta.taal?.id;\n const taal = taalId !== undefined && taalId in TAALS ? TAALS[taalId as keyof typeof TAALS] : { numBeats: 4, sam: 0 };\n const drums = options.drums ? drumPattern(taal) : undefined;\n const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });\n return {\n blob: new Blob([bytes as BlobPart], { type: 'audio/midi' }),\n filename: `${safeName(doc.meta.title)}.mid`,\n mimeType: 'audio/midi',\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/player/player.ts","../../src/player/export.ts","../../src/player/midi.ts"],"names":["beat","id","midiRange","name","off","safeName"],"mappings":";;;;;AAoDA,IAAM,aAAA,GAAgB,GAAA;AAgEtB,SAAS,UAAU,QAAA,EAA4D;AAC7E,EAAA,IAAI,EAAA,GAAK,QAAA;AACT,EAAA,IAAI,EAAA,GAAK,CAAA,QAAA;AACT,EAAA,KAAA,MAAW,EAAA,IAAM,SAAS,MAAA,EAAQ;AAChC,IAAA,KAAA,MAAW,KAAK,CAAC,EAAA,CAAG,IAAA,EAAM,EAAA,CAAG,GAAG,CAAA,EAAG;AACjC,MAAA,IAAI,MAAM,IAAA,EAAM;AACd,QAAA,IAAI,CAAA,GAAI,IAAI,EAAA,GAAK,CAAA;AACjB,QAAA,IAAI,CAAA,GAAI,IAAI,EAAA,GAAK,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,EAAA,IAAM,EAAA,GAAK,EAAE,EAAA,EAAI,IAAG,GAAI,MAAA;AACjC;AAUA,eAAe,aAAa,IAAA,EAAiC;AAC3D,EAAA,IAAI;AACF,IAAA,MAAM,KAAK,MAAA,EAAO;AAAA,EACpB,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,IAAA,CAAK,0EAA0E,GAAG,CAAA;AAAA,EAC5F;AACF;AAkBO,IAAM,kBAAN,MAAsB;AAAA,EAC3B,KAAA;AAAA;AAAA,EA0BA,SAAA;AAAA,EACA,IAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA,EACA,MAAA,GAAsB,MAAA;AAAA,EAEtB,KAAA;AAAA;AAAA,EAEA,OAAA;AAAA;AAAA;AAAA,EAGA,SAAA,uBAAgB,GAAA,EAA0D;AAAA,EAC1E,QAAA;AAAA,EACA,WAAA,GAAc,EAAA;AAAA,EACd,UAAA;AAAA;AAAA,EAEA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,eAAyB,EAAC;AAAA,EAC1B,UAAA;AAAA,EAEA,WAAA,CAAY,OAAA,GAAyB,EAAC,EAAG;AACvC,IAAA,IAAA,CAAK,KAAA,GAAQ;AAAA,MACX,aAAA,EAAe,QAAQ,aAAA,IAAiB,mBAAA;AAAA,MACxC,cAAc,OAAA,CAAQ,YAAA;AAAA,MACtB,mBAAmB,OAAA,CAAQ,iBAAA;AAAA,MAC3B,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,KAAK,OAAA,CAAQ,GAAA;AAAA,MACb,UAAA,EAAY,QAAQ,UAAA,IAAc,CAAA;AAAA,MAClC,UAAA,EAAY,QAAQ,UAAA,IAAc,OAAA;AAAA,MAClC,WAAA,EAAa,OAAA,CAAQ,WAAA,IAAe,MAAA,CAAO,KAAK,WAAW,CAAA;AAAA,MAC3D,MAAA,EAAQ,QAAQ,MAAA,IAAU,OAAA;AAAA,MAC1B,OAAA,EAAS,QAAQ,OAAA,IAAW,IAAA;AAAA,MAC5B,eAAA,EAAiB,QAAQ,eAAA,IAAmB,QAAA;AAAA,MAC5C,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,SAAS,OAAA,CAAQ;AAAA,KACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,KAAA,GAAqB;AACvB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EACA,IAAI,GAAA,GAAc;AAChB,IAAA,OAAO,IAAA,CAAK,aAAa,IAAA,CAAK,KAAA,CAAM,OAAO,IAAA,CAAK,IAAA,EAAM,KAAK,GAAA,IAAO,EAAA;AAAA,EACpE;AAAA;AAAA,EAEA,IAAI,eAAA,GAA0B;AAC5B,IAAA,OAAO,KAAK,KAAA,CAAM,eAAA;AAAA,EACpB;AAAA,EACA,IAAI,UAAA,GAA2B;AAC7B,IAAA,OAAO,KAAK,oBAAA,EAAqB;AAAA,EACnC;AAAA,EACA,IAAI,WAAA,GAA8B;AAChC,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA;AAAA,EACnC;AAAA;AAAA,EAEA,IAAI,MAAA,GAA6B;AAC/B,IAAA,OAAO,KAAK,OAAA,EAAQ;AAAA,EACtB;AAAA;AAAA,EAEA,IAAI,SAAA,GAAqB;AACvB,IAAA,MAAM,EAAA,GAAK,KAAK,OAAA,EAAQ;AACxB,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,OAAA,IAAW,EAAA,KAAO,MAAA,IAAa,aAAA,CAAc,GAAA,CAAI,EAAE,CAAA,IAAK,CAAC,CAAC,KAAK,KAAA,CAAM,aAAA;AAAA,EACpG;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAK,GAAA,EAAsB;AACzB,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,MAAA;AACb,IAAA,IAAA,CAAK,aAAA,GAAgB,MAAA;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,MAAA;AACjB,IAAA,IAAA,CAAK,UAAU,OAAO,CAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,SAAS,IAAA,EAAkB;AACzB,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,MAAA;AACZ,IAAA,IAAA,CAAK,aAAA,GAAgB,MAAA;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,MAAA;AACjB,IAAA,IAAA,CAAK,UAAU,OAAO,CAAA;AAAA,EACxB;AAAA;AAAA;AAAA,EAIA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAK,IAAA,CAAK,SAAS,MAAA,IAAa,IAAA,CAAK,UAAU,MAAA,IAAc,IAAA,CAAK,WAAW,SAAA,EAAW;AACxF,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,WAAA,EAAY;AACpC,IAAA,MAAM,KAAK,KAAA,EAAM;AAEjB,IAAA,IAAI,IAAA,CAAK,WAAW,QAAA,EAAU;AAC5B,MAAA,IAAA,CAAK,YAAA,GAAe,KAAA,EAAM;AAC1B,MAAA,IAAA,CAAK,UAAU,SAAS,CAAA;AACxB,MAAA;AAAA,IACF;AAIA,IAAA,IAAA,CAAK,WAAA,EAAY;AACjB,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA,EAAK;AACzB,IAAA,IAAA,CAAK,UAAU,SAAS,CAAA;AACxB,IAAA,MAAM,IAAA,CAAK,SAAS,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,YAAA,GAAe,KAAA,EAAM;AAC1B,IAAA,IAAA,CAAK,UAAU,SAAS,CAAA;AAGxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,CAAM,eAAA;AACzB,IAAA,IAAI,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAC1B,MAAA,IAAA,CAAK,YAAA,EAAa,CAAE,YAAA,CAAa,MAAM;AAErC,QAAA,IAAA,CAAK,OAAA,EAAQ,CAAE,QAAA,CAAS,MAAM;AAC5B,UAAA,IAAI,IAAA,CAAK,WAAW,SAAA,EAAW;AAC7B,YAAA,IAAA,CAAK,IAAA,EAAK;AACV,YAAA,IAAA,CAAK,MAAM,OAAA,IAAU;AAAA,UACvB;AAAA,QACF,CAAA,EAAG,IAAA,CAAK,KAAA,CAAO,GAAA,EAAK,CAAA;AAAA,MACtB,GAAG,KAAK,CAAA;AAAA,IACV;AAAA,EACF;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,IAAa,IAAA,CAAK,WAAW,SAAA,EAAW;AAC3D,IAAA,IAAA,CAAK,KAAA,CAAM,YAAA,EAAa,CAAE,KAAA,EAAM;AAChC,IAAA,IAAA,CAAK,UAAU,QAAQ,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAA,GAAa;AACX,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,IAAa,IAAA,CAAK,WAAW,MAAA,EAAQ;AACtD,MAAA,IAAA,CAAK,KAAA,EAAM;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,IAAA,CAAK,WAAW,SAAA,IAAa,IAAA,CAAK,WAAW,QAAA,KAAa,IAAA,CAAK,YAAY,MAAA,EAAW;AACzF,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,GAAA,EAAI;AAC3B,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,qBAAA,CAAsB,GAAG,CAAA;AAC3C,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,CAAA,EAAG,eAAe,GAAG,CAAA;AAC9C,MAAA,IAAA,CAAK,eAAe,IAAI,CAAA;AACxB,MAAA,IAAA,CAAK,UAAU,IAAA,CAAK,IAAA,IAAQ,IAAA,CAAK,KAAA,GAAQ,UAAU,MAAM,CAAA;AACzD,MAAA,IAAA,CAAK,gBAAA,EAAiB;AACtB,MAAA,IAAA,CAAK,UAAA,GAAa,WAAW,MAAM,IAAA,CAAK,OAAM,EAAG,aAAA,GAAgB,MAAO,EAAE,CAAA;AAC1E,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb;AAAA;AAAA;AAAA,EAIA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,gBAAA,EAAiB;AACtB,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAC5B,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,YAAA,EAAa;AAC1C,MAAA,SAAA,CAAU,IAAA,EAAK;AACf,MAAA,SAAA,CAAU,OAAO,CAAC,CAAA;AAClB,MAAA,SAAA,CAAU,QAAA,GAAW,CAAA;AAAA,IACvB;AAGA,IAAA,IAAA,CAAK,QAAQ,OAAA,EAAQ;AACrB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,eAAe,EAAC;AACrB,IAAA,IAAA,CAAK,eAAe,IAAI,CAAA;AACxB,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,MAAA,EAAQ,IAAA,CAAK,SAAA,CAAU,KAAK,IAAA,IAAQ,IAAA,CAAK,KAAA,GAAQ,OAAA,GAAU,MAAM,CAAA;AAAA,EACvF;AAAA,EAEA,gBAAA,GAAyB;AACvB,IAAA,IAAI,IAAA,CAAK,eAAe,MAAA,EAAW;AACjC,MAAA,YAAA,CAAa,KAAK,UAAU,CAAA;AAC5B,MAAA,IAAA,CAAK,UAAA,GAAa,MAAA;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,WAAA,GAAoB;AAClB,IAAA,IAAA,CAAK,gBAAA,EAAiB;AACtB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AAAA,EAC1B;AAAA,EAEA,kBAAA,GAA2B;AACzB,IAAA,IAAI,IAAA,CAAK,OAAA,KAAY,MAAA,IAAa,IAAA,CAAK,UAAU,MAAA,EAAW;AAC1D,MAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,qBAAA,CAAsB,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AACxD,MAAA,IAAA,CAAK,OAAA,CAAQ,KAAK,KAAA,GAAQ,CAAA;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,KAAA,EAAqB;AAC5B,IAAA,IAAA,CAAK,MAAM,KAAA,GAAQ,KAAA;AACnB,IAAA,IAAA,CAAK,QAAA,EAAS;AAAA,EAChB;AAAA,EACA,OAAO,GAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,SAAA,GAAY,GAAA;AACjB,IAAA,IAAA,CAAK,QAAA,EAAS;AAAA,EAChB;AAAA;AAAA,EAEA,gBAAgB,GAAA,EAAmB;AACjC,IAAA,IAAA,CAAK,MAAM,eAAA,GAAkB,GAAA;AAAA,EAC/B;AAAA,EACA,cAAc,UAAA,EAAgC;AAC5C,IAAA,IAAA,CAAK,MAAM,UAAA,GAAa,UAAA;AACxB,IAAA,IAAA,CAAK,QAAA,EAAS;AAAA,EAChB;AAAA,EACA,UAAU,MAAA,EAA0B;AAClC,IAAA,IAAA,CAAK,MAAM,MAAA,GAAS,MAAA;AACpB,IAAA,IAAA,CAAK,QAAA,EAAS;AAAA,EAChB;AAAA,EACA,WAAW,EAAA,EAAmB;AAC5B,IAAA,IAAA,CAAK,MAAM,OAAA,GAAU,EAAA;AACrB,IAAA,IAAA,CAAK,QAAA,EAAS;AAAA,EAChB;AAAA;AAAA,EAEA,QAAQ,EAAA,EAAyB;AAC/B,IAAA,IAAA,CAAK,gBAAgB,EAAA,IAAM,MAAA;AAC3B,IAAA,IAAA,CAAK,YAAY,EAAA,KAAO,IAAA;AACxB,IAAA,IAAA,CAAK,QAAA,EAAS;AAAA,EAChB;AAAA;AAAA;AAAA,EAIA,SAAA,CAAU,MAAA,EAAyB,IAAA,GAAyB,EAAC,EAAS;AAGpE,IAAA,MAAM,QACJ,IAAA,CAAK,UAAA,KAAe,IAAA,GAAO,QAAA,GAAW,KAAK,UAAA,IAAc,MAAA;AAC3D,IAAA,IAAA,CAAK,KAAA,CAAM,WAAA,GAAc,CAAC,IAAA,KAAS;AACjC,MAAA,MAAA,CAAO,eAAA,EAAgB;AACvB,MAAA,IAAI,SAAS,IAAA,EAAM;AACnB,MAAA,MAAM,EAAA,GAAK,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AAChC,MAAA,IAAI,KAAA,KAAU,MAAA,IAAa,EAAA,KAAO,IAAA,EAAM;AACtC,QAAA,EAAA,CAAG,eAAe,EAAE,KAAA,EAAO,QAAQ,SAAA,EAAW,QAAA,EAAU,UAAU,CAAA;AAAA,MACpE;AAAA,IACF,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAA,CAAgB,EAAA,EAAY,SAAA,EAAsC;AACtE,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,WAAA,CAAY,EAAE,CAAA;AAC/B,IAAA,IAAI,GAAA,KAAQ,QAAW,OAAO,KAAA;AAC9B,IAAA,MAAM,GAAA,GAAM,mBAAA,CAAoB,GAAA,EAAK,IAAA,CAAK,MAAM,aAAa,CAAA;AAC7D,IAAA,IAAI,GAAA,KAAQ,QAAW,OAAO,KAAA;AAC9B,IAAA,OAAO,QAAA,CAAS,KAAK,SAAS,CAAA;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAA,CACJ,GAAA,GAAgB,IAAA,CAAK,KAAA,CAAM,aAC3B,SAAA,EACkC;AAClC,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,GAAA;AAAA,MAC5B,GAAA,CAAI,GAAA,CAAI,OAAO,EAAA,KAAO,CAAC,EAAA,EAAI,MAAM,IAAA,CAAK,eAAA,CAAgB,EAAA,EAAI,SAAS,CAAC,CAAU;AAAA,KAChF;AACA,IAAA,OAAO,MAAA,CAAO,YAAY,OAAO,CAAA;AAAA,EACnC;AAAA;AAAA,EAGA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,KAAA,MAAW,EAAE,SAAQ,IAAK,IAAA,CAAK,UAAU,MAAA,EAAO,UAAW,OAAA,EAAQ;AACnE,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AACrB,IAAA,IAAA,CAAK,UAAU,OAAA,EAAQ;AACvB,IAAA,IAAA,CAAK,YAAY,OAAA,EAAQ;AACzB,IAAA,IAAA,CAAK,QAAQ,OAAA,EAAQ;AACrB,IAAA,IAAA,CAAK,QAAQ,OAAA,EAAQ;AACrB,IAAA,IAAA,CAAK,SAAS,OAAA,EAAQ;AACtB,IAAA,IAAA,CAAK,QAAA,GAAW,KAAK,UAAA,GAAa,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA,GAAS,KAAK,OAAA,GAAU,MAAA;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA,EAMA,aAAA;AAAA,EACA,SAAA,GAAY,IAAA;AAAA,EAEZ,UAAU,KAAA,EAA0B;AAClC,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,KAAA,CAAM,UAAU,KAAK,CAAA;AAAA,EAC5B;AAAA,EAEA,eAAe,IAAA,EAA2B;AACxC,IAAA,IAAA,CAAK,KAAA,CAAM,cAAc,IAAI,CAAA;AAAA,EAC/B;AAAA;AAAA;AAAA,EAIA,UAAA,GAAiC;AAC/B,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,EAAW,OAAO,KAAK,KAAA,CAAM,SAAA;AAChD,IAAA,OAAO,KAAK,SAAA,GAAY,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,MAAM,SAAA,GAAY,MAAA;AAAA,EAC5D;AAAA;AAAA;AAAA,EAIA,OAAA,GAA8B;AAC5B,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,EAAO,EAAA,KAAO,CAAC,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,aAAA,KAAkB,SAAY,IAAA,CAAK,aAAA,GAAgB,IAAA,CAAK,IAAA,EAAM,KAAK,IAAA,EAAM,EAAA,CAAA;AAC/H,IAAA,OAAO,EAAA,KAAO,MAAA,IAAa,EAAA,IAAM,KAAA,GAAS,EAAA,GAAgB,MAAA;AAAA,EAC5D;AAAA;AAAA;AAAA,EAIA,YAAY,EAAA,EAAuC;AACjD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,iBAAA,GAAoB,EAAE,CAAA;AAChD,IAAA,IAAI,QAAQ,OAAO,MAAA;AACnB,IAAA,MAAM,GAAA,GAAM,YAAY,EAAE,CAAA;AAC1B,IAAA,IAAI,GAAA,IAAO,EAAA,KAAO,OAAA,IAAW,IAAA,CAAK,MAAM,YAAA,EAAc;AACpD,MAAA,OAAO,EAAE,GAAG,GAAA,EAAK,OAAA,EAAS,cAAc,IAAA,CAAK,KAAA,CAAM,YAAY,CAAA,EAAE;AAAA,IACnE;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,mBAAA,GAA8B;AAC5B,IAAA,MAAM,UAAU,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,IAAA,CAAK,CAAC,EAAA,KAAO;AAClD,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,WAAA,CAAY,EAAE,CAAA;AAC7B,MAAA,OAAO,CAAA,KAAM,MAAA,IAAa,CAAA,CAAE,KAAA,KAAU,IAAA;AAAA,IACxC,CAAC,CAAA;AACD,IAAA,OAAO,OAAA,IAAW,WAAA;AAAA,EACpB;AAAA;AAAA;AAAA,EAIA,oBAAA,GAA+B;AAC7B,IAAA,MAAM,EAAA,GAAK,KAAK,KAAA,CAAM,UAAA;AACtB,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,WAAA,CAAY,EAAE,CAAA;AAC/B,IAAA,IAAI,GAAA,KAAQ,MAAA,EAAW,OAAO,IAAA,CAAK,mBAAA,EAAoB;AACvD,IAAA,IAAI,GAAA,CAAI,UAAU,IAAA,IAAQ,CAAC,KAAK,KAAA,CAAM,aAAA,EAAe,OAAO,IAAA,CAAK,mBAAA,EAAoB;AACrF,IAAA,OAAO,EAAA;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,aAAA,GAAwB;AACtB,IAAA,MAAM,EAAA,GAAK,KAAK,OAAA,EAAQ;AACxB,IAAA,OAAO,IAAA,CAAK,SAAA,IAAa,EAAA,GAAK,eAAA,CAAgB,IAAA,CAAK,KAAK,SAAA,CAAU,EAAE,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA;AAAA,EAChF;AAAA,EAEA,MAAM,WAAA,GAAmC;AACvC,IAAA,IAAA,CAAK,KAAA,KAAU,MAAM,OAAO,MAAM,CAAA;AAClC,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA;AAAA;AAAA,EAIA,QAAA,GAAiB;AACf,IAAA,IAAI,IAAA,CAAK,WAAW,SAAA,EAAW;AAC/B,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,KAAK,KAAK,IAAA,EAAK;AAAA,EACjB;AAAA;AAAA,EAGA,cAAc,IAAA,EAAwB;AACpC,IAAA,IAAA,CAAK,YAAY,IAAI,IAAA,CAAK,IAAA,CAAK,CAAC,EAAE,aAAA,EAAc;AAChD,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA;AAAA;AAAA,EAIA,MAAM,SAAS,IAAA,EAAiC;AAC9C,IAAA,IAAI,KAAK,KAAA,KAAU,MAAA,EAAW,OAAO,IAAA,CAAK,aAAa,IAAI,CAAA;AAC3D,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAW;AAC7B,IAAA,MAAM,IAAA,GAAO,KAAK,KAAA,CAAM,aAAA;AACxB,IAAA,MAAM,SAAA,GAAY,KAAK,YAAA,EAAa;AACpC,IAAA,SAAA,CAAU,OAAO,CAAC,CAAA;AAClB,IAAA,SAAA,CAAU,QAAA,GAAW,CAAA;AACrB,IAAA,IAAA,CAAK,eAAe,EAAC;AAGrB,IAAA,IAAA,CAAK,cAAc,IAAI,CAAA;AACvB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AAExB,IAAA,MAAM,GAAA,GAAM,KAAK,aAAA,EAAc;AAC/B,IAAA,SAAA,CAAU,IAAI,KAAA,GAAQ,GAAA;AACtB,IAAA,MAAM,MAAM,EAAA,GAAK,GAAA;AAIjB,IAAA,MAAM,QAAA,GAAqB,aAAA,CAAc,IAAA,CAAK,IAAA,EAAM;AAAA,MAClD,KAAA,EAAO,KAAK,KAAA,CAAM,KAAA;AAAA,MAClB,UAAA,EAAY,KAAK,KAAA,CAAM;AAAA,KACxB,CAAA;AACD,IAAA,MAAM,KAAA,GAAQ,UAAU,QAAQ,CAAA;AAGhC,IAAA,MAAM,MAAA,GAAS,KAAK,YAAA,CAAa,IAAA,EAAM,KAAK,oBAAA,EAAqB,EAAG,MAAM,KAAK,CAAA;AAG/E,IAAA,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA;AAG5C,IAAA,IAAA,CAAK,aAAA,CAAc,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA;AAG7C,IAAA,MAAM,aAAa,IAAI,CAAA;AAKvB,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ;AAC/B,MAAA,IAAA,CAAK,OAAO,OAAA,GAAU,eAAA,CAAgB,KAAK,MAAA,CAAO,MAAA,CAAO,UAAU,GAAG,CAAA;AAAA,IACxE;AAGA,IAAA,MAAM,IAAA,GAAO,KAAK,OAAA,EAAQ;AAI1B,IAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,WAAW,MAAA,GAAS,QAAA,CAAS,cAAc,GAAA,GAAM,CAAA;AAE1E,IAAA,KAAA,MAAW,EAAA,IAAM,SAAS,MAAA,EAAQ;AAChC,MAAA,MAAM,EAAA,GAAK,EAAA,CAAG,IAAA,GAAO,GAAA,GAAM,KAAA;AAC3B,MAAA,IAAI,EAAA,CAAG,SAAS,IAAA,EAAM;AACpB,QAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,EAAA,CAAG,IAAI,CAAA;AACrC,QAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,EAAA,CAAG,aAAA,GAAgB,KAAK,IAAI,CAAA;AACjD,QAAA,IAAI,EAAA,CAAG,QAAQ,IAAA,EAAM;AACnB,UAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,EAAA,CAAG,GAAG,CAAA;AACvC,UAAA,SAAA,CAAU,aAAa,CAAC,IAAA,KAAS,MAAA,CAAO,oBAAA,CAAqB,SAAS,GAAA,GAAM,GAAA,EAAK,IAAI,CAAA,EAAG,KAAK,GAAA,CAAI,EAAA,GAAK,GAAA,GAAM,IAAA,EAAM,CAAC,CAAC,CAAA;AAAA,QACtH;AACA,QAAA,SAAA,CAAU,YAAA,CAAa,CAAC,IAAA,KAAS,MAAA,CAAO,qBAAqB,IAAA,EAAM,GAAA,EAAK,IAAI,CAAA,EAAG,EAAE,CAAA;AAAA,MACnF;AACA,MAAA,SAAA,CAAU,YAAA,CAAa,CAAC,IAAA,KAAS;AAC/B,QAAA,IAAA,CAAK,SAAS,MAAM,IAAA,CAAK,eAAe,EAAA,CAAG,IAAI,GAAG,IAAI,CAAA;AAAA,MACxD,GAAG,EAAE,CAAA;AAAA,IACP;AAGA,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,UAAA,GAAa,GAAA,GAAM,KAAA,GAAQ,GAAA;AAClD,IAAA,SAAA,CAAU,YAAA,CAAa,CAAC,IAAA,KAAS;AAC/B,MAAA,IAAA,CAAK,SAAS,MAAM,IAAA,CAAK,cAAA,CAAe,IAAI,GAAG,IAAI,CAAA;AAAA,IACrD,CAAA,EAAG,QAAA,CAAS,UAAA,GAAa,GAAA,GAAM,KAAK,CAAA;AACpC,IAAA,SAAA,CAAU,YAAA,CAAa,MAAM,IAAA,CAAK,IAAA,IAAQ,KAAK,CAAA;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,IAAA,EAAiC;AAClD,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAC9B,IAAA,MAAM,IAAA,GAAO,KAAK,KAAA,CAAM,aAAA;AACxB,IAAA,MAAM,SAAA,GAAY,KAAK,YAAA,EAAa;AACpC,IAAA,SAAA,CAAU,OAAO,CAAC,CAAA;AAClB,IAAA,SAAA,CAAU,QAAA,GAAW,CAAA;AACrB,IAAA,IAAA,CAAK,eAAe,EAAC;AAErB,IAAA,IAAA,CAAK,cAAc,IAAI,CAAA;AACvB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AAExB,IAAA,MAAM,GAAA,GAAM,KAAK,aAAA,EAAc;AAC/B,IAAA,SAAA,CAAU,IAAI,KAAA,GAAQ,GAAA;AACtB,IAAA,MAAM,MAAM,EAAA,GAAK,GAAA;AAEjB,IAAA,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA;AAC5C,IAAA,MAAM,aAAa,IAAI,CAAA;AACvB,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ;AAC/B,MAAA,IAAA,CAAK,OAAO,OAAA,GAAU,eAAA,CAAgB,KAAK,MAAA,CAAO,MAAA,CAAO,UAAU,GAAG,CAAA;AAAA,IACxE;AAEA,IAAA,MAAM,IAAA,GAAO,KAAK,OAAA,EAAQ;AAC1B,IAAA,MAAM,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,MAAM,QAAQ,CAAA;AACzC,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,MAAM,EAAA,GAAK,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AAC5C,MAAA,MAAM,IAAI,IAAA,GAAO,CAAA;AACjB,MAAA,IAAA,CAAK,SAAS,MAAM;AAClB,QAAA,IAAA,CAAK,cAAA,CAAe,MAAA,CAAO,CAAC,CAAC,CAAA;AAC7B,QAAA,IAAA,CAAK,KAAA,CAAM,SAAS,CAAC,CAAA;AAAA,MACvB,GAAG,IAAI,CAAA;AACP,MAAA,IAAA,IAAQ,CAAA;AAAA,IACV,CAAA,EAAG,KAAK,CAAC,CAAA;AACT,IAAA,IAAA,CAAK,YAAA,CAAa,KAAK,EAAE,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,YAAA,CACE,IAAA,EACA,EAAA,EACA,IAAA,EACA,KAAA,EACa;AACb,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,WAAA,CAAY,EAAE,KAAK,WAAA,CAAY,SAAA;AAChD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA;AACtC,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,EAAE,CAAA;AACpC,IAAA,IAAI,MAAA,KAAW,KAAA,KAAU,MAAA,IAAc,MAAA,CAAO,EAAA,IAAM,MAAM,EAAA,IAAM,MAAA,CAAO,EAAA,IAAM,KAAA,CAAM,EAAA,CAAA,EAAM;AACvF,MAAA,OAAO,MAAA,CAAO,OAAA;AAAA,IAChB;AACA,IAAA,MAAA,EAAQ,QAAQ,OAAA,EAAQ;AACxB,IAAA,MAAM,OAAA,GAAU,cAAc,IAAA,EAAM,GAAA,EAAK,MAAM,KAAK,CAAA,CAAE,QAAQ,MAAM,CAAA;AACpE,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,EAAA,EAAI,EAAE,OAAA,EAAS,EAAA,EAAI,KAAA,EAAO,EAAA,IAAM,CAAA,QAAA,EAAW,EAAA,EAAI,KAAA,EAAO,EAAA,IAAM,UAAU,CAAA;AACzF,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEA,YAAA,CAAa,IAAA,EAAkB,SAAA,EAAmD,IAAA,EAA0B,GAAA,EAAmB;AAE7H,IAAA,IAAA,CAAK,QAAQ,OAAA,EAAQ;AACrB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAEd,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA;AACtC,IAAA,MAAM,MAAA,GAAS,KAAK,OAAA,EAAQ;AAC5B,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,MAAA,IAAa,aAAA,CAAc,GAAA,CAAI,MAAM,CAAA,IAAK,IAAA,KAAS,MAAA;AAElH,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,EAAO,MAAA,EAAS,eAAA,CAAgB,IAAA,CAAK,GAAA,EAAK,SAAA,CAAU,MAAO,CAAC,CAAA,EAAG,IAAA,CAAK,YAAY,CAAA;AAGrG,MAAA,MAAM,KAAA,GAAQ,mBAAA,CAAoB,IAAA,CAAK,cAAA,EAAgB,CAAA;AACvD,MAAA,IAAA,CAAK,MAAA,GAAA,CAAU,KAAA,KAAU,CAAA,GACrB,IAAI,KAAK,MAAA,CAAO,EAAE,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,CAAA,GACnC,IAAI,KAAK,WAAA,CAAY;AAAA,QACnB,GAAA;AAAA,QACA,IAAA,EAAM,IAAA;AAAA,QACN,QAAQ,KAAA,GAAQ,GAAA;AAAA,QAChB,SAAA,EAAW,eAAA;AAAA,QACX,OAAA,EAAS;AAAA,OACV,CAAA,EACH,OAAA,CAAQ,MAAM,CAAA;AAChB,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,EAAK,CAAE,KAAA,CAAM,CAAC,CAAA;AAC1B,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,OAAA,EAAS;AACjC,MAAA,IAAA,CAAK,MAAA,KAAW,aAAA,CAAc,IAAI,CAAA,CAAE,QAAQ,MAAM,CAAA;AAClD,MAAA,MAAM,QAAQ,IAAA,CAAK,MAAA;AAEnB,MAAA,MAAM,OAAO,IAAA,CAAK,KAAA,KAAU,MAAA,KAAW,MAAA,GAAY,MAAM,MAAM,CAAA,GAAI,IAAA,CAAK,IAAA,EAAM,KAAK,IAAA,CAAA,IAAS,EAAE,QAAA,EAAU,CAAA,EAAG,KAAK,CAAA,EAAE;AAClH,MAAA,MAAM,OAAA,GAAU,YAAY,IAAI,CAAA;AAChC,MAAA,IAAIA,KAAAA,GAAO,CAAA;AACX,MAAA,MAAMC,GAAAA,GAAK,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AAC5C,QAAA,MAAM,KAAA,GAAQ,OAAA,CAAQD,KAAAA,GAAO,OAAA,CAAQ,MAAM,CAAA;AAC3C,QAAA,IAAI,KAAA,CAAM,IAAI,KAAK,CAAA,QAAS,MAAA,CAAO,KAAK,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA;AACpD,QAAAA,KAAAA,IAAQ,CAAA;AAAA,MACV,CAAA,EAAG,KAAK,CAAC,CAAA;AACT,MAAA,IAAA,CAAK,YAAA,CAAa,KAAKC,GAAE,CAAA;AACzB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,MAAA,IAAU,SAAS,MAAA,EAAW;AAGxD,IAAA,IAAA,CAAK,eAAe,eAAA,CAAgB,IAAA,EAAM,IAAI,CAAA,CAAE,QAAQ,MAAM,CAAA;AAC9D,IAAA,MAAM,QAAQ,IAAA,CAAK,UAAA;AAGnB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAO,QAAA,IAAY,KAAK,IAAA,EAAM,IAAA,CAAK,MAAM,QAAA,IAAY,CAAA;AACxE,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,MAAM,EAAA,GAAK,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AAC5C,MAAA,KAAA,CAAM,OAAO,cAAA,CAAe,IAAA,GAAO,UAAU,CAAA,GAAI,CAAA,GAAI,IAAI,IAAI,CAAA;AAC7D,MAAA,KAAA,CAAM,MAAM,IAAI,CAAA;AAChB,MAAA,IAAA,IAAQ,CAAA;AAAA,IACV,CAAA,EAAG,KAAK,CAAC,CAAA;AACT,IAAA,IAAA,CAAK,YAAA,CAAa,KAAK,EAAE,CAAA;AAAA,EAC3B;AAAA,EAEA,aAAA,CAAc,IAAA,EAAkB,SAAA,EAAmD,IAAA,EAA0B,GAAA,EAAmB;AAC9H,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,CAAM,OAAA,IAAW,SAAS,MAAA,EAAW;AAG/C,IAAA,MAAM,OAAA,GAAA,CAAY,IAAA,CAAK,cAAA,EAAe,GAAI,KAAM,EAAA,IAAM,EAAA;AACtD,IAAA,MAAM,EAAA,GAAK,YAAY,OAAO,CAAA;AAC9B,IAAA,MAAM,EAAA,GAAK,WAAA,CAAA,CAAa,OAAA,GAAU,CAAA,IAAK,EAAE,CAAA;AAIzC,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AACvB,IAAA,IAAI,IAAA,CAAK,QAAA,KAAa,MAAA,IAAa,IAAA,CAAK,gBAAgB,GAAA,EAAK;AAC3D,MAAA,IAAA,CAAK,UAAU,OAAA,EAAQ;AACvB,MAAA,IAAA,CAAK,QAAA,GAAW,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,CAAC,EAAA,EAAI,EAAE,CAAC,CAAA,CAAE,OAAA,CAAQ,IAAA,CAAK,aAAA,CAAc,IAAI,CAAC,CAAA;AACpF,MAAA,IAAA,CAAK,WAAA,GAAc,GAAA;AAAA,IACrB;AACA,IAAA,MAAM,UAAU,IAAA,CAAK,QAAA;AACrB,IAAA,OAAA,CAAQ,OAAO,KAAA,GAAQ,GAAA;AAEvB,IAAA,MAAM,OAAA,GAAU,CAAC,EAAA,EAAI,EAAA,EAAI,IAAI,EAAE,CAAA;AAC/B,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,MAAM,EAAA,GAAK,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AAC5C,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,CAAA,GAAI,OAAA,CAAQ,MAAM,CAAA;AACvC,MAAA,IAAI,OAAA,CAAQ,IAAI,IAAI,CAAA,UAAW,MAAA,CAAO,IAAI,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA;AACtD,MAAA,CAAA,IAAK,CAAA;AAAA,IACP,CAAA,EAAG,GAAA,GAAM,CAAA,EAAG,CAAC,CAAA;AACb,IAAA,IAAA,CAAK,YAAA,CAAa,KAAK,EAAE,CAAA;AAAA,EAC3B;AAAA,EAEA,cAAA,GAAyB;AACvB,IAAA,OAAO,WAAW,IAAA,CAAK,KAAA,CAAM,SAAS,IAAA,CAAK,IAAA,EAAM,KAAK,KAAK,CAAA;AAAA,EAC7D;AACF;;;ACzsBA,SAASC,WAAU,QAAA,EAA4D;AAC7E,EAAA,IAAI,EAAA,GAAK,QAAA;AACT,EAAA,IAAI,EAAA,GAAK,CAAA,QAAA;AACT,EAAA,KAAA,MAAW,EAAA,IAAM,SAAS,MAAA,EAAQ;AAChC,IAAA,KAAA,MAAW,KAAK,CAAC,EAAA,CAAG,IAAA,EAAM,EAAA,CAAG,GAAG,CAAA,EAAG;AACjC,MAAA,IAAI,MAAM,IAAA,EAAM;AACd,QAAA,IAAI,CAAA,GAAI,IAAI,EAAA,GAAK,CAAA;AACjB,QAAA,IAAI,CAAA,GAAI,IAAI,EAAA,GAAK,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,EAAA,IAAM,EAAA,GAAK,EAAE,EAAA,EAAI,IAAG,GAAI,MAAA;AACjC;AAKA,SAAS,OAAA,CAAQ,IAAY,CAAA,EAAsC;AACjE,EAAA,MAAM,MAAM,CAAA,CAAE,iBAAA,GAAoB,EAAE,CAAA,IAAK,YAAY,EAAE,CAAA;AACvD,EAAA,IAAI,GAAA,KAAQ,MAAA,EAAW,OAAO,WAAA,CAAY,SAAA;AAC1C,EAAA,IAAI,EAAA,KAAO,OAAA,IAAW,CAAA,CAAE,YAAA,EAAc,OAAO,EAAE,GAAG,GAAA,EAAK,OAAA,EAAS,aAAA,CAAc,CAAA,CAAE,YAAY,CAAA,EAAE;AAC9F,EAAA,IAAI,IAAI,KAAA,KAAU,IAAA,IAAQ,CAAC,CAAA,CAAE,aAAA,SAAsB,WAAA,CAAY,SAAA;AAC/D,EAAA,OAAO,GAAA;AACT;AAIA,SAAS,WAAA,CAAY,KAAgB,IAAA,EAAsD;AACzF,EAAA,MAAM,EAAA,GAAK,SAAS,MAAA,IAAa,IAAA,KAAS,SAAS,IAAA,GAAO,GAAA,CAAI,KAAK,IAAA,EAAM,EAAA;AACzE,EAAA,OAAO,EAAA,KAAO,MAAA,IAAa,EAAA,IAAM,KAAA,GAAS,EAAA,GAAgB,MAAA;AAC5D;AAGA,SAAS,SAAS,KAAA,EAAmC;AACnD,EAAA,OAAA,CAAQ,SAAS,EAAA,EAAI,OAAA,CAAQ,kBAAkB,EAAE,CAAA,CAAE,MAAK,IAAK,WAAA;AAC/D;AAQA,eAAsB,WAAA,CACpB,GAAA,EACA,OAAA,GAA8B,EAAC,EACH;AAC5B,EAAA,MAAM,IAAA,GAAmB,MAAM,OAAO,MAAM,CAAA;AAE5C,EAAA,MAAM,IAAA,GAAO,QAAQ,aAAA,IAAiB,mBAAA;AACtC,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,KAAA;AACjC,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,CAAA;AACrC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,KAAA;AACzC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,GAAA;AACzC,EAAA,MAAM,MAAA,GAAqB,QAAQ,MAAA,IAAU,MAAA;AAC7C,EAAA,MAAM,UAAA,GAAa,QAAQ,MAAA,IAAU,IAAA;AACrC,EAAA,MAAM,WAAA,GAAc,QAAQ,OAAA,IAAW,KAAA;AAEvC,EAAA,MAAM,QAAA,GAAW,aAAA,CAAc,GAAA,EAAK,EAAE,KAAA,EAAO,QAAQ,KAAA,EAAO,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAY,CAAA;AAC5F,EAAA,MAAM,KAAA,GAAQA,WAAU,QAAQ,CAAA;AAEhC,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,GAAA,EAAK,OAAA,CAAQ,IAAI,CAAA;AAC5C,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,IAAA,KAAS,MAAA,IAAa,OAAA,CAAQ,SAAS,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,SAAA,GAAY,MAAA;AACrG,EAAA,MAAM,SAAA,GAAY,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,MAAA,IAAa,cAAc,GAAA,CAAI,MAAM,CAAA,IAAK,CAAC,CAAC,IAAA;AAE/F,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,IAAO,GAAA,CAAI,KAAK,GAAA,IAAO,EAAA;AAC9C,EAAA,MAAM,GAAA,GAAM,aAAa,MAAA,GAAS,eAAA,CAAgB,QAAQ,SAAA,CAAU,MAAM,CAAC,CAAA,GAAI,MAAA;AAC/E,EAAA,MAAM,MAAM,EAAA,GAAK,GAAA;AAEjB,EAAA,MAAM,EAAA,GAAK,QAAQ,UAAA,IAAc,OAAA;AACjC,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,EAAA,EAAI,OAAO,CAAA;AAG/B,EAAA,MAAM,KAAA,GAAQ,MAAA,KAAW,MAAA,GAAS,QAAA,CAAS,cAAc,GAAA,GAAM,CAAA;AAC/D,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,GAAA,CAAI,QAAA,CAAS,UAAA,GAAa,GAAA,GAAM,KAAA,IAAS,GAAA,CAAI,OAAA,IAAW,CAAA,CAAA,GAAK,UAAA,GAAa,GAAA,EAAK,GAAG,CAAA;AAE3G,EAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,OAAA;AAAA,IAC1B,OAAO,EAAE,SAAA,EAAU,KAAM;AACvB,MAAA,SAAA,CAAU,IAAI,KAAA,GAAQ,GAAA;AACtB,MAAA,MAAM,SAAS,IAAI,IAAA,CAAK,IAAA,CAAK,CAAC,EAAE,aAAA,EAAc;AAG9C,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,MAAM,OAAA,GAAU,cAAc,IAAA,EAAM,GAAA,EAAK,MAAM,KAAK,CAAA,CAAE,QAAQ,MAAM,CAAA;AACpE,QAAA,KAAA,MAAW,EAAA,IAAM,SAAS,MAAA,EAAQ;AAChC,UAAA,IAAI,EAAA,CAAG,SAAS,IAAA,EAAM;AACtB,UAAA,MAAM,EAAA,GAAK,EAAA,CAAG,IAAA,GAAO,GAAA,GAAM,KAAA;AAC3B,UAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,EAAA,CAAG,aAAA,GAAgB,KAAK,IAAI,CAAA;AACjD,UAAA,IAAI,EAAA,CAAG,QAAQ,IAAA,EAAM;AACnB,YAAA,MAAMC,KAAAA,GAAO,gBAAA,CAAiB,EAAA,CAAG,GAAG,CAAA;AACpC,YAAA,SAAA,CAAU,aAAa,CAAC,CAAA,KAAM,OAAA,CAAQ,oBAAA,CAAqBA,OAAM,GAAA,GAAM,GAAA,EAAK,CAAC,CAAA,EAAG,KAAK,GAAA,CAAI,EAAA,GAAK,GAAA,GAAM,IAAA,EAAM,CAAC,CAAC,CAAA;AAAA,UAC9G;AACA,UAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,EAAA,CAAG,IAAI,CAAA;AACrC,UAAA,SAAA,CAAU,YAAA,CAAa,CAAC,CAAA,KAAM,OAAA,CAAQ,qBAAqB,IAAA,EAAM,GAAA,EAAK,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,QAC9E;AAAA,MACF;AAGA,MAAA,IAAI,KAAA;AACJ,MAAA,IAAI,MAAA,KAAW,WAAW,IAAA,EAAM;AAC9B,QAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,IAAI,CAAA,CAAE,QAAQ,MAAM,CAAA;AAChD,QAAA,MAAM,IAAA,GAAO,MAAA,KAAW,MAAA,GAAY,KAAA,CAAM,MAAM,CAAA,GAAK,GAAA,CAAI,IAAA,CAAK,IAAA,IAAQ,EAAE,QAAA,EAAU,CAAA,EAAG,KAAK,CAAA,EAAE;AAC5F,QAAA,MAAM,OAAA,GAAU,YAAY,IAAI,CAAA;AAChC,QAAA,IAAI,IAAA,GAAO,CAAA;AACX,QAAA,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AACjC,UAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,IAAA,GAAO,OAAA,CAAQ,MAAM,CAAA;AAC3C,UAAA,IAAI,KAAA,CAAM,IAAI,KAAK,CAAA,QAAS,MAAA,CAAO,KAAK,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA;AACpD,UAAA,IAAA,IAAQ,CAAA;AAAA,QACV,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,MACX,CAAA,MAAA,IAAW,MAAA,KAAW,MAAA,IAAU,IAAA,EAAM;AACpC,QAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,UAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAQ,KAAK,SAAS,CAAA;AAEjD,UAAA,MAAM,KAAA,GAAQ,oBAAoB,UAAA,CAAW,OAAA,CAAQ,SAAS,GAAA,CAAI,IAAA,CAAK,KAAK,CAAC,CAAA;AAC7E,UAAA,KAAA,GAAA,CAAS,KAAA,KAAU,CAAA,GACf,IAAI,IAAA,CAAK,MAAA,CAAO,EAAE,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,CAAA,GACnC,IAAI,KAAK,WAAA,CAAY;AAAA,YACnB,GAAA;AAAA,YACA,IAAA,EAAM,IAAA;AAAA,YACN,QAAQ,KAAA,GAAQ,GAAA;AAAA,YAChB,SAAA,EAAW,eAAA;AAAA,YACX,OAAA,EAAS;AAAA,WACV,CAAA,EACH,OAAA,CAAQ,MAAM,CAAA;AAChB,UAAA,KAAA,CAAM,IAAA,EAAK,CAAE,KAAA,CAAM,CAAC,CAAA;AAAA,QACtB,CAAA,MAAO;AACL,UAAA,MAAM,QAAQ,eAAA,CAAgB,IAAA,EAAM,IAAI,CAAA,CAAE,QAAQ,MAAM,CAAA;AACxD,UAAA,MAAM,KAAA,GAAQ,MAAA,KAAW,MAAA,GAAY,KAAA,CAAM,MAAM,EAAE,QAAA,GAAY,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,QAAA,IAAY,CAAA;AAC1F,UAAA,IAAI,IAAA,GAAO,CAAA;AACX,UAAA,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AACjC,YAAA,KAAA,CAAM,OAAO,cAAA,CAAe,IAAA,GAAO,UAAU,CAAA,GAAI,CAAA,GAAI,IAAI,IAAI,CAAA;AAC7D,YAAA,KAAA,CAAM,MAAM,IAAI,CAAA;AAChB,YAAA,IAAA,IAAQ,CAAA;AAAA,UACV,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,QACX;AAAA,MACF;AAGA,MAAA,IAAI,eAAe,IAAA,EAAM;AACvB,QAAA,MAAM,OAAA,GAAA,CAAY,WAAW,OAAA,CAAQ,KAAA,IAAS,IAAI,IAAA,CAAK,KAAK,CAAA,GAAI,EAAA,GAAM,EAAA,IAAM,EAAA;AAC5E,QAAA,MAAM,EAAA,GAAK,YAAY,OAAO,CAAA;AAC9B,QAAA,MAAM,EAAA,GAAK,WAAA,CAAA,CAAa,OAAA,GAAU,CAAA,IAAK,EAAE,CAAA;AACzC,QAAA,MAAM,OAAA,GAAU,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,CAAC,IAAI,EAAE,CAAC,CAAA,CAAE,OAAA,CAAQ,MAAM,CAAA;AAClE,QAAA,OAAA,CAAQ,OAAO,KAAA,GAAQ,GAAA;AACvB,QAAA,MAAM,OAAA,GAAU,CAAC,EAAA,EAAI,EAAA,EAAI,IAAI,EAAE,CAAA;AAC/B,QAAA,IAAI,CAAA,GAAI,CAAA;AACR,QAAA,SAAA,CAAU,cAAA,CAAe,CAAC,IAAA,KAAS;AACjC,UAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,CAAA,GAAI,OAAA,CAAQ,MAAM,CAAA;AACvC,UAAA,IAAI,OAAA,CAAQ,IAAI,IAAI,CAAA,UAAW,MAAA,CAAO,IAAI,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA;AACtD,UAAA,CAAA,IAAK,CAAA;AAAA,QACP,CAAA,EAAG,GAAA,GAAM,CAAA,EAAG,CAAC,CAAA;AAAA,MACf;AAIA,MAAA,MAAM,KAAK,MAAA,EAAO;AAElB,MAAA,IAAI,KAAA,EAAO,QAAQ,MAAA,EAAQ,KAAA,CAAM,UAAU,eAAA,CAAgB,KAAA,CAAM,MAAA,CAAO,QAAA,EAAU,GAAG,CAAA;AAGrF,MAAA,IAAI,aAAa,CAAA,EAAG;AAClB,QAAA,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA,EAAG,IAAA,CAAK,IAAI,WAAA,GAAc,UAAA,EAAY,CAAC,CAAC,CAAA;AACnE,QAAA,MAAA,CAAO,IAAA,CAAK,uBAAA,CAAwB,CAAA,EAAG,WAAW,CAAA;AAAA,MACpD;AACA,MAAA,SAAA,CAAU,MAAM,CAAC,CAAA;AAAA,IACnB,CAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,QAAwB,EAAC;AAC/B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,gBAAA,EAAkB,CAAA,EAAA,EAAK,KAAA,CAAM,IAAA,CAAK,QAAA,CAAS,cAAA,CAAe,CAAC,CAAC,CAAA;AAEzF,EAAA,MAAM,KAAA,GACJ,MAAA,KAAW,KAAA,GACP,MAAM,UAAU,KAAA,EAAO,QAAA,CAAS,UAAA,EAAY,OAAA,CAAQ,OAAO,CAAA,GAC3D,SAAA,CAAU,KAAA,EAAO,SAAS,UAAU,CAAA;AAC1C,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,KAAA,GAAQ,YAAA,GAAe,WAAA;AACnD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,KAAiB,CAAA,EAAG,EAAE,IAAA,EAAM,QAAA,EAAU,CAAA;AAAA,IACtD,QAAA,EAAU,GAAG,QAAA,CAAS,GAAA,CAAI,KAAK,KAAK,CAAC,IAAI,MAAM,CAAA,CAAA;AAAA,IAC/C,QAAA;AAAA,IACA,aAAa,QAAA,CAAS;AAAA,GACxB;AACF;AAOA,SAAS,QAAQ,CAAA,EAAmB;AAClC,EAAA,MAAM,IAAI,CAAA,GAAI,EAAA,GAAK,EAAA,GAAK,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA;AACpC,EAAA,OAAO,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,KAAA,GAAS,CAAA,GAAI,KAAA;AAClC;AAOO,SAAS,SAAA,CAAU,UAA0B,UAAA,EAAgC;AAClF,EAAA,MAAM,QAAQ,QAAA,CAAS,MAAA;AACvB,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,CAAC,CAAA,EAAG,MAAA,IAAU,CAAA;AACtC,EAAA,MAAM,cAAA,GAAiB,CAAA;AACvB,EAAA,MAAM,aAAa,KAAA,GAAQ,cAAA;AAC3B,EAAA,MAAM,WAAW,MAAA,GAAS,UAAA;AAC1B,EAAA,MAAM,GAAA,GAAM,IAAI,WAAA,CAAY,EAAA,GAAK,QAAQ,CAAA;AACzC,EAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,GAAG,CAAA;AAC7B,EAAA,MAAM,GAAA,GAAM,CAACC,IAAAA,EAAa,CAAA,KAAc;AACtC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,CAAK,QAAA,CAASA,IAAAA,GAAM,CAAA,EAAG,CAAA,CAAE,UAAA,CAAW,CAAC,CAAC,CAAA;AAAA,EAC3E,CAAA;AAEA,EAAA,GAAA,CAAI,GAAG,MAAM,CAAA;AACb,EAAA,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,EAAA,GAAK,QAAA,EAAU,IAAI,CAAA;AACrC,EAAA,GAAA,CAAI,GAAG,MAAM,CAAA;AACb,EAAA,GAAA,CAAI,IAAI,MAAM,CAAA;AACd,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,EAAA,EAAI,IAAI,CAAA;AAC3B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,CAAA,EAAG,IAAI,CAAA;AAC1B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,KAAA,EAAO,IAAI,CAAA;AAC9B,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,UAAA,EAAY,IAAI,CAAA;AACnC,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,UAAA,GAAa,UAAA,EAAY,IAAI,CAAA;AAChD,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,UAAA,EAAY,IAAI,CAAA;AACnC,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,cAAA,GAAiB,CAAA,EAAG,IAAI,CAAA;AAC3C,EAAA,GAAA,CAAI,IAAI,MAAM,CAAA;AACd,EAAA,IAAA,CAAK,SAAA,CAAU,EAAA,EAAI,QAAA,EAAU,IAAI,CAAA;AAEjC,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,MAAA,IAAA,CAAK,QAAA,CAAS,KAAK,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAA,CAAG,CAAC,CAAE,CAAA,EAAG,IAAI,CAAA;AAClD,MAAA,GAAA,IAAO,CAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,IAAI,WAAW,GAAG,CAAA;AAC3B;AAGA,SAAS,aAAa,CAAA,EAA6B;AACjD,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,CAAA,CAAE,MAAM,CAAA;AACnC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,CAAC,CAAA,GAAI,OAAA,CAAQ,CAAA,CAAE,CAAC,CAAE,CAAA;AACzD,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,YAAY,MAAA,EAAkC;AACrD,EAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAC,CAAA;AACrD,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,KAAK,CAAA;AAChC,EAAA,IAAI,GAAA,GAAM,CAAA;AACV,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,GAAA,CAAI,GAAA,CAAI,GAAG,GAAG,CAAA;AACd,IAAA,GAAA,IAAO,CAAA,CAAE,MAAA;AAAA,EACX;AACA,EAAA,OAAO,GAAA;AACT;AAGA,eAAe,QAAA,GAAkG;AAC/G,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAO,MAAM,OAAO,qBAAqB,CAAA;AAI/C,IAAA,MAAM,UAAA,GAAa,GAAA,CAAI,UAAA,IAAc,GAAA,CAAI,OAAA,EAAS,UAAA;AAClD,IAAA,IAAI,CAAC,UAAA,EAAY,MAAM,IAAI,MAAM,sBAAsB,CAAA;AACvD,IAAA,OAAO,UAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACF;AAaA,eAAsB,SAAA,CACpB,QAAA,EACA,UAAA,EACA,IAAA,GAAO,GAAA,EACc;AACrB,EAAA,MAAM,UAAA,GAAa,MAAM,QAAA,EAAS;AAClC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAC,CAAA;AACzC,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,KAAA,EAAO,YAAY,IAAI,CAAA;AAClD,EAAA,MAAM,OAAO,YAAA,CAAa,QAAA,CAAS,CAAC,CAAA,IAAK,IAAI,cAAc,CAAA;AAC3D,EAAA,MAAM,QAAQ,KAAA,GAAQ,CAAA,GAAI,aAAa,QAAA,CAAS,CAAC,CAAE,CAAA,GAAI,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ,IAAA;AACd,EAAA,MAAM,MAAoB,EAAC;AAC3B,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,KAAA,EAAO;AAC3C,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,QAAA,CAAS,CAAA,EAAG,IAAI,KAAK,CAAA;AACpC,IAAA,MAAM,KAAA,GAAQ,KAAA,GAAQ,GAAA,CAAI,YAAA,CAAa,GAAG,KAAA,CAAM,QAAA,CAAS,CAAA,EAAG,CAAA,GAAI,KAAK,CAAC,CAAA,GAAI,GAAA,CAAI,aAAa,CAAC,CAAA;AAC5F,IAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG,GAAA,CAAI,KAAK,IAAI,UAAA,CAAW,KAAK,CAAC,CAAA;AAAA,EACtD;AACA,EAAA,MAAM,IAAA,GAAO,IAAI,KAAA,EAAM;AACvB,EAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG,GAAA,CAAI,KAAK,IAAI,UAAA,CAAW,IAAI,CAAC,CAAA;AAClD,EAAA,OAAO,YAAY,GAAG,CAAA;AACxB;;;AC/XA,IAAM,GAAA,GAAM,GAAA;AAuBZ,SAAS,IAAI,CAAA,EAAqB;AAChC,EAAA,MAAM,GAAA,GAAM,CAAC,CAAA,GAAI,GAAI,CAAA;AACrB,EAAA,CAAA,MAAO,CAAA;AACP,EAAA,OAAO,IAAI,CAAA,EAAG;AACZ,IAAA,GAAA,CAAI,OAAA,CAAS,CAAA,GAAI,GAAA,GAAQ,GAAI,CAAA;AAC7B,IAAA,CAAA,MAAO,CAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAGA,SAAS,KAAK,CAAA,EAAqB;AACjC,EAAA,OAAO,CAAC,GAAG,IAAI,aAAY,CAAE,MAAA,CAAO,CAAC,CAAC,CAAA;AACxC;AAEA,SAAS,MAAM,CAAA,EAAqB;AAClC,EAAA,OAAO,CAAE,CAAA,KAAM,EAAA,GAAM,GAAA,EAAO,CAAA,KAAM,EAAA,GAAM,GAAA,EAAO,CAAA,KAAM,CAAA,GAAK,GAAA,EAAM,CAAA,GAAI,GAAI,CAAA;AAC1E;AAWA,IAAM,SAAA,GAAuC;AAAA,EAC3C,IAAA,EAAM,EAAA;AAAA,EACN,KAAA,EAAO,EAAA;AAAA,EACP,KAAA,EAAO,EAAA;AAAA;AAAA,EACP,IAAA,EAAM,EAAA;AAAA,EACN,IAAA,EAAM,EAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAGA,SAAS,UAAA,CAAW,KAAiB,IAAA,EAA0B;AAG7D,EAAA,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,EAAE,IAAA,GAAO,CAAA,CAAE,IAAA,IAAQ,MAAA,CAAO,EAAE,EAAE,CAAA,GAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAC,CAAA;AACjE,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAG,IAAI,CAAA;AACtB,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,MAAW,KAAK,GAAA,EAAK;AACnB,IAAA,MAAM,UAAU,CAAA,CAAE,EAAA,GAAK,GAAA,GAAO,GAAA,IAAS,EAAE,EAAA,GAAK,EAAA;AAC9C,IAAA,KAAA,CAAM,IAAA,CAAK,GAAG,GAAA,CAAI,CAAA,CAAE,IAAA,GAAO,IAAI,CAAA,EAAG,MAAA,EAAQ,CAAA,CAAE,IAAA,GAAO,GAAA,EAAM,CAAA,CAAE,MAAM,GAAI,CAAA;AACrE,IAAA,IAAA,GAAO,CAAA,CAAE,IAAA;AAAA,EACX;AACA,EAAA,KAAA,CAAM,KAAK,GAAG,GAAA,CAAI,CAAC,CAAA,EAAG,GAAA,EAAM,IAAM,CAAI,CAAA;AACtC,EAAA,OAAO,CAAC,EAAA,EAAM,EAAA,EAAM,GAAA,EAAM,GAAA,EAAM,GAAG,KAAA,CAAM,KAAA,CAAM,MAAM,CAAA,EAAG,GAAG,KAAK,CAAA;AAClE;AAOO,SAAS,UAAA,CACd,UACA,IAAA,EACY;AACZ,EAAA,MAAM,SAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,EAAA,IAAM,SAAS,MAAA,EAAQ;AAChC,IAAA,IAAI,EAAA,CAAG,SAAS,IAAA,EAAM;AACtB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,EAAA,CAAG,OAAO,GAAG,CAAA;AACtC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,KAAA,CAAA,CAAO,EAAA,CAAG,IAAA,GAAO,EAAA,CAAG,aAAA,IAAiB,GAAG,CAAA,EAAG,KAAA,GAAQ,CAAC,CAAA;AAC9E,IAAA,IAAI,EAAA,CAAG,QAAQ,IAAA,EAAM;AACnB,MAAA,MAAM,GAAA,GAAM,KAAK,GAAA,CAAI,KAAA,GAAQ,KAAK,KAAA,CAAM,IAAA,GAAO,GAAG,CAAA,EAAG,CAAC,CAAA;AACtD,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,GAAA,EAAK,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,EAAA,CAAG,GAAA,EAAK,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,GAAG,CAAA;AACjE,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,KAAA,EAAO,EAAA,EAAI,KAAA,EAAO,IAAA,EAAM,EAAA,CAAG,GAAA,EAAK,GAAA,EAAK,CAAA,EAAG,EAAA,EAAI,GAAG,CAAA;AAAA,IACrE;AACA,IAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,KAAA,EAAO,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,EAAA,CAAG,IAAA,EAAM,GAAA,EAAK,GAAA,EAAK,EAAA,EAAI,GAAG,CAAA;AACrE,IAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,GAAA,EAAK,EAAA,EAAI,KAAA,EAAO,IAAA,EAAM,EAAA,CAAG,IAAA,EAAM,GAAA,EAAK,CAAA,EAAG,EAAA,EAAI,GAAG,CAAA;AAAA,EACpE;AAGA,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,GAAA,GAAa,KAAK,GAAG,CAAA;AAClD,EAAA,MAAM,OAAiB,EAAC;AACxB,EAAA,IAAI,IAAA,CAAK,MAAM,IAAA,CAAK,IAAA,CAAK,GAAG,GAAA,CAAI,CAAC,CAAA,EAAG,GAAA,EAAM,CAAA,EAAM,GAAG,IAAI,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,CAAE,MAAM,GAAG,GAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA;AAClG,EAAA,IAAA,CAAK,IAAA,CAAK,GAAG,GAAA,CAAI,CAAC,GAAG,GAAA,EAAM,EAAA,EAAM,CAAA,EAAO,SAAA,IAAa,KAAM,GAAA,EAAO,SAAA,IAAa,CAAA,GAAK,GAAA,EAAM,YAAY,GAAI,CAAA;AAE1G,EAAA,MAAM,MAAA,GAAS,CAAC,UAAA,CAAW,MAAA,EAAQ,IAAI,CAAC,CAAA;AAExC,EAAA,IAAI,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA,EAAG;AACvC,IAAA,MAAM,OAAmB,EAAC;AAC1B,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,UAAU,CAAA;AAChD,IAAA,KAAA,IAAS,IAAA,GAAO,CAAA,EAAG,IAAA,GAAO,UAAA,EAAY,IAAA,EAAA,EAAQ;AAC5C,MAAA,MAAM,IAAA,GAAO,UAAU,IAAA,CAAK,KAAA,CAAM,OAAO,IAAA,CAAK,KAAA,CAAM,MAAM,CAAE,CAAA;AAC5D,MAAA,MAAM,IAAI,IAAA,GAAO,GAAA;AACjB,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,CAAA,EAAG,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,GAAA,EAAK,EAAA,EAAI,CAAA,EAAG,CAAA;AACtD,MAAA,IAAA,CAAK,KAAK,EAAE,IAAA,EAAM,CAAA,GAAI,IAAA,CAAK,MAAM,GAAA,GAAM,CAAC,CAAA,EAAG,EAAA,EAAI,OAAO,IAAA,EAAM,GAAA,EAAK,CAAA,EAAG,EAAA,EAAI,GAAG,CAAA;AAAA,IAC7E;AACA,IAAA,MAAA,CAAO,IAAA,CAAK,WAAW,IAAA,EAAM,CAAC,GAAG,GAAA,CAAI,CAAC,GAAG,GAAA,EAAM,CAAA,EAAM,GAAG,GAAA,CAAI,CAAC,GAAG,EAAA,EAAM,GAAA,EAAM,KAAM,GAAA,EAAM,GAAI,CAAC,CAAC,CAAA;AAAA,EAChG;AAEA,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,CAAA,GAAI,CAAA;AACvC,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,EAAA;AAAA,IAAM,EAAA;AAAA,IAAM,GAAA;AAAA,IAAM,GAAA;AAAA,IAAM,GAAG,MAAM,CAAC,CAAA;AAAA,IAClC,CAAA;AAAA,IAAM,MAAA;AAAA,IAAS,MAAA,CAAO,UAAU,CAAA,GAAK,GAAA;AAAA,IAAM,OAAO,MAAA,GAAS,GAAA;AAAA,IAC1D,OAAO,CAAA,GAAK,GAAA;AAAA,IAAM,GAAA,GAAM;AAAA,GAC3B;AACA,EAAA,OAAO,IAAI,WAAW,CAAC,GAAG,QAAQ,GAAG,MAAA,CAAO,IAAA,EAAM,CAAC,CAAA;AACrD;AAGA,SAASC,UAAS,KAAA,EAAmC;AACnD,EAAA,OAAA,CAAQ,SAAS,EAAA,EAAI,OAAA,CAAQ,kBAAkB,EAAE,CAAA,CAAE,MAAK,IAAK,WAAA;AAC/D;AAMO,SAAS,UAAA,CAAW,GAAA,EAAgB,OAAA,GAA6B,EAAC,EAAqB;AAC5F,EAAA,MAAM,QAAA,GAAW,aAAA,CAAc,GAAA,EAAK,EAAE,KAAA,EAAO,QAAQ,KAAA,EAAO,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAY,CAAA;AAC5F,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,IAAO,GAAA,CAAI,KAAK,GAAA,IAAO,EAAA;AAC3C,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,EAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,MAAA,KAAW,MAAA,IAAa,MAAA,IAAU,KAAA,GAAQ,KAAA,CAAM,MAA4B,CAAA,GAAI,EAAE,QAAA,EAAU,CAAA,EAAG,GAAA,EAAK,CAAA,EAAE;AACnH,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,GAAQ,WAAA,CAAY,IAAI,CAAA,GAAI,MAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,QAAA,EAAU,EAAE,GAAA,EAAK,IAAA,EAAM,OAAA,CAAQ,IAAA,IAAQ,GAAA,CAAI,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,CAAA;AACvF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,KAAiB,CAAA,EAAG,EAAE,IAAA,EAAM,YAAA,EAAc,CAAA;AAAA,IAC1D,UAAU,CAAA,EAAGA,SAAAA,CAAS,GAAA,CAAI,IAAA,CAAK,KAAK,CAAC,CAAA,IAAA,CAAA;AAAA,IACrC,QAAA,EAAU;AAAA,GACZ;AACF","file":"index.js","sourcesContent":["/**\n * `SwaralipiPlayer` — the stateful audio facade over Tone.js.\n *\n * Tone is **dynamically imported** on first `play()` so that importing\n * `@ugfoundation/swaralipi-js/player` in Node/SSR (or just to reach the pure\n * helpers) never pulls in the audio engine or touches `AudioContext`. The\n * pure cores (pitch.ts, schedule.ts, instruments.ts) carry the logic; this\n * class only wires them onto the Tone Transport and drives the viewer's\n * `highlight()`.\n *\n * Model: build a beat-timed `Schedule`, lay each note onto the Transport in\n * seconds (`beat × 60/bpm`), loop a tabla (or click a metronome) underneath,\n * drone a tanpura, and fire highlights on Tone's draw thread. Any setting\n * change while playing re-arranges from the top (documented v1 behavior — no\n * mid-stream tempo ramp or seek).\n */\nimport type { Gain, GrainPlayer, Players, Player, Sampler } from 'tone';\nimport type { Swaralipi } from '../spec/index.js';\n// Taal type-only from schema.ts (not the barrel) — keeps the player zod-free,\n// same reason instruments.ts imports TAALS from the taals subpath.\nimport type { Taal } from '../spec/schema.js';\n// TAALS from the taals subpath, not the barrel — see the note in instruments.ts\n// (the barrel pulls zod, which would defeat the player's bare-load guarantee).\nimport { TAALS, type TaalId } from '../spec/taals.js';\nimport { midiToSampleName, parseTonic } from './pitch.js';\nimport { buildSchedule, type Schedule } from './schedule.js';\nimport {\n DEFAULT_SOUNDS_BASE,\n INSTRUMENTS,\n SAMPLED_TAALS,\n TABLA_GRAIN_OVERLAP_SEC,\n TABLA_GRAIN_SEC,\n TANPURA_PCS,\n createDrumKit,\n createMetronome,\n createSampler,\n createTanpura,\n drumPattern,\n instrumentSampleUrl,\n nearestTablaBpm,\n normalizeBase,\n probeUrl,\n quantizeLoopEnd,\n tablaBpms,\n tablaShiftSemitones,\n tablaUrl,\n type InstrumentDef,\n type InstrumentId,\n type ToneModule,\n} from './instruments.js';\n\n/** Seconds the accompaniment fades over when playback is stopped. */\nconst STOP_FADE_SEC = 0.5;\n\nexport type PlayerState = 'idle' | 'loading' | 'ready' | 'playing' | 'paused';\nexport type RhythmMode = 'tabla' | 'drums' | 'metronome' | 'none';\n\nexport interface PlayerOptions {\n /** Base URL for the project's samples (santoor/tabla/tanpura/metronome).\n * Defaults to `DEFAULT_SOUNDS_BASE` (the project's CORS-enabled CDN), so the\n * player works with zero config; override to self-host. */\n soundsBaseUrl?: string;\n /** Grand-piano sample host. Default: the Salamander CDN (see SALAMANDER_BASE).\n * Set this to self-host the piano under your own URL. */\n pianoBaseUrl?: string;\n /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */\n tonic?: string;\n /** Tempo in matra/min. Default 90. Snapped to the 15-grid while tabla plays. */\n bpm?: number;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Active melody instrument id (a built-in or a `customInstruments` key).\n * Default `piano` (CDN-hosted, zero-config). Built-ins: piano, santoor,\n * harmonium, violin, flute. */\n instrument?: InstrumentId;\n /** Instrument ids the UI may expose. Default: all built-ins. Narrow it to\n * e.g. `['santoor','harmonium']` to limit the picker. */\n instruments?: InstrumentId[];\n /** Extra melody instruments, keyed by id — e.g. a self-hosted sitar/sarod.\n * (True sampled sitar/sarod/sarangi/esraj have no free web set; supply your\n * own Tone.Sampler `urls` + `baseUrl` here and they slot into the picker.) */\n customInstruments?: Record<string, InstrumentDef>;\n /** Rhythm accompaniment. Default `tabla` (→ metronome for unsampled taals).\n * `drums` sequences a Western drumset (kick/snare/hihat) to the taal's\n * sam/tali/khaali structure — a genuine \"drum atlas\"-style translation,\n * not a loop; works for every taal (no per-taal sample requirement). */\n rhythm?: RhythmMode;\n /** Play the tanpura drone. Default true. */\n tanpura?: boolean;\n /** Stop playback after this many seconds (a policy-neutral preview cap).\n * Default `Infinity` (unlimited). The library only enforces the number it is\n * handed; who is capped / the value / the upsell is the consumer's policy. */\n previewLimitSec?: number;\n /** Called when the preview cap stops playback (so the consumer can show an\n * upsell). Not called on a normal stop()/end. */\n onLimit?: () => void;\n /** Called with the active note's `part.row.note` path, or null when idle. */\n onHighlight?: (path: string | null) => void;\n /** Taal mode only (`loadTaal`): called on the draw thread once per matra with\n * the beat index within the cycle (0-based, `< numBeats`), so a consumer can\n * highlight the active theka bol / drum-staff note. Never fired in doc mode. */\n onBeat?: (beatInCycle: number) => void;\n /** Called on every state transition. */\n onState?: (state: PlayerState) => void;\n}\n\n/** Minimal shape of the viewer element the player drives (avoids a hard\n * dependency on the viewer module). `highlight` returns the highlighted node\n * (or null) so bindSheet can optionally scroll it into view. */\ninterface HighlightTarget {\n highlight(path: string): HTMLElement | null;\n clearHighlights(): void;\n}\n\n/** MIDI span (incl. kan graces) of a schedule's notes, for sample subsetting;\n * undefined when there are no pitched events. */\nfunction midiRange(schedule: Schedule): { lo: number; hi: number } | undefined {\n let lo = Infinity;\n let hi = -Infinity;\n for (const ev of schedule.events) {\n for (const m of [ev.midi, ev.kan]) {\n if (m !== null) {\n if (m < lo) lo = m;\n if (m > hi) hi = m;\n }\n }\n }\n return lo <= hi ? { lo, hi } : undefined;\n}\n\n/**\n * Wait for sample buffers, but never let one bad URL strand playback.\n * `Tone.loaded()` rejects as soon as any buffer fails, so an unreachable\n * accompaniment sample (CDN down, wrong `soundsBaseUrl`, a taal with no\n * recorded loop at that tempo) would otherwise leave `play()` hung in the\n * `loading` state with nothing sounding — including the melody, which loaded\n * fine. Warn and carry on: the voices that did load still play.\n */\nasync function loadedOrWarn(Tone: ToneModule): Promise<void> {\n try {\n await Tone.loaded();\n } catch (err) {\n console.warn('[swaralipi] some audio samples failed to load; continuing without them', err);\n }\n}\n\nexport interface BindSheetOptions {\n /** Keep the active note visible while playing by calling `scrollIntoView`\n * on it. `true` → `'center'` (keeps the note's lyric, rendered just below it,\n * on screen too); or pass an explicit block position (`'nearest'`, `'start'`,\n * …). Default off so it never fights a host that manages its own scrolling.\n * Works through the shadow boundary, so it scrolls the page, not just the\n * sheet. */\n autoScroll?: boolean | ScrollLogicalPosition;\n}\n\n/** The common melody-voice call the scheduler needs — both melody instruments\n * are `Tone.Sampler`s, but this keeps the call site decoupled from the class. */\ninterface MelodyVoice {\n triggerAttackRelease(note: string, duration: number, time: number): unknown;\n}\n\nexport class SwaralipiPlayer {\n #opts: Required<\n Omit<\n PlayerOptions,\n | 'soundsBaseUrl'\n | 'pianoBaseUrl'\n | 'customInstruments'\n | 'onHighlight'\n | 'onBeat'\n | 'onState'\n | 'onLimit'\n | 'tonic'\n | 'bpm'\n >\n > & {\n soundsBaseUrl?: string;\n pianoBaseUrl?: string;\n customInstruments?: Record<string, InstrumentDef>;\n tonic?: string;\n /** Pinned constructor tempo: overrides meta.bpm, survives load(). */\n bpm?: number;\n onHighlight?: (path: string | null) => void;\n onBeat?: (beatInCycle: number) => void;\n onState?: (state: PlayerState) => void;\n onLimit?: () => void;\n };\n /** Per-song setBpm tweak: beats meta.bpm but is cleared by the next load(). */\n #bpmTweak: number | undefined;\n #doc: Swaralipi | undefined;\n /** Taal mode (`loadTaal`): loops one avartan of this taal's theka with no\n * melody. Mutually exclusive with `#doc`. */\n #taal: Taal | undefined;\n #state: PlayerState = 'idle';\n\n #tone: ToneModule | undefined;\n /** Master bus everything routes through, so stop() can fade it out. */\n #master: Gain | undefined;\n /** Cached melody samplers keyed by instrument id, with the MIDI range each\n * was loaded for (rebuilt when a song needs a wider range). */\n #samplers = new Map<string, { sampler: Sampler; lo: number; hi: number }>();\n #tanpura: Players | undefined;\n #tanpuraKey = '';\n #metronome: Player | undefined;\n /** The looping tabla — a `GrainPlayer` when the tonic needs it retuned. */\n #tabla: Player | GrainPlayer | undefined;\n #drums: Players | undefined;\n #scheduleIds: number[] = [];\n #fadeTimer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(options: PlayerOptions = {}) {\n this.#opts = {\n soundsBaseUrl: options.soundsBaseUrl ?? DEFAULT_SOUNDS_BASE,\n pianoBaseUrl: options.pianoBaseUrl,\n customInstruments: options.customInstruments,\n tonic: options.tonic,\n bpm: options.bpm,\n baseOctave: options.baseOctave ?? 4,\n instrument: options.instrument ?? 'piano',\n instruments: options.instruments ?? Object.keys(INSTRUMENTS),\n rhythm: options.rhythm ?? 'tabla',\n tanpura: options.tanpura ?? true,\n previewLimitSec: options.previewLimitSec ?? Infinity,\n onHighlight: options.onHighlight,\n onBeat: options.onBeat,\n onState: options.onState,\n onLimit: options.onLimit,\n };\n }\n\n /* ----------------------------------------------------------------- */\n /* Public getters */\n /* ----------------------------------------------------------------- */\n\n get state(): PlayerState {\n return this.#state;\n }\n get bpm(): number {\n return this.#bpmTweak ?? this.#opts.bpm ?? this.#doc?.meta.bpm ?? 90;\n }\n /** The preview cap in seconds (`Infinity` = unlimited). */\n get previewLimitSec(): number {\n return this.#opts.previewLimitSec;\n }\n get instrument(): InstrumentId {\n return this.#effectiveInstrument();\n }\n get instruments(): InstrumentId[] {\n return [...this.#opts.instruments];\n }\n /** The taal currently driving the rhythm (auto from the doc, or an override). */\n get taalId(): TaalId | undefined {\n return this.#taalId();\n }\n /** Whether the effective rhythm will actually be a sampled tabla. */\n get usesTabla(): boolean {\n const id = this.#taalId();\n return this.#opts.rhythm === 'tabla' && id !== undefined && SAMPLED_TAALS.has(id) && !!this.#opts.soundsBaseUrl;\n }\n\n /* ----------------------------------------------------------------- */\n /* Loading + transport control */\n /* ----------------------------------------------------------------- */\n\n /** Set the document to play. Stops any current playback. */\n load(doc: Swaralipi): void {\n this.#halt();\n this.#doc = doc;\n this.#taal = undefined;\n this.#taalOverride = undefined;\n this.#bpmTweak = undefined; // per-song tweak does not carry across songs\n this.#setState('ready');\n }\n\n /**\n * Taal mode: loop one avartan of a taal's theka (rhythm only, no melody) —\n * for a standalone theka player. `play()` loops indefinitely (tabla loop /\n * drum sequence) and fires `onBeat(beatInCycle)` once per matra on the draw\n * thread so the consumer can highlight the active bol / drum-staff note. The\n * taal's `id` should be a sampled taal (see `SAMPLED_TAALS`) for tabla audio;\n * `variation` picks a tabla loop variation. Stops any current playback.\n */\n loadTaal(taal: Taal): void {\n this.#halt();\n this.#taal = taal;\n this.#doc = undefined;\n this.#taalOverride = undefined;\n this.#bpmTweak = undefined;\n this.#setState('ready');\n }\n\n /** Start (or resume) playback. Must be triggered from a user gesture so the\n * browser permits `AudioContext` resume. */\n async play(): Promise<void> {\n if ((this.#doc === undefined && this.#taal === undefined) || this.#state === 'playing') return;\n const Tone = await this.#ensureTone();\n await Tone.start();\n\n if (this.#state === 'paused') {\n Tone.getTransport().start();\n this.#setState('playing');\n return;\n }\n\n // Fresh start — abort any in-flight stop fade and clear a left-running\n // transport before re-arranging.\n this.#cancelFade();\n Tone.getTransport().stop();\n this.#setState('loading');\n await this.#arrange(Tone);\n Tone.getTransport().start();\n this.#setState('playing');\n\n // Preview cap: stop at the limit and notify (policy is the consumer's).\n const limit = this.#opts.previewLimitSec;\n if (Number.isFinite(limit)) {\n Tone.getTransport().scheduleOnce(() => {\n // getDraw defers to the visual frame so stop() runs off the audio thread.\n Tone.getDraw().schedule(() => {\n if (this.#state === 'playing') {\n this.stop();\n this.#opts.onLimit?.();\n }\n }, this.#tone!.now());\n }, limit);\n }\n }\n\n /** Pause; `play()` resumes from the same position. */\n pause(): void {\n if (this.#tone === undefined || this.#state !== 'playing') return;\n this.#tone.getTransport().pause();\n this.#setState('paused');\n }\n\n /**\n * Stop and rewind to the start. When sounding, the accompaniment (tabla /\n * metronome / tanpura, which otherwise loop) is gently faded out over\n * STOP_FADE_SEC before the transport is halted, so the rhythm doesn't keep\n * playing or cut abruptly; otherwise it halts immediately.\n */\n stop(): void {\n if (this.#tone === undefined || this.#state === 'idle') {\n this.#halt();\n return;\n }\n if ((this.#state === 'playing' || this.#state === 'paused') && this.#master !== undefined) {\n const now = this.#tone.now();\n this.#master.gain.cancelScheduledValues(now);\n this.#master.gain.rampTo(0, STOP_FADE_SEC, now);\n this.#emitHighlight(null);\n this.#setState(this.#doc || this.#taal ? 'ready' : 'idle');\n this.#cancelFadeTimer();\n this.#fadeTimer = setTimeout(() => this.#halt(), STOP_FADE_SEC * 1000 + 60);\n return;\n }\n this.#halt();\n }\n\n /** Immediate, synchronous teardown: stop+rewind the transport, kill the\n * looping accompaniment, restore the master gain for the next play. */\n #halt(): void {\n this.#cancelFadeTimer();\n if (this.#tone !== undefined) {\n const transport = this.#tone.getTransport();\n transport.stop();\n transport.cancel(0);\n transport.position = 0;\n }\n // Dispose the looping tabla outright — a synced loop can outlive a bare\n // transport.stop(); #arrange recreates it on the next play.\n this.#tabla?.dispose();\n this.#tabla = undefined;\n this.#restoreMasterGain();\n this.#scheduleIds = [];\n this.#emitHighlight(null);\n if (this.#state !== 'idle') this.#setState(this.#doc || this.#taal ? 'ready' : 'idle');\n }\n\n #cancelFadeTimer(): void {\n if (this.#fadeTimer !== undefined) {\n clearTimeout(this.#fadeTimer);\n this.#fadeTimer = undefined;\n }\n }\n\n /** Abort an in-flight stop-fade (timer + gain ramp) without touching the\n * transport — used when play() interrupts a fade. */\n #cancelFade(): void {\n this.#cancelFadeTimer();\n this.#restoreMasterGain();\n }\n\n #restoreMasterGain(): void {\n if (this.#master !== undefined && this.#tone !== undefined) {\n this.#master.gain.cancelScheduledValues(this.#tone.now());\n this.#master.gain.value = 1;\n }\n }\n\n /* ----------------------------------------------------------------- */\n /* Settings (re-arrange from the top if mid-playback) */\n /* ----------------------------------------------------------------- */\n\n setTonic(tonic: string): void {\n this.#opts.tonic = tonic;\n this.#reapply();\n }\n setBpm(bpm: number): void {\n this.#bpmTweak = bpm;\n this.#reapply();\n }\n /** Update the preview cap (`Infinity` lifts it). Takes effect on next play. */\n setPreviewLimit(sec: number): void {\n this.#opts.previewLimitSec = sec;\n }\n setInstrument(instrument: InstrumentId): void {\n this.#opts.instrument = instrument;\n this.#reapply();\n }\n setRhythm(rhythm: RhythmMode): void {\n this.#opts.rhythm = rhythm;\n this.#reapply();\n }\n setTanpura(on: boolean): void {\n this.#opts.tanpura = on;\n this.#reapply();\n }\n /** Override the taal (null restores auto-detection from `meta.taal`). */\n setTaal(id: TaalId | null): void {\n this.#taalOverride = id ?? undefined;\n this.#taalAuto = id === null;\n this.#reapply();\n }\n\n /** Wire highlight output to a `<swaralipi-sheet>` (or any highlight target).\n * Pass `{ autoScroll }` to keep the active note in view while playing. */\n bindSheet(target: HighlightTarget, opts: BindSheetOptions = {}): void {\n // 'center' so the lyric directly below the note stays on screen too\n // ('nearest' can park the note at the bottom edge, hiding its lyric).\n const block: ScrollLogicalPosition | undefined =\n opts.autoScroll === true ? 'center' : opts.autoScroll || undefined;\n this.#opts.onHighlight = (path) => {\n target.clearHighlights();\n if (path === null) return;\n const el = target.highlight(path);\n if (block !== undefined && el !== null) {\n el.scrollIntoView({ block, inline: 'nearest', behavior: 'smooth' });\n }\n };\n }\n\n /**\n * Check whether an instrument's samples are reachable (its CDN is up / the\n * local files exist), via a one-byte ranged fetch of a representative sample.\n * A pass means Tone can actually load it. `false` for unknown ids, local\n * instruments with no `soundsBaseUrl`, or any network failure/timeout.\n */\n async checkInstrument(id: string, timeoutMs?: number): Promise<boolean> {\n const def = this.#resolveDef(id);\n if (def === undefined) return false;\n const url = instrumentSampleUrl(def, this.#opts.soundsBaseUrl);\n if (url === undefined) return false;\n return probeUrl(url, timeoutMs);\n }\n\n /**\n * Probe several instruments at once (default: the exposed `instruments`),\n * returning `{ id: available }`. Use it to disable picker options whose CDN\n * or files are down. Probes run concurrently; a check never throws.\n */\n async checkInstruments(\n ids: string[] = this.#opts.instruments,\n timeoutMs?: number,\n ): Promise<Record<string, boolean>> {\n const entries = await Promise.all(\n ids.map(async (id) => [id, await this.checkInstrument(id, timeoutMs)] as const),\n );\n return Object.fromEntries(entries);\n }\n\n /** Release all audio resources. The instance is unusable afterward. */\n dispose(): void {\n this.#halt();\n for (const { sampler } of this.#samplers.values()) sampler.dispose();\n this.#samplers.clear();\n this.#tanpura?.dispose();\n this.#metronome?.dispose();\n this.#tabla?.dispose();\n this.#drums?.dispose();\n this.#master?.dispose();\n this.#tanpura = this.#metronome = this.#tabla = this.#drums = this.#master = undefined;\n }\n\n /* ----------------------------------------------------------------- */\n /* Internals */\n /* ----------------------------------------------------------------- */\n\n #taalOverride: TaalId | undefined;\n #taalAuto = true;\n\n #setState(state: PlayerState): void {\n this.#state = state;\n this.#opts.onState?.(state);\n }\n\n #emitHighlight(path: string | null): void {\n this.#opts.onHighlight?.(path);\n }\n\n /** The tabla variation to play: taal-mode taal's, the doc's own (auto mode),\n * else none — an explicit setTaal override plays the taal's default variation. */\n #variation(): string | undefined {\n if (this.#taal !== undefined) return this.#taal.variation;\n return this.#taalAuto ? this.#doc?.meta.taal?.variation : undefined;\n }\n\n /** Resolve the active taal id: taal mode's taal, an explicit override, else\n * the doc's — but only when it names a sampled taal. */\n #taalId(): TaalId | undefined {\n const id = this.#taal?.id ?? (!this.#taalAuto && this.#taalOverride !== undefined ? this.#taalOverride : this.#doc?.meta.taal?.id);\n return id !== undefined && id in TAALS ? (id as TaalId) : undefined;\n }\n\n /** Resolve an instrument id to its def (custom overrides built-ins; the\n * `pianoBaseUrl` shortcut self-hosts the built-in piano). */\n #resolveDef(id: string): InstrumentDef | undefined {\n const custom = this.#opts.customInstruments?.[id];\n if (custom) return custom;\n const def = INSTRUMENTS[id];\n if (def && id === 'piano' && this.#opts.pianoBaseUrl) {\n return { ...def, baseUrl: normalizeBase(this.#opts.pianoBaseUrl) };\n }\n return def;\n }\n\n /** A CDN-backed instrument that needs no `soundsBaseUrl` — used when the\n * chosen one is local-only and no samples host is set, or is unknown. */\n #fallbackInstrument(): string {\n const exposed = this.#opts.instruments.find((id) => {\n const d = this.#resolveDef(id);\n return d !== undefined && d.local !== true;\n });\n return exposed ?? 'harmonium';\n }\n\n /** The instrument actually used: the chosen one, unless it's local-only with\n * no `soundsBaseUrl` (or unknown), in which case a CDN fallback. */\n #effectiveInstrument(): string {\n const id = this.#opts.instrument;\n const def = this.#resolveDef(id);\n if (def === undefined) return this.#fallbackInstrument();\n if (def.local === true && !this.#opts.soundsBaseUrl) return this.#fallbackInstrument();\n return id;\n }\n\n /** Effective tempo: snapped to the taal's available tabla tempos while a\n * tabla actually plays (so it never snaps to a bpm that taal lacks). */\n #effectiveBpm(): number {\n const id = this.#taalId();\n return this.usesTabla && id ? nearestTablaBpm(this.bpm, tablaBpms(id)) : this.bpm;\n }\n\n async #ensureTone(): Promise<ToneModule> {\n this.#tone ??= await import('tone');\n return this.#tone;\n }\n\n /** Hard stop → rebuild → restart, only while playing; otherwise just store.\n * Uses #halt (not the stop() fade) so a setting change restarts instantly. */\n #reapply(): void {\n if (this.#state !== 'playing') return;\n this.#halt();\n void this.play();\n }\n\n /** The master bus everything routes through (created once, → destination). */\n #ensureMaster(Tone: ToneModule): Gain {\n this.#master ??= new Tone.Gain(1).toDestination();\n return this.#master;\n }\n\n /** Build the melody instrument(s)/rhythm/drone for the current settings and\n * wait for their buffers, then schedule everything on the Transport. */\n async #arrange(Tone: ToneModule): Promise<void> {\n if (this.#taal !== undefined) return this.#arrangeTaal(Tone);\n if (this.#doc === undefined) return;\n const base = this.#opts.soundsBaseUrl;\n const transport = Tone.getTransport();\n transport.cancel(0);\n transport.position = 0;\n this.#scheduleIds = [];\n\n // Fresh master bus at unity gain (a prior stop-fade may have lowered it).\n this.#ensureMaster(Tone);\n this.#restoreMasterGain();\n\n const bpm = this.#effectiveBpm();\n transport.bpm.value = bpm;\n const spb = 60 / bpm;\n\n // Build the (pure) schedule first so the melody sampler loads only the\n // samples covering this song's pitch range — fewer files to fetch.\n const schedule: Schedule = buildSchedule(this.#doc, {\n tonic: this.#opts.tonic,\n baseOctave: this.#opts.baseOctave,\n });\n const range = midiRange(schedule);\n\n // --- melody instrument ---\n const melody = this.#buildMelody(Tone, this.#effectiveInstrument(), base, range);\n\n // --- rhythm ---\n this.#buildRhythm(Tone, transport, base, spb);\n\n // --- tanpura drone ---\n this.#buildTanpura(Tone, transport, base, spb);\n\n // wait for the (now-minimal) sample set to load before scheduling\n await loadedOrWarn(Tone);\n\n // Snap the tabla loop to a whole number of beats now its buffer length is\n // known, so a mistrimmed loop phase-locks instead of drifting (no-op for a\n // correctly trimmed file).\n if (this.#tabla?.buffer?.loaded) {\n this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);\n }\n\n // --- notes + highlights ---\n const draw = Tone.getDraw();\n // Pickup shift: push the melody so its sam locks to the looping accompaniment\n // (the rhythm keeps looping from 0, filling the count-in). Only when a rhythm\n // actually loops — with rhythm 'none' the shift would be pointless lead-in.\n const shift = this.#opts.rhythm !== 'none' ? schedule.pickupBeats * spb : 0;\n\n for (const ev of schedule.events) {\n const at = ev.beat * spb + shift;\n if (ev.midi !== null) {\n const name = midiToSampleName(ev.midi);\n const dur = Math.max(ev.durationBeats * spb, 0.05);\n if (ev.kan !== null) {\n const kanName = midiToSampleName(ev.kan);\n transport.scheduleOnce((time) => melody.triggerAttackRelease(kanName, spb * 0.4, time), Math.max(at - spb * 0.12, 0));\n }\n transport.scheduleOnce((time) => melody.triggerAttackRelease(name, dur, time), at);\n }\n transport.scheduleOnce((time) => {\n draw.schedule(() => this.#emitHighlight(ev.path), time);\n }, at);\n }\n\n // clear highlight + auto-stop at the end\n const endAt = schedule.totalBeats * spb + shift + spb;\n transport.scheduleOnce((time) => {\n draw.schedule(() => this.#emitHighlight(null), time);\n }, schedule.totalBeats * spb + shift);\n transport.scheduleOnce(() => this.stop(), endAt);\n }\n\n /** Taal mode: loop one avartan forever (tabla loop / drum sequence via\n * #buildRhythm), firing onBeat + a `String(beatInCycle)` highlight each matra\n * on the draw thread. No melody, no tanpura, no auto-stop — it plays until\n * stop(). */\n async #arrangeTaal(Tone: ToneModule): Promise<void> {\n if (this.#taal === undefined) return;\n const base = this.#opts.soundsBaseUrl;\n const transport = Tone.getTransport();\n transport.cancel(0);\n transport.position = 0;\n this.#scheduleIds = [];\n\n this.#ensureMaster(Tone);\n this.#restoreMasterGain();\n\n const bpm = this.#effectiveBpm();\n transport.bpm.value = bpm;\n const spb = 60 / bpm;\n\n this.#buildRhythm(Tone, transport, base, spb);\n await loadedOrWarn(Tone);\n if (this.#tabla?.buffer?.loaded) {\n this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);\n }\n\n const draw = Tone.getDraw();\n const n = Math.max(1, this.#taal.numBeats);\n let beat = 0;\n const id = transport.scheduleRepeat((time) => {\n const b = beat % n;\n draw.schedule(() => {\n this.#emitHighlight(String(b));\n this.#opts.onBeat?.(b);\n }, time);\n beat += 1;\n }, spb, 0);\n this.#scheduleIds.push(id);\n }\n\n /** Build (or reuse) the melody sampler for `id`, loading only the samples\n * covering `range`. A cached sampler is reused while it still covers the\n * range; otherwise it's rebuilt (so changing songs/tonic widens it lazily). */\n #buildMelody(\n Tone: ToneModule,\n id: string,\n base: string | undefined,\n range: { lo: number; hi: number } | undefined,\n ): MelodyVoice {\n const def = this.#resolveDef(id) ?? INSTRUMENTS.harmonium!;\n const master = this.#ensureMaster(Tone);\n const cached = this.#samplers.get(id);\n if (cached && (range === undefined || (cached.lo <= range.lo && cached.hi >= range.hi))) {\n return cached.sampler;\n }\n cached?.sampler.dispose();\n const sampler = createSampler(Tone, def, base, range).connect(master);\n this.#samplers.set(id, { sampler, lo: range?.lo ?? -Infinity, hi: range?.hi ?? Infinity });\n return sampler;\n }\n\n #buildRhythm(Tone: ToneModule, transport: ReturnType<ToneModule['getTransport']>, base: string | undefined, spb: number): void {\n // dispose a stale tabla loop so it doesn't double up after a re-arrange\n this.#tabla?.dispose();\n this.#tabla = undefined;\n\n const master = this.#ensureMaster(Tone);\n const taalId = this.#taalId();\n const wantsTabla = this.#opts.rhythm === 'tabla' && taalId !== undefined && SAMPLED_TAALS.has(taalId) && base !== undefined;\n\n if (wantsTabla) {\n const url = tablaUrl(base!, taalId!, nearestTablaBpm(this.bpm, tablaBpms(taalId!)), this.#variation());\n // Retune the recorded loop to the song's Sa; an unshifted tonic stays on\n // the plain Player (no granulation at all).\n const shift = tablaShiftSemitones(this.#tonicSemitone());\n this.#tabla = (shift === 0\n ? new Tone.Player({ url, loop: true })\n : new Tone.GrainPlayer({\n url,\n loop: true,\n detune: shift * 100,\n grainSize: TABLA_GRAIN_SEC,\n overlap: TABLA_GRAIN_OVERLAP_SEC,\n })\n ).connect(master);\n this.#tabla.sync().start(0);\n return;\n }\n\n if (this.#opts.rhythm === 'drums') {\n this.#drums ??= createDrumKit(Tone).connect(master);\n const drums = this.#drums;\n // Prefer the canonical TAALS def (has khaali), else the doc's own taal.\n const taal = this.#taal ?? (taalId !== undefined ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };\n const pattern = drumPattern(taal);\n let beat = 0;\n const id = transport.scheduleRepeat((time) => {\n const voice = pattern[beat % pattern.length]!;\n if (drums.has(voice)) drums.player(voice).start(time);\n beat += 1;\n }, spb, 0);\n this.#scheduleIds.push(id);\n return;\n }\n\n if (this.#opts.rhythm === 'none' || base === undefined) return;\n\n // metronome fallback: a click each beat, louder on sam (cycle start)\n this.#metronome ??= createMetronome(Tone, base).connect(master);\n const metro = this.#metronome;\n // Accent every cycle start: the doc's own numBeats even when its taal isn't\n // sampled (so a pickup song's sam accent lands right, matching the shift).\n const cycle = this.#taal?.numBeats ?? this.#doc?.meta.taal?.numBeats ?? 4;\n let beat = 0;\n const id = transport.scheduleRepeat((time) => {\n metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);\n metro.start(time);\n beat += 1;\n }, spb, 0);\n this.#scheduleIds.push(id);\n }\n\n #buildTanpura(Tone: ToneModule, transport: ReturnType<ToneModule['getTransport']>, base: string | undefined, spb: number): void {\n if (!this.#opts.tanpura || base === undefined) return;\n\n // Pa–Sa–Sa–Sa drone, one pluck every two beats, cycling.\n const tonicPc = ((this.#tonicSemitone() % 12) + 12) % 12;\n const sa = TANPURA_PCS[tonicPc]!;\n const pa = TANPURA_PCS[(tonicPc + 7) % 12]!;\n\n // Load only the two pitch classes the drone uses (not all 12); rebuild if\n // the tonic changed.\n const key = `${sa},${pa}`;\n if (this.#tanpura === undefined || this.#tanpuraKey !== key) {\n this.#tanpura?.dispose();\n this.#tanpura = createTanpura(Tone, base, [sa, pa]).connect(this.#ensureMaster(Tone));\n this.#tanpuraKey = key;\n }\n const players = this.#tanpura;\n players.volume.value = -14;\n\n const pattern = [pa, sa, sa, sa];\n let i = 0;\n const id = transport.scheduleRepeat((time) => {\n const stem = pattern[i % pattern.length]!;\n if (players.has(stem)) players.player(stem).start(time);\n i += 1;\n }, spb * 2, 0);\n this.#scheduleIds.push(id);\n }\n\n #tonicSemitone(): number {\n return parseTonic(this.#opts.tonic ?? this.#doc?.meta.tonic);\n }\n}\n","/**\n * Offline audio export — render a swaralipi doc to a downloadable WAV or MP3\n * **entirely in the browser** (no server): `Tone.Offline` renders the same\n * melody / tabla / tanpura arrangement the live player builds into an\n * OfflineAudioContext (faster than real time), then a pure encoder writes the\n * PCM out.\n *\n * Like the player, Tone is **dynamically imported** (so importing this module\n * in Node/SSR never touches `AudioContext`) and the MP3 path dynamically\n * imports lamejs (an *optional* peer dep — WAV needs no dependency at all).\n * The actual render needs a browser (Node has no OfflineAudioContext); the two\n * encoders below are pure and unit-testable on their own.\n *\n * ponytail: the melody/rhythm/tanpura scheduling mirrors player.ts `#arrange`\n * (minus highlights / caching / fades) — keep the two in sync.\n */\nimport type { GrainPlayer, Player } from 'tone';\nimport type { Swaralipi } from '../spec/index.js';\nimport { TAALS, type TaalId } from '../spec/taals.js';\nimport { midiToSampleName, parseTonic } from './pitch.js';\nimport { buildSchedule, type Schedule } from './schedule.js';\nimport type { RhythmMode } from './player.js';\nimport {\n DEFAULT_SOUNDS_BASE,\n INSTRUMENTS,\n SAMPLED_TAALS,\n TABLA_GRAIN_OVERLAP_SEC,\n TABLA_GRAIN_SEC,\n TANPURA_PCS,\n createDrumKit,\n createMetronome,\n createSampler,\n createTanpura,\n drumPattern,\n nearestTablaBpm,\n normalizeBase,\n quantizeLoopEnd,\n tablaBpms,\n tablaShiftSemitones,\n tablaUrl,\n type InstrumentDef,\n type InstrumentId,\n type ToneModule,\n} from './instruments.js';\n\nexport type AudioFormat = 'wav' | 'mp3';\n\nexport interface AudioExportOptions {\n /** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */\n soundsBaseUrl?: string;\n /** Self-host the grand piano (see the player's `pianoBaseUrl`). */\n pianoBaseUrl?: string;\n /** Extra melody instruments, keyed by id (see the player's option). */\n customInstruments?: Record<string, InstrumentDef>;\n /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */\n tonic?: string;\n /** Tempo in matra/min. Default: `meta.bpm`, else 90. Snapped to the tabla grid\n * when a sampled tabla actually plays. */\n bpm?: number;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Melody instrument id. Default `piano`. */\n instrument?: InstrumentId;\n /** Include the notation (melody) voice. Default true. */\n melody?: boolean;\n /** Rhythm accompaniment: `tabla` (→ metronome for unsampled taals), `drums`\n * (Western drumset following sam/tali/khaali), `metronome`, or `none`.\n * Default `none` — the base export is notation only. */\n rhythm?: RhythmMode;\n /** Taal for the rhythm: an explicit id, or `auto` (from `meta.taal`). Default\n * `auto`. Only used when `rhythm` isn't `none`. */\n taal?: TaalId | 'auto';\n /** Include the tanpura drone. Default false. */\n tanpura?: boolean;\n /** Output format. Default `wav` (lossless, zero-dependency). `mp3` needs the\n * optional peer dep `@breezystack/lamejs`. */\n format?: AudioFormat;\n /** MP3 bitrate in kbps. Default 192. */\n mp3Kbps?: number;\n /** Output channels. Default 1 (the content is monophonic and centred, so mono\n * halves the file with no audible loss); set 2 for stereo. */\n channels?: 1 | 2;\n /** Sample rate. Default 44100. */\n sampleRate?: number;\n /** Fade the last N seconds so a looped tabla/tanpura doesn't hard-cut at the\n * render boundary. Default 0.4. */\n fadeOutSec?: number;\n}\n\nexport interface AudioExportResult {\n /** The encoded audio, ready to download or upload. */\n blob: Blob;\n /** Suggested `<title>.<ext>` download filename. */\n filename: string;\n mimeType: string;\n /** Rendered length in seconds. */\n durationSec: number;\n}\n\n/** MIDI span (incl. kan graces) of a schedule, for sample subsetting. */\nfunction midiRange(schedule: Schedule): { lo: number; hi: number } | undefined {\n let lo = Infinity;\n let hi = -Infinity;\n for (const ev of schedule.events) {\n for (const m of [ev.midi, ev.kan]) {\n if (m !== null) {\n if (m < lo) lo = m;\n if (m > hi) hi = m;\n }\n }\n }\n return lo <= hi ? { lo, hi } : undefined;\n}\n\n/** Resolve the melody instrument def (mirrors the player's #resolveDef): a\n * custom override, the piano self-host shortcut, a CDN fallback for a local\n * instrument with no host, else the requested built-in. */\nfunction pickDef(id: string, o: AudioExportOptions): InstrumentDef {\n const def = o.customInstruments?.[id] ?? INSTRUMENTS[id];\n if (def === undefined) return INSTRUMENTS.harmonium!; // unknown id → CDN fallback\n if (id === 'piano' && o.pianoBaseUrl) return { ...def, baseUrl: normalizeBase(o.pianoBaseUrl) };\n if (def.local === true && !o.soundsBaseUrl) return INSTRUMENTS.harmonium!; // local needs a host\n return def;\n}\n\n/** The active taal id (explicit override, else the doc's), or undefined for an\n * unknown/absent taal (→ metronome falls back to a 4-beat cycle). */\nfunction resolveTaal(doc: Swaralipi, taal: AudioExportOptions['taal']): TaalId | undefined {\n const id = taal !== undefined && taal !== 'auto' ? taal : doc.meta.taal?.id;\n return id !== undefined && id in TAALS ? (id as TaalId) : undefined;\n}\n\n/** Strip filesystem-hostile characters (keeps Bengali/Unicode letters). */\nfunction safeName(title: string | undefined): string {\n return (title ?? '').replace(/[\\\\/:*?\"<>|]+/g, '').trim() || 'swaralipi';\n}\n\n/**\n * Render a swaralipi doc to an audio Blob in the browser. Runs in an\n * OfflineAudioContext — no user gesture and no server needed. Throws in an\n * environment without Web Audio (Node/SSR) or, for `format: 'mp3'`, when the\n * optional `@breezystack/lamejs` peer dependency is not installed.\n */\nexport async function exportAudio(\n doc: Swaralipi,\n options: AudioExportOptions = {},\n): Promise<AudioExportResult> {\n const Tone: ToneModule = await import('tone');\n\n const base = options.soundsBaseUrl ?? DEFAULT_SOUNDS_BASE;\n const format = options.format ?? 'wav';\n const channels = options.channels ?? 1;\n const sampleRate = options.sampleRate ?? 44100;\n const fadeOutSec = options.fadeOutSec ?? 0.4;\n const rhythm: RhythmMode = options.rhythm ?? 'none';\n const withMelody = options.melody ?? true;\n const withTanpura = options.tanpura ?? false;\n\n const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });\n const range = midiRange(schedule);\n\n const taalId = resolveTaal(doc, options.taal);\n const variation = options.taal === undefined || options.taal === 'auto' ? doc.meta.taal?.variation : undefined;\n const usesTabla = rhythm === 'tabla' && taalId !== undefined && SAMPLED_TAALS.has(taalId) && !!base;\n\n const reqBpm = options.bpm ?? doc.meta.bpm ?? 90;\n const bpm = usesTabla && taalId ? nearestTablaBpm(reqBpm, tablaBpms(taalId)) : reqBpm;\n const spb = 60 / bpm;\n\n const id = options.instrument ?? 'piano';\n const def = pickDef(id, options);\n // Pickup shift: push the melody so its sam locks to the looping accompaniment\n // (mirrors the player; only when a rhythm loops). Lengthens the render tail.\n const shift = rhythm !== 'none' ? schedule.pickupBeats * spb : 0;\n const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);\n\n const rendered = await Tone.Offline(\n async ({ transport }) => {\n transport.bpm.value = bpm;\n const master = new Tone.Gain(1).toDestination();\n\n // --- melody (the notation, sounded) ---\n if (withMelody) {\n const sampler = createSampler(Tone, def, base, range).connect(master);\n for (const ev of schedule.events) {\n if (ev.midi === null) continue;\n const at = ev.beat * spb + shift;\n const dur = Math.max(ev.durationBeats * spb, 0.05);\n if (ev.kan !== null) {\n const name = midiToSampleName(ev.kan);\n transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(at - spb * 0.12, 0));\n }\n const name = midiToSampleName(ev.midi);\n transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, dur, t), at);\n }\n }\n\n // --- rhythm (looped tabla, drumset, or a metronome click) ---\n let tabla: Player | GrainPlayer | undefined;\n if (rhythm === 'drums' && base) {\n const drums = createDrumKit(Tone).connect(master);\n const taal = taalId !== undefined ? TAALS[taalId] : (doc.meta.taal ?? { numBeats: 4, sam: 0 });\n const pattern = drumPattern(taal);\n let beat = 0;\n transport.scheduleRepeat((time) => {\n const voice = pattern[beat % pattern.length]!;\n if (drums.has(voice)) drums.player(voice).start(time);\n beat += 1;\n }, spb, 0);\n } else if (rhythm !== 'none' && base) {\n if (usesTabla && taalId) {\n const url = tablaUrl(base, taalId, bpm, variation);\n // Retune the recorded loop to the song's Sa (mirrors the player).\n const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));\n tabla = (semis === 0\n ? new Tone.Player({ url, loop: true })\n : new Tone.GrainPlayer({\n url,\n loop: true,\n detune: semis * 100,\n grainSize: TABLA_GRAIN_SEC,\n overlap: TABLA_GRAIN_OVERLAP_SEC,\n })\n ).connect(master);\n tabla.sync().start(0);\n } else {\n const metro = createMetronome(Tone, base).connect(master);\n const cycle = taalId !== undefined ? TAALS[taalId].numBeats : (doc.meta.taal?.numBeats ?? 4);\n let beat = 0;\n transport.scheduleRepeat((time) => {\n metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);\n metro.start(time);\n beat += 1;\n }, spb, 0);\n }\n }\n\n // --- tanpura drone (Pa–Sa–Sa–Sa, one pluck every two beats) ---\n if (withTanpura && base) {\n const tonicPc = ((parseTonic(options.tonic ?? doc.meta.tonic) % 12) + 12) % 12;\n const sa = TANPURA_PCS[tonicPc]!;\n const pa = TANPURA_PCS[(tonicPc + 7) % 12]!;\n const players = createTanpura(Tone, base, [sa, pa]).connect(master);\n players.volume.value = -14;\n const pattern = [pa, sa, sa, sa];\n let i = 0;\n transport.scheduleRepeat((time) => {\n const stem = pattern[i % pattern.length]!;\n if (players.has(stem)) players.player(stem).start(time);\n i += 1;\n }, spb * 2, 0);\n }\n\n // Buffers must be loaded before the (one-shot) render, or they arrive too\n // late to be heard — the async callback is awaited, so this is the fix.\n await Tone.loaded();\n // Snap a mistrimmed tabla loop to whole beats now its length is known.\n if (tabla?.buffer?.loaded) tabla.loopEnd = quantizeLoopEnd(tabla.buffer.duration, spb);\n\n // Soften the tail so looped accompaniment doesn't click off at the edge.\n if (fadeOutSec > 0) {\n master.gain.setValueAtTime(1, Math.max(durationSec - fadeOutSec, 0));\n master.gain.linearRampToValueAtTime(0, durationSec);\n }\n transport.start(0);\n },\n durationSec,\n channels,\n sampleRate,\n );\n\n const chans: Float32Array[] = [];\n for (let c = 0; c < rendered.numberOfChannels; c++) chans.push(rendered.getChannelData(c));\n\n const bytes =\n format === 'mp3'\n ? await encodeMp3(chans, rendered.sampleRate, options.mp3Kbps)\n : encodeWav(chans, rendered.sampleRate);\n const mimeType = format === 'mp3' ? 'audio/mpeg' : 'audio/wav';\n return {\n blob: new Blob([bytes as BlobPart], { type: mimeType }),\n filename: `${safeName(doc.meta.title)}.${format}`,\n mimeType,\n durationSec: rendered.duration,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Pure encoders (browser-free, unit-testable) */\n/* ------------------------------------------------------------------ */\n\n/** Clamp a float sample to a 16-bit signed integer. */\nfunction toInt16(f: number): number {\n const s = f < -1 ? -1 : f > 1 ? 1 : f;\n return s < 0 ? s * 0x8000 : s * 0x7fff;\n}\n\n/**\n * Encode planar float channels to a 16-bit PCM WAV (RIFF). Pure — no Web Audio\n * — so it runs (and tests) in Node. 16-bit PCM is lossless CD quality and plays\n * everywhere; float/24-bit skipped (add if mastering-grade output is needed).\n */\nexport function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array {\n const numCh = channels.length;\n const frames = channels[0]?.length ?? 0;\n const bytesPerSample = 2;\n const blockAlign = numCh * bytesPerSample;\n const dataSize = frames * blockAlign;\n const buf = new ArrayBuffer(44 + dataSize);\n const view = new DataView(buf);\n const str = (off: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i));\n };\n\n str(0, 'RIFF');\n view.setUint32(4, 36 + dataSize, true);\n str(8, 'WAVE');\n str(12, 'fmt ');\n view.setUint32(16, 16, true); // PCM fmt chunk size\n view.setUint16(20, 1, true); // format = PCM\n view.setUint16(22, numCh, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * blockAlign, true); // byte rate\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bytesPerSample * 8, true); // bits per sample\n str(36, 'data');\n view.setUint32(40, dataSize, true);\n\n let off = 44;\n for (let f = 0; f < frames; f++) {\n for (let c = 0; c < numCh; c++) {\n view.setInt16(off, toInt16(channels[c]![f]!), true);\n off += 2;\n }\n }\n return new Uint8Array(buf);\n}\n\n/** Convert a float channel to the Int16 samples lamejs expects. */\nfunction floatToInt16(f: Float32Array): Int16Array {\n const out = new Int16Array(f.length);\n for (let i = 0; i < f.length; i++) out[i] = toInt16(f[i]!);\n return out;\n}\n\nfunction concatBytes(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const out = new Uint8Array(total);\n let off = 0;\n for (const c of chunks) {\n out.set(c, off);\n off += c.length;\n }\n return out;\n}\n\n/** Load the optional lamejs encoder, tolerating named/default export shapes. */\nasync function loadLame(): Promise<{ new (channels: number, sampleRate: number, kbps: number): Mp3EncoderLike }> {\n try {\n const mod = (await import('@breezystack/lamejs')) as unknown as {\n Mp3Encoder?: new (c: number, s: number, k: number) => Mp3EncoderLike;\n default?: { Mp3Encoder: new (c: number, s: number, k: number) => Mp3EncoderLike };\n };\n const Mp3Encoder = mod.Mp3Encoder ?? mod.default?.Mp3Encoder;\n if (!Mp3Encoder) throw new Error('no Mp3Encoder export');\n return Mp3Encoder;\n } catch {\n throw new Error(\n 'MP3 export needs the optional peer dependency \"@breezystack/lamejs\" — ' +\n 'install it, or export as WAV (format: \"wav\").',\n );\n }\n}\n\ninterface Mp3EncoderLike {\n encodeBuffer(left: Int16Array, right?: Int16Array): Int8Array;\n flush(): Int8Array;\n}\n\n/**\n * Encode planar float channels to MP3 via lamejs. lamejs is **dynamically\n * imported** and an *optional* peer dependency (LGPL-3.0) — WAV needs no\n * dependency, so only reach for this when a compressed file is wanted. Samples\n * are fed in 1152-frame blocks (a multiple of 576, lamejs' preferred size).\n */\nexport async function encodeMp3(\n channels: Float32Array[],\n sampleRate: number,\n kbps = 192,\n): Promise<Uint8Array> {\n const Mp3Encoder = await loadLame();\n const numCh = Math.min(channels.length, 2); // lamejs is mono or stereo\n const enc = new Mp3Encoder(numCh, sampleRate, kbps);\n const left = floatToInt16(channels[0] ?? new Float32Array());\n const right = numCh > 1 ? floatToInt16(channels[1]!) : undefined;\n const block = 1152;\n const out: Uint8Array[] = [];\n for (let i = 0; i < left.length; i += block) {\n const l = left.subarray(i, i + block);\n const chunk = right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l);\n if (chunk.length > 0) out.push(new Uint8Array(chunk));\n }\n const tail = enc.flush();\n if (tail.length > 0) out.push(new Uint8Array(tail));\n return concatBytes(out);\n}\n","/**\n * MIDI export — write a swaralipi doc to a downloadable Standard MIDI File.\n *\n * Unlike the audio export this needs **no Tone and no AudioContext**: MIDI is\n * just note events, and `buildSchedule` already gives us (midi, beat,\n * durationBeats) per note. So the whole thing is pure bytes — it runs (and\n * tests) in plain Node, and carries no dependency (the SMF format is trivial),\n * which keeps it clear of any redistribution/license question.\n *\n * Scope = the **melody voice**, plus — when `drums` is on — the Western-drumset\n * translation of the taal (the same `drumPattern` the player sequences) written\n * as a **separate GM channel-10 track**. Tabla is prerecorded audio loops with\n * no per-stroke note data, so there's nothing to turn into MIDI notes; tanpura\n * is a fixed background drone. A `.mid` here is the notation line (+ optional\n * drums) — the user opens it in any DAW and assigns instruments.\n */\nimport type { Swaralipi } from '../spec/index.js';\nimport { TAALS } from '../spec/taals.js';\nimport { buildSchedule, type Schedule } from './schedule.js';\nimport { drumPattern, type DrumVoice } from './instruments.js';\n\n/** Pulses per quarter note. One matra (beat) == one quarter note. */\nconst PPQ = 480;\n\nexport interface MidiExportOptions {\n /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */\n tonic?: string;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Tempo in matra/min written as the file's tempo. Default `meta.bpm`, else 90. */\n bpm?: number;\n /** Track name meta-event. Default the song title. */\n name?: string;\n /** Also export the taal's drumset pattern as a separate GM channel-10 track. */\n drums?: boolean;\n}\n\nexport interface MidiExportResult {\n blob: Blob;\n /** Suggested `<title>.mid` filename. */\n filename: string;\n mimeType: string;\n}\n\n/** Variable-length quantity (7 bits/byte, high bit = continue). */\nfunction vlq(n: number): number[] {\n const out = [n & 0x7f];\n n >>>= 7;\n while (n > 0) {\n out.unshift((n & 0x7f) | 0x80);\n n >>>= 7;\n }\n return out;\n}\n\n/** UTF-8 bytes of a string (Bengali titles survive; most DAWs read UTF-8). */\nfunction utf8(s: string): number[] {\n return [...new TextEncoder().encode(s)];\n}\n\nfunction u32be(n: number): number[] {\n return [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];\n}\n\ninterface RawEvent {\n tick: number;\n on: boolean;\n note: number;\n vel: number;\n ch: number;\n}\n\n/** General MIDI channel-10 (index 9) percussion note per drumset voice. */\nconst DRUM_NOTE: Record<DrumVoice, number> = {\n kick: 36,\n snare: 38,\n hihat: 42, // closed hi-hat\n tom1: 48,\n tom2: 45,\n tom3: 41,\n};\n\n/** Serialise sorted raw events into an MTrk chunk (bytes, incl. header). */\nfunction trackChunk(raw: RawEvent[], head: number[]): number[] {\n // Time order; at equal ticks release before attacking so a repeated pitch\n // doesn't leave a hung note.\n raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));\n const track = [...head];\n let last = 0;\n for (const e of raw) {\n const status = (e.on ? 0x90 : 0x80) | (e.ch & 0x0f);\n track.push(...vlq(e.tick - last), status, e.note & 0x7f, e.vel & 0x7f);\n last = e.tick;\n }\n track.push(...vlq(0), 0xff, 0x2f, 0x00); // end of track\n return [0x4d, 0x54, 0x72, 0x6b, ...u32be(track.length), ...track];\n}\n\n/**\n * Encode a schedule to a Standard MIDI File. Melody rides channel 0; when\n * `drums` is given, its per-beat voices ride a second GM channel-10 track\n * (the file becomes format 1). Pure — no Tone, no browser — directly testable.\n */\nexport function encodeMidi(\n schedule: Schedule,\n opts: { bpm: number; name?: string; drums?: DrumVoice[] },\n): Uint8Array {\n const melody: RawEvent[] = [];\n for (const ev of schedule.events) {\n if (ev.midi === null) continue;\n const start = Math.round(ev.beat * PPQ);\n const end = Math.max(Math.round((ev.beat + ev.durationBeats) * PPQ), start + 1);\n if (ev.kan !== null) {\n const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);\n melody.push({ tick: kOn, on: true, note: ev.kan, vel: 70, ch: 0 });\n melody.push({ tick: start, on: false, note: ev.kan, vel: 0, ch: 0 }); // grace releases at the main attack\n }\n melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });\n melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });\n }\n\n // Tempo lives on the first track alongside the name.\n const usPerBeat = Math.round(60_000_000 / opts.bpm);\n const head: number[] = [];\n if (opts.name) head.push(...vlq(0), 0xff, 0x03, ...vlq(utf8(opts.name).length), ...utf8(opts.name));\n head.push(...vlq(0), 0xff, 0x51, 0x03, (usPerBeat >> 16) & 0xff, (usPerBeat >> 8) & 0xff, usPerBeat & 0xff);\n\n const chunks = [trackChunk(melody, head)];\n\n if (opts.drums && opts.drums.length > 0) {\n const drum: RawEvent[] = [];\n const totalBeats = Math.ceil(schedule.totalBeats);\n for (let beat = 0; beat < totalBeats; beat++) {\n const note = DRUM_NOTE[opts.drums[beat % opts.drums.length]!];\n const t = beat * PPQ;\n drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });\n drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 }); // one-shot: short gate\n }\n chunks.push(trackChunk(drum, [...vlq(0), 0xff, 0x03, ...vlq(5), 0x44, 0x72, 0x75, 0x6d, 0x73])); // \"Drums\"\n }\n\n const format = chunks.length > 1 ? 1 : 0;\n const header = [\n 0x4d, 0x54, 0x68, 0x64, ...u32be(6),\n 0x00, format, (chunks.length >> 8) & 0xff, chunks.length & 0xff,\n (PPQ >> 8) & 0xff, PPQ & 0xff,\n ];\n return new Uint8Array([...header, ...chunks.flat()]);\n}\n\n/** Strip filesystem-hostile characters (keeps Bengali/Unicode letters). */\nfunction safeName(title: string | undefined): string {\n return (title ?? '').replace(/[\\\\/:*?\"<>|]+/g, '').trim() || 'swaralipi';\n}\n\n/**\n * Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs\n * beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.\n */\nexport function exportMidi(doc: Swaralipi, options: MidiExportOptions = {}): MidiExportResult {\n const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });\n const bpm = options.bpm ?? doc.meta.bpm ?? 90;\n const taalId = doc.meta.taal?.id;\n const taal = taalId !== undefined && taalId in TAALS ? TAALS[taalId as keyof typeof TAALS] : { numBeats: 4, sam: 0 };\n const drums = options.drums ? drumPattern(taal) : undefined;\n const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });\n return {\n blob: new Blob([bytes as BlobPart], { type: 'audio/midi' }),\n filename: `${safeName(doc.meta.title)}.mid`,\n mimeType: 'audio/midi',\n };\n}\n"]}
|