@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.
Files changed (39) hide show
  1. package/README.md +8 -8
  2. package/dist/{batch-Y3HKAL3B.js → batch-UETOBPHT.js} +4 -4
  3. package/dist/{batch-Y3HKAL3B.js.map → batch-UETOBPHT.js.map} +1 -1
  4. package/dist/{chunk-AIGSKUD3.js → chunk-C6TRCMPL.js} +24 -5
  5. package/dist/chunk-C6TRCMPL.js.map +1 -0
  6. package/dist/{chunk-KPKD53NA.js → chunk-ID44OP2N.js} +3 -3
  7. package/dist/{chunk-KPKD53NA.js.map → chunk-ID44OP2N.js.map} +1 -1
  8. package/dist/{chunk-IG2CIUTP.js → chunk-UD2K4UIC.js} +3 -3
  9. package/dist/{chunk-IG2CIUTP.js.map → chunk-UD2K4UIC.js.map} +1 -1
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +2 -2
  13. package/dist/convert/index.cjs +1 -1
  14. package/dist/convert/index.cjs.map +1 -1
  15. package/dist/convert/index.js +2 -2
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/player/index.cjs +58 -15
  20. package/dist/player/index.cjs.map +1 -1
  21. package/dist/player/index.d.cts +2 -2
  22. package/dist/player/index.d.ts +2 -2
  23. package/dist/player/index.js +39 -15
  24. package/dist/player/index.js.map +1 -1
  25. package/dist/{schedule-dXV5cUQI.d.ts → schedule-BaYIJf8y.d.ts} +14 -4
  26. package/dist/{schedule-C2WN7YOK.d.cts → schedule-bBvy18MO.d.cts} +14 -4
  27. package/dist/spec/index.cjs +1 -1
  28. package/dist/spec/index.cjs.map +1 -1
  29. package/dist/spec/index.d.cts +1 -1
  30. package/dist/spec/index.d.ts +1 -1
  31. package/dist/spec/index.js +1 -1
  32. package/dist/viewer/index.cjs +27 -13
  33. package/dist/viewer/index.cjs.map +1 -1
  34. package/dist/viewer/index.d.cts +1 -1
  35. package/dist/viewer/index.d.ts +1 -1
  36. package/dist/viewer/index.js +14 -12
  37. package/dist/viewer/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-AIGSKUD3.js.map +0 -1
@@ -99,15 +99,25 @@ function sampleFileName(midi) {
99
99
 
100
100
  // src/player/schedule.ts
101
101
  var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true;
102
+ var SAM_TICK = /^\d+[fF]$/;
102
103
  function buildSchedule(doc, opts = {}) {
103
104
  const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
104
105
  const baseOctave = opts.baseOctave ?? 4;
105
106
  const events = [];
106
107
  let cursor = 0;
107
108
  let lastPitched = -1;
109
+ let samBeat;
108
110
  doc.parts.forEach((part, p) => {
109
111
  part.rows.forEach((row, r) => {
110
112
  row.notes.forEach((note, i) => {
113
+ if (samBeat === void 0 && note.above !== void 0) {
114
+ for (const a of note.above) {
115
+ if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
116
+ samBeat = cursor;
117
+ break;
118
+ }
119
+ }
120
+ }
111
121
  if (note.bar !== void 0 || isSilent(note)) return;
112
122
  const path = `${p}.${r}.${i}`;
113
123
  const dur = note.durationMatra ?? 1;
@@ -131,11 +141,13 @@ function buildSchedule(doc, opts = {}) {
131
141
  });
132
142
  });
133
143
  });
134
- return { events, totalBeats: cursor };
144
+ const numBeats = doc.meta.taal?.numBeats;
145
+ const pickupBeats = samBeat !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samBeat % numBeats) % numBeats : 0;
146
+ return { events, totalBeats: cursor, pickupBeats };
135
147
  }
136
148
 
137
149
  // src/player/instruments.ts
138
- var DEFAULT_SOUNDS_BASE = "https://cdn.daserve.in/swaralipijs-instruments/";
150
+ var DEFAULT_SOUNDS_BASE = "https://bitoron.pages.dev/audio/instruments/";
139
151
  var SALAMANDER_BASE = "https://tonejs.github.io/audio/salamander/";
140
152
  var TONEJS_INSTRUMENTS_BASE = "https://nbrosowsky.github.io/tonejs-instruments/samples/";
