@ugfoundation/swaralipi-js 0.2.0 → 0.3.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 (37) hide show
  1. package/dist/{batch-CIAXVKP5.js → batch-QPSHVNDS.js} +4 -4
  2. package/dist/{batch-CIAXVKP5.js.map → batch-QPSHVNDS.js.map} +1 -1
  3. package/dist/{chunk-2NBNF6JP.js → chunk-4YTOLN3C.js} +3 -3
  4. package/dist/{chunk-2NBNF6JP.js.map → chunk-4YTOLN3C.js.map} +1 -1
  5. package/dist/chunk-V5ZSUJCA.js +354 -0
  6. package/dist/chunk-V5ZSUJCA.js.map +1 -0
  7. package/dist/{chunk-SH2INDZ2.js → chunk-VCRWS3F3.js} +3 -3
  8. package/dist/{chunk-SH2INDZ2.js.map → chunk-VCRWS3F3.js.map} +1 -1
  9. package/dist/cli.cjs +1 -1
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +2 -2
  12. package/dist/convert/index.cjs +1 -1
  13. package/dist/convert/index.cjs.map +1 -1
  14. package/dist/convert/index.js +2 -2
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/player/index.cjs +58 -2
  19. package/dist/player/index.cjs.map +1 -1
  20. package/dist/player/index.d.cts +12 -158
  21. package/dist/player/index.d.ts +12 -158
  22. package/dist/player/index.js +31 -324
  23. package/dist/player/index.js.map +1 -1
  24. package/dist/schedule-DhO_Xzz4.d.cts +167 -0
  25. package/dist/schedule-PTHUONrA.d.ts +167 -0
  26. package/dist/spec/index.cjs +1 -1
  27. package/dist/spec/index.cjs.map +1 -1
  28. package/dist/spec/index.d.cts +1 -1
  29. package/dist/spec/index.d.ts +1 -1
  30. package/dist/spec/index.js +1 -1
  31. package/dist/viewer/index.cjs +684 -202
  32. package/dist/viewer/index.cjs.map +1 -1
  33. package/dist/viewer/index.d.cts +217 -64
  34. package/dist/viewer/index.d.ts +217 -64
  35. package/dist/viewer/index.js +421 -177
  36. package/dist/viewer/index.js.map +1 -1
  37. package/package.json +7 -2
@@ -1,326 +1,7 @@
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-V5ZSUJCA.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-V5ZSUJCA.js';
1
3
  import { TAALS } from '../chunk-H6HC44PZ.js';
2
4
 
