@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.cjs
CHANGED
|
@@ -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
|
-
|
|
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://
|
|
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
|
|
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
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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);
|