141
153
  function keyMap(keys) {
@@ -326,6 +338,13 @@ function tablaUrl(base, taalId, bpm, variation) {
326
338
  const seg = tablaSeg(taalId, variation);
327
339
  return `${normalizeBase(base)}tabla/${seg}/${seg}${bpm}bpm.mp3`;
328
340
  }
341
+ var TABLA_TONIC_SEMITONE = 0;
342
+ function tablaShiftSemitones(tonicSemitone) {
343
+ const d = ((tonicSemitone - TABLA_TONIC_SEMITONE) % 12 + 12) % 12;
344
+ return d > 6 ? d - 12 : d;
345
+ }
346
+ var TABLA_GRAIN_SEC = 0.06;
347
+ var TABLA_GRAIN_OVERLAP_SEC = 0.03;
329
348
  function metronomeUrl(base) {
330
349
  return `${normalizeBase(base)}metronome/metro2.mp3`;
331
350
  }
@@ -387,6 +406,13 @@ function midiRange(schedule) {
387
406
  }
388
407
  return lo <= hi ? { lo, hi } : void 0;
389
408
  }
409
+ async function loadedOrWarn(Tone) {
410
+ try {
411
+ await Tone.loaded();
412
+ } catch (err) {
413
+ console.warn("[swaralipi] some audio samples failed to load; continuing without them", err);
414
+ }
415
+ }
390
416
  var SwaralipiPlayer = class {
391
417
  #opts;
392
418
  /** Per-song setBpm tweak: beats meta.bpm but is cleared by the next load(). */
@@ -405,6 +431,7 @@ var SwaralipiPlayer = class {
405
431
  #tanpura;
406
432
  #tanpuraKey = "";
407
433
  #metronome;
434
+ /** The looping tabla — a `GrainPlayer` when the tonic needs it retuned. */
408
435
  #tabla;
409
436
  #drums;
410
437
  #scheduleIds = [];
@@ -757,13 +784,14 @@ var SwaralipiPlayer = class {
757
784
  const melody = this.#buildMelody(Tone, this.#effectiveInstrument(), base, range);
758
785
  this.#buildRhythm(Tone, transport, base, spb);
759
786
  this.#buildTanpura(Tone, transport, base, spb);
760
- await Tone.loaded();
787
+ await loadedOrWarn(Tone);
761
788
  if (this.#tabla?.buffer?.loaded) {
762
789
  this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
763
790
  }
764
791
  const draw = Tone.getDraw();
792
+ const shift = this.#opts.rhythm !== "none" ? schedule.pickupBeats * spb : 0;
765
793
  for (const ev of schedule.events) {
766
- const at = ev.beat * spb;
794
+ const at = ev.beat * spb + shift;
767
795
  if (ev.midi !== null) {
768
796
  const name = midiToSampleName(ev.midi);
769
797
  const dur = Math.max(ev.durationBeats * spb, 0.05);
@@ -777,10 +805,10 @@ var SwaralipiPlayer = class {
777
805
  draw.schedule(() => this.#emitHighlight(ev.path), time);
778
806
  }, at);
779
807
  }
780
- const endAt = schedule.totalBeats * spb + spb;
808
+ const endAt = schedule.totalBeats * spb + shift + spb;
781
809
  transport.scheduleOnce((time) => {
782
810
  draw.schedule(() => this.#emitHighlight(null), time);
783
- }, schedule.totalBeats * spb);
811
+ }, schedule.totalBeats * spb + shift);
784
812
  transport.scheduleOnce(() => this.stop(), endAt);
785
813
  }
786
814
  /** Taal mode: loop one avartan forever (tabla loop / drum sequence via
@@ -800,7 +828,7 @@ var SwaralipiPlayer = class {
800
828
  transport.bpm.value = bpm;
801
829
  const spb = 60 / bpm;
802
830
  this.#buildRhythm(Tone, transport, base, spb);
803
- await Tone.loaded();
831
+ await loadedOrWarn(Tone);
804
832
  if (this.#tabla?.buffer?.loaded) {
805
833
  this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
806
834
  }
@@ -840,14 +868,21 @@ var SwaralipiPlayer = class {
840
868
  const wantsTabla = this.#opts.rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0;
841
869
  if (wantsTabla) {
842
870
  const url = tablaUrl(base, taalId, nearestTablaBpm(this.bpm, tablaBpms(taalId)), this.#variation());
843
- this.#tabla = new Tone.Player({ url, loop: true }).connect(master);
871
+ const shift = tablaShiftSemitones(this.#tonicSemitone());
872
+ this.#tabla = (shift === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
873
+ url,
874
+ loop: true,
875
+ detune: shift * 100,
876
+ grainSize: TABLA_GRAIN_SEC,
877
+ overlap: TABLA_GRAIN_OVERLAP_SEC
878
+ })).connect(master);
844
879
  this.#tabla.sync().start(0);
845
880
  return;
846
881
  }
847
882
  if (this.#opts.rhythm === "drums") {
848
883
  this.#drums ??= createDrumKit(Tone).connect(master);
849
884
  const drums = this.#drums;
850
- const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : { numBeats: 4, sam: 0 });
885
+ const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };
851
886
  const pattern = drumPattern(taal);
852
887
  let beat2 = 0;
853
888
  const id2 = transport.scheduleRepeat((time) => {
@@ -861,7 +896,7 @@ var SwaralipiPlayer = class {
861
896
  if (this.#opts.rhythm === "none" || base === void 0) return;
862
897
  this.#metronome ??= createMetronome(Tone, base).connect(master);
863
898
  const metro = this.#metronome;
864
- const cycle = this.#taal?.numBeats ?? (taalId !== void 0 ? TAALS[taalId].numBeats : 4);
899
+ const cycle = this.#taal?.numBeats ?? this.#doc?.meta.taal?.numBeats ?? 4;
865
900
  let beat = 0;
866
901
  const id = transport.scheduleRepeat((time) => {
867
902
  metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
@@ -945,7 +980,8 @@ async function exportAudio(doc, options = {}) {
945
980
  const spb = 60 / bpm;
946
981
  const id = options.instrument ?? "piano";
947
982
  const def = pickDef(id, options);
948
- const durationSec = Math.max(schedule.totalBeats * spb + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
983
+ const shift = rhythm !== "none" ? schedule.pickupBeats * spb : 0;
984
+ const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
949
985
  const rendered = await Tone.Offline(
950
986
  async ({ transport }) => {
951
987
  transport.bpm.value = bpm;
@@ -954,7 +990,7 @@ async function exportAudio(doc, options = {}) {
954
990
  const sampler = createSampler(Tone, def, base, range).connect(master);
955
991
  for (const ev of schedule.events) {
956
992
  if (ev.midi === null) continue;
957
- const at = ev.beat * spb;
993
+ const at = ev.beat * spb + shift;
958
994
  const dur = Math.max(ev.durationBeats * spb, 0.05);
959
995
  if (ev.kan !== null) {
960
996
  const name2 = midiToSampleName(ev.kan);
@@ -967,7 +1003,7 @@ async function exportAudio(doc, options = {}) {
967
1003
  let tabla;
968
1004
  if (rhythm === "drums" && base) {
969
1005
  const drums = createDrumKit(Tone).connect(master);
970
- const taal = taalId !== void 0 ? TAALS[taalId] : { numBeats: 4, sam: 0 };
1006
+ const taal = taalId !== void 0 ? TAALS[taalId] : doc.meta.taal ?? { numBeats: 4, sam: 0 };
971
1007
  const pattern = drumPattern(taal);
972
1008
  let beat = 0;
973
1009
  transport.scheduleRepeat((time) => {
@@ -978,11 +1014,18 @@ async function exportAudio(doc, options = {}) {
978
1014
  } else if (rhythm !== "none" && base) {
979
1015
  if (usesTabla && taalId) {
980
1016
  const url = tablaUrl(base, taalId, bpm, variation);
981
- tabla = new Tone.Player({ url, loop: true }).connect(master);
1017
+ const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));
1018
+ tabla = (semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
1019
+ url,
1020
+ loop: true,
1021
+ detune: semis * 100,
1022
+ grainSize: TABLA_GRAIN_SEC,
1023
+ overlap: TABLA_GRAIN_OVERLAP_SEC
1024
+ })).connect(master);
982
1025
  tabla.sync().start(0);
983
1026
  } else {
984
1027
  const metro = createMetronome(Tone, base).connect(master);
985
- const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : 4;
1028
+ const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : doc.meta.taal?.numBeats ?? 4;
986
1029
  let beat = 0;
987
1030
  transport.scheduleRepeat((time) => {
988
1031
  metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);