@ugfoundation/swaralipi-js 0.8.2 → 0.9.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/README.md +1 -1
- package/dist/{batch-6FPBT5DP.js → batch-NEM7JXHV.js} +5 -5
- package/dist/{batch-6FPBT5DP.js.map → batch-NEM7JXHV.js.map} +1 -1
- package/dist/{chunk-UMBKIGVQ.js → chunk-DOEFSS66.js} +5 -4
- package/dist/chunk-DOEFSS66.js.map +1 -0
- package/dist/{chunk-4HNT6OAT.js → chunk-MYVIQU4O.js} +12 -3
- package/dist/chunk-MYVIQU4O.js.map +1 -0
- package/dist/{chunk-4IVUPMRK.js → chunk-SEN266BY.js} +28 -13
- package/dist/chunk-SEN266BY.js.map +1 -0
- package/dist/{chunk-FULC22MN.js → chunk-Z6A3CP5V.js} +28 -19
- package/dist/chunk-Z6A3CP5V.js.map +1 -0
- package/dist/cli.cjs +33 -19
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/convert/index.cjs +31 -17
- 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 +38 -17
- 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/{lay-Csa4CD46.d.cts → lay-CZIzR9cM.d.ts} +16 -2
- package/dist/{lay-Ct9ky_EE.d.ts → lay-CfDlPhH2.d.cts} +16 -2
- package/dist/player/index.cjs +116 -71
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +11 -9
- package/dist/player/index.d.ts +11 -9
- package/dist/player/index.js +89 -66
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-B_UaluBi.d.cts → schedule-BaovCtmf.d.ts} +31 -12
- package/dist/{schedule-CdILTNsk.d.ts → schedule-CU-VECDH.d.cts} +31 -12
- package/dist/{schema-DGA_bNVZ.d.ts → schema-Cf_epUIC.d.cts} +333 -63
- package/dist/{schema-DGA_bNVZ.d.cts → schema-Cf_epUIC.d.ts} +333 -63
- package/dist/spec/index.cjs +38 -17
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +26 -8
- package/dist/spec/index.d.ts +26 -8
- package/dist/spec/index.js +2 -2
- package/dist/viewer/index.cjs +156 -73
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +25 -7
- package/dist/viewer/index.d.ts +25 -7
- package/dist/viewer/index.js +78 -27
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4HNT6OAT.js.map +0 -1
- package/dist/chunk-4IVUPMRK.js.map +0 -1
- package/dist/chunk-FULC22MN.js.map +0 -1
- package/dist/chunk-UMBKIGVQ.js.map +0 -1
package/dist/player/index.cjs
CHANGED
|
@@ -22,6 +22,11 @@ var TAALS = {
|
|
|
22
22
|
dhamaar: { id: "dhamaar", numBeats: 14, bhaags: [5, 2, 3, 4], sam: 0, khaali: [7] }
|
|
23
23
|
// ধামার
|
|
24
24
|
};
|
|
25
|
+
function sameTaal(a, b) {
|
|
26
|
+
if (a === b) return true;
|
|
27
|
+
if (!a || !b) return false;
|
|
28
|
+
return a.id === b.id && a.numBeats === b.numBeats && a.variation === b.variation && String(a.bhaags) === String(b.bhaags);
|
|
29
|
+
}
|
|
25
30
|
|
|
26
31
|
// src/spec/grace.ts
|
|
27
32
|
function graceNotes(grace) {
|
|
@@ -173,6 +178,13 @@ function glideTargets(events, i) {
|
|
|
173
178
|
}
|
|
174
179
|
return out;
|
|
175
180
|
}
|
|
181
|
+
var GRACE_STEP = 0.12;
|
|
182
|
+
function graceTouches(ev) {
|
|
183
|
+
return [
|
|
184
|
+
...ev.kan.map((midi, g) => ({ midi, beat: ev.beat - GRACE_STEP * (ev.kan.length - g) })),
|
|
185
|
+
...ev.resh.map((midi, g) => ({ midi, beat: ev.beat + GRACE_STEP * (g + 1) }))
|
|
186
|
+
];
|
|
187
|
+
}
|
|
176
188
|
var SAM_TICK = /^\d+[fF]$/;
|
|
177
189
|
function buildSchedule(doc, opts = {}) {
|
|
178
190
|
const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
|
|
@@ -184,18 +196,23 @@ function buildSchedule(doc, opts = {}) {
|
|
|
184
196
|
let matra = 0;
|
|
185
197
|
const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);
|
|
186
198
|
const withLay = opts.lay !== false;
|
|
187
|
-
const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut" }];
|
|
199
|
+
const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut", taal: doc.meta.taal }];
|
|
188
200
|
let scale = 1;
|
|
201
|
+
let taal = doc.meta.taal;
|
|
189
202
|
doc.parts.forEach((part, p) => {
|
|
190
203
|
part.rows.forEach((row, r) => {
|
|
191
204
|
const lay = withLay ? rowLay(row) : void 0;
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
const layChanged = lay !== void 0 && speeds[lay] !== scale;
|
|
206
|
+
const nextTaal = row.taal !== void 0 ? row.taal : taal;
|
|
207
|
+
if (layChanged || !sameTaal(nextTaal, taal)) {
|
|
208
|
+
if (layChanged) scale = speeds[lay];
|
|
209
|
+
taal = nextTaal;
|
|
194
210
|
const last = tempo[tempo.length - 1];
|
|
195
|
-
|
|
211
|
+
const seg = { beat: cursor, endBeat: cursor, scale, lay: layChanged ? lay : last.lay, taal };
|
|
212
|
+
if (last.beat === cursor) tempo[tempo.length - 1] = seg;
|
|
196
213
|
else {
|
|
197
214
|
last.endBeat = cursor;
|
|
198
|
-
tempo.push(
|
|
215
|
+
tempo.push(seg);
|
|
199
216
|
}
|
|
200
217
|
}
|
|
201
218
|
const glides = meendContinuations(row.notes);
|
|
@@ -212,12 +229,15 @@ function buildSchedule(doc, opts = {}) {
|
|
|
212
229
|
const path = `${p}.${r}.${i}`;
|
|
213
230
|
const written = note.durationMatra ?? 1;
|
|
214
231
|
const dur = written / scale;
|
|
232
|
+
const kan = graceMidis(note.kan, tonicPc, baseOctave);
|
|
233
|
+
const resh = graceMidis(note.resh, tonicPc, baseOctave);
|
|
215
234
|
if (note.svara !== void 0) {
|
|
216
235
|
events.push({
|
|
217
236
|
beat: cursor,
|
|
218
237
|
durationBeats: dur,
|
|
219
238
|
midi: svaraToMidi(note, tonicPc, baseOctave),
|
|
220
|
-
kan
|
|
239
|
+
kan,
|
|
240
|
+
resh,
|
|
221
241
|
path,
|
|
222
242
|
kind: note.sustain === true ? "sustain" : "note",
|
|
223
243
|
...glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
|
|
@@ -225,16 +245,16 @@ function buildSchedule(doc, opts = {}) {
|
|
|
225
245
|
lastPitched = events.length - 1;
|
|
226
246
|
} else if (note.sustain === true) {
|
|
227
247
|
if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
|
|
228
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan
|
|
248
|
+
events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "sustain" });
|
|
229
249
|
} else {
|
|
230
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan
|
|
250
|
+
events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
|
|
231
251
|
}
|
|
232
252
|
cursor += dur;
|
|
233
253
|
matra += written;
|
|
234
254
|
});
|
|
235
255
|
});
|
|
236
256
|
});
|
|
237
|
-
const numBeats =
|
|
257
|
+
const numBeats = tempo[0].taal?.numBeats;
|
|
238
258
|
const pickupMatra = samMatra !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samMatra % numBeats) % numBeats : 0;
|
|
239
259
|
tempo[tempo.length - 1].endBeat = cursor;
|
|
240
260
|
return { events, totalBeats: cursor, pickupBeats: pickupMatra / tempo[0].scale, tempo };
|
|
@@ -545,7 +565,7 @@ function midiRange(schedule) {
|
|
|
545
565
|
let lo = Infinity;
|
|
546
566
|
let hi = -Infinity;
|
|
547
567
|
for (const ev of schedule.events) {
|
|
548
|
-
for (const m of [ev.midi, ...ev.kan]) {
|
|
568
|
+
for (const m of [ev.midi, ...ev.kan, ...ev.resh]) {
|
|
549
569
|
if (m !== null) {
|
|
550
570
|
if (m < lo) lo = m;
|
|
551
571
|
if (m > hi) hi = m;
|
|
@@ -888,16 +908,26 @@ var SwaralipiPlayer = class {
|
|
|
888
908
|
#emitHighlight(path) {
|
|
889
909
|
this.#opts.onHighlight?.(path);
|
|
890
910
|
}
|
|
891
|
-
/** The
|
|
892
|
-
*
|
|
893
|
-
|
|
911
|
+
/** The taal one accompaniment segment plays: taal mode's, an explicit
|
|
912
|
+
* setTaal override (pinned across the whole song, তালফেরতা included), else
|
|
913
|
+
* the segment's own. `null` = a মুক্তছন্দ passage. */
|
|
914
|
+
#segmentTaal(seg) {
|
|
915
|
+
if (this.#taal !== void 0) return this.#taal;
|
|
916
|
+
if (!this.#taalAuto && this.#taalOverride !== void 0) return TAALS[this.#taalOverride];
|
|
917
|
+
return seg.taal;
|
|
918
|
+
}
|
|
919
|
+
/** The tabla variation to play: taal-mode taal's, the segment's own (auto
|
|
920
|
+
* mode), else none — an explicit setTaal override plays the default variation. */
|
|
921
|
+
#variation(taal) {
|
|
894
922
|
if (this.#taal !== void 0) return this.#taal.variation;
|
|
895
|
-
return this.#taalAuto ?
|
|
923
|
+
return this.#taalAuto ? taal?.variation : void 0;
|
|
896
924
|
}
|
|
897
925
|
/** Resolve the active taal id: taal mode's taal, an explicit override, else
|
|
898
|
-
* the doc's
|
|
926
|
+
* the doc's opening one (a তালফেরতা doc may carry it on its first row) — but
|
|
927
|
+
* only when it names a sampled taal. */
|
|
899
928
|
#taalId() {
|
|
900
|
-
const
|
|
929
|
+
const opening = this.#doc?.parts[0]?.rows[0]?.taal || this.#doc?.meta.taal;
|
|
930
|
+
const id = this.#taal?.id ?? (!this.#taalAuto && this.#taalOverride !== void 0 ? this.#taalOverride : opening?.id);
|
|
901
931
|
return id !== void 0 && id in TAALS ? id : void 0;
|
|
902
932
|
}
|
|
903
933
|
/** Resolve an instrument id to its def (custom overrides built-ins; the
|
|
@@ -988,14 +1018,15 @@ var SwaralipiPlayer = class {
|
|
|
988
1018
|
const mediaLead = Math.min(MEDIA_LEAD_SEC, spb);
|
|
989
1019
|
schedule.events.forEach((ev, i) => {
|
|
990
1020
|
const at = ev.beat * spb + shift;
|
|
1021
|
+
if (ev.glide !== true) {
|
|
1022
|
+
for (const g of graceTouches(ev)) {
|
|
1023
|
+
const graceName = midiToSampleName(g.midi);
|
|
1024
|
+
transport.scheduleOnce((time) => melody.triggerAttackRelease(graceName, spb * 0.4, time), Math.max(g.beat * spb + shift, 0));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
991
1027
|
if (ev.midi !== null && ev.glide !== true) {
|
|
992
1028
|
const name = midiToSampleName(ev.midi);
|
|
993
1029
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
994
|
-
ev.kan.forEach((k, gi) => {
|
|
995
|
-
const kanName = midiToSampleName(k);
|
|
996
|
-
const lead = spb * 0.12 * (ev.kan.length - gi);
|
|
997
|
-
transport.scheduleOnce((time) => melody.triggerAttackRelease(kanName, spb * 0.4, time), Math.max(at - lead, 0));
|
|
998
|
-
});
|
|
999
1030
|
const targets = glide !== void 0 ? glideTargets(schedule.events, i) : [];
|
|
1000
1031
|
if (targets.length > 0 && glide !== void 0) {
|
|
1001
1032
|
const last = targets[targets.length - 1];
|
|
@@ -1092,34 +1123,35 @@ var SwaralipiPlayer = class {
|
|
|
1092
1123
|
return entry;
|
|
1093
1124
|
}
|
|
1094
1125
|
/**
|
|
1095
|
-
* Accompaniment, one লয় segment at a time. A tempo change under the
|
|
1096
|
-
* is a real tempo change under the drum: the tabla loop is rebuilt at
|
|
1097
|
-
* segment's own bpm — its own recorded file when the set has one at that
|
|
1126
|
+
* Accompaniment, one লয় / তাল segment at a time. A tempo change under the
|
|
1127
|
+
* melody is a real tempo change under the drum: the tabla loop is rebuilt at
|
|
1128
|
+
* the segment's own bpm — its own recorded file when the set has one at that
|
|
1098
1129
|
* tempo, else the nearest file time-stretched by `GrainPlayer` (which
|
|
1099
1130
|
* stretches *without* transposing, unlike a plain playbackRate) — and the
|
|
1100
|
-
* click / drum / drone patterns re-space their interval. A
|
|
1101
|
-
*
|
|
1131
|
+
* click / drum / drone patterns re-space their interval. A তালফেরতা swaps the
|
|
1132
|
+
* theka the same way: each segment loops its own taal's file (or pattern, or
|
|
1133
|
+
* click cycle), and a মুক্তছন্দ segment rests. A document with neither mark
|
|
1134
|
+
* has exactly one segment, so this is the old single-loop path.
|
|
1102
1135
|
*/
|
|
1103
1136
|
#buildRhythm(Tone, transport, base, spb, segments, shift) {
|
|
1104
1137
|
this.#disposeTabla();
|
|
1105
1138
|
const master = this.#ensureMaster(Tone);
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
if (!wantsTabla && this.#opts.rhythm !== "drums" && (this.#opts.rhythm === "none" || base === void 0)) return;
|
|
1109
|
-
const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };
|
|
1110
|
-
const pattern = this.#opts.rhythm === "drums" ? drumPattern(taal) : [];
|
|
1111
|
-
const cycle = this.#taal?.numBeats ?? this.#doc?.meta.taal?.numBeats ?? 4;
|
|
1139
|
+
const rhythm = this.#opts.rhythm;
|
|
1140
|
+
if (rhythm === "none" || base === void 0 && rhythm !== "drums") return;
|
|
1112
1141
|
segments.forEach((seg, i) => {
|
|
1142
|
+
const own = this.#segmentTaal(seg);
|
|
1143
|
+
if (own === null) return;
|
|
1144
|
+
const taalId = own !== void 0 && own.id in TAALS ? own.id : void 0;
|
|
1113
1145
|
const from = i === 0 ? 0 : seg.beat * spb + shift;
|
|
1114
1146
|
const to = i === segments.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
1115
1147
|
const beatSec = spb / seg.scale;
|
|
1116
1148
|
const span = to === Infinity ? void 0 : to - from;
|
|
1117
|
-
if (
|
|
1149
|
+
if (rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0) {
|
|
1118
1150
|
const bpm = 60 / beatSec;
|
|
1119
1151
|
const fileBpm = nearestTablaBpm(bpm, tablaBpms(taalId));
|
|
1120
1152
|
const rate = bpm / fileBpm;
|
|
1121
1153
|
const semis = tablaShiftSemitones(this.#tonicSemitone());
|
|
1122
|
-
const url = tablaUrl(base, taalId, fileBpm, this.#variation());
|
|
1154
|
+
const url = tablaUrl(base, taalId, fileBpm, this.#variation(own));
|
|
1123
1155
|
const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
1124
1156
|
url,
|
|
1125
1157
|
loop: true,
|
|
@@ -1141,9 +1173,10 @@ var SwaralipiPlayer = class {
|
|
|
1141
1173
|
this.#tabla.push({ loop, fileSpb: 60 / fileBpm });
|
|
1142
1174
|
return;
|
|
1143
1175
|
}
|
|
1144
|
-
if (
|
|
1176
|
+
if (rhythm === "drums") {
|
|
1145
1177
|
this.#drums ??= createDrumKit(Tone).connect(master);
|
|
1146
1178
|
const drums = this.#drums;
|
|
1179
|
+
const pattern = drumPattern(this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : own) ?? { numBeats: 4, sam: 0 });
|
|
1147
1180
|
let beat2 = 0;
|
|
1148
1181
|
this.#scheduleIds.push(
|
|
1149
1182
|
transport.scheduleRepeat(
|
|
@@ -1161,6 +1194,7 @@ var SwaralipiPlayer = class {
|
|
|
1161
1194
|
}
|
|
1162
1195
|
this.#metronome ??= createMetronome(Tone, base).connect(master);
|
|
1163
1196
|
const metro = this.#metronome;
|
|
1197
|
+
const cycle = (this.#taal ?? seg.taal)?.numBeats ?? 4;
|
|
1164
1198
|
let beat = 0;
|
|
1165
1199
|
this.#scheduleIds.push(
|
|
1166
1200
|
transport.scheduleRepeat(
|
|
@@ -1219,7 +1253,7 @@ function midiRange2(schedule) {
|
|
|
1219
1253
|
let lo = Infinity;
|
|
1220
1254
|
let hi = -Infinity;
|
|
1221
1255
|
for (const ev of schedule.events) {
|
|
1222
|
-
for (const m of [ev.midi, ...ev.kan]) {
|
|
1256
|
+
for (const m of [ev.midi, ...ev.kan, ...ev.resh]) {
|
|
1223
1257
|
if (m !== null) {
|
|
1224
1258
|
if (m < lo) lo = m;
|
|
1225
1259
|
if (m > hi) hi = m;
|
|
@@ -1236,7 +1270,7 @@ function pickDef(id, o) {
|
|
|
1236
1270
|
return def;
|
|
1237
1271
|
}
|
|
1238
1272
|
function resolveTaal(doc, taal) {
|
|
1239
|
-
const id = taal !== void 0 && taal !== "auto" ? taal : doc.meta.taal?.id;
|
|
1273
|
+
const id = taal !== void 0 && taal !== "auto" ? taal : (doc.parts[0]?.rows[0]?.taal || doc.meta.taal)?.id;
|
|
1240
1274
|
return id !== void 0 && id in TAALS ? id : void 0;
|
|
1241
1275
|
}
|
|
1242
1276
|
function safeName(title) {
|
|
@@ -1259,7 +1293,7 @@ async function exportAudio(doc, options = {}) {
|
|
|
1259
1293
|
});
|
|
1260
1294
|
const range = midiRange2(schedule);
|
|
1261
1295
|
const taalId = resolveTaal(doc, options.taal);
|
|
1262
|
-
const
|
|
1296
|
+
const pinned = options.taal !== void 0 && options.taal !== "auto" && taalId !== void 0 ? TAALS[taalId] : void 0;
|
|
1263
1297
|
const usesTabla = rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && !!base;
|
|
1264
1298
|
const reqBpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
1265
1299
|
const bpm = usesTabla && taalId ? nearestTablaBpm(reqBpm, tablaBpms(taalId)) : reqBpm;
|
|
@@ -1276,14 +1310,14 @@ async function exportAudio(doc, options = {}) {
|
|
|
1276
1310
|
const sampler = createSampler(Tone, def, base, range).connect(master);
|
|
1277
1311
|
const glide = schedule.events.some((ev) => ev.glide === true) ? createBuffers(Tone, def, base, range) : void 0;
|
|
1278
1312
|
schedule.events.forEach((ev, i) => {
|
|
1279
|
-
if (ev.
|
|
1313
|
+
if (ev.glide === true) return;
|
|
1314
|
+
for (const g of graceTouches(ev)) {
|
|
1315
|
+
const name = midiToSampleName(g.midi);
|
|
1316
|
+
transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(g.beat * spb + shift, 0));
|
|
1317
|
+
}
|
|
1318
|
+
if (ev.midi === null) return;
|
|
1280
1319
|
const at = ev.beat * spb + shift;
|
|
1281
1320
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
1282
|
-
ev.kan.forEach((k, gi) => {
|
|
1283
|
-
const name = midiToSampleName(k);
|
|
1284
|
-
const lead = spb * 0.12 * (ev.kan.length - gi);
|
|
1285
|
-
transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(at - lead, 0));
|
|
1286
|
-
});
|
|
1287
1321
|
const targets = glide !== void 0 ? glideTargets(schedule.events, i) : [];
|
|
1288
1322
|
if (targets.length > 0 && glide !== void 0) {
|
|
1289
1323
|
const last = targets[targets.length - 1];
|
|
@@ -1311,22 +1345,22 @@ async function exportAudio(doc, options = {}) {
|
|
|
1311
1345
|
}
|
|
1312
1346
|
const tablas = [];
|
|
1313
1347
|
if (rhythm !== "none" && base) {
|
|
1314
|
-
const taal = taalId !== void 0 ? TAALS[taalId] : doc.meta.taal ?? { numBeats: 4, sam: 0 };
|
|
1315
|
-
const pattern = rhythm === "drums" ? drumPattern(taal) : [];
|
|
1316
1348
|
const drums = rhythm === "drums" ? createDrumKit(Tone).connect(master) : void 0;
|
|
1317
|
-
|
|
1318
|
-
const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : doc.meta.taal?.numBeats ?? 4;
|
|
1349
|
+
let metro;
|
|
1319
1350
|
const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));
|
|
1320
1351
|
schedule.tempo.forEach((seg, i) => {
|
|
1352
|
+
const own = pinned ?? seg.taal;
|
|
1353
|
+
if (own === null) return;
|
|
1354
|
+
const segId = own !== void 0 && own.id in TAALS ? own.id : void 0;
|
|
1321
1355
|
const from = i === 0 ? 0 : seg.beat * spb + shift;
|
|
1322
1356
|
const to = i === schedule.tempo.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
1323
1357
|
const beatSec = spb / seg.scale;
|
|
1324
1358
|
const span = to === Infinity ? void 0 : to - from;
|
|
1325
|
-
if (
|
|
1359
|
+
if (rhythm === "tabla" && segId !== void 0 && SAMPLED_TAALS.has(segId)) {
|
|
1326
1360
|
const segBpm = 60 / beatSec;
|
|
1327
|
-
const fileBpm = nearestTablaBpm(segBpm, tablaBpms(
|
|
1361
|
+
const fileBpm = nearestTablaBpm(segBpm, tablaBpms(segId));
|
|
1328
1362
|
const rate = segBpm / fileBpm;
|
|
1329
|
-
const url = tablaUrl(base,
|
|
1363
|
+
const url = tablaUrl(base, segId, fileBpm, pinned === void 0 ? own?.variation : void 0);
|
|
1330
1364
|
const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
1331
1365
|
url,
|
|
1332
1366
|
loop: true,
|
|
@@ -1345,6 +1379,7 @@ async function exportAudio(doc, options = {}) {
|
|
|
1345
1379
|
}
|
|
1346
1380
|
tablas.push({ loop, fileSpb: 60 / fileBpm });
|
|
1347
1381
|
} else if (drums !== void 0) {
|
|
1382
|
+
const pattern = drumPattern(segId !== void 0 ? TAALS[segId] : own ?? { numBeats: 4, sam: 0 });
|
|
1348
1383
|
let beat = 0;
|
|
1349
1384
|
transport.scheduleRepeat(
|
|
1350
1385
|
(time) => {
|
|
@@ -1356,12 +1391,14 @@ async function exportAudio(doc, options = {}) {
|
|
|
1356
1391
|
from,
|
|
1357
1392
|
span
|
|
1358
1393
|
);
|
|
1359
|
-
} else
|
|
1394
|
+
} else {
|
|
1395
|
+
const click = metro ??= createMetronome(Tone, base).connect(master);
|
|
1396
|
+
const cycle = own?.numBeats ?? 4;
|
|
1360
1397
|
let beat = 0;
|
|
1361
1398
|
transport.scheduleRepeat(
|
|
1362
1399
|
(time) => {
|
|
1363
|
-
|
|
1364
|
-
|
|
1400
|
+
click.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
|
|
1401
|
+
click.start(time);
|
|
1365
1402
|
beat += 1;
|
|
1366
1403
|
},
|
|
1367
1404
|
beatSec,
|
|
@@ -1556,17 +1593,17 @@ function encodeMidi(schedule, opts) {
|
|
|
1556
1593
|
const hasGlide = schedule.events.some((ev) => ev.glide === true);
|
|
1557
1594
|
if (hasGlide) melody.push(...bendRangeSetup());
|
|
1558
1595
|
schedule.events.forEach((ev, i) => {
|
|
1559
|
-
if (ev.
|
|
1596
|
+
if (ev.glide === true) return;
|
|
1597
|
+
for (const g of graceTouches(ev)) {
|
|
1598
|
+
const on = Math.max(Math.round(g.beat * PPQ), 0);
|
|
1599
|
+
melody.push({ tick: on, on: true, note: g.midi, vel: 70, ch: 0 });
|
|
1600
|
+
melody.push({ tick: Math.max(on + 1, Math.round((g.beat + GRACE_STEP) * PPQ)), on: false, note: g.midi, vel: 0, ch: 0 });
|
|
1601
|
+
}
|
|
1602
|
+
if (ev.midi === null) return;
|
|
1560
1603
|
const start = Math.round(ev.beat * PPQ);
|
|
1561
1604
|
const targets = glideTargets(schedule.events, i);
|
|
1562
1605
|
const last = targets[targets.length - 1] ?? ev;
|
|
1563
1606
|
const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ), start + 1);
|
|
1564
|
-
ev.kan.forEach((k, gi) => {
|
|
1565
|
-
const gap = Math.round(0.12 * PPQ);
|
|
1566
|
-
const kOn = Math.max(start - gap * (ev.kan.length - gi), 0);
|
|
1567
|
-
melody.push({ tick: kOn, on: true, note: k, vel: 70, ch: 0 });
|
|
1568
|
-
melody.push({ tick: Math.max(kOn + 1, start - gap * (ev.kan.length - gi - 1)), on: false, note: k, vel: 0, ch: 0 });
|
|
1569
|
-
});
|
|
1570
1607
|
melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
|
|
1571
1608
|
melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
|
|
1572
1609
|
let from = ev;
|
|
@@ -1587,14 +1624,18 @@ function encodeMidi(schedule, opts) {
|
|
|
1587
1624
|
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
1588
1625
|
head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
|
|
1589
1626
|
const chunks = [trackChunk(melody, head)];
|
|
1590
|
-
if (opts.drums && opts.drums.length > 0) {
|
|
1627
|
+
if (typeof opts.drums === "function" || opts.drums && opts.drums.length > 0) {
|
|
1591
1628
|
const drum = [];
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
|
|
1629
|
+
for (const seg of schedule.tempo) {
|
|
1630
|
+
const pattern = typeof opts.drums === "function" ? opts.drums(seg) : opts.drums;
|
|
1631
|
+
if (!pattern?.length) continue;
|
|
1632
|
+
const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
|
|
1633
|
+
for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
|
|
1634
|
+
const note = DRUM_NOTE[pattern[k % pattern.length]];
|
|
1635
|
+
const t = Math.round((seg.beat + k / seg.scale) * PPQ);
|
|
1636
|
+
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
1637
|
+
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
1638
|
+
}
|
|
1598
1639
|
}
|
|
1599
1640
|
chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
|
|
1600
1641
|
}
|
|
@@ -1624,9 +1665,11 @@ function exportMidi(doc, options = {}) {
|
|
|
1624
1665
|
laySpeeds: options.laySpeeds
|
|
1625
1666
|
});
|
|
1626
1667
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
|
|
1668
|
+
const drums = options.drums ? (seg) => {
|
|
1669
|
+
const id = seg.taal?.id;
|
|
1670
|
+
if (seg.taal === null) return void 0;
|
|
1671
|
+
return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
|
|
1672
|
+
} : void 0;
|
|
1630
1673
|
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
|
|
1631
1674
|
return {
|
|
1632
1675
|
blob: new Blob([bytes], { type: "audio/midi" }),
|
|
@@ -1638,6 +1681,7 @@ function exportMidi(doc, options = {}) {
|
|
|
1638
1681
|
exports.ARC_RAW_RE = ARC_RAW_RE;
|
|
1639
1682
|
exports.DEFAULT_SOUNDS_BASE = DEFAULT_SOUNDS_BASE;
|
|
1640
1683
|
exports.DRUM_KIT_BASE = DRUM_KIT_BASE;
|
|
1684
|
+
exports.GRACE_STEP = GRACE_STEP;
|
|
1641
1685
|
exports.INSTRUMENTS = INSTRUMENTS;
|
|
1642
1686
|
exports.LAYS = LAYS;
|
|
1643
1687
|
exports.LAY_SPEEDS = LAY_SPEEDS;
|
|
@@ -1659,6 +1703,7 @@ exports.encodeWav = encodeWav;
|
|
|
1659
1703
|
exports.exportAudio = exportAudio;
|
|
1660
1704
|
exports.exportMidi = exportMidi;
|
|
1661
1705
|
exports.glideTargets = glideTargets;
|
|
1706
|
+
exports.graceTouches = graceTouches;
|
|
1662
1707
|
exports.instrumentSampleUrl = instrumentSampleUrl;
|
|
1663
1708
|
exports.isArcRaw = isArcRaw;
|
|
1664
1709
|
exports.meendContinuations = meendContinuations;
|