@ugfoundation/swaralipi-js 0.8.2 → 0.9.1

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 (52) hide show
  1. package/README.md +1 -1
  2. package/dist/{batch-6FPBT5DP.js → batch-NEM7JXHV.js} +5 -5
  3. package/dist/{batch-6FPBT5DP.js.map → batch-NEM7JXHV.js.map} +1 -1
  4. package/dist/{chunk-UMBKIGVQ.js → chunk-DOEFSS66.js} +5 -4
  5. package/dist/chunk-DOEFSS66.js.map +1 -0
  6. package/dist/{chunk-4HNT6OAT.js → chunk-MYVIQU4O.js} +12 -3
  7. package/dist/chunk-MYVIQU4O.js.map +1 -0
  8. package/dist/{chunk-4IVUPMRK.js → chunk-SEN266BY.js} +28 -13
  9. package/dist/chunk-SEN266BY.js.map +1 -0
  10. package/dist/{chunk-FULC22MN.js → chunk-Z6A3CP5V.js} +28 -19
  11. package/dist/chunk-Z6A3CP5V.js.map +1 -0
  12. package/dist/cli.cjs +33 -19
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +3 -3
  15. package/dist/convert/index.cjs +31 -17
  16. package/dist/convert/index.cjs.map +1 -1
  17. package/dist/convert/index.d.cts +1 -1
  18. package/dist/convert/index.d.ts +1 -1
  19. package/dist/convert/index.js +3 -3
  20. package/dist/index.cjs +38 -17
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +2 -2
  25. package/dist/{lay-Csa4CD46.d.cts → lay-CZIzR9cM.d.ts} +16 -2
  26. package/dist/{lay-Ct9ky_EE.d.ts → lay-CfDlPhH2.d.cts} +16 -2
  27. package/dist/player/index.cjs +116 -71
  28. package/dist/player/index.cjs.map +1 -1
  29. package/dist/player/index.d.cts +11 -9
  30. package/dist/player/index.d.ts +11 -9
  31. package/dist/player/index.js +89 -66
  32. package/dist/player/index.js.map +1 -1
  33. package/dist/{schedule-B_UaluBi.d.cts → schedule-BaovCtmf.d.ts} +31 -12
  34. package/dist/{schedule-CdILTNsk.d.ts → schedule-CU-VECDH.d.cts} +31 -12
  35. package/dist/{schema-DGA_bNVZ.d.ts → schema-Cf_epUIC.d.cts} +333 -63
  36. package/dist/{schema-DGA_bNVZ.d.cts → schema-Cf_epUIC.d.ts} +333 -63
  37. package/dist/spec/index.cjs +38 -17
  38. package/dist/spec/index.cjs.map +1 -1
  39. package/dist/spec/index.d.cts +26 -8
  40. package/dist/spec/index.d.ts +26 -8
  41. package/dist/spec/index.js +2 -2
  42. package/dist/viewer/index.cjs +156 -73
  43. package/dist/viewer/index.cjs.map +1 -1
  44. package/dist/viewer/index.d.cts +25 -7
  45. package/dist/viewer/index.d.ts +25 -7
  46. package/dist/viewer/index.js +78 -27
  47. package/dist/viewer/index.js.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/chunk-4HNT6OAT.js.map +0 -1
  50. package/dist/chunk-4IVUPMRK.js.map +0 -1
  51. package/dist/chunk-FULC22MN.js.map +0 -1
  52. package/dist/chunk-UMBKIGVQ.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-CU-VECDH.cjs';
