@ugfoundation/swaralipi-js 0.11.1 → 0.13.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 +7 -1
- package/dist/{batch-RAM2S4QO.js → batch-BS2AIZOR.js} +6 -5
- package/dist/batch-BS2AIZOR.js.map +1 -0
- package/dist/{chunk-3EWAKXLM.js → chunk-3ZBNANDF.js} +3 -3
- package/dist/{chunk-3EWAKXLM.js.map → chunk-3ZBNANDF.js.map} +1 -1
- package/dist/{chunk-LRIAUFKF.js → chunk-5N6HGPFH.js} +8 -168
- package/dist/chunk-5N6HGPFH.js.map +1 -0
- package/dist/chunk-BSZ3WQUP.js +164 -0
- package/dist/chunk-BSZ3WQUP.js.map +1 -0
- package/dist/{chunk-SR7NFEGL.js → chunk-KOHSKHXI.js} +5 -3
- package/dist/chunk-KOHSKHXI.js.map +1 -0
- package/dist/{chunk-CF3JWIHP.js → chunk-ZNJTMQIX.js} +228 -64
- package/dist/chunk-ZNJTMQIX.js.map +1 -0
- package/dist/cli.cjs +6 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/convert/index.cjs +6 -4
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +4 -3
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{lay-DypMIIuf.d.ts → lay-CWlEhImP.d.ts} +8 -1
- package/dist/{lay-CvZeVon3.d.cts → lay-CXhC-HRO.d.cts} +8 -1
- package/dist/player/index.cjs +473 -123
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +14 -4
- package/dist/player/index.d.ts +14 -4
- package/dist/player/index.js +87 -62
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-DjcqM6A4.d.ts → schedule-ClyON98C.d.ts} +23 -11
- package/dist/{schedule-C4sP7uFu.d.cts → schedule-V-fgepvP.d.cts} +23 -11
- package/dist/spec/index.cjs +4 -2
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +2 -2
- package/dist/spec/index.d.ts +2 -2
- package/dist/spec/index.js +2 -2
- package/dist/viewer/index.cjs +369 -62
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +2 -2
- package/dist/viewer/index.d.ts +2 -2
- package/dist/viewer/index.js +3 -2
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/batch-RAM2S4QO.js.map +0 -1
- package/dist/chunk-CF3JWIHP.js.map +0 -1
- package/dist/chunk-LRIAUFKF.js.map +0 -1
- package/dist/chunk-SR7NFEGL.js.map +0 -1
package/dist/player/index.cjs
CHANGED
|
@@ -21,8 +21,10 @@ var TAALS = {
|
|
|
21
21
|
// ঝম্পক
|
|
22
22
|
dhamaar: { id: "dhamaar", numBeats: 14, bhaags: [5, 2, 3, 4], sam: 0, khaali: [7] },
|
|
23
23
|
// ধামার
|
|
24
|
-
rupakra: { id: "rupakra", numBeats: 8, bhaags: [3, 2, 3], sam: 0 }
|
|
24
|
+
rupakra: { id: "rupakra", numBeats: 8, bhaags: [3, 2, 3], sam: 0 },
|
|
25
25
|
// রূপকড়া
|
|
26
|
+
khemta: { id: "khemta", numBeats: 12, bhaags: [3, 3, 3, 3], sam: 0, khaali: [6] }
|
|
27
|
+
// খেমটা
|
|
26
28
|
};
|
|
27
29
|
function sameTaal(a, b) {
|
|
28
30
|
if (a === b) return true;
|
|
@@ -143,9 +145,305 @@ function rowLay(row) {
|
|
|
143
145
|
return row.lay ?? printedLay(row.notes);
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
// src/
|
|
148
|
+
// src/convert/codec.ts
|
|
149
|
+
var LETTERS = {
|
|
150
|
+
s: { letter: "s" },
|
|
151
|
+
r: { letter: "r" },
|
|
152
|
+
g: { letter: "g" },
|
|
153
|
+
m: { letter: "m" },
|
|
154
|
+
p: { letter: "p" },
|
|
155
|
+
q: { letter: "d" },
|
|
156
|
+
// ধ — shuddha dha
|
|
157
|
+
n: { letter: "n" },
|
|
158
|
+
d: { letter: "d", komal: true },
|
|
159
|
+
// দ
|
|
160
|
+
t: { letter: "g", komal: true },
|
|
161
|
+
// জ্ঞ
|
|
162
|
+
k: { letter: "m", tivra: true },
|
|
163
|
+
// ক্ষ
|
|
164
|
+
u: { letter: "n", komal: true },
|
|
165
|
+
// ণ
|
|
166
|
+
v: { letter: "r", komal: true }
|
|
167
|
+
// ঋ
|
|
168
|
+
};
|
|
169
|
+
var STRUCTURALS = {
|
|
170
|
+
"%": () => ({ svara: ["madhyam", "s"] }),
|
|
171
|
+
// precomposed সা
|
|
172
|
+
"&": () => ({ svara: ["madhyam", "n"] })
|
|
173
|
+
// precomposed নি
|
|
174
|
+
};
|
|
175
|
+
var BAR_RUN_RE = /^(-)?([lLA[\]]+)$/;
|
|
176
|
+
function decodeBarRun(body) {
|
|
177
|
+
const m = body.match(BAR_RUN_RE);
|
|
178
|
+
if (!m || !/[lLA]/.test(m[2])) return void 0;
|
|
179
|
+
const notes = [];
|
|
180
|
+
if (m[1]) notes.push({ sustain: true });
|
|
181
|
+
const run = m[2];
|
|
182
|
+
let i = 0;
|
|
183
|
+
while (i < run.length) {
|
|
184
|
+
const ch = run[i];
|
|
185
|
+
if (ch === "l" || ch === "L") {
|
|
186
|
+
let j = i;
|
|
187
|
+
while (j < run.length && run[j] === ch) j++;
|
|
188
|
+
const strokes = (j - i) * (ch === "L" ? 2 : 1);
|
|
189
|
+
notes.push({ bar: strokes >= 4 ? "quad" : strokes >= 2 ? "double" : "single" });
|
|
190
|
+
i = j;
|
|
191
|
+
} else if (ch === "A") {
|
|
192
|
+
notes.push({ bar: "dari" });
|
|
193
|
+
i++;
|
|
194
|
+
} else {
|
|
195
|
+
notes.push({ brackets: [{ kind: "alt", edge: ch === "[" ? "open" : "close" }] });
|
|
196
|
+
i++;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return notes;
|
|
200
|
+
}
|
|
201
|
+
var KNOWN_RAW_RE = [
|
|
202
|
+
/^[wxyjEJ$<>=_]+$/,
|
|
203
|
+
// arc & line drawing segments
|
|
204
|
+
/^\|+$/,
|
|
205
|
+
// thin yugal-dari stop mark
|
|
206
|
+
/^[0-9!@#]{1,2}[fF]?:?$/,
|
|
207
|
+
// beat-number labels incl. 10–16 (sam tick = f/F, e.g. 1f / 1F / 2:)
|
|
208
|
+
/^[oOe`+,.;:iI~^*bcBCWXYFHZz'"?]$/
|
|
209
|
+
// dots, ticks, duration marks, legacy forms
|
|
210
|
+
];
|
|
211
|
+
var OPEN_BRACKETS = {
|
|
212
|
+
"{": "repeat",
|
|
213
|
+
"(": "skip",
|
|
214
|
+
"[": "alt"
|
|
215
|
+
};
|
|
216
|
+
var CLOSE_BRACKETS = {
|
|
217
|
+
"}": "repeat",
|
|
218
|
+
")": "skip",
|
|
219
|
+
"]": "alt"
|
|
220
|
+
};
|
|
221
|
+
function octaveOf(mark) {
|
|
222
|
+
if (mark === "f" || mark === "F") return "taar";
|
|
223
|
+
if (mark === "h" || mark === "H") return "mandra";
|
|
224
|
+
return "madhyam";
|
|
225
|
+
}
|
|
226
|
+
var BODY_RE = /^([0-9!@][fF]?)?([zj]*)(-)?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?((?:[srgmpqndtkuv][fh]?[i:]?)+)?(a)?([fh])?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?([0-9!@][fF]?)?(-)?$/;
|
|
227
|
+
function decodeChunk(chunk) {
|
|
228
|
+
const opens = [];
|
|
229
|
+
const closes = [];
|
|
230
|
+
let body = chunk;
|
|
231
|
+
while (body.length > 0) {
|
|
232
|
+
const kind = OPEN_BRACKETS[body[0]];
|
|
233
|
+
if (kind === void 0) break;
|
|
234
|
+
opens.push(kind);
|
|
235
|
+
body = body.slice(1);
|
|
236
|
+
}
|
|
237
|
+
while (body.length > 0) {
|
|
238
|
+
const kind = CLOSE_BRACKETS[body[body.length - 1]];
|
|
239
|
+
if (kind === void 0) break;
|
|
240
|
+
closes.unshift(kind);
|
|
241
|
+
body = body.slice(0, -1);
|
|
242
|
+
}
|
|
243
|
+
const finish = (notes, kind) => {
|
|
244
|
+
if (notes.length === 0) notes.push({});
|
|
245
|
+
const first = notes[0];
|
|
246
|
+
const last = notes[notes.length - 1];
|
|
247
|
+
if (opens.length > 0) first.brackets = [...opens.map((kind_) => ({ kind: kind_, edge: "open" })), ...first.brackets ?? []];
|
|
248
|
+
if (closes.length > 0) last.brackets = [...last.brackets ?? [], ...closes.map((kind_) => ({ kind: kind_, edge: "close" }))];
|
|
249
|
+
return { notes, kind };
|
|
250
|
+
};
|
|
251
|
+
if (body === "") return finish([{}], "decoded");
|
|
252
|
+
const barRun = decodeBarRun(body);
|
|
253
|
+
if (barRun) return finish(barRun, "decoded");
|
|
254
|
+
const structural = STRUCTURALS[body];
|
|
255
|
+
if (structural) return finish([structural()], "decoded");
|
|
256
|
+
if (KNOWN_RAW_RE.some((re) => re.test(body))) return finish([{}], "known");
|
|
257
|
+
const m = body.match(BODY_RE);
|
|
258
|
+
if (m) {
|
|
259
|
+
const [, , , hyphen, , kanLetter, kanOct, unitsStr, aakar, postOct, , trailKan, trailKanOct] = m;
|
|
260
|
+
const attachKan = (note, letter, oct) => {
|
|
261
|
+
const def = LETTERS[letter.toLowerCase()];
|
|
262
|
+
note.kan = [octaveOf(oct?.toLowerCase()), def.letter];
|
|
263
|
+
};
|
|
264
|
+
if (unitsStr) {
|
|
265
|
+
const units = unitsStr.match(/[srgmpqndtkuv][fh]?/g) ?? [];
|
|
266
|
+
const notes = units.map((unit) => {
|
|
267
|
+
const def = LETTERS[unit[0]];
|
|
268
|
+
const note = { svara: [octaveOf(unit[1]), def.letter] };
|
|
269
|
+
if (def.komal) note.komal = true;
|
|
270
|
+
if (def.tivra) note.tivra = true;
|
|
271
|
+
if (units.length > 1) note.durationMatra = 1 / units.length;
|
|
272
|
+
return note;
|
|
273
|
+
});
|
|
274
|
+
const last = notes[notes.length - 1];
|
|
275
|
+
if (postOct && last.svara[0] === "madhyam") {
|
|
276
|
+
last.svara = [octaveOf(postOct), last.svara[1]];
|
|
277
|
+
}
|
|
278
|
+
if (hyphen) notes[0].sustain = true;
|
|
279
|
+
if (kanLetter) attachKan(notes[0], kanLetter, kanOct);
|
|
280
|
+
if (trailKan && last.kan === void 0) attachKan(last, trailKan, trailKanOct);
|
|
281
|
+
return finish(notes, "decoded");
|
|
282
|
+
}
|
|
283
|
+
if (aakar) {
|
|
284
|
+
const note = hyphen ? { sustain: true } : { rest: true };
|
|
285
|
+
if (trailKan) attachKan(note, trailKan, trailKanOct);
|
|
286
|
+
else if (kanLetter) attachKan(note, kanLetter, kanOct);
|
|
287
|
+
return finish([note], "decoded");
|
|
288
|
+
}
|
|
289
|
+
if (hyphen && !kanLetter && !trailKan) {
|
|
290
|
+
return finish([{ sustain: true }], "decoded");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return finish([{}], "unknown");
|
|
294
|
+
}
|
|
295
|
+
function decodeCell(text) {
|
|
296
|
+
const chunks = text.split(/\s+/).filter((c) => c.length > 0);
|
|
297
|
+
const out = { notes: [], unknownTokens: [], knownRawTokens: [], decoded: false };
|
|
298
|
+
for (const chunk of chunks) {
|
|
299
|
+
const { notes, kind } = decodeChunk(chunk);
|
|
300
|
+
notes[0].raw = chunk;
|
|
301
|
+
if (kind === "decoded") out.decoded = true;
|
|
302
|
+
else if (kind === "known") out.knownRawTokens.push(chunk);
|
|
303
|
+
else out.unknownTokens.push(chunk);
|
|
304
|
+
out.notes.push(...notes);
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/player/repeats.ts
|
|
147
310
|
var UNDECODED_CELL = /[srgmpqnvtduka]/i;
|
|
148
311
|
var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true && !(n.raw !== void 0 && UNDECODED_CELL.test(n.raw));
|
|
312
|
+
var STOP_MARK = /^\|+$/;
|
|
313
|
+
var EPS = 1e-6;
|
|
314
|
+
var edges = (n, kind, edge) => n.brackets?.filter((b) => b.kind === kind && b.edge === edge).length ?? 0;
|
|
315
|
+
var sounds = (n) => n.svara !== void 0 || n.rest === true || n.sustain === true;
|
|
316
|
+
var matra = (notes) => notes.reduce((sum, n) => sum + (n.durationMatra ?? 1), 0);
|
|
317
|
+
function playOrder(doc) {
|
|
318
|
+
const tokens = [];
|
|
319
|
+
doc.parts.forEach(
|
|
320
|
+
(part, p) => part.rows.forEach(
|
|
321
|
+
(row, r) => row.notes.forEach((note, i) => tokens.push({ p, r, i, note, timed: note.bar === void 0 && !isSilent(note) }))
|
|
322
|
+
)
|
|
323
|
+
);
|
|
324
|
+
const { asthayi, stop, returns } = form(tokens);
|
|
325
|
+
const changed = changedNotes(tokens);
|
|
326
|
+
const out = [];
|
|
327
|
+
const sing = (lo, hi, useChanged) => {
|
|
328
|
+
const repeats = [];
|
|
329
|
+
let owed = 0;
|
|
330
|
+
for (let k = lo; k < hi; ) {
|
|
331
|
+
const t = tokens[k];
|
|
332
|
+
if (edges(t.note, "repeat", "open") > 0 && repeats.at(-1)?.open !== k) repeats.push({ open: k, again: false });
|
|
333
|
+
const again = repeats.at(-1)?.again === true;
|
|
334
|
+
const skipTo = again && edges(t.note, "skip", "open") > 0 ? skipEnd(tokens, k, hi) : -1;
|
|
335
|
+
if (skipTo >= 0) {
|
|
336
|
+
if (edges(tokens[skipTo].note, "repeat", "close") > 0) repeats.pop();
|
|
337
|
+
k = skipTo + 1;
|
|
338
|
+
owed = 0;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (owed > EPS && t.timed) owed -= matra([t.note]);
|
|
342
|
+
else {
|
|
343
|
+
const sub = useChanged || again ? changed.get(k) : void 0;
|
|
344
|
+
const fits = sub !== void 0 && matra(sub) >= matra([t.note]) - EPS;
|
|
345
|
+
for (const note of fits ? sub : [t.note]) out.push({ p: t.p, r: t.r, i: t.i, note });
|
|
346
|
+
owed = fits ? matra(sub) - matra([t.note]) : 0;
|
|
347
|
+
}
|
|
348
|
+
const loop = edges(t.note, "repeat", "close") > 0 ? repeats.at(-1) : void 0;
|
|
349
|
+
if (loop !== void 0 && !loop.again) {
|
|
350
|
+
loop.again = true;
|
|
351
|
+
k = loop.open;
|
|
352
|
+
owed = 0;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (loop !== void 0) repeats.pop();
|
|
356
|
+
k++;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
let from = 0;
|
|
360
|
+
for (const ret of returns) {
|
|
361
|
+
sing(from, ret.resume, false);
|
|
362
|
+
sing(asthayi, stop + 1, ret.changed);
|
|
363
|
+
from = ret.resume;
|
|
364
|
+
}
|
|
365
|
+
sing(from, tokens.length, false);
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
function form(tokens) {
|
|
369
|
+
const first = /* @__PURE__ */ new Map();
|
|
370
|
+
const last = /* @__PURE__ */ new Map();
|
|
371
|
+
tokens.forEach((t, k) => {
|
|
372
|
+
if (!t.timed) return;
|
|
373
|
+
const row = `${t.p}.${t.r}`;
|
|
374
|
+
if (!first.has(row)) first.set(row, k);
|
|
375
|
+
last.set(row, k);
|
|
376
|
+
});
|
|
377
|
+
const opens = [];
|
|
378
|
+
let opening = false;
|
|
379
|
+
tokens.forEach((t, k) => {
|
|
380
|
+
if (t.note.bar !== "double") return;
|
|
381
|
+
const row = `${t.p}.${t.r}`;
|
|
382
|
+
const before = (first.get(row) ?? Infinity) < k;
|
|
383
|
+
const after = (last.get(row) ?? -1) > k;
|
|
384
|
+
opening = before !== after ? after : !opening;
|
|
385
|
+
if (opening) opens.push(k);
|
|
386
|
+
});
|
|
387
|
+
const returns = [];
|
|
388
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
389
|
+
let at = -1;
|
|
390
|
+
let open = false;
|
|
391
|
+
let changed = false;
|
|
392
|
+
let end = false;
|
|
393
|
+
for (; k < tokens.length && !tokens[k].timed; k++) {
|
|
394
|
+
const n = tokens[k].note;
|
|
395
|
+
if (n.bar === "quad") end = true;
|
|
396
|
+
if (n.bar === "double" && !opens.includes(k)) at = k;
|
|
397
|
+
if (edges(n, "alt", "open") > 0) open = true;
|
|
398
|
+
if (open && edges(n, "alt", "close") > 0) {
|
|
399
|
+
at = k;
|
|
400
|
+
changed = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (at >= 0 && !end) returns.push({ resume: at + 1, changed });
|
|
404
|
+
}
|
|
405
|
+
const top = opens[0];
|
|
406
|
+
const asthayi = top !== void 0 && top < (returns[0]?.resume ?? Infinity) ? top : 0;
|
|
407
|
+
const asthayiEnd = Math.min(returns[0]?.resume ?? Infinity, opens.find((k) => k > asthayi) ?? Infinity, tokens.length);
|
|
408
|
+
const lastSung = tokens.reduce((last2, t, k) => t.timed ? k : last2, -1);
|
|
409
|
+
const stop = tokens.findIndex(
|
|
410
|
+
(t, k) => k >= asthayi && k < asthayiEnd && k < lastSung && t.note.above?.some((a) => STOP_MARK.test(a.raw ?? "")) === true
|
|
411
|
+
);
|
|
412
|
+
if (stop < 0) return { asthayi, stop, returns: [] };
|
|
413
|
+
const atClose = returns[0]?.resume === asthayiEnd && !tokens.slice(stop + 1, asthayiEnd).some((t) => t.timed);
|
|
414
|
+
return { asthayi, stop, returns: atClose ? returns.slice(1) : returns };
|
|
415
|
+
}
|
|
416
|
+
function changedNotes(tokens) {
|
|
417
|
+
const out = /* @__PURE__ */ new Map();
|
|
418
|
+
let row = "";
|
|
419
|
+
let inside = false;
|
|
420
|
+
tokens.forEach((t, k) => {
|
|
421
|
+
if (row !== `${t.p}.${t.r}`) {
|
|
422
|
+
row = `${t.p}.${t.r}`;
|
|
423
|
+
inside = false;
|
|
424
|
+
}
|
|
425
|
+
const notes = [];
|
|
426
|
+
for (const above of t.note.above ?? []) {
|
|
427
|
+
const read = above.raw !== void 0 && !sounds(above) && above.brackets === void 0;
|
|
428
|
+
for (const a of read ? decodeCell(above.raw).notes : [above]) {
|
|
429
|
+
if (edges(a, "alt", "open") > 0) inside = true;
|
|
430
|
+
if (inside && sounds(a)) notes.push(a);
|
|
431
|
+
if (edges(a, "alt", "close") > 0) inside = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (notes.length > 0) out.set(k, notes);
|
|
435
|
+
});
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
function skipEnd(tokens, k, hi) {
|
|
439
|
+
for (let depth = 0, j = k; j < hi; j++) {
|
|
440
|
+
depth += edges(tokens[j].note, "skip", "open") - edges(tokens[j].note, "skip", "close");
|
|
441
|
+
if (depth <= 0) return j;
|
|
442
|
+
}
|
|
443
|
+
return -1;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/player/schedule.ts
|
|
149
447
|
var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
|
|
150
448
|
var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
|
|
151
449
|
var MEEND_MARKS = { start: "w", end: "x", both: "xw" };
|
|
@@ -188,26 +486,45 @@ function graceTouches(ev) {
|
|
|
188
486
|
...ev.resh.map((midi, g) => ({ midi, beat: ev.beat + GRACE_STEP * (g + 1) }))
|
|
189
487
|
];
|
|
190
488
|
}
|
|
191
|
-
var rowOf = (path) => path.slice(0,
|
|
489
|
+
var rowOf = (path) => path.split(".").slice(0, 2).join(".");
|
|
490
|
+
var noteOf = (path) => Number(path.split(".")[2] ?? 0);
|
|
491
|
+
var byRow = (a, b) => {
|
|
492
|
+
const [ap = 0, ar = 0] = a.split(".").map(Number);
|
|
493
|
+
const [bp = 0, br = 0] = b.split(".").map(Number);
|
|
494
|
+
return ap - bp || ar - br;
|
|
495
|
+
};
|
|
192
496
|
function scheduleLines(schedule) {
|
|
193
497
|
const lines = [];
|
|
498
|
+
let note = Infinity;
|
|
194
499
|
schedule.events.forEach((ev, i) => {
|
|
195
|
-
|
|
500
|
+
const n = noteOf(ev.path);
|
|
501
|
+
if (lines.at(-1)?.row !== rowOf(ev.path) || n < note) lines.push({ row: rowOf(ev.path), first: i, beat: ev.beat, endBeat: 0 });
|
|
502
|
+
note = n;
|
|
196
503
|
});
|
|
197
504
|
lines.forEach((line, k) => line.endBeat = lines[k + 1]?.beat ?? schedule.totalBeats);
|
|
198
505
|
return lines;
|
|
199
506
|
}
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
507
|
+
function lineAt(lines, i) {
|
|
508
|
+
let k = 0;
|
|
509
|
+
while (k + 1 < lines.length && lines[k + 1].first <= i) k++;
|
|
510
|
+
return k;
|
|
511
|
+
}
|
|
512
|
+
function lineFor(lines, path, near) {
|
|
513
|
+
let row;
|
|
514
|
+
for (const line of lines) if (byRow(line.row, rowOf(path)) >= 0 && (row === void 0 || byRow(line.row, row) < 0)) row = line.row;
|
|
515
|
+
let best = -1;
|
|
516
|
+
lines.forEach((line, k) => {
|
|
517
|
+
if (line.row === row && (best < 0 || Math.abs(k - near) < Math.abs(best - near))) best = k;
|
|
206
518
|
});
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
519
|
+
return best;
|
|
520
|
+
}
|
|
521
|
+
function eventAt(events, path, near = -1) {
|
|
522
|
+
const lines = scheduleLines({ events, totalBeats: 0 });
|
|
523
|
+
const k = lineFor(lines, path, near < 0 ? -1 : lineAt(lines, near));
|
|
524
|
+
if (k < 0) return -1;
|
|
525
|
+
const end = lines[k + 1]?.first ?? events.length;
|
|
526
|
+
let i = lines[k].first;
|
|
527
|
+
if (lines[k].row === rowOf(path)) while (i < end - 1 && noteOf(events[i].path) < noteOf(path)) i++;
|
|
211
528
|
while (i > 0 && events[i].glide === true) i--;
|
|
212
529
|
return i;
|
|
213
530
|
}
|
|
@@ -219,67 +536,76 @@ function buildSchedule(doc, opts = {}) {
|
|
|
219
536
|
let cursor = 0;
|
|
220
537
|
let lastPitched = -1;
|
|
221
538
|
let samMatra;
|
|
222
|
-
let
|
|
539
|
+
let matra2 = 0;
|
|
223
540
|
const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);
|
|
224
541
|
const withLay = opts.lay !== false;
|
|
225
542
|
const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut", taal: doc.meta.taal }];
|
|
543
|
+
const rows = /* @__PURE__ */ new Map();
|
|
226
544
|
let scale = 1;
|
|
545
|
+
let layNow = "drut";
|
|
227
546
|
let taal = doc.meta.taal;
|
|
228
|
-
doc.parts.forEach(
|
|
229
|
-
part.rows.forEach((row, r) => {
|
|
547
|
+
doc.parts.forEach(
|
|
548
|
+
(part, p) => part.rows.forEach((row, r) => {
|
|
230
549
|
const lay = withLay ? rowLay(row) : void 0;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
550
|
+
if (lay !== void 0 && speeds[lay] !== scale) {
|
|
551
|
+
scale = speeds[lay];
|
|
552
|
+
layNow = lay;
|
|
553
|
+
}
|
|
554
|
+
if (row.taal !== void 0) taal = row.taal;
|
|
555
|
+
rows.set(`${p}.${r}`, { scale, lay: layNow, taal, glides: meendContinuations(row.notes) });
|
|
556
|
+
})
|
|
557
|
+
);
|
|
558
|
+
const visits = opts.repeats === true ? playOrder(doc) : doc.parts.flatMap((part, p) => part.rows.flatMap((row, r) => row.notes.map((note, i) => ({ p, r, i, note }))));
|
|
559
|
+
let rowNow = "";
|
|
560
|
+
for (const { p, r, i, note } of visits) {
|
|
561
|
+
const row = rows.get(`${p}.${r}`);
|
|
562
|
+
if (rowNow !== `${p}.${r}`) {
|
|
563
|
+
rowNow = `${p}.${r}`;
|
|
564
|
+
const last = tempo[tempo.length - 1];
|
|
565
|
+
if (row.scale !== last.scale || !sameTaal(row.taal, last.taal)) {
|
|
566
|
+
const seg = { beat: cursor, endBeat: cursor, scale: row.scale, lay: row.lay, taal: row.taal };
|
|
238
567
|
if (last.beat === cursor) tempo[tempo.length - 1] = seg;
|
|
239
568
|
else {
|
|
240
569
|
last.endBeat = cursor;
|
|
241
570
|
tempo.push(seg);
|
|
242
571
|
}
|
|
243
572
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (note.bar !== void 0 || isSilent(note)) return;
|
|
255
|
-
const path = `${p}.${r}.${i}`;
|
|
256
|
-
const written = note.durationMatra ?? 1;
|
|
257
|
-
const dur = written / scale;
|
|
258
|
-
const kan = graceMidis(note.kan, tonicPc, baseOctave);
|
|
259
|
-
const resh = graceMidis(note.resh, tonicPc, baseOctave);
|
|
260
|
-
if (note.svara !== void 0) {
|
|
261
|
-
events.push({
|
|
262
|
-
beat: cursor,
|
|
263
|
-
durationBeats: dur,
|
|
264
|
-
midi: svaraToMidi(note, tonicPc, baseOctave),
|
|
265
|
-
kan,
|
|
266
|
-
resh,
|
|
267
|
-
path,
|
|
268
|
-
kind: note.sustain === true ? "sustain" : "note",
|
|
269
|
-
...glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
|
|
270
|
-
});
|
|
271
|
-
lastPitched = events.length - 1;
|
|
272
|
-
} else if (note.sustain === true) {
|
|
273
|
-
if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
|
|
274
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "sustain" });
|
|
275
|
-
} else {
|
|
276
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
|
|
573
|
+
}
|
|
574
|
+
if (samMatra === void 0 && note.above !== void 0) {
|
|
575
|
+
for (const a of note.above) {
|
|
576
|
+
if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
|
|
577
|
+
samMatra = matra2;
|
|
578
|
+
break;
|
|
277
579
|
}
|
|
278
|
-
|
|
279
|
-
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (note.bar !== void 0 || isSilent(note)) continue;
|
|
583
|
+
const path = `${p}.${r}.${i}`;
|
|
584
|
+
const written = note.durationMatra ?? 1;
|
|
585
|
+
const dur = written / row.scale;
|
|
586
|
+
const kan = graceMidis(note.kan, tonicPc, baseOctave);
|
|
587
|
+
const resh = graceMidis(note.resh, tonicPc, baseOctave);
|
|
588
|
+
if (note.svara !== void 0) {
|
|
589
|
+
events.push({
|
|
590
|
+
beat: cursor,
|
|
591
|
+
durationBeats: dur,
|
|
592
|
+
midi: svaraToMidi(note, tonicPc, baseOctave),
|
|
593
|
+
kan,
|
|
594
|
+
resh,
|
|
595
|
+
path,
|
|
596
|
+
kind: note.sustain === true ? "sustain" : "note",
|
|
597
|
+
...row.glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
|
|
280
598
|
});
|
|
281
|
-
|
|
282
|
-
|
|
599
|
+
lastPitched = events.length - 1;
|
|
600
|
+
} else if (note.sustain === true) {
|
|
601
|
+
if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
|
|
602
|
+
events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "sustain" });
|
|
603
|
+
} else {
|
|
604
|
+
events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
|
|
605
|
+
}
|
|
606
|
+
cursor += dur;
|
|
607
|
+
matra2 += written;
|
|
608
|
+
}
|
|
283
609
|
const numBeats = tempo[0].taal?.numBeats;
|
|
284
610
|
const pickupMatra = samMatra !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samMatra % numBeats) % numBeats : 0;
|
|
285
611
|
tempo[tempo.length - 1].endBeat = cursor;
|
|
@@ -587,14 +913,6 @@ var TAAL_MODE_TEMPO = [
|
|
|
587
913
|
{ beat: 0, endBeat: Infinity, scale: 1, lay: "drut" }
|
|
588
914
|
];
|
|
589
915
|
var MEDIA_LEAD_SEC = 0.25;
|
|
590
|
-
function lineOf(lines, path) {
|
|
591
|
-
const [p = 0, r = 0] = path.split(".").map(Number);
|
|
592
|
-
const k = lines.findIndex((line) => {
|
|
593
|
-
const [lp = 0, lr = 0] = line.row.split(".").map(Number);
|
|
594
|
-
return lp > p || lp === p && lr >= r;
|
|
595
|
-
});
|
|
596
|
-
return k < 0 ? lines.length - 1 : k;
|
|
597
|
-
}
|
|
598
916
|
var seekableGrain = (Tone) => class extends Tone.GrainPlayer {
|
|
599
917
|
_start(time, offset, duration) {
|
|
600
918
|
super._start(time, offset === void 0 ? offset : this.toSeconds(offset) * this.playbackRate, duration);
|
|
@@ -655,9 +973,13 @@ var SwaralipiPlayer = class {
|
|
|
655
973
|
/* Practice: where the playhead is, what repeats, whose turn it is. */
|
|
656
974
|
/** The last note played or sought — what "this line" means to the controls. */
|
|
657
975
|
#cursor = null;
|
|
976
|
+
/** The cursor as an event index: which pass of a line sung more than once. */
|
|
977
|
+
#at = -1;
|
|
658
978
|
/** Where the next fresh play() starts (a seek made while stopped). */
|
|
659
979
|
#startAt = null;
|
|
660
980
|
#loop = null;
|
|
981
|
+
/** The repeat as line indices, the passes it holds. */
|
|
982
|
+
#span = null;
|
|
661
983
|
#echo = false;
|
|
662
984
|
/** With echo on, every other pass is the learner's: the melody rests. */
|
|
663
985
|
#turn = false;
|
|
@@ -683,6 +1005,7 @@ var SwaralipiPlayer = class {
|
|
|
683
1005
|
instruments: options.instruments ?? Object.keys(INSTRUMENTS),
|
|
684
1006
|
rhythm: options.rhythm ?? "tabla",
|
|
685
1007
|
tanpura: options.tanpura ?? true,
|
|
1008
|
+
repeats: options.repeats ?? true,
|
|
686
1009
|
previewLimitSec: options.previewLimitSec ?? Infinity,
|
|
687
1010
|
onHighlight: options.onHighlight,
|
|
688
1011
|
onLoop: options.onLoop,
|
|
@@ -830,22 +1153,25 @@ var SwaralipiPlayer = class {
|
|
|
830
1153
|
*/
|
|
831
1154
|
seek(path) {
|
|
832
1155
|
const plan = this.#plan();
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1156
|
+
if (plan !== void 0) this.#seekTo(plan, eventAt(plan.schedule.events, path, this.#at));
|
|
1157
|
+
}
|
|
1158
|
+
/** seek(), by event index — the pass, not just the line. */
|
|
1159
|
+
#seekTo(plan, i) {
|
|
1160
|
+
if (i < 0) return;
|
|
836
1161
|
const at = plan.schedule.events[i].path;
|
|
837
1162
|
this.#cursor = at;
|
|
1163
|
+
this.#at = i;
|
|
838
1164
|
this.#turn = false;
|
|
839
|
-
const k =
|
|
840
|
-
const [a, b] = this.#loopSpan(
|
|
841
|
-
if (this.#
|
|
1165
|
+
const k = lineAt(plan.lines, i);
|
|
1166
|
+
const [a, b] = this.#loopSpan();
|
|
1167
|
+
if (this.#span !== null && (k < a || k > b)) this.#setSpan(plan.lines, k, k);
|
|
842
1168
|
this.#emitLoop();
|
|
843
1169
|
if (this.#timeline !== void 0 && (this.#state === "playing" || this.#state === "paused")) {
|
|
844
1170
|
this.#applyLoop();
|
|
845
1171
|
this.#jump(this.#eventSec(i));
|
|
846
1172
|
if (this.#state === "paused") this.#emitHighlight(at);
|
|
847
1173
|
} else {
|
|
848
|
-
this.#startAt =
|
|
1174
|
+
this.#startAt = i;
|
|
849
1175
|
this.#emitHighlight(at);
|
|
850
1176
|
}
|
|
851
1177
|
}
|
|
@@ -858,20 +1184,41 @@ var SwaralipiPlayer = class {
|
|
|
858
1184
|
setLoop(from, to) {
|
|
859
1185
|
this.#turn = false;
|
|
860
1186
|
if (from === null) {
|
|
861
|
-
this.#loop = null;
|
|
1187
|
+
this.#span = this.#loop = null;
|
|
862
1188
|
this.#echo = false;
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const here = this.#cursor === null ? -1 : lineOf(lines, this.#cursor);
|
|
867
|
-
const a = from === void 0 ? Math.max(here, 0) : lineOf(lines, from);
|
|
868
|
-
const b = to === void 0 ? a : lineOf(lines, to);
|
|
869
|
-
this.#loop = { from: lines[Math.min(a, b)].row, to: lines[Math.max(a, b)].row };
|
|
870
|
-
if (here < Math.min(a, b) || here > Math.max(a, b)) return this.seek(this.#loop.from);
|
|
1189
|
+
this.#applyLoop();
|
|
1190
|
+
this.#emitLoop();
|
|
1191
|
+
return;
|
|
871
1192
|
}
|
|
1193
|
+
const plan = this.#plan();
|
|
1194
|
+
if (plan === void 0 || plan.lines.length === 0) return;
|
|
1195
|
+
const { lines } = plan;
|
|
1196
|
+
const here = this.#at < 0 ? -1 : lineAt(lines, this.#at);
|
|
1197
|
+
const a = from === void 0 ? Math.max(here, 0) : this.#lineOf(lines, from, here);
|
|
1198
|
+
this.#repeat(plan, a, to === void 0 ? a : this.#lineOf(lines, to, a));
|
|
1199
|
+
}
|
|
1200
|
+
/** Repeat lines a..b (indices, either order); the playhead joins them. */
|
|
1201
|
+
#repeat(plan, a, b) {
|
|
1202
|
+
this.#setSpan(plan.lines, a, b);
|
|
1203
|
+
const [lo, hi] = this.#loopSpan();
|
|
1204
|
+
const here = this.#at < 0 ? -1 : lineAt(plan.lines, this.#at);
|
|
1205
|
+
if (here < lo || here > hi) return this.#seekTo(plan, plan.lines[lo].first);
|
|
872
1206
|
this.#applyLoop();
|
|
873
1207
|
this.#emitLoop();
|
|
874
1208
|
}
|
|
1209
|
+
/** The repeat's passes, and the rows they cover for the sheet's shading —
|
|
1210
|
+
* written order, since a span can cross a jump back to an earlier row. */
|
|
1211
|
+
#setSpan(lines, a, b) {
|
|
1212
|
+
this.#span = [Math.min(a, b), Math.max(a, b)];
|
|
1213
|
+
const rows = lines.slice(this.#span[0], this.#span[1] + 1).map((line) => line.row).sort(byRow);
|
|
1214
|
+
this.#loop = { from: rows[0], to: rows.at(-1) };
|
|
1215
|
+
}
|
|
1216
|
+
/** The line `path` names, at its pass nearest line `near`; the last line
|
|
1217
|
+
* when nothing sounds from there on. */
|
|
1218
|
+
#lineOf(lines, path, near) {
|
|
1219
|
+
const k = lineFor(lines, path, near);
|
|
1220
|
+
return k < 0 ? lines.length - 1 : k;
|
|
1221
|
+
}
|
|
875
1222
|
/**
|
|
876
1223
|
* Follow every pass of the repeat with a silent one: the tabla and tanpura
|
|
877
1224
|
* keep time and the highlight keeps moving, but the melody rests so the
|
|
@@ -893,22 +1240,22 @@ var SwaralipiPlayer = class {
|
|
|
893
1240
|
const plan = this.#plan();
|
|
894
1241
|
if (plan === void 0 || plan.lines.length === 0) return;
|
|
895
1242
|
const { lines, schedule } = plan;
|
|
896
|
-
const k = this.#
|
|
897
|
-
const into = k < 0 ? 0 : schedule.events[
|
|
1243
|
+
const k = this.#at < 0 ? -1 : lineAt(lines, this.#at);
|
|
1244
|
+
const into = k < 0 ? 0 : schedule.events[this.#at].beat - lines[k].beat;
|
|
898
1245
|
const step = delta < 0 && this.#state === "playing" && into > 1 ? delta + 1 : delta;
|
|
899
|
-
this
|
|
1246
|
+
this.#seekTo(plan, lines[Math.min(Math.max(k + step, 0), lines.length - 1)].first);
|
|
900
1247
|
}
|
|
901
1248
|
/** Move by single notes (rests and holds included — each is a column). */
|
|
902
1249
|
stepNote(delta) {
|
|
903
|
-
const
|
|
904
|
-
|
|
1250
|
+
const plan = this.#plan();
|
|
1251
|
+
const events = plan?.schedule.events ?? [];
|
|
1252
|
+
let i = this.#at;
|
|
905
1253
|
for (let left = Math.abs(delta); left > 0; left--) {
|
|
906
1254
|
do
|
|
907
1255
|
i += Math.sign(delta);
|
|
908
1256
|
while (events[i]?.glide === true);
|
|
909
1257
|
}
|
|
910
|
-
|
|
911
|
-
if (ev !== void 0) this.seek(ev.path);
|
|
1258
|
+
if (plan !== void 0 && events.length > 0) this.#seekTo(plan, Math.min(Math.max(i, 0), events.length - 1));
|
|
912
1259
|
}
|
|
913
1260
|
/**
|
|
914
1261
|
* Stop and rewind to the start. When sounding, the accompaniment (tabla /
|
|
@@ -1103,21 +1450,21 @@ var SwaralipiPlayer = class {
|
|
|
1103
1450
|
}
|
|
1104
1451
|
/** Grow the repeat (or start one from the current line) to take in `path`'s line. */
|
|
1105
1452
|
#extendLoop(path) {
|
|
1106
|
-
const
|
|
1107
|
-
if (lines.length === 0) return;
|
|
1108
|
-
const
|
|
1109
|
-
const
|
|
1110
|
-
const [a, b] = this.#
|
|
1111
|
-
this
|
|
1453
|
+
const plan = this.#plan();
|
|
1454
|
+
if (plan === void 0 || plan.lines.length === 0) return;
|
|
1455
|
+
const here = this.#at < 0 ? -1 : lineAt(plan.lines, this.#at);
|
|
1456
|
+
const k = this.#lineOf(plan.lines, path, here);
|
|
1457
|
+
const [a, b] = this.#span ?? (here < 0 ? [k, k] : [here, here]);
|
|
1458
|
+
this.#repeat(plan, Math.min(a, k), Math.max(b, k));
|
|
1112
1459
|
}
|
|
1113
1460
|
/** Shift+↑/↓: one more line above / below the repeat. */
|
|
1114
1461
|
#extendBy(delta) {
|
|
1115
|
-
const
|
|
1116
|
-
if (lines.length === 0) return;
|
|
1117
|
-
const here = this.#
|
|
1118
|
-
const [a, b] = this.#
|
|
1119
|
-
const k = Math.min(Math.max((delta < 0 ? a : b) + delta, 0), lines.length - 1);
|
|
1120
|
-
this.#
|
|
1462
|
+
const plan = this.#plan();
|
|
1463
|
+
if (plan === void 0 || plan.lines.length === 0) return;
|
|
1464
|
+
const here = this.#at < 0 ? 0 : lineAt(plan.lines, this.#at);
|
|
1465
|
+
const [a, b] = this.#span ?? [here, here];
|
|
1466
|
+
const k = Math.min(Math.max((delta < 0 ? a : b) + delta, 0), plan.lines.length - 1);
|
|
1467
|
+
this.#repeat(plan, Math.min(a, k), Math.max(b, k));
|
|
1121
1468
|
}
|
|
1122
1469
|
/**
|
|
1123
1470
|
* Check whether an instrument's samples are reachable (its CDN is up / the
|
|
@@ -1177,7 +1524,8 @@ var SwaralipiPlayer = class {
|
|
|
1177
1524
|
}
|
|
1178
1525
|
/** A new document starts with nothing repeated, from the top. */
|
|
1179
1526
|
#resetPractice() {
|
|
1180
|
-
this.#cursor = this.#startAt = this.#loop = null;
|
|
1527
|
+
this.#cursor = this.#startAt = this.#loop = this.#span = null;
|
|
1528
|
+
this.#at = -1;
|
|
1181
1529
|
this.#echo = this.#turn = false;
|
|
1182
1530
|
this.#listened = 0;
|
|
1183
1531
|
this.#emitLoop();
|
|
@@ -1191,12 +1539,9 @@ var SwaralipiPlayer = class {
|
|
|
1191
1539
|
const schedule = buildSchedule(this.#doc, this.#scheduleOpts());
|
|
1192
1540
|
return { schedule, lines: scheduleLines(schedule) };
|
|
1193
1541
|
}
|
|
1194
|
-
#lines() {
|
|
1195
|
-
return this.#plan()?.lines ?? [];
|
|
1196
|
-
}
|
|
1197
1542
|
/** The repeat as line indices (`[0, -1]`, i.e. empty, when off). */
|
|
1198
|
-
#loopSpan(
|
|
1199
|
-
return this.#
|
|
1543
|
+
#loopSpan() {
|
|
1544
|
+
return this.#span ?? [0, -1];
|
|
1200
1545
|
}
|
|
1201
1546
|
/** The repeat's lines on the arranged timeline, cached for the per-note
|
|
1202
1547
|
* checks (#hears), which run on every note. */
|
|
@@ -1225,8 +1570,8 @@ var SwaralipiPlayer = class {
|
|
|
1225
1570
|
#applyLoop() {
|
|
1226
1571
|
const transport = this.#tone?.getTransport();
|
|
1227
1572
|
if (transport === void 0 || this.#timeline === void 0) return;
|
|
1228
|
-
this.#loopLines = this.#loopSpan(
|
|
1229
|
-
if (this.#
|
|
1573
|
+
this.#loopLines = this.#loopSpan();
|
|
1574
|
+
if (this.#span === null) {
|
|
1230
1575
|
transport.loop = false;
|
|
1231
1576
|
return;
|
|
1232
1577
|
}
|
|
@@ -1237,8 +1582,8 @@ var SwaralipiPlayer = class {
|
|
|
1237
1582
|
* else the top. */
|
|
1238
1583
|
#startSec() {
|
|
1239
1584
|
const events = this.#timeline?.schedule.events ?? [];
|
|
1240
|
-
if (this.#startAt !== null) return this.#eventSec(Math.
|
|
1241
|
-
return this.#
|
|
1585
|
+
if (this.#startAt !== null) return this.#eventSec(Math.min(this.#startAt, events.length - 1));
|
|
1586
|
+
return this.#span === null ? 0 : this.#loopSec()[0];
|
|
1242
1587
|
}
|
|
1243
1588
|
/** Move the transport; notes still ringing from the old place are let go. */
|
|
1244
1589
|
#jump(sec) {
|
|
@@ -1327,7 +1672,8 @@ var SwaralipiPlayer = class {
|
|
|
1327
1672
|
return {
|
|
1328
1673
|
tonic: this.#opts.tonic,
|
|
1329
1674
|
baseOctave: this.#opts.baseOctave,
|
|
1330
|
-
laySpeeds: this.#opts.laySpeeds
|
|
1675
|
+
laySpeeds: this.#opts.laySpeeds,
|
|
1676
|
+
repeats: this.#opts.repeats
|
|
1331
1677
|
};
|
|
1332
1678
|
}
|
|
1333
1679
|
/** Effective tempo: snapped to the taal's available tabla tempos while a
|
|
@@ -1345,9 +1691,10 @@ var SwaralipiPlayer = class {
|
|
|
1345
1691
|
* from the note it was on — slowing down a repeated line keeps repeating it. */
|
|
1346
1692
|
#reapply() {
|
|
1347
1693
|
if (this.#state !== "playing") return;
|
|
1348
|
-
const at = this.#
|
|
1694
|
+
const at = this.#at;
|
|
1349
1695
|
this.#halt();
|
|
1350
|
-
|
|
1696
|
+
this.#startAt = at < 0 ? null : at;
|
|
1697
|
+
void this.play();
|
|
1351
1698
|
}
|
|
1352
1699
|
/** The master bus everything routes through (created once, → destination). */
|
|
1353
1700
|
#ensureMaster(Tone) {
|
|
@@ -1391,13 +1738,13 @@ var SwaralipiPlayer = class {
|
|
|
1391
1738
|
while (lines[line + 1] !== void 0 && lines[line + 1].first <= i) line++;
|
|
1392
1739
|
const k = line;
|
|
1393
1740
|
const heard = (time) => time >= this.#fence && this.#hears(k);
|
|
1394
|
-
const
|
|
1741
|
+
const sounds2 = (time) => heard(time) && !this.#turn;
|
|
1395
1742
|
const at = ev.beat * spb + shift;
|
|
1396
1743
|
if (ev.glide !== true) {
|
|
1397
1744
|
for (const g of graceTouches(ev)) {
|
|
1398
1745
|
const graceName = midiToSampleName(g.midi);
|
|
1399
1746
|
transport.schedule((time) => {
|
|
1400
|
-
if (
|
|
1747
|
+
if (sounds2(time)) melody.triggerAttackRelease(graceName, spb * 0.4, time);
|
|
1401
1748
|
}, Math.max(g.beat * spb + shift, 0));
|
|
1402
1749
|
}
|
|
1403
1750
|
}
|
|
@@ -1413,7 +1760,7 @@ var SwaralipiPlayer = class {
|
|
|
1413
1760
|
];
|
|
1414
1761
|
const end = (last.beat + last.durationBeats - ev.beat) * spb;
|
|
1415
1762
|
transport.schedule((time) => {
|
|
1416
|
-
if (!
|
|
1763
|
+
if (!sounds2(time)) return;
|
|
1417
1764
|
triggerGlide(
|
|
1418
1765
|
Tone,
|
|
1419
1766
|
glide.buffers,
|
|
@@ -1426,7 +1773,7 @@ var SwaralipiPlayer = class {
|
|
|
1426
1773
|
}, at);
|
|
1427
1774
|
} else {
|
|
1428
1775
|
transport.schedule((time) => {
|
|
1429
|
-
if (
|
|
1776
|
+
if (sounds2(time)) melody.triggerAttackRelease(name, dur, time);
|
|
1430
1777
|
}, at);
|
|
1431
1778
|
}
|
|
1432
1779
|
}
|
|
@@ -1439,6 +1786,7 @@ var SwaralipiPlayer = class {
|
|
|
1439
1786
|
if (!heard(time)) return;
|
|
1440
1787
|
const turn = this.#turn;
|
|
1441
1788
|
this.#cursor = ev.path;
|
|
1789
|
+
this.#at = i;
|
|
1442
1790
|
draw.schedule(() => this.#emitHighlight(ev.path, turn), time);
|
|
1443
1791
|
}, at);
|
|
1444
1792
|
});
|
|
@@ -1675,7 +2023,8 @@ async function exportAudio(doc, options = {}) {
|
|
|
1675
2023
|
const schedule = buildSchedule(doc, {
|
|
1676
2024
|
tonic: options.tonic,
|
|
1677
2025
|
baseOctave: options.baseOctave,
|
|
1678
|
-
laySpeeds: options.laySpeeds
|
|
2026
|
+
laySpeeds: options.laySpeeds,
|
|
2027
|
+
repeats: options.repeats ?? true
|
|
1679
2028
|
});
|
|
1680
2029
|
const range = midiRange2(schedule);
|
|
1681
2030
|
const taalId = resolveTaal(doc, options.taal);
|
|
@@ -2048,7 +2397,8 @@ function exportMidi(doc, options = {}) {
|
|
|
2048
2397
|
const schedule = buildSchedule(doc, {
|
|
2049
2398
|
tonic: options.tonic,
|
|
2050
2399
|
baseOctave: options.baseOctave,
|
|
2051
|
-
laySpeeds: options.laySpeeds
|
|
2400
|
+
laySpeeds: options.laySpeeds,
|
|
2401
|
+
repeats: options.repeats ?? true
|
|
2052
2402
|
});
|
|
2053
2403
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
2054
2404
|
const drums = options.drums ? (seg) => {
|