@ugfoundation/swaralipi-js 0.8.1 → 0.9.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.
Files changed (51) hide show
  1. package/dist/{batch-NDL6CW7Q.js → batch-PYBXQ4OM.js} +5 -5
  2. package/dist/{batch-NDL6CW7Q.js.map → batch-PYBXQ4OM.js.map} +1 -1
  3. package/dist/{chunk-LPCKL3AM.js → chunk-3ZQPM2CE.js} +28 -19
  4. package/dist/chunk-3ZQPM2CE.js.map +1 -0
  5. package/dist/{chunk-4IVUPMRK.js → chunk-7SMRG4IS.js} +14 -9
  6. package/dist/chunk-7SMRG4IS.js.map +1 -0
  7. package/dist/{chunk-4HNT6OAT.js → chunk-MYVIQU4O.js} +12 -3
  8. package/dist/chunk-MYVIQU4O.js.map +1 -0
  9. package/dist/{chunk-Z42M5JAA.js → chunk-RT4SVJMK.js} +5 -4
  10. package/dist/chunk-RT4SVJMK.js.map +1 -0
  11. package/dist/cli.cjs +33 -19
  12. package/dist/cli.cjs.map +1 -1
  13. package/dist/cli.js +3 -3
  14. package/dist/convert/index.cjs +31 -17
  15. package/dist/convert/index.cjs.map +1 -1
  16. package/dist/convert/index.d.cts +1 -1
  17. package/dist/convert/index.d.ts +1 -1
  18. package/dist/convert/index.js +3 -3
  19. package/dist/index.cjs +38 -17
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +2 -2
  24. package/dist/{lay-Csa4CD46.d.cts → lay-CZIzR9cM.d.ts} +16 -2
  25. package/dist/{lay-Ct9ky_EE.d.ts → lay-CfDlPhH2.d.cts} +16 -2
  26. package/dist/player/index.cjs +80 -48
  27. package/dist/player/index.cjs.map +1 -1
  28. package/dist/player/index.d.cts +11 -9
  29. package/dist/player/index.d.ts +11 -9
  30. package/dist/player/index.js +68 -46
  31. package/dist/player/index.js.map +1 -1
  32. package/dist/{schedule-B_UaluBi.d.cts → schedule-9uvjnCG_.d.ts} +15 -10
  33. package/dist/{schedule-CdILTNsk.d.ts → schedule-TxSfHL-T.d.cts} +15 -10
  34. package/dist/{schema-DGA_bNVZ.d.ts → schema-Cf_epUIC.d.cts} +333 -63
  35. package/dist/{schema-DGA_bNVZ.d.cts → schema-Cf_epUIC.d.ts} +333 -63
  36. package/dist/spec/index.cjs +38 -17
  37. package/dist/spec/index.cjs.map +1 -1
  38. package/dist/spec/index.d.cts +26 -8
  39. package/dist/spec/index.d.ts +26 -8
  40. package/dist/spec/index.js +2 -2
  41. package/dist/viewer/index.cjs +306 -178
  42. package/dist/viewer/index.cjs.map +1 -1
  43. package/dist/viewer/index.d.cts +36 -12
  44. package/dist/viewer/index.d.ts +36 -12
  45. package/dist/viewer/index.js +234 -135
  46. package/dist/viewer/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/chunk-4HNT6OAT.js.map +0 -1
  49. package/dist/chunk-4IVUPMRK.js.map +0 -1
  50. package/dist/chunk-LPCKL3AM.js.map +0 -1
  51. package/dist/chunk-Z42M5JAA.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DGA_bNVZ.cjs';