3
- // src/player/pitch.ts
4
- var OCTAVE_OFFSET = {
5
- "ati-mandra": -2,
6
- mandra: -1,
7
- madhyam: 0,
8
- taar: 1,
9
- "ati-taar": 2
10
- };
11
- var SHUDDHA = {
12
- s: 0,
13
- r: 2,
14
- g: 4,
15
- m: 5,
16
- p: 7,
17
- d: 9,
18
- n: 11
19
- };
20
- var SHARP_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
21
- var PITCH_CLASS = {
22
- c: 0,
23
- "c#": 1,
24
- db: 1,
25
- d: 2,
26
- "d#": 3,
27
- eb: 3,
28
- e: 4,
29
- "e#": 5,
30
- fb: 4,
31
- f: 5,
32
- "f#": 6,
33
- gb: 6,
34
- g: 7,
35
- "g#": 8,
36
- ab: 8,
37
- a: 9,
38
- "a#": 10,
39
- bb: 10,
40
- b: 11,
41
- "b#": 0,
42
- cb: 11
43
- };
44
- function parseTonic(tonic) {
45
- if (!tonic) return 0;
46
- const key = tonic.trim().toLowerCase().replace(/s/g, "#");
47
- return PITCH_CLASS[key] ?? 0;
48
- }
49
- function svaraSemitone(note) {
50
- if (note.svara === void 0) return null;
51
- const [, letter] = note.svara;
52
- let semi = SHUDDHA[letter];
53
- if (note.komal === true || note.microtone !== void 0) semi -= 1;
54
- if (note.tivra === true) semi += 1;
55
- return semi;
56
- }
57
- function svaraToMidi(note, tonicPc, baseOctave = 4) {
58
- const semi = svaraSemitone(note);
59
- if (semi === null || note.svara === void 0) return null;
60
- const [octave] = note.svara;
61
- return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + semi;
62
- }
63
- function kanToMidi(svara, tonicPc, baseOctave = 4) {
64
- if (svara === void 0) return null;
65
- const [octave, letter] = svara;
66
- return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + SHUDDHA[letter];
67
- }
68
- function midiToSampleName(midi) {
69
- const pc = (midi % 12 + 12) % 12;
70
- const octave = Math.floor(midi / 12) - 1;
71
- return `${SHARP_NAMES[pc]}${octave}`;
72
- }
73
- function sampleFileName(midi) {
74
- return midiToSampleName(midi).toLowerCase().replace("#", "s");
75
- }
76
-
77
- // src/player/schedule.ts
78
- var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true;
79
- function buildSchedule(doc, opts = {}) {
80
- const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
81
- const baseOctave = opts.baseOctave ?? 4;
82
- const events = [];
83
- let cursor = 0;
84
- let lastPitched = -1;
85
- doc.parts.forEach((part, p) => {
86
- part.rows.forEach((row, r) => {
87
- row.notes.forEach((note, i) => {
88
- if (note.bar !== void 0 || isSilent(note)) return;
89
- const path = `${p}.${r}.${i}`;
90
- const dur = note.durationMatra ?? 1;
91
- if (note.svara !== void 0) {
92
- events.push({
93
- beat: cursor,
94
- durationBeats: dur,
95
- midi: svaraToMidi(note, tonicPc, baseOctave),
96
- kan: kanToMidi(note.kan, tonicPc, baseOctave),
97
- path,
98
- kind: note.sustain === true ? "sustain" : "note"
99
- });
100
- lastPitched = events.length - 1;
101
- } else if (note.sustain === true) {
102
- if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
103
- events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "sustain" });
104
- } else {
105
- events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "rest" });
106
- }
107
- cursor += dur;
108
- });
109
- });
110
- });
111
- return { events, totalBeats: cursor };
112
- }
113
-
114
- // src/player/instruments.ts
115
- var DEFAULT_SOUNDS_BASE = "https://cdn.daserve.in/swaralipijs-instruments/";
116
- var SALAMANDER_BASE = "https://tonejs.github.io/audio/salamander/";
117
- var TONEJS_INSTRUMENTS_BASE = "https://nbrosowsky.github.io/tonejs-instruments/samples/";
118
- function keyMap(keys) {
119
- const m = {};
120
- for (const k of keys) m[k] = `${k.replace("#", "s")}.mp3`;
121
- return m;
122
- }
123
- var PIANO_KEYS = [
124
- "A0",
125
- "C1",
126
- "D#1",
127
- "F#1",
128
- "A1",
129
- "C2",
130
- "D#2",
131
- "F#2",
132
- "A2",
133
- "C3",
134
- "D#3",
135
- "F#3",
136
- "A3",
137
- "C4",
138
- "D#4",
139
- "F#4",
140
- "A4",
141
- "C5",
142
- "D#5",
143
- "F#5",
144
- "A5",
145
- "C6",
146
- "D#6",
147
- "F#6",
148
- "A6",
149
- "C7",
150
- "D#7",
151
- "F#7",
152
- "A7",
153
- "C8"
154
- ];
155
- var HARMONIUM_KEYS = [
156
- "C2",
157
- "C#2",
158
- "D2",
159
- "D#2",
160
- "E2",
161
- "F2",
162
- "F#2",
163
- "G2",
164
- "G#2",
165
- "A2",
166
- "A#2",
167
- "C3",
168
- "C#3",
169
- "D3",
170
- "D#3",
171
- "E3",
172
- "F3",
173
- "F#3",
174
- "G3",
175
- "G#3",
176
- "A3",
177
- "A#3",
178
- "C4",
179
- "C#4",
180
- "D4",
181
- "D#4",
182
- "E4",
183
- "F4",
184
- "F#4",
185
- "G4",
186
- "G#4",
187
- "A4",
188
- "A#4",
189
- "C5",
190
- "C#5",
191
- "D5"
192
- ];
193
- var VIOLIN_KEYS = ["A3", "C4", "E4", "G4", "A4", "C5", "E5", "G5", "A5", "C6", "E6", "G6", "A6", "C7"];
194
- var FLUTE_KEYS = ["C4", "E4", "A4", "C5", "E5", "A5", "C6", "E6", "A6", "C7"];
195
- var INSTRUMENTS = {
196
- piano: { label: "Grand piano", urls: keyMap(PIANO_KEYS), baseUrl: SALAMANDER_BASE, release: 1 },
197
- santoor: { label: "Santoor", urls: santoorUrls(), baseUrl: "santoor/", local: true, release: 1.2 },
198
- harmonium: { label: "Harmonium", urls: keyMap(HARMONIUM_KEYS), baseUrl: `${TONEJS_INSTRUMENTS_BASE}harmonium/`, release: 1.4 },
199
- violin: { label: "Violin", urls: keyMap(VIOLIN_KEYS), baseUrl: `${TONEJS_INSTRUMENTS_BASE}violin/`, release: 1 },
200
- flute: { label: "Flute", urls: keyMap(FLUTE_KEYS), baseUrl: `${TONEJS_INSTRUMENTS_BASE}flute/`, release: 1 }
201
- };
202
- var SAMPLED_TAALS = new Set(
203
- Object.keys(TAALS)
204
- );
205
- var TABLA_BPMS = Array.from({ length: 17 }, (_, i) => 60 + i * 15);
206
- var TABLA_VARIATIONS = {
207
- // ষষ্ঠী loops live under `tabla/shashthi-pro/`; full bpm grid recorded except 225.
208
- shashthi: { variations: ["pro"], skipBpms: [225] },
209
- // Kehrwa keeps its unnamed default loop; `qawwali` (tabla/kehrwa-qawwali/) is a
210
- // premium upsell variation recorded 90–300 (no 60/75).
211
- // ponytail: skipBpms is per-taal, so it can't restrict qawwali's 60/75 without
212
- // also dropping them from the default (which has them) — a qawwali doc pinned
213
- // ≤67 bpm 404s harmlessly. Make skipBpms per-variation only if that edge bites.
214
- kehrwa: { variations: ["", "qawwali"] }
215
- };
216
- function tablaBpms(taalId) {
217
- const skip = TABLA_VARIATIONS[taalId]?.skipBpms;
218
- return skip && skip.length ? TABLA_BPMS.filter((b) => !skip.includes(b)) : TABLA_BPMS;
219
- }
220
- function tablaSeg(taalId, variation) {
221
- const v = variation ?? TABLA_VARIATIONS[taalId]?.variations[0];
222
- return v ? `${taalId}-${v}` : taalId;
223
- }
224
- function normalizeBase(url) {
225
- return url.endsWith("/") ? url : `${url}/`;
226
- }
227
- function santoorUrls() {
228
- const urls = {};
229
- const add = (midi) => {
230
- const stem = sampleFileName(midi);
231
- const name = stem.replace("s", "#").replace(/^(.)/, (c) => c.toUpperCase());
232
- urls[name] = `${stem}.mp3`;
233
- };
234
- for (let midi = 60; midi <= 95; midi++) add(midi);
235
- add(96);
236
- return urls;
237
- }
238
- var PC_BASE = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
239
- function noteToMidi(name) {
240
- const m = /^([a-g])([#sb]?)(-?\d+)$/i.exec(name.trim());
241
- if (!m) return NaN;
242
- const base = PC_BASE[m[1].toLowerCase()];
243
- const acc = m[2] === "b" ? -1 : m[2] === "" ? 0 : 1;
244
- return (Number(m[3]) + 1) * 12 + base + acc;
245
- }
246
- function subsetUrls(urls, lo, hi) {
247
- const entries = Object.entries(urls).map(([k, f]) => ({ k, f, midi: noteToMidi(k) })).filter((e) => Number.isFinite(e.midi)).sort((a, b) => a.midi - b.midi);
248
- if (entries.length === 0) return { ...urls };
249
- const keep = /* @__PURE__ */ new Set();
250
- for (const e of entries) if (e.midi >= lo && e.midi <= hi) keep.add(e.k);
251
- let floor;
252
- let ceil;
253
- for (const e of entries) if (e.midi <= lo) floor = e.k;
254
- for (const e of entries) if (e.midi >= hi) {
255
- ceil = e.k;
256
- break;
257
- }
258
- if (floor) keep.add(floor);
259
- if (ceil) keep.add(ceil);
260
- if (keep.size === 0) {
261
- const mid = (lo + hi) / 2;
262
- const nearest = entries.reduce((a, b) => Math.abs(b.midi - mid) < Math.abs(a.midi - mid) ? b : a);
263
- keep.add(nearest.k);
264
- }
265
- const out = {};
266
- for (const e of entries) if (keep.has(e.k)) out[e.k] = e.f;
267
- return out;
268
- }
269
- function instrumentSampleUrl(def, soundsBaseUrl) {
270
- if (def.local && soundsBaseUrl === void 0) return void 0;
271
- const base = def.local ? `${normalizeBase(soundsBaseUrl ?? "")}${def.baseUrl}` : def.baseUrl;
272
- const file = Object.values(def.urls)[0];
273
- return file === void 0 ? void 0 : `${normalizeBase(base)}${file}`;
274
- }
275
- async function probeUrl(url, timeoutMs = 6e3) {
276
- if (typeof fetch === "undefined") return false;
277
- const ctrl = typeof AbortController !== "undefined" ? new AbortController() : void 0;
278
- const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : void 0;
279
- try {
280
- const res = await fetch(url, {
281
- method: "GET",
282
- headers: { Range: "bytes=0-0" },
283
- cache: "no-store",
284
- signal: ctrl?.signal
285
- });
286
- return res.ok;
287
- } catch {
288
- return false;
289
- } finally {
290
- if (timer !== void 0) clearTimeout(timer);
291
- }
292
- }
293
- function quantizeLoopEnd(durationSec, spb) {
294
- if (spb <= 0) return durationSec;
295
- const beats = Math.round(durationSec / spb);
296
- if (beats < 1) return durationSec;
297
- return Math.min(beats * spb, durationSec);
298
- }
299
- function nearestTablaBpm(bpm, bpms = TABLA_BPMS) {
300
- return bpms.reduce((best, b) => Math.abs(b - bpm) < Math.abs(best - bpm) ? b : best, bpms[0]);
301
- }
302
- function tablaUrl(base, taalId, bpm, variation) {
303
- const seg = tablaSeg(taalId, variation);
304
- return `${normalizeBase(base)}tabla/${seg}/${seg}${bpm}bpm.mp3`;
305
- }
306
- function metronomeUrl(base) {
307
- return `${normalizeBase(base)}metronome/metro2.mp3`;
308
- }
309
- function createMetronome(Tone, base) {
310
- return new Tone.Player(metronomeUrl(base));
311
- }
312
- var TANPURA_PCS = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"];
313
- function createSampler(Tone, def, soundsBaseUrl, range) {
314
- const base = def.local ? `${normalizeBase(soundsBaseUrl ?? "")}${def.baseUrl}` : def.baseUrl;
315
- const urls = range ? subsetUrls(def.urls, range.lo, range.hi) : def.urls;
316
- return new Tone.Sampler({ urls, baseUrl: normalizeBase(base), release: def.release ?? 1 });
317
- }
318
- function createTanpura(Tone, base, stems = TANPURA_PCS) {
319
- const urls = {};
320
- for (const s of stems) urls[s] = `${s}.mp3`;
321
- return new Tone.Players({ urls, baseUrl: `${normalizeBase(base)}tanpura/` });
322
- }
323
-
324
5
  // src/player/player.ts
325
6
  var STOP_FADE_SEC = 0.5;
326
7
  function midiRange(schedule) {
@@ -352,6 +33,7 @@ var SwaralipiPlayer = class {
352
33
  #tanpuraKey = "";
353
34
  #metronome;
354
35
  #tabla;
36
+ #drums;
355
37
  #scheduleIds = [];
356
38
  #fadeTimer;
357
39
  constructor(options = {}) {
@@ -582,8 +264,9 @@ var SwaralipiPlayer = class {
582
264
  this.#tanpura?.dispose();
583
265
  this.#metronome?.dispose();
584
266
  this.#tabla?.dispose();
267
+ this.#drums?.dispose();
585
268
  this.#master?.dispose();
586
- this.#tanpura = this.#metronome = this.#tabla = this.#master = void 0;
269
+ this.#tanpura = this.#metronome = this.#tabla = this.#drums = this.#master = void 0;
587
270
  }
588
271
  /* ----------------------------------------------------------------- */
589
272
  /* Internals */
@@ -734,6 +417,20 @@ var SwaralipiPlayer = class {
734
417
  this.#tabla.sync().start(0);
735
418
  return;
736
419
  }
420
+ if (this.#opts.rhythm === "drums") {
421
+ this.#drums ??= createDrumKit(Tone).connect(master);
422
+ const drums = this.#drums;
423
+ const taal = taalId !== void 0 ? TAALS[taalId] : { numBeats: 4, sam: 0 };
424
+ const pattern = drumPattern(taal);
425
+ let beat2 = 0;
426
+ const id2 = transport.scheduleRepeat((time) => {
427
+ const voice = pattern[beat2 % pattern.length];
428
+ if (drums.has(voice)) drums.player(voice).start(time);
429
+ beat2 += 1;
430
+ }, spb, 0);
431
+ this.#scheduleIds.push(id2);
432
+ return;
433
+ }
737
434
  if (this.#opts.rhythm === "none" || base === void 0) return;
738
435
  this.#metronome ??= createMetronome(Tone, base).connect(master);
739
436
  const metro = this.#metronome;
@@ -841,7 +538,17 @@ async function exportAudio(doc, options = {}) {
841
538
  }
842
539
  }
843
540
  let tabla;
844
- if (rhythm !== "none" && base) {
541
+ if (rhythm === "drums" && base) {
542
+ const drums = createDrumKit(Tone).connect(master);
543
+ const taal = taalId !== void 0 ? TAALS[taalId] : { numBeats: 4, sam: 0 };
544
+ const pattern = drumPattern(taal);
545
+ let beat = 0;
546
+ transport.scheduleRepeat((time) => {
547
+ const voice = pattern[beat % pattern.length];
548
+ if (drums.has(voice)) drums.player(voice).start(time);
549
+ beat += 1;
550
+ }, spb, 0);
551
+ } else if (rhythm !== "none" && base) {
845
552
  if (usesTabla && taalId) {
846
553
  const url = tablaUrl(base, taalId, bpm, variation);
847
554
  tabla = new Tone.Player({ url, loop: true }).connect(master);
@@ -1036,6 +743,6 @@ function exportMidi(doc, options = {}) {
1036
743
  };
1037
744
  }
1038
745
 
1039
- export { DEFAULT_SOUNDS_BASE, INSTRUMENTS, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, SwaralipiPlayer, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl };
746
+ export { SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi };
1040
747
  //# sourceMappingURL=index.js.map
1041
748
  //# sourceMappingURL=index.js.map