@ugfoundation/swaralipi-js 0.14.0 → 0.14.2
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 +4 -0
- package/dist/{batch-D4HRATTH.js → batch-QEIW6XAA.js} +5 -5
- package/dist/{batch-D4HRATTH.js.map → batch-QEIW6XAA.js.map} +1 -1
- package/dist/{chunk-QQOC6LRA.js → chunk-6AKH2NLW.js} +6 -6
- package/dist/{chunk-QQOC6LRA.js.map → chunk-6AKH2NLW.js.map} +1 -1
- package/dist/{chunk-UV5OJYT3.js → chunk-CJI7A6AM.js} +3 -3
- package/dist/{chunk-UV5OJYT3.js.map → chunk-CJI7A6AM.js.map} +1 -1
- package/dist/{chunk-KOHSKHXI.js → chunk-K73Y46WU.js} +3 -3
- package/dist/{chunk-KOHSKHXI.js.map → chunk-K73Y46WU.js.map} +1 -1
- package/dist/{chunk-YBFM5NLW.js → chunk-WEXTZRRB.js} +29 -4
- package/dist/chunk-WEXTZRRB.js.map +1 -0
- package/dist/cli.cjs +4 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/convert/index.cjs +4 -4
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.d.cts +1 -1
- package/dist/convert/index.d.ts +1 -1
- package/dist/convert/index.js +3 -3
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{schedule-oJgg_bnc.d.cts → instruments-C3CbtYdm.d.ts} +171 -162
- package/dist/{schedule-DsVESieT.d.ts → instruments-CfHu4bSQ.d.cts} +171 -162
- package/dist/{lay-CXhC-HRO.d.cts → lay-Bi87MnkT.d.cts} +1 -1
- package/dist/{lay-CWlEhImP.d.ts → lay-CFLy_wd_.d.ts} +1 -1
- package/dist/player/index.cjs +163 -63
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +37 -17
- package/dist/player/index.d.ts +37 -17
- package/dist/player/index.js +139 -67
- package/dist/player/index.js.map +1 -1
- package/dist/spec/index.cjs +2 -2
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +12 -12
- package/dist/spec/index.d.ts +12 -12
- package/dist/spec/index.js +2 -2
- package/dist/viewer/index.cjs +127 -4
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +64 -4
- package/dist/viewer/index.d.ts +64 -4
- package/dist/viewer/index.js +124 -5
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-YBFM5NLW.js.map +0 -1
- package/dist/{schema-DZag9qkF.d.ts → schema-DyL0bMCf.d.cts} +36 -36
- package/dist/{schema-DZag9qkF.d.cts → schema-DyL0bMCf.d.ts} +36 -36
package/dist/player/index.cjs
CHANGED
|
@@ -13,7 +13,7 @@ var TAALS = {
|
|
|
13
13
|
shashthi: { id: "shashthi", numBeats: 6, bhaags: [2, 4], sam: 0 },
|
|
14
14
|
// ষষ্ঠী
|
|
15
15
|
teora: { id: "teora", numBeats: 7, bhaags: [3, 2, 2], sam: 0 },
|
|
16
|
-
//
|
|
16
|
+
// তেওরা
|
|
17
17
|
teentaal: { id: "teentaal", numBeats: 16, bhaags: [4, 4, 4, 4], sam: 0, khaali: [8] },
|
|
18
18
|
aratheka: { id: "aratheka", numBeats: 16, bhaags: [4, 4, 4, 4], sam: 0, khaali: [8] },
|
|
19
19
|
// আড়াঠেকা
|
|
@@ -146,6 +146,31 @@ function rowLay(row) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// src/player/lyrics.ts
|
|
149
|
+
function lyricCues(events, map, text) {
|
|
150
|
+
const tokens = [...text.matchAll(/\S+/g)].map((m) => ({
|
|
151
|
+
text: m[0],
|
|
152
|
+
start: m.index,
|
|
153
|
+
end: m.index + m[0].length,
|
|
154
|
+
line: text.slice(0, m.index).split("\n").length - 1
|
|
155
|
+
}));
|
|
156
|
+
const cues = [];
|
|
157
|
+
for (const ev of events) {
|
|
158
|
+
const span = map.get(ev.path);
|
|
159
|
+
const index = span ? tokens.findIndex((t) => t.start <= span[0] && span[0] < t.end) : -1;
|
|
160
|
+
if (index < 0) continue;
|
|
161
|
+
const token = tokens[index];
|
|
162
|
+
let cue = cues.at(-1);
|
|
163
|
+
if (!cue || cue.line !== token.line || index < cue.last) {
|
|
164
|
+
cue = { beat: ev.beat, line: token.line, last: -1, words: [] };
|
|
165
|
+
cues.push(cue);
|
|
166
|
+
}
|
|
167
|
+
if (index !== cue.last) {
|
|
168
|
+
cue.words.push({ beat: ev.beat, text: token.text });
|
|
169
|
+
cue.last = index;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return cues.map(({ beat, words }) => ({ beat, words }));
|
|
173
|
+
}
|
|
149
174
|
var MATCH = 2;
|
|
150
175
|
var GAP = 1;
|
|
151
176
|
var JUMP = 4;
|
|
@@ -2141,6 +2166,38 @@ function midiRange2(schedule) {
|
|
|
2141
2166
|
}
|
|
2142
2167
|
return lo <= hi ? { lo, hi } : void 0;
|
|
2143
2168
|
}
|
|
2169
|
+
var AHEAD_SEC = 2;
|
|
2170
|
+
async function renderOffline(Tone, build, durationSec, channels, sampleRate) {
|
|
2171
|
+
const chunked = "suspend" in OfflineAudioContext.prototype && "positionX" in AudioListener.prototype;
|
|
2172
|
+
const context = chunked ? new Tone.OfflineContext(new OfflineAudioContext(channels, Math.ceil(durationSec * sampleRate), sampleRate)) : new Tone.OfflineContext(channels, durationSec, sampleRate);
|
|
2173
|
+
const raw = context.rawContext;
|
|
2174
|
+
const original = Tone.getContext();
|
|
2175
|
+
Tone.setContext(context);
|
|
2176
|
+
try {
|
|
2177
|
+
await build(context);
|
|
2178
|
+
} finally {
|
|
2179
|
+
Tone.setContext(original);
|
|
2180
|
+
}
|
|
2181
|
+
const clock = context;
|
|
2182
|
+
const run = (until) => {
|
|
2183
|
+
while (clock._currentTime <= Math.min(until, durationSec)) {
|
|
2184
|
+
clock.emit("tick");
|
|
2185
|
+
clock._currentTime += 128 / sampleRate;
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
if (chunked) {
|
|
2189
|
+
run(2 * AHEAD_SEC);
|
|
2190
|
+
for (let t = AHEAD_SEC; t < durationSec; t += AHEAD_SEC) {
|
|
2191
|
+
void raw.suspend(t).then(() => {
|
|
2192
|
+
run(t + 2 * AHEAD_SEC);
|
|
2193
|
+
void raw.resume();
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
} else {
|
|
2197
|
+
run(Infinity);
|
|
2198
|
+
}
|
|
2199
|
+
return raw.startRendering();
|
|
2200
|
+
}
|
|
2144
2201
|
function pickDef(id, o) {
|
|
2145
2202
|
const def = o.customInstruments?.[id] ?? INSTRUMENTS[id];
|
|
2146
2203
|
if (def === void 0) return INSTRUMENTS.harmonium;
|
|
@@ -2169,7 +2226,8 @@ async function exportAudio(doc, options = {}) {
|
|
|
2169
2226
|
tonic: options.tonic,
|
|
2170
2227
|
baseOctave: options.baseOctave,
|
|
2171
2228
|
laySpeeds: options.laySpeeds,
|
|
2172
|
-
repeats: options.repeats ?? true
|
|
2229
|
+
repeats: options.repeats ?? true,
|
|
2230
|
+
excerpt: options.excerpt
|
|
2173
2231
|
});
|
|
2174
2232
|
const range = midiRange2(schedule);
|
|
2175
2233
|
const taalId = resolveTaal(doc, options.taal);
|
|
@@ -2182,7 +2240,8 @@ async function exportAudio(doc, options = {}) {
|
|
|
2182
2240
|
const def = pickDef(id, options);
|
|
2183
2241
|
const shift = rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
2184
2242
|
const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
|
|
2185
|
-
const rendered = await
|
|
2243
|
+
const rendered = await renderOffline(
|
|
2244
|
+
Tone,
|
|
2186
2245
|
async ({ transport }) => {
|
|
2187
2246
|
transport.bpm.value = bpm;
|
|
2188
2247
|
const master = new Tone.Gain(1).toDestination();
|
|
@@ -2329,11 +2388,16 @@ async function exportAudio(doc, options = {}) {
|
|
|
2329
2388
|
for (let c = 0; c < rendered.numberOfChannels; c++) chans.push(rendered.getChannelData(c));
|
|
2330
2389
|
const bytes = format === "mp3" ? await encodeMp3(chans, rendered.sampleRate, options.mp3Kbps) : encodeWav(chans, rendered.sampleRate);
|
|
2331
2390
|
const mimeType = format === "mp3" ? "audio/mpeg" : "audio/wav";
|
|
2391
|
+
const cues = options.lyrics === void 0 ? void 0 : lyricCues(schedule.events, alignLyrics(doc, options.lyrics), options.lyrics).map((cue) => ({
|
|
2392
|
+
sec: cue.beat * spb + shift,
|
|
2393
|
+
words: cue.words.map((word) => ({ sec: word.beat * spb + shift, text: word.text }))
|
|
2394
|
+
}));
|
|
2332
2395
|
return {
|
|
2333
2396
|
blob: new Blob([bytes], { type: mimeType }),
|
|
2334
2397
|
filename: `${safeName(doc.meta.title)}.${format}`,
|
|
2335
2398
|
mimeType,
|
|
2336
|
-
durationSec: rendered.duration
|
|
2399
|
+
durationSec: rendered.duration,
|
|
2400
|
+
...cues === void 0 ? {} : { cues }
|
|
2337
2401
|
};
|
|
2338
2402
|
}
|
|
2339
2403
|
function toInt16(f) {
|
|
@@ -2400,22 +2464,32 @@ async function loadLame() {
|
|
|
2400
2464
|
);
|
|
2401
2465
|
}
|
|
2402
2466
|
}
|
|
2403
|
-
async function
|
|
2467
|
+
async function mp3Stream(sampleRate = 44100, kbps = 192, channels = 1) {
|
|
2404
2468
|
const Mp3Encoder = await loadLame();
|
|
2405
|
-
const
|
|
2406
|
-
const enc = new Mp3Encoder(numCh, sampleRate, kbps);
|
|
2407
|
-
const left = floatToInt16(channels[0] ?? new Float32Array());
|
|
2408
|
-
const right = numCh > 1 ? floatToInt16(channels[1]) : void 0;
|
|
2409
|
-
const block = 1152;
|
|
2469
|
+
const enc = new Mp3Encoder(channels, sampleRate, kbps);
|
|
2410
2470
|
const out = [];
|
|
2411
|
-
|
|
2412
|
-
const l = left.subarray(i, i + block);
|
|
2413
|
-
const chunk = right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l);
|
|
2471
|
+
const keep = (chunk) => {
|
|
2414
2472
|
if (chunk.length > 0) out.push(new Uint8Array(chunk));
|
|
2415
|
-
}
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2473
|
+
};
|
|
2474
|
+
const block = 1152;
|
|
2475
|
+
return {
|
|
2476
|
+
push(left, right) {
|
|
2477
|
+
for (let i = 0; i < left.length; i += block) {
|
|
2478
|
+
const l = left.subarray(i, i + block);
|
|
2479
|
+
keep(right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l));
|
|
2480
|
+
}
|
|
2481
|
+
},
|
|
2482
|
+
end() {
|
|
2483
|
+
keep(enc.flush());
|
|
2484
|
+
return out;
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
async function encodeMp3(channels, sampleRate, kbps = 192) {
|
|
2489
|
+
const numCh = Math.min(channels.length, 2);
|
|
2490
|
+
const mp3 = await mp3Stream(sampleRate, kbps, numCh);
|
|
2491
|
+
mp3.push(floatToInt16(channels[0] ?? new Float32Array()), numCh > 1 ? floatToInt16(channels[1]) : void 0);
|
|
2492
|
+
return concatBytes(mp3.end());
|
|
2419
2493
|
}
|
|
2420
2494
|
|
|
2421
2495
|
// src/player/midi.ts
|
|
@@ -2461,6 +2535,11 @@ function trackChunk(raw, head) {
|
|
|
2461
2535
|
const track = [...head];
|
|
2462
2536
|
let last = 0;
|
|
2463
2537
|
for (const e of raw) {
|
|
2538
|
+
if (e.meta) {
|
|
2539
|
+
track.push(...vlq(e.tick - last), ...e.meta);
|
|
2540
|
+
last = e.tick;
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2464
2543
|
const status = e.status !== void 0 ? e.status | e.ch & 15 : (e.on ? 144 : 128) | e.ch & 15;
|
|
2465
2544
|
track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
|
|
2466
2545
|
last = e.tick;
|
|
@@ -2468,22 +2547,22 @@ function trackChunk(raw, head) {
|
|
|
2468
2547
|
track.push(...vlq(0), 255, 47, 0);
|
|
2469
2548
|
return [77, 84, 114, 107, ...u32be(track.length), ...track];
|
|
2470
2549
|
}
|
|
2471
|
-
function
|
|
2550
|
+
function melodyEvents(schedule, offset = 0) {
|
|
2472
2551
|
const melody = [];
|
|
2473
2552
|
const hasGlide = schedule.events.some((ev) => ev.glide === true);
|
|
2474
2553
|
if (hasGlide) melody.push(...bendRangeSetup());
|
|
2475
2554
|
schedule.events.forEach((ev, i) => {
|
|
2476
2555
|
if (ev.glide === true) return;
|
|
2477
2556
|
for (const g of graceTouches(ev)) {
|
|
2478
|
-
const on = Math.max(Math.round(g.beat * PPQ), 0);
|
|
2557
|
+
const on = Math.max(Math.round(g.beat * PPQ) + offset, 0);
|
|
2479
2558
|
melody.push({ tick: on, on: true, note: g.midi, vel: 70, ch: 0 });
|
|
2480
|
-
melody.push({ tick: Math.max(on + 1, Math.round((g.beat + GRACE_STEP) * PPQ)), on: false, note: g.midi, vel: 0, ch: 0 });
|
|
2559
|
+
melody.push({ tick: Math.max(on + 1, Math.round((g.beat + GRACE_STEP) * PPQ) + offset), on: false, note: g.midi, vel: 0, ch: 0 });
|
|
2481
2560
|
}
|
|
2482
2561
|
if (ev.midi === null) return;
|
|
2483
|
-
const start = Math.round(ev.beat * PPQ);
|
|
2562
|
+
const start = Math.round(ev.beat * PPQ) + offset;
|
|
2484
2563
|
const targets = glideTargets(schedule.events, i);
|
|
2485
2564
|
const last = targets[targets.length - 1] ?? ev;
|
|
2486
|
-
const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ), start + 1);
|
|
2565
|
+
const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ) + offset, start + 1);
|
|
2487
2566
|
melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
|
|
2488
2567
|
melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
|
|
2489
2568
|
let from = ev;
|
|
@@ -2493,71 +2572,89 @@ function encodeMidi(schedule, opts) {
|
|
|
2493
2572
|
const u = n / BEND_STEPS;
|
|
2494
2573
|
const beat = glideStart + (to.beat - glideStart) * u;
|
|
2495
2574
|
const semis = from.midi - ev.midi + (to.midi - from.midi) * sCurve(u);
|
|
2496
|
-
melody.push(bendEvent(Math.round(beat * PPQ), semis));
|
|
2575
|
+
melody.push(bendEvent(Math.round(beat * PPQ) + offset, semis));
|
|
2497
2576
|
}
|
|
2498
2577
|
from = to;
|
|
2499
2578
|
}
|
|
2500
2579
|
if (targets.length > 0) melody.push(bendEvent(end, 0));
|
|
2501
2580
|
});
|
|
2581
|
+
return melody;
|
|
2582
|
+
}
|
|
2583
|
+
function drumEvents(schedule, drums, offset = 0) {
|
|
2584
|
+
const drum = [];
|
|
2585
|
+
for (const seg of schedule.tempo) {
|
|
2586
|
+
const pattern = typeof drums === "function" ? drums(seg) : drums;
|
|
2587
|
+
if (!pattern?.length) continue;
|
|
2588
|
+
const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
|
|
2589
|
+
for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
|
|
2590
|
+
const note = DRUM_NOTE[pattern[k % pattern.length]];
|
|
2591
|
+
const t = Math.round((seg.beat + k / seg.scale) * PPQ) + offset;
|
|
2592
|
+
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
2593
|
+
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
return drum;
|
|
2597
|
+
}
|
|
2598
|
+
function midiFile(chunks) {
|
|
2599
|
+
const format = chunks.length > 1 ? 1 : 0;
|
|
2600
|
+
const header = [77, 84, 104, 100, ...u32be(6), 0, format, chunks.length >> 8 & 255, chunks.length & 255, PPQ >> 8 & 255, PPQ & 255];
|
|
2601
|
+
return new Uint8Array([...header, ...chunks.flat()]);
|
|
2602
|
+
}
|
|
2603
|
+
function meta(type, data) {
|
|
2604
|
+
return [255, type, ...vlq(data.length), ...data];
|
|
2605
|
+
}
|
|
2606
|
+
function encodeMidi(schedule, opts) {
|
|
2502
2607
|
const usPerBeat = Math.round(6e7 / opts.bpm);
|
|
2503
2608
|
const head = [];
|
|
2504
2609
|
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
2505
2610
|
head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
|
|
2506
|
-
const chunks = [trackChunk(
|
|
2611
|
+
const chunks = [trackChunk(melodyEvents(schedule), head)];
|
|
2507
2612
|
if (typeof opts.drums === "function" || opts.drums && opts.drums.length > 0) {
|
|
2508
|
-
const drum =
|
|
2509
|
-
for (const seg of schedule.tempo) {
|
|
2510
|
-
const pattern = typeof opts.drums === "function" ? opts.drums(seg) : opts.drums;
|
|
2511
|
-
if (!pattern?.length) continue;
|
|
2512
|
-
const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
|
|
2513
|
-
for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
|
|
2514
|
-
const note = DRUM_NOTE[pattern[k % pattern.length]];
|
|
2515
|
-
const t = Math.round((seg.beat + k / seg.scale) * PPQ);
|
|
2516
|
-
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
2517
|
-
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2613
|
+
const drum = drumEvents(schedule, opts.drums);
|
|
2520
2614
|
chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
|
|
2521
2615
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
chunks.length >> 8 & 255,
|
|
2532
|
-
chunks.length & 255,
|
|
2533
|
-
PPQ >> 8 & 255,
|
|
2534
|
-
PPQ & 255
|
|
2535
|
-
];
|
|
2536
|
-
return new Uint8Array([...header, ...chunks.flat()]);
|
|
2616
|
+
return midiFile(chunks);
|
|
2617
|
+
}
|
|
2618
|
+
function exportSchedule(doc, o) {
|
|
2619
|
+
return buildSchedule(doc, { tonic: o.tonic, baseOctave: o.baseOctave, laySpeeds: o.laySpeeds, repeats: o.repeats ?? true, excerpt: o.excerpt });
|
|
2620
|
+
}
|
|
2621
|
+
function taalDrums(seg) {
|
|
2622
|
+
if (seg.taal === null) return void 0;
|
|
2623
|
+
const id = seg.taal?.id;
|
|
2624
|
+
return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
|
|
2537
2625
|
}
|
|
2538
2626
|
function safeName2(title) {
|
|
2539
2627
|
return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
|
|
2540
2628
|
}
|
|
2541
2629
|
function exportMidi(doc, options = {}) {
|
|
2542
|
-
const schedule =
|
|
2543
|
-
tonic: options.tonic,
|
|
2544
|
-
baseOctave: options.baseOctave,
|
|
2545
|
-
laySpeeds: options.laySpeeds,
|
|
2546
|
-
repeats: options.repeats ?? true
|
|
2547
|
-
});
|
|
2630
|
+
const schedule = exportSchedule(doc, options);
|
|
2548
2631
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
2549
|
-
const
|
|
2550
|
-
const id = seg.taal?.id;
|
|
2551
|
-
if (seg.taal === null) return void 0;
|
|
2552
|
-
return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
|
|
2553
|
-
} : void 0;
|
|
2554
|
-
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
|
|
2632
|
+
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums: options.drums ? taalDrums : void 0 });
|
|
2555
2633
|
return {
|
|
2556
2634
|
blob: new Blob([bytes], { type: "audio/midi" }),
|
|
2557
2635
|
filename: `${safeName2(doc.meta.title)}.mid`,
|
|
2558
2636
|
mimeType: "audio/midi"
|
|
2559
2637
|
};
|
|
2560
2638
|
}
|
|
2639
|
+
function exportMidiSet(items, name) {
|
|
2640
|
+
const melody = [];
|
|
2641
|
+
const drum = [];
|
|
2642
|
+
let beat = 0;
|
|
2643
|
+
for (const { doc, options = {} } of items) {
|
|
2644
|
+
const schedule = exportSchedule(doc, options);
|
|
2645
|
+
const tick = beat * PPQ;
|
|
2646
|
+
const usPerBeat = Math.round(6e7 / (options.bpm ?? doc.meta.bpm ?? 90));
|
|
2647
|
+
const at = (bytes2) => ({ tick, on: false, note: 0, vel: 0, ch: 0, meta: bytes2 });
|
|
2648
|
+
melody.push(at(meta(81, [usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255])));
|
|
2649
|
+
melody.push(at(meta(6, utf8(options.name ?? doc.meta.title ?? ""))), ...melodyEvents(schedule, tick));
|
|
2650
|
+
if (options.drums) drum.push(...drumEvents(schedule, taalDrums, tick));
|
|
2651
|
+
beat += Math.ceil(schedule.totalBeats) + 2;
|
|
2652
|
+
}
|
|
2653
|
+
const chunks = [trackChunk(melody, [...vlq(0), ...meta(3, utf8(name))])];
|
|
2654
|
+
if (drum.length > 0) chunks.push(trackChunk(drum, [...vlq(0), ...meta(3, utf8("Drums"))]));
|
|
2655
|
+
const bytes = midiFile(chunks);
|
|
2656
|
+
return { blob: new Blob([bytes], { type: "audio/midi" }), filename: `${safeName2(name)}.mid`, mimeType: "audio/midi" };
|
|
2657
|
+
}
|
|
2561
2658
|
|
|
2562
2659
|
exports.ARC_RAW_RE = ARC_RAW_RE;
|
|
2563
2660
|
exports.DEFAULT_SOUNDS_BASE = DEFAULT_SOUNDS_BASE;
|
|
@@ -2585,12 +2682,15 @@ exports.encodeWav = encodeWav;
|
|
|
2585
2682
|
exports.eventAt = eventAt;
|
|
2586
2683
|
exports.exportAudio = exportAudio;
|
|
2587
2684
|
exports.exportMidi = exportMidi;
|
|
2685
|
+
exports.exportMidiSet = exportMidiSet;
|
|
2588
2686
|
exports.glideTargets = glideTargets;
|
|
2589
2687
|
exports.graceTouches = graceTouches;
|
|
2590
2688
|
exports.instrumentSampleUrl = instrumentSampleUrl;
|
|
2591
2689
|
exports.isArcRaw = isArcRaw;
|
|
2690
|
+
exports.lyricCues = lyricCues;
|
|
2592
2691
|
exports.meendContinuations = meendContinuations;
|
|
2593
2692
|
exports.midiToSampleName = midiToSampleName;
|
|
2693
|
+
exports.mp3Stream = mp3Stream;
|
|
2594
2694
|
exports.nearestSampleKey = nearestSampleKey;
|
|
2595
2695
|
exports.nearestTablaBpm = nearestTablaBpm;
|
|
2596
2696
|
exports.normalizeBase = normalizeBase;
|