2
- import { a as LaySpeeds, T as TaalId } from '../lay-Csa4CD46.cjs';
3
- export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-Csa4CD46.cjs';
4
- import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-B_UaluBi.cjs';
5
- export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, 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 TempoSegment, k as ToneModule, l as buildSchedule, m as createBuffers, n as drumPattern, o as glideTargets, p as instrumentSampleUrl, q as isArcRaw, r as meendContinuations, s as nearestSampleKey, t as nearestTablaBpm, u as normalizeBase, v as probeUrl, w as quantizeLoopEnd, x as santoorUrls, y as subsetUrls, z as tablaBpms, B as tablaUrl, C as triggerGlide } from '../schedule-B_UaluBi.cjs';
1
+ import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-Cf_epUIC.cjs';
2
+ import { a as LaySpeeds, T as TaalId } from '../lay-CfDlPhH2.cjs';
3
+ export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-CfDlPhH2.cjs';
4
+ import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice, T as TempoSegment } from '../schedule-TxSfHL-T.cjs';
5
+ export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, h as TABLA_BPMS, i as TABLA_VARIATIONS, j as TONEJS_INSTRUMENTS_BASE, k as ToneModule, l as buildSchedule, m as createBuffers, n as drumPattern, o as glideTargets, p as instrumentSampleUrl, q as isArcRaw, r as meendContinuations, s as nearestSampleKey, t as nearestTablaBpm, u as normalizeBase, v as probeUrl, w as quantizeLoopEnd, x as santoorUrls, y as subsetUrls, z as tablaBpms, B as tablaUrl, C as triggerGlide } from '../schedule-TxSfHL-T.cjs';
6
6
  import 'zod';
7
7
  import 'tone';
8
8
 
@@ -280,13 +280,15 @@ interface MidiExportResult {
280
280
  }
281
281
  /**
282
282
  * Encode a schedule to a Standard MIDI File. Melody rides channel 0; when
283
- * `drums` is given, its per-beat voices ride a second GM channel-10 track
284
- * (the file becomes format 1). Pure — no Tone, no browser — directly testable.
283
+ * `drums` is given, its per-matra voices ride a second GM channel-10 track
284
+ * (the file becomes format 1). `drums` is one pattern for the whole song, or a
285
+ * function picking each লয় / তাল segment's own (তালফেরতা; `undefined` = rest).
286
+ * Pure — no Tone, no browser — directly testable.
285
287
  */
286
288
  declare function encodeMidi(schedule: Schedule, opts: {
287
289
  bpm: number;
288
290
  name?: string;
289
- drums?: DrumVoice[];
291
+ drums?: DrumVoice[] | ((seg: TempoSegment) => DrumVoice[] | undefined);
290
292
  }): Uint8Array;
291
293
  /**
292
294
  * Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs
@@ -340,4 +342,4 @@ declare function sampleFileName(midi: number): string;
340
342
  */
341
343
  declare function sCurve(x: number, steepness?: number): number;
342
344
 
343
- export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
345
+ export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, TempoSegment, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
@@ -1,8 +1,8 @@
1
- import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DGA_bNVZ.js';
2
- import { a as LaySpeeds, T as TaalId } from '../lay-Ct9ky_EE.js';
3
- export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-Ct9ky_EE.js';
4
- import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-CdILTNsk.js';
5
- export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, 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 TempoSegment, k as ToneModule, l as buildSchedule, m as createBuffers, n as drumPattern, o as glideTargets, p as instrumentSampleUrl, q as isArcRaw, r as meendContinuations, s as nearestSampleKey, t as nearestTablaBpm, u as normalizeBase, v as probeUrl, w as quantizeLoopEnd, x as santoorUrls, y as subsetUrls, z as tablaBpms, B as tablaUrl, C as triggerGlide } from '../schedule-CdILTNsk.js';
1
+ import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-Cf_epUIC.js';
2
+ import { a as LaySpeeds, T as TaalId } from '../lay-CZIzR9cM.js';
3
+ export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-CZIzR9cM.js';
4
+ import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice, T as TempoSegment } from '../schedule-9uvjnCG_.js';
5
+ export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, h as TABLA_BPMS, i as TABLA_VARIATIONS, j as TONEJS_INSTRUMENTS_BASE, k as ToneModule, l as buildSchedule, m as createBuffers, n as drumPattern, o as glideTargets, p as instrumentSampleUrl, q as isArcRaw, r as meendContinuations, s as nearestSampleKey, t as nearestTablaBpm, u as normalizeBase, v as probeUrl, w as quantizeLoopEnd, x as santoorUrls, y as subsetUrls, z as tablaBpms, B as tablaUrl, C as triggerGlide } from '../schedule-9uvjnCG_.js';
6
6
  import 'zod';