5
+ export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GRACE_STEP, d as GlideLeg, e as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, f as SALAMANDER_BASE, g as SAMPLED_TAALS, h as ScheduleOptions, i as TABLA_BPMS, j as TABLA_VARIATIONS, k as TONEJS_INSTRUMENTS_BASE, l as ToneModule, m as buildSchedule, n as createBuffers, o as drumPattern, p as glideTargets, q as graceTouches, r as instrumentSampleUrl, s as isArcRaw, t as meendContinuations, u as nearestSampleKey, v as nearestTablaBpm, w as normalizeBase, x as probeUrl, y as quantizeLoopEnd, z as santoorUrls, B as subsetUrls, C as tablaBpms, E as tablaUrl, F as triggerGlide } from '../schedule-CU-VECDH.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-BaovCtmf.js';
5
+ export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GRACE_STEP, d as GlideLeg, e as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, f as SALAMANDER_BASE, g as SAMPLED_TAALS, h as ScheduleOptions, i as TABLA_BPMS, j as TABLA_VARIATIONS, k as TONEJS_INSTRUMENTS_BASE, l as ToneModule, m as buildSchedule, n as createBuffers, o as drumPattern, p as glideTargets, q as graceTouches, r as instrumentSampleUrl, s as isArcRaw, t as meendContinuations, u as nearestSampleKey, v as nearestTablaBpm, w as normalizeBase, x as probeUrl, y as quantizeLoopEnd, z as santoorUrls, B as subsetUrls, C as tablaBpms, E as tablaUrl, F as triggerGlide } from '../schedule-BaovCtmf.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, graceTouches, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic, GRACE_STEP, MEEND_HOLD, sCurve } from '../chunk-SEN266BY.js';
2
+ export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-SEN266BY.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;
@@ -14,7 +14,7 @@ function midiRange(schedule) {
14
14
  let lo = Infinity;
15
15
  let hi = -Infinity;
16
16
  for (const ev of schedule.events) {
17
- for (const m of [ev.midi, ...ev.kan]) {
17
+ for (const m of [ev.midi, ...ev.kan, ...ev.resh]) {
18
18
  if (m !== null) {
19
19
  if (m < lo) lo = m;
20
20
  if (m > hi) hi = m;
@@ -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
@@ -457,14 +467,15 @@ var SwaralipiPlayer = class {
457
467
  const mediaLead = Math.min(MEDIA_LEAD_SEC, spb);
458
468
  schedule.events.forEach((ev, i) => {
459
469
  const at = ev.beat * spb + shift;
470
+ if (ev.glide !== true) {
471
+ for (const g of graceTouches(ev)) {
472
+ const graceName = midiToSampleName(g.midi);
473
+ transport.scheduleOnce((time) => melody.triggerAttackRelease(graceName, spb * 0.4, time), Math.max(g.beat * spb + shift, 0));
474
+ }
475
+ }
460
476
  if (ev.midi !== null && ev.glide !== true) {
461
477
  const name = midiToSampleName(ev.midi);
462
478
  const dur = Math.max(ev.durationBeats * spb, 0.05);
463
- ev.kan.forEach((k, gi) => {
464
- const kanName = midiToSampleName(k);
465
- const lead = spb * 0.12 * (ev.kan.length - gi);
466
- transport.scheduleOnce((time) => melody.triggerAttackRelease(kanName, spb * 0.4, time), Math.max(at - lead, 0));
467
- });
468
479
  const targets = glide !== void 0 ? glideTargets(schedule.events, i) : [];
469
480
  if (targets.length > 0 && glide !== void 0) {
470
481
  const last = targets[targets.length - 1];
@@ -561,34 +572,35 @@ var SwaralipiPlayer = class {
561
572
  return entry;
562
573
  }
563
574
  /**
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
575
+ * Accompaniment, one লয় / তাল segment at a time. A tempo change under the
576
+ * melody is a real tempo change under the drum: the tabla loop is rebuilt at
577
+ * the segment's own bpm — its own recorded file when the set has one at that
567
578
  * tempo, else the nearest file time-stretched by `GrainPlayer` (which
568
579
  * 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.
580
+ * click / drum / drone patterns re-space their interval. A তালফেরতা swaps the
581
+ * theka the same way: each segment loops its own taal's file (or pattern, or
582
+ * click cycle), and a মুক্তছন্দ segment rests. A document with neither mark
583
+ * has exactly one segment, so this is the old single-loop path.
571
584
  */
572
585
  #buildRhythm(Tone, transport, base, spb, segments, shift) {
573
586
  this.#disposeTabla();
574
587
  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;
588
+ const rhythm = this.#opts.rhythm;
589
+ if (rhythm === "none" || base === void 0 && rhythm !== "drums") return;
581
590
  segments.forEach((seg, i) => {
591
+ const own = this.#segmentTaal(seg);
592
+ if (own === null) return;
593
+ const taalId = own !== void 0 && own.id in TAALS ? own.id : void 0;
582
594
  const from = i === 0 ? 0 : seg.beat * spb + shift;
583
595
  const to = i === segments.length - 1 ? Infinity : seg.endBeat * spb + shift;
584
596
  const beatSec = spb / seg.scale;
585
597
  const span = to === Infinity ? void 0 : to - from;
586
- if (wantsTabla) {
598
+ if (rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0) {
587
599
  const bpm = 60 / beatSec;
588
600
  const fileBpm = nearestTablaBpm(bpm, tablaBpms(taalId));
589
601
  const rate = bpm / fileBpm;
590
602
  const semis = tablaShiftSemitones(this.#tonicSemitone());
591
- const url = tablaUrl(base, taalId, fileBpm, this.#variation());
603
+ const url = tablaUrl(base, taalId, fileBpm, this.#variation(own));
592
604
  const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
593
605
  url,
594
606
  loop: true,
@@ -610,9 +622,10 @@ var SwaralipiPlayer = class {
610
622
  this.#tabla.push({ loop, fileSpb: 60 / fileBpm });
611
623
  return;
612
624
  }
613
- if (this.#opts.rhythm === "drums") {
625
+ if (rhythm === "drums") {
614
626
  this.#drums ??= createDrumKit(Tone).connect(master);
615
627
  const drums = this.#drums;
628
+ const pattern = drumPattern(this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : own) ?? { numBeats: 4, sam: 0 });
616
629
  let beat2 = 0;
617
630
  this.#scheduleIds.push(
618
631
  transport.scheduleRepeat(
@@ -630,6 +643,7 @@ var SwaralipiPlayer = class {
630
643
  }
631
644
  this.#metronome ??= createMetronome(Tone, base).connect(master);
632
645
  const metro = this.#metronome;
646
+ const cycle = (this.#taal ?? seg.taal)?.numBeats ?? 4;
633
647
  let beat = 0;
634
648
  this.#scheduleIds.push(
635
649
  transport.scheduleRepeat(
@@ -688,7 +702,7 @@ function midiRange2(schedule) {
688
702
  let lo = Infinity;
689
703
  let hi = -Infinity;
690
704
  for (const ev of schedule.events) {
691
- for (const m of [ev.midi, ...ev.kan]) {
705
+ for (const m of [ev.midi, ...ev.kan, ...ev.resh]) {
692
706
  if (m !== null) {
693
707
  if (m < lo) lo = m;
694
708
  if (m > hi) hi = m;
@@ -705,7 +719,7 @@ function pickDef(id, o) {
705
719
  return def;
706
720
  }
707
721
  function resolveTaal(doc, taal) {
708
- const id = taal !== void 0 && taal !== "auto" ? taal : doc.meta.taal?.id;
722
+ const id = taal !== void 0 && taal !== "auto" ? taal : (doc.parts[0]?.rows[0]?.taal || doc.meta.taal)?.id;
709
723
  return id !== void 0 && id in TAALS ? id : void 0;
710
724
  }
711
725
  function safeName(title) {
@@ -728,7 +742,7 @@ async function exportAudio(doc, options = {}) {
728
742
  });
729
743
  const range = midiRange2(schedule);
730
744
  const taalId = resolveTaal(doc, options.taal);
731
- const variation = options.taal === void 0 || options.taal === "auto" ? doc.meta.taal?.variation : void 0;
745
+ const pinned = options.taal !== void 0 && options.taal !== "auto" && taalId !== void 0 ? TAALS[taalId] : void 0;
732
746
  const usesTabla = rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && !!base;
733
747
  const reqBpm = options.bpm ?? doc.meta.bpm ?? 90;
734
748
  const bpm = usesTabla && taalId ? nearestTablaBpm(reqBpm, tablaBpms(taalId)) : reqBpm;
@@ -745,14 +759,14 @@ async function exportAudio(doc, options = {}) {
745
759
  const sampler = createSampler(Tone, def, base, range).connect(master);
746
760
  const glide = schedule.events.some((ev) => ev.glide === true) ? createBuffers(Tone, def, base, range) : void 0;
747
761
  schedule.events.forEach((ev, i) => {
748
- if (ev.midi === null || ev.glide === true) return;
762
+ if (ev.glide === true) return;
763
+ for (const g of graceTouches(ev)) {
764
+ const name = midiToSampleName(g.midi);
765
+ transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(g.beat * spb + shift, 0));
766
+ }
767
+ if (ev.midi === null) return;
749
768
  const at = ev.beat * spb + shift;
750
769
  const dur = Math.max(ev.durationBeats * spb, 0.05);
751
- ev.kan.forEach((k, gi) => {
752
- const name = midiToSampleName(k);
753
- const lead = spb * 0.12 * (ev.kan.length - gi);
754
- transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(at - lead, 0));
755
- });
756
770
  const targets = glide !== void 0 ? glideTargets(schedule.events, i) : [];
757
771
  if (targets.length > 0 && glide !== void 0) {
758
772
  const last = targets[targets.length - 1];
@@ -780,22 +794,22 @@ async function exportAudio(doc, options = {}) {
780
794
  }
781
795
  const tablas = [];
782
796
  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
797
  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;
798
+ let metro;
788
799
  const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));
789
800
  schedule.tempo.forEach((seg, i) => {
801
+ const own = pinned ?? seg.taal;
802
+ if (own === null) return;
803
+ const segId = own !== void 0 && own.id in TAALS ? own.id : void 0;
790
804
  const from = i === 0 ? 0 : seg.beat * spb + shift;
791
805
  const to = i === schedule.tempo.length - 1 ? Infinity : seg.endBeat * spb + shift;
792
806
  const beatSec = spb / seg.scale;
793
807
  const span = to === Infinity ? void 0 : to - from;
794
- if (usesTabla && taalId) {
808
+ if (rhythm === "tabla" && segId !== void 0 && SAMPLED_TAALS.has(segId)) {
795
809
  const segBpm = 60 / beatSec;
796
- const fileBpm = nearestTablaBpm(segBpm, tablaBpms(taalId));
810
+ const fileBpm = nearestTablaBpm(segBpm, tablaBpms(segId));
797
811
  const rate = segBpm / fileBpm;
798
- const url = tablaUrl(base, taalId, fileBpm, variation);
812
+ const url = tablaUrl(base, segId, fileBpm, pinned === void 0 ? own?.variation : void 0);
799
813
  const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
800
814
  url,
801
815
  loop: true,
@@ -814,6 +828,7 @@ async function exportAudio(doc, options = {}) {
814
828
  }
815
829
  tablas.push({ loop, fileSpb: 60 / fileBpm });
816
830
  } else if (drums !== void 0) {
831
+ const pattern = drumPattern(segId !== void 0 ? TAALS[segId] : own ?? { numBeats: 4, sam: 0 });
817
832
  let beat = 0;
818
833
  transport.scheduleRepeat(
819
834
  (time) => {
@@ -825,12 +840,14 @@ async function exportAudio(doc, options = {}) {
825
840
  from,
826
841
  span
827
842
  );
828
- } else if (metro !== void 0) {
843
+ } else {
844
+ const click = metro ??= createMetronome(Tone, base).connect(master);
845
+ const cycle = own?.numBeats ?? 4;
829
846
  let beat = 0;
830
847
  transport.scheduleRepeat(
831
848
  (time) => {
832
- metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
833
- metro.start(time);
849
+ click.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
850
+ click.start(time);
834
851
  beat += 1;
835
852
  },
836
853
  beatSec,
@@ -1025,17 +1042,17 @@ function encodeMidi(schedule, opts) {
1025
1042
  const hasGlide = schedule.events.some((ev) => ev.glide === true);
1026
1043
  if (hasGlide) melody.push(...bendRangeSetup());
1027
1044
  schedule.events.forEach((ev, i) => {
1028
- if (ev.midi === null || ev.glide === true) return;
1045
+ if (ev.glide === true) return;
1046
+ for (const g of graceTouches(ev)) {
1047
+ const on = Math.max(Math.round(g.beat * PPQ), 0);
1048
+ melody.push({ tick: on, on: true, note: g.midi, vel: 70, ch: 0 });
1049
+ melody.push({ tick: Math.max(on + 1, Math.round((g.beat + GRACE_STEP) * PPQ)), on: false, note: g.midi, vel: 0, ch: 0 });
1050
+ }
1051
+ if (ev.midi === null) return;
1029
1052
  const start = Math.round(ev.beat * PPQ);
1030
1053
  const targets = glideTargets(schedule.events, i);
1031
1054
  const last = targets[targets.length - 1] ?? ev;
1032
1055
  const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ), start + 1);
1033
- ev.kan.forEach((k, gi) => {
1034
- const gap = Math.round(0.12 * PPQ);
1035
- const kOn = Math.max(start - gap * (ev.kan.length - gi), 0);
1036
- melody.push({ tick: kOn, on: true, note: k, vel: 70, ch: 0 });
1037
- melody.push({ tick: Math.max(kOn + 1, start - gap * (ev.kan.length - gi - 1)), on: false, note: k, vel: 0, ch: 0 });
1038
- });
1039
1056
  melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
1040
1057
  melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
1041
1058
  let from = ev;
@@ -1056,14 +1073,18 @@ function encodeMidi(schedule, opts) {
1056
1073
  if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
1057
1074
  head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
1058
1075
  const chunks = [trackChunk(melody, head)];
1059
- if (opts.drums && opts.drums.length > 0) {
1076
+ if (typeof opts.drums === "function" || opts.drums && opts.drums.length > 0) {
1060
1077
  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 });
1078
+ for (const seg of schedule.tempo) {
1079
+ const pattern = typeof opts.drums === "function" ? opts.drums(seg) : opts.drums;
1080
+ if (!pattern?.length) continue;
1081
+ const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
1082
+ for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
1083
+ const note = DRUM_NOTE[pattern[k % pattern.length]];
1084
+ const t = Math.round((seg.beat + k / seg.scale) * PPQ);
1085
+ drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
1086
+ drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
1087
+ }
1067
1088
  }
1068
1089
  chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
1069
1090
  }
@@ -1093,9 +1114,11 @@ function exportMidi(doc, options = {}) {
1093
1114
  laySpeeds: options.laySpeeds
1094
1115
  });
1095
1116
  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;
1117
+ const drums = options.drums ? (seg) => {
1118
+ const id = seg.taal?.id;
1119
+ if (seg.taal === null) return void 0;
1120
+ return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
1121
+ } : void 0;
1099
1122
  const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
1100
1123
  return {
1101
1124
  blob: new Blob([bytes], { type: "audio/midi" }),