@ugfoundation/swaralipi-js 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/{batch-V4X3GN4N.js → batch-RUZGM2NR.js} +4 -4
- package/dist/{batch-V4X3GN4N.js.map → batch-RUZGM2NR.js.map} +1 -1
- package/dist/{chunk-6QW7MSJO.js → chunk-GHWURC4U.js} +98 -6
- package/dist/chunk-GHWURC4U.js.map +1 -0
- package/dist/{chunk-RTKWFJFI.js → chunk-ULK7NVKH.js} +8 -6
- package/dist/chunk-ULK7NVKH.js.map +1 -0
- package/dist/{chunk-ZS43BDP7.js → chunk-ZNVTOY7J.js} +7 -6
- package/dist/chunk-ZNVTOY7J.js.map +1 -0
- package/dist/cli.cjs +11 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +10 -7
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.d.cts +4 -3
- package/dist/convert/index.d.ts +4 -3
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +6 -4
- 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 +1 -1
- package/dist/player/index.cjs +222 -23
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +16 -4
- package/dist/player/index.d.ts +16 -4
- package/dist/player/index.js +120 -22
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-CAns78TD.d.ts → schedule-8WFUBJqB.d.ts} +82 -7
- package/dist/{schedule-D3JyEhuO.d.cts → schedule-CjPcD-Hf.d.cts} +82 -7
- package/dist/{schema-D3wyNRqM.d.ts → schema-D_86x4oV.d.cts} +21 -6
- package/dist/{schema-D3wyNRqM.d.cts → schema-D_86x4oV.d.ts} +21 -6
- package/dist/spec/index.cjs +6 -4
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +3 -3
- package/dist/spec/index.d.ts +3 -3
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +253 -172
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +16 -23
- package/dist/viewer/index.d.ts +16 -23
- package/dist/viewer/index.js +88 -47
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6QW7MSJO.js.map +0 -1
- package/dist/chunk-RTKWFJFI.js.map +0 -1
- package/dist/chunk-ZS43BDP7.js.map +0 -1
package/dist/player/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-
|
|
1
|
+
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-D_86x4oV.cjs';
|
|
2
2
|
import { T as TaalId } from '../taals-CjzBuZ35.cjs';
|
|
3
|
-
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-
|
|
4
|
-
export { b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, d as INSTRUMENTS, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as
|
|
3
|
+
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-CjPcD-Hf.cjs';
|
|
4
|
+
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as createBuffers, m as drumPattern, n as glideTargets, o as instrumentSampleUrl, p as isArcRaw, q as meendContinuations, r as nearestSampleKey, s as nearestTablaBpm, t as normalizeBase, u as probeUrl, v as quantizeLoopEnd, w as santoorUrls, x as subsetUrls, y as tablaBpms, z as tablaUrl, B as triggerGlide } from '../schedule-CjPcD-Hf.cjs';
|
|
5
5
|
import 'zod';
|
|
6
6
|
import 'tone';
|
|
7
7
|
|
|
@@ -301,5 +301,17 @@ declare function svaraToMidi(note: Note, tonicPc: number, baseOctave?: number):
|
|
|
301
301
|
declare function midiToSampleName(midi: number): string;
|
|
302
302
|
/** MIDI → santoor sample filename stem (`cs4`): lowercase, `#` → `s`. */
|
|
303
303
|
declare function sampleFileName(midi: number): string;
|
|
304
|
+
/**
|
|
305
|
+
* Logistic S-curve on [0, 1], normalised so f(0) = 0 and f(1) = 1.
|
|
306
|
+
*
|
|
307
|
+
* A মীড় is **not** a linear ramp. The hand (or the voice) accelerates away
|
|
308
|
+
* from the source pitch and decelerates into the target — "if the pull is too
|
|
309
|
+
* fast, the melody loses its vocal quality; if it is too slow, the note dies
|
|
310
|
+
* before reaching its destination". That S shape is the established model for
|
|
311
|
+
* portamento pitch contours (Yang & Chew's logistic fit, ISMIR 2016), and its
|
|
312
|
+
* coefficient has a direct musical meaning: `steepness` is how abruptly the
|
|
313
|
+
* pitch leaves one svara for the next.
|
|
314
|
+
*/
|
|
315
|
+
declare function sCurve(x: number, steepness?: number): number;
|
|
304
316
|
|
|
305
|
-
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sampleFileName, svaraSemitone, svaraToMidi };
|
|
317
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
package/dist/player/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-
|
|
1
|
+
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-D_86x4oV.js';
|
|
2
2
|
import { T as TaalId } from '../taals-CjzBuZ35.js';
|
|
3
|
-
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-
|
|
4
|
-
export { b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, d as INSTRUMENTS, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as
|
|
3
|
+
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-8WFUBJqB.js';
|
|
4
|
+
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as ToneModule, k as buildSchedule, l as createBuffers, m as drumPattern, n as glideTargets, o as instrumentSampleUrl, p as isArcRaw, q as meendContinuations, r as nearestSampleKey, s as nearestTablaBpm, t as normalizeBase, u as probeUrl, v as quantizeLoopEnd, w as santoorUrls, x as subsetUrls, y as tablaBpms, z as tablaUrl, B as triggerGlide } from '../schedule-8WFUBJqB.js';
|
|
5
5
|
import 'zod';
|
|
6
6
|
import 'tone';
|
|
7
7
|
|
|
@@ -301,5 +301,17 @@ declare function svaraToMidi(note: Note, tonicPc: number, baseOctave?: number):
|
|
|
301
301
|
declare function midiToSampleName(midi: number): string;
|
|
302
302
|
/** MIDI → santoor sample filename stem (`cs4`): lowercase, `#` → `s`. */
|
|
303
303
|
declare function sampleFileName(midi: number): string;
|
|
304
|
+
/**
|
|
305
|
+
* Logistic S-curve on [0, 1], normalised so f(0) = 0 and f(1) = 1.
|
|
306
|
+
*
|
|
307
|
+
* A মীড় is **not** a linear ramp. The hand (or the voice) accelerates away
|
|
308
|
+
* from the source pitch and decelerates into the target — "if the pull is too
|
|
309
|
+
* fast, the melody loses its vocal quality; if it is too slow, the note dies
|
|
310
|
+
* before reaching its destination". That S shape is the established model for
|
|
311
|
+
* portamento pitch contours (Yang & Chew's logistic fit, ISMIR 2016), and its
|
|
312
|
+
* coefficient has a direct musical meaning: `steepness` is how abruptly the
|
|
313
|
+
* pitch leaves one svara for the next.
|
|
314
|
+
*/
|
|
315
|
+
declare function sCurve(x: number, steepness?: number): number;
|
|
304
316
|
|
|
305
|
-
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sampleFileName, svaraSemitone, svaraToMidi };
|
|
317
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
package/dist/player/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, buildSchedule, quantizeLoopEnd, midiToSampleName, createSampler, tablaUrl, tablaShiftSemitones, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic } from '../chunk-
|
|
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-
|
|
1
|
+
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, buildSchedule, quantizeLoopEnd, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, tablaUrl, tablaShiftSemitones, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic, MEEND_HOLD, sCurve } from '../chunk-GHWURC4U.js';
|
|
2
|
+
export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, glideTargets, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-GHWURC4U.js';
|
|
3
3
|
import { TAALS } from '../chunk-6ETPFHZ6.js';
|
|
4
4
|
|
|
5
5
|
// src/player/player.ts
|
|
@@ -17,6 +17,13 @@ function midiRange(schedule) {
|
|
|
17
17
|
}
|
|
18
18
|
return lo <= hi ? { lo, hi } : void 0;
|
|
19
19
|
}
|
|
20
|
+
async function loadedOrWarn(Tone) {
|
|
21
|
+
try {
|
|
22
|
+
await Tone.loaded();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.warn("[swaralipi] some audio samples failed to load; continuing without them", err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
var SwaralipiPlayer = class {
|
|
21
28
|
#opts;
|
|
22
29
|
/** Per-song setBpm tweak: beats meta.bpm but is cleared by the next load(). */
|
|
@@ -32,6 +39,8 @@ var SwaralipiPlayer = class {
|
|
|
32
39
|
/** Cached melody samplers keyed by instrument id, with the MIDI range each
|
|
33
40
|
* was loaded for (rebuilt when a song needs a wider range). */
|
|
34
41
|
#samplers = /* @__PURE__ */ new Map();
|
|
42
|
+
/** Raw sample buffers for the মীড় glide voice, keyed like #samplers. */
|
|
43
|
+
#glide = /* @__PURE__ */ new Map();
|
|
35
44
|
#tanpura;
|
|
36
45
|
#tanpuraKey = "";
|
|
37
46
|
#metronome;
|
|
@@ -283,6 +292,8 @@ var SwaralipiPlayer = class {
|
|
|
283
292
|
this.#halt();
|
|
284
293
|
for (const { sampler } of this.#samplers.values()) sampler.dispose();
|
|
285
294
|
this.#samplers.clear();
|
|
295
|
+
for (const { buffers } of this.#glide.values()) buffers.dispose();
|
|
296
|
+
this.#glide.clear();
|
|
286
297
|
this.#tanpura?.dispose();
|
|
287
298
|
this.#metronome?.dispose();
|
|
288
299
|
this.#tabla?.dispose();
|
|
@@ -385,30 +396,54 @@ var SwaralipiPlayer = class {
|
|
|
385
396
|
baseOctave: this.#opts.baseOctave
|
|
386
397
|
});
|
|
387
398
|
const range = midiRange(schedule);
|
|
388
|
-
const
|
|
399
|
+
const id = this.#effectiveInstrument();
|
|
400
|
+
const def = this.#resolveDef(id) ?? INSTRUMENTS.harmonium;
|
|
401
|
+
const melody = this.#buildMelody(Tone, id, base, range);
|
|
402
|
+
const glide = schedule.events.some((ev) => ev.glide === true) ? this.#buildGlide(Tone, id, base, range) : void 0;
|
|
389
403
|
this.#buildRhythm(Tone, transport, base, spb);
|
|
390
404
|
this.#buildTanpura(Tone, transport, base, spb);
|
|
391
|
-
await Tone
|
|
405
|
+
await loadedOrWarn(Tone);
|
|
392
406
|
if (this.#tabla?.buffer?.loaded) {
|
|
393
407
|
this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
|
|
394
408
|
}
|
|
395
409
|
const draw = Tone.getDraw();
|
|
396
410
|
const shift = this.#opts.rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
397
|
-
|
|
411
|
+
schedule.events.forEach((ev, i) => {
|
|
398
412
|
const at = ev.beat * spb + shift;
|
|
399
|
-
if (ev.midi !== null) {
|
|
413
|
+
if (ev.midi !== null && ev.glide !== true) {
|
|
400
414
|
const name = midiToSampleName(ev.midi);
|
|
401
415
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
402
416
|
if (ev.kan !== null) {
|
|
403
417
|
const kanName = midiToSampleName(ev.kan);
|
|
404
418
|
transport.scheduleOnce((time) => melody.triggerAttackRelease(kanName, spb * 0.4, time), Math.max(at - spb * 0.12, 0));
|
|
405
419
|
}
|
|
406
|
-
|
|
420
|
+
const targets = glide !== void 0 ? glideTargets(schedule.events, i) : [];
|
|
421
|
+
if (targets.length > 0 && glide !== void 0) {
|
|
422
|
+
const last = targets[targets.length - 1];
|
|
423
|
+
const legs = [
|
|
424
|
+
{ midi: ev.midi, at: 0 },
|
|
425
|
+
...targets.map((t) => ({ midi: t.midi, at: (t.beat - ev.beat) * spb }))
|
|
426
|
+
];
|
|
427
|
+
const end = (last.beat + last.durationBeats - ev.beat) * spb;
|
|
428
|
+
transport.scheduleOnce((time) => {
|
|
429
|
+
triggerGlide(
|
|
430
|
+
Tone,
|
|
431
|
+
glide.buffers,
|
|
432
|
+
glide.urls,
|
|
433
|
+
this.#ensureMaster(Tone),
|
|
434
|
+
legs.map((l) => ({ midi: l.midi, at: time + l.at })),
|
|
435
|
+
time + end,
|
|
436
|
+
def.release ?? 1
|
|
437
|
+
);
|
|
438
|
+
}, at);
|
|
439
|
+
} else {
|
|
440
|
+
transport.scheduleOnce((time) => melody.triggerAttackRelease(name, dur, time), at);
|
|
441
|
+
}
|
|
407
442
|
}
|
|
408
443
|
transport.scheduleOnce((time) => {
|
|
409
444
|
draw.schedule(() => this.#emitHighlight(ev.path), time);
|
|
410
445
|
}, at);
|
|
411
|
-
}
|
|
446
|
+
});
|
|
412
447
|
const endAt = schedule.totalBeats * spb + shift + spb;
|
|
413
448
|
transport.scheduleOnce((time) => {
|
|
414
449
|
draw.schedule(() => this.#emitHighlight(null), time);
|
|
@@ -432,7 +467,7 @@ var SwaralipiPlayer = class {
|
|
|
432
467
|
transport.bpm.value = bpm;
|
|
433
468
|
const spb = 60 / bpm;
|
|
434
469
|
this.#buildRhythm(Tone, transport, base, spb);
|
|
435
|
-
await Tone
|
|
470
|
+
await loadedOrWarn(Tone);
|
|
436
471
|
if (this.#tabla?.buffer?.loaded) {
|
|
437
472
|
this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
|
|
438
473
|
}
|
|
@@ -464,6 +499,19 @@ var SwaralipiPlayer = class {
|
|
|
464
499
|
this.#samplers.set(id, { sampler, lo: range?.lo ?? -Infinity, hi: range?.hi ?? Infinity });
|
|
465
500
|
return sampler;
|
|
466
501
|
}
|
|
502
|
+
/** Build (or reuse) the মীড় glide buffers for `id` — the same samples the
|
|
503
|
+
* Sampler uses, kept as raw buffers so a note can be started once and its
|
|
504
|
+
* playbackRate automated. Cached and range-widened exactly like #buildMelody. */
|
|
505
|
+
#buildGlide(Tone, id, base, range) {
|
|
506
|
+
const cached = this.#glide.get(id);
|
|
507
|
+
if (cached && (range === void 0 || cached.lo <= range.lo && cached.hi >= range.hi)) return cached;
|
|
508
|
+
cached?.buffers.dispose();
|
|
509
|
+
const def = this.#resolveDef(id) ?? INSTRUMENTS.harmonium;
|
|
510
|
+
const built = createBuffers(Tone, def, base, range);
|
|
511
|
+
const entry = { ...built, lo: range?.lo ?? -Infinity, hi: range?.hi ?? Infinity };
|
|
512
|
+
this.#glide.set(id, entry);
|
|
513
|
+
return entry;
|
|
514
|
+
}
|
|
467
515
|
#buildRhythm(Tone, transport, base, spb) {
|
|
468
516
|
this.#tabla?.dispose();
|
|
469
517
|
this.#tabla = void 0;
|
|
@@ -592,17 +640,39 @@ async function exportAudio(doc, options = {}) {
|
|
|
592
640
|
const master = new Tone.Gain(1).toDestination();
|
|
593
641
|
if (withMelody) {
|
|
594
642
|
const sampler = createSampler(Tone, def, base, range).connect(master);
|
|
595
|
-
|
|
596
|
-
|
|
643
|
+
const glide = schedule.events.some((ev) => ev.glide === true) ? createBuffers(Tone, def, base, range) : void 0;
|
|
644
|
+
schedule.events.forEach((ev, i) => {
|
|
645
|
+
if (ev.midi === null || ev.glide === true) return;
|
|
597
646
|
const at = ev.beat * spb + shift;
|
|
598
647
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
599
648
|
if (ev.kan !== null) {
|
|
600
|
-
const
|
|
601
|
-
transport.scheduleOnce((t) => sampler.triggerAttackRelease(
|
|
649
|
+
const name = midiToSampleName(ev.kan);
|
|
650
|
+
transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, spb * 0.4, t), Math.max(at - spb * 0.12, 0));
|
|
602
651
|
}
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
652
|
+
const targets = glide !== void 0 ? glideTargets(schedule.events, i) : [];
|
|
653
|
+
if (targets.length > 0 && glide !== void 0) {
|
|
654
|
+
const last = targets[targets.length - 1];
|
|
655
|
+
const legs = [
|
|
656
|
+
{ midi: ev.midi, at: 0 },
|
|
657
|
+
...targets.map((t) => ({ midi: t.midi, at: (t.beat - ev.beat) * spb }))
|
|
658
|
+
];
|
|
659
|
+
const end = (last.beat + last.durationBeats - ev.beat) * spb;
|
|
660
|
+
transport.scheduleOnce((t) => {
|
|
661
|
+
triggerGlide(
|
|
662
|
+
Tone,
|
|
663
|
+
glide.buffers,
|
|
664
|
+
glide.urls,
|
|
665
|
+
master,
|
|
666
|
+
legs.map((l) => ({ midi: l.midi, at: t + l.at })),
|
|
667
|
+
t + end,
|
|
668
|
+
def.release ?? 1
|
|
669
|
+
);
|
|
670
|
+
}, at);
|
|
671
|
+
} else {
|
|
672
|
+
const name = midiToSampleName(ev.midi);
|
|
673
|
+
transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, dur, t), at);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
606
676
|
}
|
|
607
677
|
let tabla;
|
|
608
678
|
if (rhythm === "drums" && base) {
|
|
@@ -774,6 +844,17 @@ function utf8(s) {
|
|
|
774
844
|
function u32be(n) {
|
|
775
845
|
return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
|
|
776
846
|
}
|
|
847
|
+
var BEND_RANGE = 12;
|
|
848
|
+
var BEND_STEPS = 16;
|
|
849
|
+
function bendEvent(tick, semitones) {
|
|
850
|
+
const raw = Math.round(8192 + semitones / BEND_RANGE * 8192);
|
|
851
|
+
const value = Math.min(Math.max(raw, 0), 16383);
|
|
852
|
+
return { tick, on: false, status: 224, note: value & 127, vel: value >> 7 & 127, ch: 0 };
|
|
853
|
+
}
|
|
854
|
+
function bendRangeSetup() {
|
|
855
|
+
const cc = (num, val) => ({ tick: 0, on: false, status: 176, note: num, vel: val, ch: 0 });
|
|
856
|
+
return [cc(101, 0), cc(100, 0), cc(6, BEND_RANGE), cc(38, 0), cc(101, 127), cc(100, 127)];
|
|
857
|
+
}
|
|
777
858
|
var DRUM_NOTE = {
|
|
778
859
|
kick: 36,
|
|
779
860
|
snare: 38,
|
|
@@ -784,11 +865,12 @@ var DRUM_NOTE = {
|
|
|
784
865
|
tom3: 41
|
|
785
866
|
};
|
|
786
867
|
function trackChunk(raw, head) {
|
|
787
|
-
|
|
868
|
+
const rank = (e) => e.status !== void 0 ? 1 : e.on ? 2 : 0;
|
|
869
|
+
raw.sort((a, b) => a.tick - b.tick || rank(a) - rank(b));
|
|
788
870
|
const track = [...head];
|
|
789
871
|
let last = 0;
|
|
790
872
|
for (const e of raw) {
|
|
791
|
-
const status = (e.on ? 144 : 128) | e.ch & 15;
|
|
873
|
+
const status = e.status !== void 0 ? e.status | e.ch & 15 : (e.on ? 144 : 128) | e.ch & 15;
|
|
792
874
|
track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
|
|
793
875
|
last = e.tick;
|
|
794
876
|
}
|
|
@@ -797,10 +879,14 @@ function trackChunk(raw, head) {
|
|
|
797
879
|
}
|
|
798
880
|
function encodeMidi(schedule, opts) {
|
|
799
881
|
const melody = [];
|
|
800
|
-
|
|
801
|
-
|
|
882
|
+
const hasGlide = schedule.events.some((ev) => ev.glide === true);
|
|
883
|
+
if (hasGlide) melody.push(...bendRangeSetup());
|
|
884
|
+
schedule.events.forEach((ev, i) => {
|
|
885
|
+
if (ev.midi === null || ev.glide === true) return;
|
|
802
886
|
const start = Math.round(ev.beat * PPQ);
|
|
803
|
-
const
|
|
887
|
+
const targets = glideTargets(schedule.events, i);
|
|
888
|
+
const last = targets[targets.length - 1] ?? ev;
|
|
889
|
+
const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ), start + 1);
|
|
804
890
|
if (ev.kan !== null) {
|
|
805
891
|
const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);
|
|
806
892
|
melody.push({ tick: kOn, on: true, note: ev.kan, vel: 70, ch: 0 });
|
|
@@ -808,7 +894,19 @@ function encodeMidi(schedule, opts) {
|
|
|
808
894
|
}
|
|
809
895
|
melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
|
|
810
896
|
melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
|
|
811
|
-
|
|
897
|
+
let from = ev;
|
|
898
|
+
for (const to of targets) {
|
|
899
|
+
const glideStart = from.beat + (to.beat - from.beat) * MEEND_HOLD;
|
|
900
|
+
for (let n = 0; n <= BEND_STEPS; n++) {
|
|
901
|
+
const u = n / BEND_STEPS;
|
|
902
|
+
const beat = glideStart + (to.beat - glideStart) * u;
|
|
903
|
+
const semis = from.midi - ev.midi + (to.midi - from.midi) * sCurve(u);
|
|
904
|
+
melody.push(bendEvent(Math.round(beat * PPQ), semis));
|
|
905
|
+
}
|
|
906
|
+
from = to;
|
|
907
|
+
}
|
|
908
|
+
if (targets.length > 0) melody.push(bendEvent(end, 0));
|
|
909
|
+
});
|
|
812
910
|
const usPerBeat = Math.round(6e7 / opts.bpm);
|
|
813
911
|
const head = [];
|
|
814
912
|
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|