7
7
  import 'tone';
8
8
 
@@ -280,13 +280,15 @@ interface MidiExportResult {
280
280
  }
281
281
  /**
282
282
  * Encode a schedule to a Standard MIDI File. Melody rides channel 0; when
283
- * `drums` is given, its per-beat voices ride a second GM channel-10 track
284
- * (the file becomes format 1). Pure — no Tone, no browser — directly testable.
283
+ * `drums` is given, its per-matra voices ride a second GM channel-10 track
284
+ * (the file becomes format 1). `drums` is one pattern for the whole song, or a
285
+ * function picking each লয় / তাল segment's own (তালফেরতা; `undefined` = rest).
286
+ * Pure — no Tone, no browser — directly testable.
285
287
  */
286
288
  declare function encodeMidi(schedule: Schedule, opts: {
287
289
  bpm: number;
288
290
  name?: string;
289
- drums?: DrumVoice[];
291
+ drums?: DrumVoice[] | ((seg: TempoSegment) => DrumVoice[] | undefined);
290
292
  }): Uint8Array;
291
293
  /**
292
294
  * Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs
@@ -340,4 +342,4 @@ declare function sampleFileName(midi: number): string;
340
342
  */
341
343
  declare function sCurve(x: number, steepness?: number): number;
342
344
 
343
- export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
345
+ export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, TempoSegment, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
@@ -1,7 +1,7 @@
1
- import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, quantizeLoopEnd, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, drumPattern, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, createMetronome, TANPURA_PCS, createTanpura, parseTonic, MEEND_HOLD, sCurve } from '../chunk-4IVUPMRK.js';
2
- export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, glideTargets, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-4IVUPMRK.js';
3
- import { TAALS } from '../chunk-4HNT6OAT.js';
4
- export { LAYS, LAY_SPEEDS, printedLay, resolveLaySpeeds, rowLay } from '../chunk-4HNT6OAT.js';
1
+ import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, quantizeLoopEnd, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic, MEEND_HOLD, sCurve } from '../chunk-7SMRG4IS.js';
2
+ export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, glideTargets, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-7SMRG4IS.js';
3
+ import { TAALS } from '../chunk-MYVIQU4O.js';
4
+ export { LAYS, LAY_SPEEDS, printedLay, resolveLaySpeeds, rowLay } from '../chunk-MYVIQU4O.js';
5
5
 
6
6
  // src/player/player.ts
7
7
  var STOP_FADE_SEC = 0.5;
@@ -357,16 +357,26 @@ var SwaralipiPlayer = class {
357
357
  #emitHighlight(path) {
358
358
  this.#opts.onHighlight?.(path);
359
359
  }
