@ugfoundation/swaralipi-js 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{batch-UETOBPHT.js → batch-E76HM4GM.js} +4 -4
- package/dist/{batch-UETOBPHT.js.map → batch-E76HM4GM.js.map} +1 -1
- package/dist/{chunk-UD2K4UIC.js → chunk-52EPQY47.js} +8 -6
- package/dist/chunk-52EPQY47.js.map +1 -0
- package/dist/{chunk-C6TRCMPL.js → chunk-GHWURC4U.js} +97 -5
- package/dist/chunk-GHWURC4U.js.map +1 -0
- package/dist/{chunk-ID44OP2N.js → chunk-MSVF43WQ.js} +7 -6
- package/dist/chunk-MSVF43WQ.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 +212 -20
- 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 +111 -20
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-BaYIJf8y.d.ts → schedule-8WFUBJqB.d.ts} +78 -3
- package/dist/{schedule-bBvy18MO.d.cts → schedule-CjPcD-Hf.d.cts} +78 -3
- 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 +264 -166
- 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 +99 -41
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-C6TRCMPL.js.map +0 -1
- package/dist/chunk-ID44OP2N.js.map +0 -1
- package/dist/chunk-UD2K4UIC.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
|
|
@@ -39,6 +39,8 @@ var SwaralipiPlayer = class {
|
|
|
39
39
|
/** Cached melody samplers keyed by instrument id, with the MIDI range each
|
|
40
40
|
* was loaded for (rebuilt when a song needs a wider range). */
|
|
41
41
|
#samplers = /* @__PURE__ */ new Map();
|
|
42
|
+
/** Raw sample buffers for the মীড় glide voice, keyed like #samplers. */
|
|
43
|
+
#glide = /* @__PURE__ */ new Map();
|
|
42
44
|
#tanpura;
|
|
43
45
|
#tanpuraKey = "";
|
|
44
46
|
#metronome;
|
|
@@ -290,6 +292,8 @@ var SwaralipiPlayer = class {
|
|
|
290
292
|
this.#halt();
|
|
291
293
|
for (const { sampler } of this.#samplers.values()) sampler.dispose();
|
|
292
294
|
this.#samplers.clear();
|
|
295
|
+
for (const { buffers } of this.#glide.values()) buffers.dispose();
|
|
296
|
+
this.#glide.clear();
|
|
293
297
|
this.#tanpura?.dispose();
|
|
294
298
|
this.#metronome?.dispose();
|
|
295
299
|
this.#tabla?.dispose();
|
|
@@ -392,7 +396,10 @@ var SwaralipiPlayer = class {
|
|
|
392
396
|
baseOctave: this.#opts.baseOctave
|
|
393
397
|
});
|
|
394
398
|
const range = midiRange(schedule);
|
|
395
|
-
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;
|
|
396
403
|
this.#buildRhythm(Tone, transport, base, spb);
|
|
397
404
|
this.#buildTanpura(Tone, transport, base, spb);
|
|
398
405
|
await loadedOrWarn(Tone);
|
|
@@ -401,21 +408,42 @@ var SwaralipiPlayer = class {
|
|
|
401
408
|
}
|
|
402
409
|
const draw = Tone.getDraw();
|
|
403
410
|
const shift = this.#opts.rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
404
|
-
|
|
411
|
+
schedule.events.forEach((ev, i) => {
|
|
405
412
|
const at = ev.beat * spb + shift;
|
|
406
|
-
if (ev.midi !== null) {
|
|
413
|
+
if (ev.midi !== null && ev.glide !== true) {
|
|
407
414
|
const name = midiToSampleName(ev.midi);
|
|
408
415
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
409
416
|
if (ev.kan !== null) {
|
|
410
417
|
const kanName = midiToSampleName(ev.kan);
|
|
411
418
|
transport.scheduleOnce((time) => melody.triggerAttackRelease(kanName, spb * 0.4, time), Math.max(at - spb * 0.12, 0));
|
|
412
419
|
}
|
|
413
|
-
|
|
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
|
+
}
|
|
414
442
|
}
|
|
415
443
|
transport.scheduleOnce((time) => {
|
|
416
444
|
draw.schedule(() => this.#emitHighlight(ev.path), time);
|
|
417
445
|
}, at);
|
|
418
|
-
}
|
|
446
|
+
});
|
|
419
447
|
const endAt = schedule.totalBeats * spb + shift + spb;
|
|
420
448
|
transport.scheduleOnce((time) => {
|
|
421
449
|
draw.schedule(() => this.#emitHighlight(null), time);
|
|
@@ -471,6 +499,19 @@ var SwaralipiPlayer = class {
|
|
|
471
499
|
this.#samplers.set(id, { sampler, lo: range?.lo ?? -Infinity, hi: range?.hi ?? Infinity });
|
|
472
500
|
return sampler;
|
|
473
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
|
+
}
|
|
474
515
|
#buildRhythm(Tone, transport, base, spb) {
|
|
475
516
|
this.#tabla?.dispose();
|
|
476
517
|
this.#tabla = void 0;
|
|
@@ -599,17 +640,39 @@ async function exportAudio(doc, options = {}) {
|
|
|
599
640
|
const master = new Tone.Gain(1).toDestination();
|
|
600
641
|
if (withMelody) {
|
|
601
642
|
const sampler = createSampler(Tone, def, base, range).connect(master);
|
|
602
|
-
|
|
603
|
-
|
|
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;
|
|
604
646
|
const at = ev.beat * spb + shift;
|
|
605
647
|
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
606
648
|
if (ev.kan !== null) {
|
|
607
|
-
const
|
|
608
|
-
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));
|
|
609
651
|
}
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
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
|
+
});
|
|
613
676
|
}
|
|
614
677
|
let tabla;
|
|
615
678
|
if (rhythm === "drums" && base) {
|
|
@@ -781,6 +844,17 @@ function utf8(s) {
|
|
|
781
844
|
function u32be(n) {
|
|
782
845
|
return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
|
|
783
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
|
+
}
|
|
784
858
|
var DRUM_NOTE = {
|
|
785
859
|
kick: 36,
|
|
786
860
|
snare: 38,
|
|
@@ -791,11 +865,12 @@ var DRUM_NOTE = {
|
|
|
791
865
|
tom3: 41
|
|
792
866
|
};
|
|
793
867
|
function trackChunk(raw, head) {
|
|
794
|
-
|
|
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));
|
|
795
870
|
const track = [...head];
|
|
796
871
|
let last = 0;
|
|
797
872
|
for (const e of raw) {
|
|
798
|
-
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;
|
|
799
874
|
track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
|
|
800
875
|
last = e.tick;
|
|
801
876
|
}
|
|
@@ -804,10 +879,14 @@ function trackChunk(raw, head) {
|
|
|
804
879
|
}
|
|
805
880
|
function encodeMidi(schedule, opts) {
|
|
806
881
|
const melody = [];
|
|
807
|
-
|
|
808
|
-
|
|
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;
|
|
809
886
|
const start = Math.round(ev.beat * PPQ);
|
|
810
|
-
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);
|
|
811
890
|
if (ev.kan !== null) {
|
|
812
891
|
const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);
|
|
813
892
|
melody.push({ tick: kOn, on: true, note: ev.kan, vel: 70, ch: 0 });
|
|
@@ -815,7 +894,19 @@ function encodeMidi(schedule, opts) {
|
|
|
815
894
|
}
|
|
816
895
|
melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
|
|
817
896
|
melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
|
|
818
|
-
|
|
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
|
+
});
|
|
819
910
|
const usPerBeat = Math.round(6e7 / opts.bpm);
|
|
820
911
|
const head = [];
|
|
821
912
|
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|