@ugfoundation/swaralipi-js 0.2.1 → 0.3.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.
- package/dist/{batch-SCSIUDB3.js → batch-TVEIT2OJ.js} +4 -4
- package/dist/{batch-SCSIUDB3.js.map → batch-TVEIT2OJ.js.map} +1 -1
- package/dist/{chunk-KYNCQ7OF.js → chunk-HDN3YZ6K.js} +3 -3
- package/dist/{chunk-KYNCQ7OF.js.map → chunk-HDN3YZ6K.js.map} +1 -1
- package/dist/chunk-V5ZSUJCA.js +354 -0
- package/dist/chunk-V5ZSUJCA.js.map +1 -0
- package/dist/{chunk-DINRYGRI.js → chunk-WBSIKYWB.js} +3 -3
- package/dist/{chunk-DINRYGRI.js.map → chunk-WBSIKYWB.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 +117 -20
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +24 -164
- package/dist/player/index.d.ts +24 -164
- package/dist/player/index.js +91 -343
- package/dist/player/index.js.map +1 -1
- package/dist/schedule-BhiP_maG.d.ts +167 -0
- package/dist/schedule-Dsb2paUE.d.cts +167 -0
- 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 +584 -54
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +177 -18
- package/dist/viewer/index.d.ts +177 -18
- package/dist/viewer/index.js +322 -30
- package/dist/viewer/index.js.map +1 -1
- package/package.json +7 -2
package/dist/player/index.js
CHANGED
|
@@ -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
|
|
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);
|
|
@@ -993,34 +700,72 @@ function utf8(s) {
|
|
|
993
700
|
function u32be(n) {
|
|
994
701
|
return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
|
|
995
702
|
}
|
|
703
|
+
var DRUM_NOTE = {
|
|
704
|
+
kick: 36,
|
|
705
|
+
snare: 38,
|
|
706
|
+
hihat: 42,
|
|
707
|
+
// closed hi-hat
|
|
708
|
+
tom1: 48,
|
|
709
|
+
tom2: 45,
|
|
710
|
+
tom3: 41
|
|
711
|
+
};
|
|
712
|
+
function trackChunk(raw, head) {
|
|
713
|
+
raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));
|
|
714
|
+
const track = [...head];
|
|
715
|
+
let last = 0;
|
|
716
|
+
for (const e of raw) {
|
|
717
|
+
const status = (e.on ? 144 : 128) | e.ch & 15;
|
|
718
|
+
track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
|
|
719
|
+
last = e.tick;
|
|
720
|
+
}
|
|
721
|
+
track.push(...vlq(0), 255, 47, 0);
|
|
722
|
+
return [77, 84, 114, 107, ...u32be(track.length), ...track];
|
|
723
|
+
}
|
|
996
724
|
function encodeMidi(schedule, opts) {
|
|
997
|
-
const
|
|
725
|
+
const melody = [];
|
|
998
726
|
for (const ev of schedule.events) {
|
|
999
727
|
if (ev.midi === null) continue;
|
|
1000
728
|
const start = Math.round(ev.beat * PPQ);
|
|
1001
729
|
const end = Math.max(Math.round((ev.beat + ev.durationBeats) * PPQ), start + 1);
|
|
1002
730
|
if (ev.kan !== null) {
|
|
1003
731
|
const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);
|
|
1004
|
-
|
|
1005
|
-
|
|
732
|
+
melody.push({ tick: kOn, on: true, note: ev.kan, vel: 70, ch: 0 });
|
|
733
|
+
melody.push({ tick: start, on: false, note: ev.kan, vel: 0, ch: 0 });
|
|
1006
734
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
735
|
+
melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
|
|
736
|
+
melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
|
|
1009
737
|
}
|
|
1010
|
-
raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));
|
|
1011
|
-
const track = [];
|
|
1012
|
-
if (opts.name) track.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
1013
738
|
const usPerBeat = Math.round(6e7 / opts.bpm);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
739
|
+
const head = [];
|
|
740
|
+
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
741
|
+
head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
|
|
742
|
+
const chunks = [trackChunk(melody, head)];
|
|
743
|
+
if (opts.drums && opts.drums.length > 0) {
|
|
744
|
+
const drum = [];
|
|
745
|
+
const totalBeats = Math.ceil(schedule.totalBeats);
|
|
746
|
+
for (let beat = 0; beat < totalBeats; beat++) {
|
|
747
|
+
const note = DRUM_NOTE[opts.drums[beat % opts.drums.length]];
|
|
748
|
+
const t = beat * PPQ;
|
|
749
|
+
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
750
|
+
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
751
|
+
}
|
|
752
|
+
chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
|
|
753
|
+
}
|
|
754
|
+
const format = chunks.length > 1 ? 1 : 0;
|
|
755
|
+
const header = [
|
|
756
|
+
77,
|
|
757
|
+
84,
|
|
758
|
+
104,
|
|
759
|
+
100,
|
|
760
|
+
...u32be(6),
|
|
761
|
+
0,
|
|
762
|
+
format,
|
|
763
|
+
chunks.length >> 8 & 255,
|
|
764
|
+
chunks.length & 255,
|
|
765
|
+
PPQ >> 8 & 255,
|
|
766
|
+
PPQ & 255
|
|
767
|
+
];
|
|
768
|
+
return new Uint8Array([...header, ...chunks.flat()]);
|
|
1024
769
|
}
|
|
1025
770
|
function safeName2(title) {
|
|
1026
771
|
return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
|
|
@@ -1028,7 +773,10 @@ function safeName2(title) {
|
|
|
1028
773
|
function exportMidi(doc, options = {}) {
|
|
1029
774
|
const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });
|
|
1030
775
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
1031
|
-
const
|
|
776
|
+
const taalId = doc.meta.taal?.id;
|
|
777
|
+
const taal = taalId !== void 0 && taalId in TAALS ? TAALS[taalId] : { numBeats: 4, sam: 0 };
|
|
778
|
+
const drums = options.drums ? drumPattern(taal) : void 0;
|
|
779
|
+
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
|
|
1032
780
|
return {
|
|
1033
781
|
blob: new Blob([bytes], { type: "audio/midi" }),
|
|
1034
782
|
filename: `${safeName2(doc.meta.title)}.mid`,
|
|
@@ -1036,6 +784,6 @@ function exportMidi(doc, options = {}) {
|
|
|
1036
784
|
};
|
|
1037
785
|
}
|
|
1038
786
|
|
|
1039
|
-
export {
|
|
787
|
+
export { SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi };
|
|
1040
788
|
//# sourceMappingURL=index.js.map
|
|
1041
789
|
//# sourceMappingURL=index.js.map
|