360
- /** The tabla variation to play: taal-mode taal's, the doc's own (auto mode),
361
- * else none — an explicit setTaal override plays the taal's default variation. */
362
- #variation() {
360
+ /** The taal one accompaniment segment plays: taal mode's, an explicit
361
+ * setTaal override (pinned across the whole song, তালফেরতা included), else
362
+ * the segment's own. `null` = a মুক্তছন্দ passage. */
363
+ #segmentTaal(seg) {
364
+ if (this.#taal !== void 0) return this.#taal;
365
+ if (!this.#taalAuto && this.#taalOverride !== void 0) return TAALS[this.#taalOverride];
366
+ return seg.taal;
367
+ }
368
+ /** The tabla variation to play: taal-mode taal's, the segment's own (auto
369
+ * mode), else none — an explicit setTaal override plays the default variation. */
370
+ #variation(taal) {
363
371
  if (this.#taal !== void 0) return this.#taal.variation;
364
- return this.#taalAuto ? this.#doc?.meta.taal?.variation : void 0;
372
+ return this.#taalAuto ? taal?.variation : void 0;
365
373
  }
366
374
  /** Resolve the active taal id: taal mode's taal, an explicit override, else
367
- * the doc's — but only when it names a sampled taal. */
375
+ * the doc's opening one (a তালফেরতা doc may carry it on its first row) — but
376
+ * only when it names a sampled taal. */
368
377
  #taalId() {
369
- const id = this.#taal?.id ?? (!this.#taalAuto && this.#taalOverride !== void 0 ? this.#taalOverride : this.#doc?.meta.taal?.id);
378
+ const opening = this.#doc?.parts[0]?.rows[0]?.taal || this.#doc?.meta.taal;
379
+ const id = this.#taal?.id ?? (!this.#taalAuto && this.#taalOverride !== void 0 ? this.#taalOverride : opening?.id);
370
380
  return id !== void 0 && id in TAALS ? id : void 0;
371
381
  }
372
382
  /** Resolve an instrument id to its def (custom overrides built-ins; the
@@ -561,34 +571,35 @@ var SwaralipiPlayer = class {
561
571
  return entry;
562
572
  }
563
573
  /**
564
- * Accompaniment, one লয় segment at a time. A tempo change under the melody
565
- * is a real tempo change under the drum: the tabla loop is rebuilt at the
566
- * segment's own bpm — its own recorded file when the set has one at that
574
+ * Accompaniment, one লয় / তাল segment at a time. A tempo change under the
575
+ * melody is a real tempo change under the drum: the tabla loop is rebuilt at
576
+ * the segment's own bpm — its own recorded file when the set has one at that
567
577
  * tempo, else the nearest file time-stretched by `GrainPlayer` (which
568
578
  * stretches *without* transposing, unlike a plain playbackRate) — and the
569
- * click / drum / drone patterns re-space their interval. A document with no
570
- * লয় marks has exactly one segment, so this is the old single-loop path.
579
+ * click / drum / drone patterns re-space their interval. A তালফেরতা swaps the
580
+ * theka the same way: each segment loops its own taal's file (or pattern, or
581
+ * click cycle), and a মুক্তছন্দ segment rests. A document with neither mark
582
+ * has exactly one segment, so this is the old single-loop path.
571
583
  */
572
584
  #buildRhythm(Tone, transport, base, spb, segments, shift) {
573
585
  this.#disposeTabla();
574
586
  const master = this.#ensureMaster(Tone);
575
- const taalId = this.#taalId();
576
- const wantsTabla = this.#opts.rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0;
577
- if (!wantsTabla && this.#opts.rhythm !== "drums" && (this.#opts.rhythm === "none" || base === void 0)) return;
578
- const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };
579
- const pattern = this.#opts.rhythm === "drums" ? drumPattern(taal) : [];
580
- const cycle = this.#taal?.numBeats ?? this.#doc?.meta.taal?.numBeats ?? 4;
587
+ const rhythm = this.#opts.rhythm;
588
+ if (rhythm === "none" || base === void 0 && rhythm !== "drums") return;
581
589
  segments.forEach((seg, i) => {
590
+ const own = this.#segmentTaal(seg);
591
+ if (own === null) return;
592
+ const taalId = own !== void 0 && own.id in TAALS ? own.id : void 0;
582
593
  const from = i === 0 ? 0 : seg.beat * spb + shift;
583
594
  const to = i === segments.length - 1 ? Infinity : seg.endBeat * spb + shift;
584
595
  const beatSec = spb / seg.scale;
585
596
  const span = to === Infinity ? void 0 : to - from;
586
- if (wantsTabla) {
597
+ if (rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0) {
587
598
  const bpm = 60 / beatSec;
588
599
  const fileBpm = nearestTablaBpm(bpm, tablaBpms(taalId));
589
600
  const rate = bpm / fileBpm;
590
601
  const semis = tablaShiftSemitones(this.#tonicSemitone());
591
- const url = tablaUrl(base, taalId, fileBpm, this.#variation());
602
+ const url = tablaUrl(base, taalId, fileBpm, this.#variation(own));
592
603
  const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
593
604
  url,
594
605
  loop: true,
@@ -610,9 +621,10 @@ var SwaralipiPlayer = class {
610
621
  this.#tabla.push({ loop, fileSpb: 60 / fileBpm });
611
622
  return;
612
623
  }
613
- if (this.#opts.rhythm === "drums") {
624
+ if (rhythm === "drums") {
614
625
  this.#drums ??= createDrumKit(Tone).connect(master);
615
626
  const drums = this.#drums;
627
+ const pattern = drumPattern(this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : own) ?? { numBeats: 4, sam: 0 });
616
628
  let beat2 = 0;
617
629
  this.#scheduleIds.push(
618
630
  transport.scheduleRepeat(
@@ -630,6 +642,7 @@ var SwaralipiPlayer = class {
630
642
  }
631
643
  this.#metronome ??= createMetronome(Tone, base).connect(master);
632
644
  const metro = this.#metronome;
645
+ const cycle = (this.#taal ?? seg.taal)?.numBeats ?? 4;
633
646
  let beat = 0;
634
647
  this.#scheduleIds.push(
635
648
  transport.scheduleRepeat(
@@ -705,7 +718,7 @@ function pickDef(id, o) {
705
718
  return def;
706
719
  }
707
720
  function resolveTaal(doc, taal) {
708
- const id = taal !== void 0 && taal !== "auto" ? taal : doc.meta.taal?.id;
721
+ const id = taal !== void 0 && taal !== "auto" ? taal : (doc.parts[0]?.rows[0]?.taal || doc.meta.taal)?.id;
709
722
  return id !== void 0 && id in TAALS ? id : void 0;
710
723
  }
711
724
  function safeName(title) {
@@ -728,7 +741,7 @@ async function exportAudio(doc, options = {}) {
728
741
  });
729
742
  const range = midiRange2(schedule);
730
743
  const taalId = resolveTaal(doc, options.taal);
731
- const variation = options.taal === void 0 || options.taal === "auto" ? doc.meta.taal?.variation : void 0;
744
+ const pinned = options.taal !== void 0 && options.taal !== "auto" && taalId !== void 0 ? TAALS[taalId] : void 0;
732
745
  const usesTabla = rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && !!base;
733
746
  const reqBpm = options.bpm ?? doc.meta.bpm ?? 90;
734
747
  const bpm = usesTabla && taalId ? nearestTablaBpm(reqBpm, tablaBpms(taalId)) : reqBpm;
@@ -780,22 +793,22 @@ async function exportAudio(doc, options = {}) {
780
793
  }
781
794
  const tablas = [];
782
795
  if (rhythm !== "none" && base) {
783
- const taal = taalId !== void 0 ? TAALS[taalId] : doc.meta.taal ?? { numBeats: 4, sam: 0 };
784
- const pattern = rhythm === "drums" ? drumPattern(taal) : [];
785
796
  const drums = rhythm === "drums" ? createDrumKit(Tone).connect(master) : void 0;
786
- const metro = rhythm !== "drums" && !usesTabla ? createMetronome(Tone, base).connect(master) : void 0;
787
- const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : doc.meta.taal?.numBeats ?? 4;
797
+ let metro;
788
798
  const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));
789
799
  schedule.tempo.forEach((seg, i) => {
800
+ const own = pinned ?? seg.taal;
801
+ if (own === null) return;
802
+ const segId = own !== void 0 && own.id in TAALS ? own.id : void 0;
790
803
  const from = i === 0 ? 0 : seg.beat * spb + shift;
791
804
  const to = i === schedule.tempo.length - 1 ? Infinity : seg.endBeat * spb + shift;
792
805
  const beatSec = spb / seg.scale;
793
806
  const span = to === Infinity ? void 0 : to - from;
794
- if (usesTabla && taalId) {
807
+ if (rhythm === "tabla" && segId !== void 0 && SAMPLED_TAALS.has(segId)) {
795
808
  const segBpm = 60 / beatSec;
796
- const fileBpm = nearestTablaBpm(segBpm, tablaBpms(taalId));
809
+ const fileBpm = nearestTablaBpm(segBpm, tablaBpms(segId));
797
810
  const rate = segBpm / fileBpm;
798
- const url = tablaUrl(base, taalId, fileBpm, variation);
811
+ const url = tablaUrl(base, segId, fileBpm, pinned === void 0 ? own?.variation : void 0);
799
812
  const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
800
813
  url,
801
814
  loop: true,
@@ -814,6 +827,7 @@ async function exportAudio(doc, options = {}) {
814
827
  }
815
828
  tablas.push({ loop, fileSpb: 60 / fileBpm });
816
829
  } else if (drums !== void 0) {
830
+ const pattern = drumPattern(segId !== void 0 ? TAALS[segId] : own ?? { numBeats: 4, sam: 0 });
817
831
  let beat = 0;
818
832
  transport.scheduleRepeat(
819
833
  (time) => {
@@ -825,12 +839,14 @@ async function exportAudio(doc, options = {}) {
825
839
  from,
826
840
  span
827
841
  );
828
- } else if (metro !== void 0) {
842
+ } else {
843
+ const click = metro ??= createMetronome(Tone, base).connect(master);
844
+ const cycle = own?.numBeats ?? 4;
829
845
  let beat = 0;
830
846
  transport.scheduleRepeat(
831
847
  (time) => {
832
- metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
833
- metro.start(time);
848
+ click.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
849
+ click.start(time);
834
850
  beat += 1;
835
851
  },
836
852
  beatSec,
@@ -1056,14 +1072,18 @@ function encodeMidi(schedule, opts) {
1056
1072
  if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
1057
1073
  head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
1058
1074
  const chunks = [trackChunk(melody, head)];
1059
- if (opts.drums && opts.drums.length > 0) {
1075
+ if (typeof opts.drums === "function" || opts.drums && opts.drums.length > 0) {
1060
1076
  const drum = [];
1061
- const totalBeats = Math.ceil(schedule.totalBeats);
1062
- for (let beat = 0; beat < totalBeats; beat++) {
1063
- const note = DRUM_NOTE[opts.drums[beat % opts.drums.length]];
1064
- const t = beat * PPQ;
1065
- drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
1066
- drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
1077
+ for (const seg of schedule.tempo) {
1078
+ const pattern = typeof opts.drums === "function" ? opts.drums(seg) : opts.drums;
1079
+ if (!pattern?.length) continue;
1080
+ const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
1081
+ for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
1082
+ const note = DRUM_NOTE[pattern[k % pattern.length]];
1083
+ const t = Math.round((seg.beat + k / seg.scale) * PPQ);
1084
+ drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
1085
+ drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
1086
+ }
1067
1087
  }
1068
1088
  chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
1069
1089
  }
@@ -1093,9 +1113,11 @@ function exportMidi(doc, options = {}) {
1093
1113
  laySpeeds: options.laySpeeds
1094
1114
  });
1095
1115
  const bpm = options.bpm ?? doc.meta.bpm ?? 90;
1096
- const taalId = doc.meta.taal?.id;
1097
- const taal = taalId !== void 0 && taalId in TAALS ? TAALS[taalId] : { numBeats: 4, sam: 0 };
1098
- const drums = options.drums ? drumPattern(taal) : void 0;
1116
+ const drums = options.drums ? (seg) => {
1117
+ const id = seg.taal?.id;
1118
+ if (seg.taal === null) return void 0;
1119
+ return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
1120
+ } : void 0;
1099
1121
  const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
1100
1122
  return {
1101
1123
  blob: new Blob([bytes], { type: "audio/midi" }),