@ugfoundation/swaralipi-js 0.13.1 → 0.14.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 +25 -0
- package/dist/{batch-XVQRHLTN.js → batch-J72GGBG2.js} +4 -4
- package/dist/{batch-XVQRHLTN.js.map → batch-J72GGBG2.js.map} +1 -1
- package/dist/{chunk-MORX7G6E.js → chunk-4WDAVRET.js} +3 -3
- package/dist/{chunk-MORX7G6E.js.map → chunk-4WDAVRET.js.map} +1 -1
- package/dist/{chunk-L6YZ3GC6.js → chunk-GIOGYJMM.js} +180 -19
- package/dist/chunk-GIOGYJMM.js.map +1 -0
- package/dist/{chunk-GKS4WZZ6.js → chunk-KUZSW4FN.js} +3 -3
- package/dist/{chunk-GKS4WZZ6.js.map → chunk-KUZSW4FN.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{schedule-V-fgepvP.d.cts → instruments-DTIuTlda.d.cts} +174 -140
- package/dist/{schedule-ClyON98C.d.ts → instruments-NcUb0ozc.d.ts} +174 -140
- package/dist/player/index.cjs +330 -84
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +40 -16
- package/dist/player/index.d.ts +40 -16
- package/dist/player/index.js +150 -69
- package/dist/player/index.js.map +1 -1
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +1 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +189 -31
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +62 -2
- package/dist/viewer/index.d.ts +62 -2
- package/dist/viewer/index.js +123 -4
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-L6YZ3GC6.js.map +0 -1
package/dist/player/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DZag9qkF.cjs';
|
|
2
2
|
import { a as LaySpeeds, T as TaalId } from '../lay-CXhC-HRO.cjs';
|
|
3
3
|
export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-CXhC-HRO.cjs';
|
|
4
|
-
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice, T as TempoSegment } from '../
|
|
5
|
-
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GRACE_STEP, d as GlideLeg, e as INSTRUMENTS, L as Line, M as MEEND_HOLD, P as PlayEvent,
|
|
4
|
+
import { I as InstrumentId, a as InstrumentDef, E as Excerpt, S as Schedule, D as DrumVoice, T as TempoSegment } from '../instruments-DTIuTlda.cjs';
|
|
5
|
+
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GRACE_STEP, d as GlideLeg, e as INSTRUMENTS, L as Line, f as LyricMap, M as MEEND_HOLD, P as PlayEvent, g as SALAMANDER_BASE, h as SAMPLED_TAALS, i as ScheduleOptions, j as TABLA_BPMS, k as TABLA_VARIATIONS, l as TONEJS_INSTRUMENTS_BASE, m as ToneModule, n as alignLyrics, o as buildSchedule, p as createBuffers, q as drumPattern, r as eventAt, s as glideTargets, t as graceTouches, u as instrumentSampleUrl, v as isArcRaw, w as lyricCues, x as meendContinuations, y as nearestSampleKey, z as nearestTablaBpm, B as normalizeBase, C as probeUrl, F as quantizeLoopEnd, H as santoorUrls, J as scheduleLines, K as subsetUrls, N as tablaBpms, O as tablaUrl, Q as triggerGlide } from '../instruments-DTIuTlda.cjs';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import 'tone';
|
|
8
8
|
|
|
@@ -140,8 +140,12 @@ declare class SwaralipiPlayer {
|
|
|
140
140
|
get loop(): LineRange | null;
|
|
141
141
|
/** Whether each repeat is followed by a silent pass for the learner. */
|
|
142
142
|
get echo(): boolean;
|
|
143
|
-
/** Set the document to play. Stops any current playback.
|
|
144
|
-
|
|
143
|
+
/** Set the document to play. Stops any current playback. With `excerpt`,
|
|
144
|
+
* only the notes singing those stretches of the song's text play, in order
|
|
145
|
+
* (see `alignLyrics`); the length, seeks and repeats all keep to them. */
|
|
146
|
+
load(doc: Swaralipi, { excerpt }?: {
|
|
147
|
+
excerpt?: Excerpt;
|
|
148
|
+
}): void;
|
|
145
149
|
/**
|
|
146
150
|
* Taal mode: loop one avartan of a taal's theka (rhythm only, no melody) —
|
|
147
151
|
* for a standalone theka player. `play()` loops indefinitely (tabla loop /
|
|
@@ -235,6 +239,10 @@ declare class SwaralipiPlayer {
|
|
|
235
239
|
|
|
236
240
|
type AudioFormat = 'wav' | 'mp3';
|
|
237
241
|
interface AudioExportOptions {
|
|
242
|
+
/** Play only the notes singing these stretches of the text (see alignLyrics). */
|
|
243
|
+
excerpt?: Excerpt;
|
|
244
|
+
/** The song's words: when given, the result carries their `cues` (an LRC's timing). */
|
|
245
|
+
lyrics?: string;
|
|
238
246
|
/** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */
|
|
239
247
|
soundsBaseUrl?: string;
|
|
240
248
|
/** Self-host the grand piano (see the player's `pianoBaseUrl`). */
|
|
@@ -289,6 +297,15 @@ interface AudioExportResult {
|
|
|
289
297
|
mimeType: string;
|
|
290
298
|
/** Rendered length in seconds. */
|
|
291
299
|
durationSec: number;
|
|
300
|
+
/** Each sung line and its words, in seconds on this file's timeline (given `lyrics`). */
|
|
301
|
+
cues?: LyricCue[];
|
|
302
|
+
}
|
|
303
|
+
interface LyricCue {
|
|
304
|
+
sec: number;
|
|
305
|
+
words: {
|
|
306
|
+
sec: number;
|
|
307
|
+
text: string;
|
|
308
|
+
}[];
|
|
292
309
|
}
|
|
293
310
|
/**
|
|
294
311
|
* Render a swaralipi doc to an audio Blob in the browser. Runs in an
|
|
@@ -304,11 +321,17 @@ declare function exportAudio(doc: Swaralipi, options?: AudioExportOptions): Prom
|
|
|
304
321
|
*/
|
|
305
322
|
declare function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array;
|
|
306
323
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
324
|
+
* A streaming MP3 encoder: feed 16-bit PCM in any number of pieces, get one MP3 — one encoder
|
|
325
|
+
* delay, so a track joined from several renders keeps its timing. lamejs is **dynamically
|
|
326
|
+
* imported** and an *optional* peer dependency (LGPL-3.0). Samples go in 1152-frame blocks (a
|
|
327
|
+
* multiple of 576, lamejs' preferred size).
|
|
311
328
|
*/
|
|
329
|
+
declare function mp3Stream(sampleRate?: number, kbps?: number, channels?: 1 | 2): Promise<{
|
|
330
|
+
push(left: Int16Array, right?: Int16Array): void;
|
|
331
|
+
end(): Uint8Array[];
|
|
332
|
+
}>;
|
|
333
|
+
/** Encode planar float channels to MP3 via lamejs (see mp3Stream). WAV needs no dependency, so
|
|
334
|
+
* only reach for this when a compressed file is wanted. */
|
|
312
335
|
declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?: number): Promise<Uint8Array>;
|
|
313
336
|
|
|
314
337
|
/**
|
|
@@ -329,6 +352,8 @@ declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?:
|
|
|
329
352
|
*/
|
|
330
353
|
|
|
331
354
|
interface MidiExportOptions {
|
|
355
|
+
/** Play only the notes singing these stretches of the text (see alignLyrics). */
|
|
356
|
+
excerpt?: Excerpt;
|
|
332
357
|
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
333
358
|
tonic?: string;
|
|
334
359
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
@@ -353,13 +378,6 @@ interface MidiExportResult {
|
|
|
353
378
|
filename: string;
|
|
354
379
|
mimeType: string;
|
|
355
380
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Encode a schedule to a Standard MIDI File. Melody rides channel 0; when
|
|
358
|
-
* `drums` is given, its per-matra voices ride a second GM channel-10 track
|
|
359
|
-
* (the file becomes format 1). `drums` is one pattern for the whole song, or a
|
|
360
|
-
* function picking each লয় / তাল segment's own (তালফেরতা; `undefined` = rest).
|
|
361
|
-
* Pure — no Tone, no browser — directly testable.
|
|
362
|
-
*/
|
|
363
381
|
declare function encodeMidi(schedule: Schedule, opts: {
|
|
364
382
|
bpm: number;
|
|
365
383
|
name?: string;
|
|
@@ -370,6 +388,12 @@ declare function encodeMidi(schedule: Schedule, opts: {
|
|
|
370
388
|
* beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.
|
|
371
389
|
*/
|
|
372
390
|
declare function exportMidi(doc: Swaralipi, options?: MidiExportOptions): MidiExportResult;
|
|
391
|
+
/** Several songs on one timeline (a whole dance-drama), back to back with two beats' rest:
|
|
392
|
+
* each song opens with its own tempo and a marker naming it; drums share one channel-10 track. */
|
|
393
|
+
declare function exportMidiSet(items: {
|
|
394
|
+
doc: Swaralipi;
|
|
395
|
+
options?: MidiExportOptions;
|
|
396
|
+
}[], name: string): MidiExportResult;
|
|
373
397
|
|
|
374
398
|
/** Octave register offset in octaves, relative to madhyam (middle). */
|
|
375
399
|
declare const OCTAVE_OFFSET: Record<Octave, number>;
|
|
@@ -417,4 +441,4 @@ declare function sampleFileName(midi: number): string;
|
|
|
417
441
|
*/
|
|
418
442
|
declare function sCurve(x: number, steepness?: number): number;
|
|
419
443
|
|
|
420
|
-
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type LineRange, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type PracticeState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, TempoSegment, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
|
444
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, Excerpt, InstrumentDef, InstrumentId, LaySpeeds, type LineRange, type LyricCue, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type PracticeState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, TempoSegment, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, exportMidiSet, midiToSampleName, mp3Stream, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
package/dist/player/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DZag9qkF.js';
|
|
2
2
|
import { a as LaySpeeds, T as TaalId } from '../lay-CWlEhImP.js';
|
|
3
3
|
export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-CWlEhImP.js';
|
|
4
|
-
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice, T as TempoSegment } from '../
|
|
5
|
-
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GRACE_STEP, d as GlideLeg, e as INSTRUMENTS, L as Line, M as MEEND_HOLD, P as PlayEvent,
|
|
4
|
+
import { I as InstrumentId, a as InstrumentDef, E as Excerpt, S as Schedule, D as DrumVoice, T as TempoSegment } from '../instruments-NcUb0ozc.js';
|
|
5
|
+
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GRACE_STEP, d as GlideLeg, e as INSTRUMENTS, L as Line, f as LyricMap, M as MEEND_HOLD, P as PlayEvent, g as SALAMANDER_BASE, h as SAMPLED_TAALS, i as ScheduleOptions, j as TABLA_BPMS, k as TABLA_VARIATIONS, l as TONEJS_INSTRUMENTS_BASE, m as ToneModule, n as alignLyrics, o as buildSchedule, p as createBuffers, q as drumPattern, r as eventAt, s as glideTargets, t as graceTouches, u as instrumentSampleUrl, v as isArcRaw, w as lyricCues, x as meendContinuations, y as nearestSampleKey, z as nearestTablaBpm, B as normalizeBase, C as probeUrl, F as quantizeLoopEnd, H as santoorUrls, J as scheduleLines, K as subsetUrls, N as tablaBpms, O as tablaUrl, Q as triggerGlide } from '../instruments-NcUb0ozc.js';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import 'tone';
|
|
8
8
|
|
|
@@ -140,8 +140,12 @@ declare class SwaralipiPlayer {
|
|
|
140
140
|
get loop(): LineRange | null;
|
|
141
141
|
/** Whether each repeat is followed by a silent pass for the learner. */
|
|
142
142
|
get echo(): boolean;
|
|
143
|
-
/** Set the document to play. Stops any current playback.
|
|
144
|
-
|
|
143
|
+
/** Set the document to play. Stops any current playback. With `excerpt`,
|
|
144
|
+
* only the notes singing those stretches of the song's text play, in order
|
|
145
|
+
* (see `alignLyrics`); the length, seeks and repeats all keep to them. */
|
|
146
|
+
load(doc: Swaralipi, { excerpt }?: {
|
|
147
|
+
excerpt?: Excerpt;
|
|
148
|
+
}): void;
|
|
145
149
|
/**
|
|
146
150
|
* Taal mode: loop one avartan of a taal's theka (rhythm only, no melody) —
|
|
147
151
|
* for a standalone theka player. `play()` loops indefinitely (tabla loop /
|
|
@@ -235,6 +239,10 @@ declare class SwaralipiPlayer {
|
|
|
235
239
|
|
|
236
240
|
type AudioFormat = 'wav' | 'mp3';
|
|
237
241
|
interface AudioExportOptions {
|
|
242
|
+
/** Play only the notes singing these stretches of the text (see alignLyrics). */
|
|
243
|
+
excerpt?: Excerpt;
|
|
244
|
+
/** The song's words: when given, the result carries their `cues` (an LRC's timing). */
|
|
245
|
+
lyrics?: string;
|
|
238
246
|
/** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */
|
|
239
247
|
soundsBaseUrl?: string;
|
|
240
248
|
/** Self-host the grand piano (see the player's `pianoBaseUrl`). */
|
|
@@ -289,6 +297,15 @@ interface AudioExportResult {
|
|
|
289
297
|
mimeType: string;
|
|
290
298
|
/** Rendered length in seconds. */
|
|
291
299
|
durationSec: number;
|
|
300
|
+
/** Each sung line and its words, in seconds on this file's timeline (given `lyrics`). */
|
|
301
|
+
cues?: LyricCue[];
|
|
302
|
+
}
|
|
303
|
+
interface LyricCue {
|
|
304
|
+
sec: number;
|
|
305
|
+
words: {
|
|
306
|
+
sec: number;
|
|
307
|
+
text: string;
|
|
308
|
+
}[];
|
|
292
309
|
}
|
|
293
310
|
/**
|
|
294
311
|
* Render a swaralipi doc to an audio Blob in the browser. Runs in an
|
|
@@ -304,11 +321,17 @@ declare function exportAudio(doc: Swaralipi, options?: AudioExportOptions): Prom
|
|
|
304
321
|
*/
|
|
305
322
|
declare function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array;
|
|
306
323
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
324
|
+
* A streaming MP3 encoder: feed 16-bit PCM in any number of pieces, get one MP3 — one encoder
|
|
325
|
+
* delay, so a track joined from several renders keeps its timing. lamejs is **dynamically
|
|
326
|
+
* imported** and an *optional* peer dependency (LGPL-3.0). Samples go in 1152-frame blocks (a
|
|
327
|
+
* multiple of 576, lamejs' preferred size).
|
|
311
328
|
*/
|
|
329
|
+
declare function mp3Stream(sampleRate?: number, kbps?: number, channels?: 1 | 2): Promise<{
|
|
330
|
+
push(left: Int16Array, right?: Int16Array): void;
|
|
331
|
+
end(): Uint8Array[];
|
|
332
|
+
}>;
|
|
333
|
+
/** Encode planar float channels to MP3 via lamejs (see mp3Stream). WAV needs no dependency, so
|
|
334
|
+
* only reach for this when a compressed file is wanted. */
|
|
312
335
|
declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?: number): Promise<Uint8Array>;
|
|
313
336
|
|
|
314
337
|
/**
|
|
@@ -329,6 +352,8 @@ declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?:
|
|
|
329
352
|
*/
|
|
330
353
|
|
|
331
354
|
interface MidiExportOptions {
|
|
355
|
+
/** Play only the notes singing these stretches of the text (see alignLyrics). */
|
|
356
|
+
excerpt?: Excerpt;
|
|
332
357
|
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
333
358
|
tonic?: string;
|
|
334
359
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
@@ -353,13 +378,6 @@ interface MidiExportResult {
|
|
|
353
378
|
filename: string;
|
|
354
379
|
mimeType: string;
|
|
355
380
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Encode a schedule to a Standard MIDI File. Melody rides channel 0; when
|
|
358
|
-
* `drums` is given, its per-matra voices ride a second GM channel-10 track
|
|
359
|
-
* (the file becomes format 1). `drums` is one pattern for the whole song, or a
|
|
360
|
-
* function picking each লয় / তাল segment's own (তালফেরতা; `undefined` = rest).
|
|
361
|
-
* Pure — no Tone, no browser — directly testable.
|
|
362
|
-
*/
|
|
363
381
|
declare function encodeMidi(schedule: Schedule, opts: {
|
|
364
382
|
bpm: number;
|
|
365
383
|
name?: string;
|
|
@@ -370,6 +388,12 @@ declare function encodeMidi(schedule: Schedule, opts: {
|
|
|
370
388
|
* beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.
|
|
371
389
|
*/
|
|
372
390
|
declare function exportMidi(doc: Swaralipi, options?: MidiExportOptions): MidiExportResult;
|
|
391
|
+
/** Several songs on one timeline (a whole dance-drama), back to back with two beats' rest:
|
|
392
|
+
* each song opens with its own tempo and a marker naming it; drums share one channel-10 track. */
|
|
393
|
+
declare function exportMidiSet(items: {
|
|
394
|
+
doc: Swaralipi;
|
|
395
|
+
options?: MidiExportOptions;
|
|
396
|
+
}[], name: string): MidiExportResult;
|
|
373
397
|
|
|
374
398
|
/** Octave register offset in octaves, relative to madhyam (middle). */
|
|
375
399
|
declare const OCTAVE_OFFSET: Record<Octave, number>;
|
|
@@ -417,4 +441,4 @@ declare function sampleFileName(midi: number): string;
|
|
|
417
441
|
*/
|
|
418
442
|
declare function sCurve(x: number, steepness?: number): number;
|
|
419
443
|
|
|
420
|
-
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type LineRange, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type PracticeState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, TempoSegment, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
|
444
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, Excerpt, InstrumentDef, InstrumentId, LaySpeeds, type LineRange, type LyricCue, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type PracticeState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, TempoSegment, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, exportMidiSet, midiToSampleName, mp3Stream, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
package/dist/player/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, eventAt, lineAt, byRow, lineFor, quantizeLoopEnd, instrumentSampleUrl, probeUrl, scheduleLines, GRACE_STEP, normalizeBase, nearestTablaBpm, tablaBpms, graceTouches, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic, MEEND_HOLD, sCurve } from '../chunk-
|
|
2
|
-
export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, eventAt, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, scheduleLines, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-
|
|
1
|
+
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, eventAt, lineAt, byRow, lineFor, quantizeLoopEnd, instrumentSampleUrl, probeUrl, scheduleLines, GRACE_STEP, normalizeBase, nearestTablaBpm, tablaBpms, graceTouches, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic, lyricCues, alignLyrics, MEEND_HOLD, sCurve } from '../chunk-GIOGYJMM.js';
|
|
2
|
+
export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, alignLyrics, buildSchedule, createBuffers, drumPattern, eventAt, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, lyricCues, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, scheduleLines, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-GIOGYJMM.js';
|
|
3
3
|
import '../chunk-BSZ3WQUP.js';
|
|
4
4
|
import { TAALS } from '../chunk-KOHSKHXI.js';
|
|
5
5
|
export { LAYS, LAY_SPEEDS, printedLay, resolveLaySpeeds, rowLay } from '../chunk-KOHSKHXI.js';
|
|
@@ -45,6 +45,8 @@ var SwaralipiPlayer = class {
|
|
|
45
45
|
/** Per-song setBpm tweak: beats meta.bpm but is cleared by the next load(). */
|
|
46
46
|
#bpmTweak;
|
|
47
47
|
#doc;
|
|
48
|
+
/** Play only these stretches of the song's text (see `load`). */
|
|
49
|
+
#excerpt;
|
|
48
50
|
/** Taal mode (`loadTaal`): loops one avartan of this taal's theka with no
|
|
49
51
|
* melody. Mutually exclusive with `#doc`. */
|
|
50
52
|
#taal;
|
|
@@ -87,6 +89,8 @@ var SwaralipiPlayer = class {
|
|
|
87
89
|
/** Listeners on the bound sheet, removed on rebind. */
|
|
88
90
|
#unbind;
|
|
89
91
|
#sheet;
|
|
92
|
+
/** The bound sheet's highlight — beside, never instead of, the consumer's `onHighlight`. */
|
|
93
|
+
#paint;
|
|
90
94
|
/** Preview cap, counted in listening time so a seek or a loop can't dodge it. */
|
|
91
95
|
#listened = 0;
|
|
92
96
|
#listenedSince = 0;
|
|
@@ -177,10 +181,13 @@ var SwaralipiPlayer = class {
|
|
|
177
181
|
/* ----------------------------------------------------------------- */
|
|
178
182
|
/* Loading + transport control */
|
|
179
183
|
/* ----------------------------------------------------------------- */
|
|
180
|
-
/** Set the document to play. Stops any current playback.
|
|
181
|
-
|
|
184
|
+
/** Set the document to play. Stops any current playback. With `excerpt`,
|
|
185
|
+
* only the notes singing those stretches of the song's text play, in order
|
|
186
|
+
* (see `alignLyrics`); the length, seeks and repeats all keep to them. */
|
|
187
|
+
load(doc, { excerpt } = {}) {
|
|
182
188
|
this.#halt();
|
|
183
189
|
this.#doc = doc;
|
|
190
|
+
this.#excerpt = excerpt;
|
|
184
191
|
this.#taal = void 0;
|
|
185
192
|
this.#taalOverride = void 0;
|
|
186
193
|
this.#bpmTweak = void 0;
|
|
@@ -503,7 +510,7 @@ var SwaralipiPlayer = class {
|
|
|
503
510
|
*/
|
|
504
511
|
bindSheet(target, opts = {}) {
|
|
505
512
|
const block = opts.autoScroll === true ? "center" : opts.autoScroll || void 0;
|
|
506
|
-
this.#
|
|
513
|
+
this.#paint = (path, yourTurn) => {
|
|
507
514
|
target.clearHighlights();
|
|
508
515
|
if (path === null) return;
|
|
509
516
|
const el2 = target.highlight(path, yourTurn);
|
|
@@ -615,6 +622,7 @@ var SwaralipiPlayer = class {
|
|
|
615
622
|
this.#opts.onState?.(state);
|
|
616
623
|
}
|
|
617
624
|
#emitHighlight(path, yourTurn = false) {
|
|
625
|
+
this.#paint?.(path, yourTurn);
|
|
618
626
|
this.#opts.onHighlight?.(path, yourTurn);
|
|
619
627
|
}
|
|
620
628
|
#emitLoop() {
|
|
@@ -772,7 +780,8 @@ var SwaralipiPlayer = class {
|
|
|
772
780
|
tonic: this.#opts.tonic,
|
|
773
781
|
baseOctave: this.#opts.baseOctave,
|
|
774
782
|
laySpeeds: this.#opts.laySpeeds,
|
|
775
|
-
repeats: this.#opts.repeats
|
|
783
|
+
repeats: this.#opts.repeats,
|
|
784
|
+
excerpt: this.#excerpt
|
|
776
785
|
};
|
|
777
786
|
}
|
|
778
787
|
/** Effective tempo: snapped to the taal's available tabla tempos while a
|
|
@@ -1095,6 +1104,38 @@ function midiRange2(schedule) {
|
|
|
1095
1104
|
}
|
|
1096
1105
|
return lo <= hi ? { lo, hi } : void 0;
|
|
1097
1106
|
}
|
|
1107
|
+
var AHEAD_SEC = 2;
|
|
1108
|
+
async function renderOffline(Tone, build, durationSec, channels, sampleRate) {
|
|
1109
|
+
const chunked = "suspend" in OfflineAudioContext.prototype && "positionX" in AudioListener.prototype;
|
|
1110
|
+
const context = chunked ? new Tone.OfflineContext(new OfflineAudioContext(channels, Math.ceil(durationSec * sampleRate), sampleRate)) : new Tone.OfflineContext(channels, durationSec, sampleRate);
|
|
1111
|
+
const raw = context.rawContext;
|
|
1112
|
+
const original = Tone.getContext();
|
|
1113
|
+
Tone.setContext(context);
|
|
1114
|
+
try {
|
|
1115
|
+
await build(context);
|
|
1116
|
+
} finally {
|
|
1117
|
+
Tone.setContext(original);
|
|
1118
|
+
}
|
|
1119
|
+
const clock = context;
|
|
1120
|
+
const run = (until) => {
|
|
1121
|
+
while (clock._currentTime <= Math.min(until, durationSec)) {
|
|
1122
|
+
clock.emit("tick");
|
|
1123
|
+
clock._currentTime += 128 / sampleRate;
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
if (chunked) {
|
|
1127
|
+
run(2 * AHEAD_SEC);
|
|
1128
|
+
for (let t = AHEAD_SEC; t < durationSec; t += AHEAD_SEC) {
|
|
1129
|
+
void raw.suspend(t).then(() => {
|
|
1130
|
+
run(t + 2 * AHEAD_SEC);
|
|
1131
|
+
void raw.resume();
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
} else {
|
|
1135
|
+
run(Infinity);
|
|
1136
|
+
}
|
|
1137
|
+
return raw.startRendering();
|
|
1138
|
+
}
|
|
1098
1139
|
function pickDef(id, o) {
|
|
1099
1140
|
const def = o.customInstruments?.[id] ?? INSTRUMENTS[id];
|
|
1100
1141
|
if (def === void 0) return INSTRUMENTS.harmonium;
|
|
@@ -1123,7 +1164,8 @@ async function exportAudio(doc, options = {}) {
|
|
|
1123
1164
|
tonic: options.tonic,
|
|
1124
1165
|
baseOctave: options.baseOctave,
|
|
1125
1166
|
laySpeeds: options.laySpeeds,
|
|
1126
|
-
repeats: options.repeats ?? true
|
|
1167
|
+
repeats: options.repeats ?? true,
|
|
1168
|
+
excerpt: options.excerpt
|
|
1127
1169
|
});
|
|
1128
1170
|
const range = midiRange2(schedule);
|
|
1129
1171
|
const taalId = resolveTaal(doc, options.taal);
|
|
@@ -1136,7 +1178,8 @@ async function exportAudio(doc, options = {}) {
|
|
|
1136
1178
|
const def = pickDef(id, options);
|
|
1137
1179
|
const shift = rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
1138
1180
|
const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
|
|
1139
|
-
const rendered = await
|
|
1181
|
+
const rendered = await renderOffline(
|
|
1182
|
+
Tone,
|
|
1140
1183
|
async ({ transport }) => {
|
|
1141
1184
|
transport.bpm.value = bpm;
|
|
1142
1185
|
const master = new Tone.Gain(1).toDestination();
|
|
@@ -1283,11 +1326,16 @@ async function exportAudio(doc, options = {}) {
|
|
|
1283
1326
|
for (let c = 0; c < rendered.numberOfChannels; c++) chans.push(rendered.getChannelData(c));
|
|
1284
1327
|
const bytes = format === "mp3" ? await encodeMp3(chans, rendered.sampleRate, options.mp3Kbps) : encodeWav(chans, rendered.sampleRate);
|
|
1285
1328
|
const mimeType = format === "mp3" ? "audio/mpeg" : "audio/wav";
|
|
1329
|
+
const cues = options.lyrics === void 0 ? void 0 : lyricCues(schedule.events, alignLyrics(doc, options.lyrics), options.lyrics).map((cue) => ({
|
|
1330
|
+
sec: cue.beat * spb + shift,
|
|
1331
|
+
words: cue.words.map((word) => ({ sec: word.beat * spb + shift, text: word.text }))
|
|
1332
|
+
}));
|
|
1286
1333
|
return {
|
|
1287
1334
|
blob: new Blob([bytes], { type: mimeType }),
|
|
1288
1335
|
filename: `${safeName(doc.meta.title)}.${format}`,
|
|
1289
1336
|
mimeType,
|
|
1290
|
-
durationSec: rendered.duration
|
|
1337
|
+
durationSec: rendered.duration,
|
|
1338
|
+
...cues === void 0 ? {} : { cues }
|
|
1291
1339
|
};
|
|
1292
1340
|
}
|
|
1293
1341
|
function toInt16(f) {
|
|
@@ -1354,22 +1402,32 @@ async function loadLame() {
|
|
|
1354
1402
|
);
|
|
1355
1403
|
}
|
|
1356
1404
|
}
|
|
1357
|
-
async function
|
|
1405
|
+
async function mp3Stream(sampleRate = 44100, kbps = 192, channels = 1) {
|
|
1358
1406
|
const Mp3Encoder = await loadLame();
|
|
1359
|
-
const
|
|
1360
|
-
const enc = new Mp3Encoder(numCh, sampleRate, kbps);
|
|
1361
|
-
const left = floatToInt16(channels[0] ?? new Float32Array());
|
|
1362
|
-
const right = numCh > 1 ? floatToInt16(channels[1]) : void 0;
|
|
1363
|
-
const block = 1152;
|
|
1407
|
+
const enc = new Mp3Encoder(channels, sampleRate, kbps);
|
|
1364
1408
|
const out = [];
|
|
1365
|
-
|
|
1366
|
-
const l = left.subarray(i, i + block);
|
|
1367
|
-
const chunk = right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l);
|
|
1409
|
+
const keep = (chunk) => {
|
|
1368
1410
|
if (chunk.length > 0) out.push(new Uint8Array(chunk));
|
|
1369
|
-
}
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1411
|
+
};
|
|
1412
|
+
const block = 1152;
|
|
1413
|
+
return {
|
|
1414
|
+
push(left, right) {
|
|
1415
|
+
for (let i = 0; i < left.length; i += block) {
|
|
1416
|
+
const l = left.subarray(i, i + block);
|
|
1417
|
+
keep(right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l));
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
end() {
|
|
1421
|
+
keep(enc.flush());
|
|
1422
|
+
return out;
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
async function encodeMp3(channels, sampleRate, kbps = 192) {
|
|
1427
|
+
const numCh = Math.min(channels.length, 2);
|
|
1428
|
+
const mp3 = await mp3Stream(sampleRate, kbps, numCh);
|
|
1429
|
+
mp3.push(floatToInt16(channels[0] ?? new Float32Array()), numCh > 1 ? floatToInt16(channels[1]) : void 0);
|
|
1430
|
+
return concatBytes(mp3.end());
|
|
1373
1431
|
}
|
|
1374
1432
|
|
|
1375
1433
|
// src/player/midi.ts
|
|
@@ -1415,6 +1473,11 @@ function trackChunk(raw, head) {
|
|
|
1415
1473
|
const track = [...head];
|
|
1416
1474
|
let last = 0;
|
|
1417
1475
|
for (const e of raw) {
|
|
1476
|
+
if (e.meta) {
|
|
1477
|
+
track.push(...vlq(e.tick - last), ...e.meta);
|
|
1478
|
+
last = e.tick;
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1418
1481
|
const status = e.status !== void 0 ? e.status | e.ch & 15 : (e.on ? 144 : 128) | e.ch & 15;
|
|
1419
1482
|
track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
|
|
1420
1483
|
last = e.tick;
|
|
@@ -1422,22 +1485,22 @@ function trackChunk(raw, head) {
|
|
|
1422
1485
|
track.push(...vlq(0), 255, 47, 0);
|
|
1423
1486
|
return [77, 84, 114, 107, ...u32be(track.length), ...track];
|
|
1424
1487
|
}
|
|
1425
|
-
function
|
|
1488
|
+
function melodyEvents(schedule, offset = 0) {
|
|
1426
1489
|
const melody = [];
|
|
1427
1490
|
const hasGlide = schedule.events.some((ev) => ev.glide === true);
|
|
1428
1491
|
if (hasGlide) melody.push(...bendRangeSetup());
|
|
1429
1492
|
schedule.events.forEach((ev, i) => {
|
|
1430
1493
|
if (ev.glide === true) return;
|
|
1431
1494
|
for (const g of graceTouches(ev)) {
|
|
1432
|
-
const on = Math.max(Math.round(g.beat * PPQ), 0);
|
|
1495
|
+
const on = Math.max(Math.round(g.beat * PPQ) + offset, 0);
|
|
1433
1496
|
melody.push({ tick: on, on: true, note: g.midi, vel: 70, ch: 0 });
|
|
1434
|
-
melody.push({ tick: Math.max(on + 1, Math.round((g.beat + GRACE_STEP) * PPQ)), on: false, note: g.midi, vel: 0, ch: 0 });
|
|
1497
|
+
melody.push({ tick: Math.max(on + 1, Math.round((g.beat + GRACE_STEP) * PPQ) + offset), on: false, note: g.midi, vel: 0, ch: 0 });
|
|
1435
1498
|
}
|
|
1436
1499
|
if (ev.midi === null) return;
|
|
1437
|
-
const start = Math.round(ev.beat * PPQ);
|
|
1500
|
+
const start = Math.round(ev.beat * PPQ) + offset;
|
|
1438
1501
|
const targets = glideTargets(schedule.events, i);
|
|
1439
1502
|
const last = targets[targets.length - 1] ?? ev;
|
|
1440
|
-
const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ), start + 1);
|
|
1503
|
+
const end = Math.max(Math.round((last.beat + last.durationBeats) * PPQ) + offset, start + 1);
|
|
1441
1504
|
melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
|
|
1442
1505
|
melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
|
|
1443
1506
|
let from = ev;
|
|
@@ -1447,72 +1510,90 @@ function encodeMidi(schedule, opts) {
|
|
|
1447
1510
|
const u = n / BEND_STEPS;
|
|
1448
1511
|
const beat = glideStart + (to.beat - glideStart) * u;
|
|
1449
1512
|
const semis = from.midi - ev.midi + (to.midi - from.midi) * sCurve(u);
|
|
1450
|
-
melody.push(bendEvent(Math.round(beat * PPQ), semis));
|
|
1513
|
+
melody.push(bendEvent(Math.round(beat * PPQ) + offset, semis));
|
|
1451
1514
|
}
|
|
1452
1515
|
from = to;
|
|
1453
1516
|
}
|
|
1454
1517
|
if (targets.length > 0) melody.push(bendEvent(end, 0));
|
|
1455
1518
|
});
|
|
1519
|
+
return melody;
|
|
1520
|
+
}
|
|
1521
|
+
function drumEvents(schedule, drums, offset = 0) {
|
|
1522
|
+
const drum = [];
|
|
1523
|
+
for (const seg of schedule.tempo) {
|
|
1524
|
+
const pattern = typeof drums === "function" ? drums(seg) : drums;
|
|
1525
|
+
if (!pattern?.length) continue;
|
|
1526
|
+
const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
|
|
1527
|
+
for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
|
|
1528
|
+
const note = DRUM_NOTE[pattern[k % pattern.length]];
|
|
1529
|
+
const t = Math.round((seg.beat + k / seg.scale) * PPQ) + offset;
|
|
1530
|
+
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
1531
|
+
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return drum;
|
|
1535
|
+
}
|
|
1536
|
+
function midiFile(chunks) {
|
|
1537
|
+
const format = chunks.length > 1 ? 1 : 0;
|
|
1538
|
+
const header = [77, 84, 104, 100, ...u32be(6), 0, format, chunks.length >> 8 & 255, chunks.length & 255, PPQ >> 8 & 255, PPQ & 255];
|
|
1539
|
+
return new Uint8Array([...header, ...chunks.flat()]);
|
|
1540
|
+
}
|
|
1541
|
+
function meta(type, data) {
|
|
1542
|
+
return [255, type, ...vlq(data.length), ...data];
|
|
1543
|
+
}
|
|
1544
|
+
function encodeMidi(schedule, opts) {
|
|
1456
1545
|
const usPerBeat = Math.round(6e7 / opts.bpm);
|
|
1457
1546
|
const head = [];
|
|
1458
1547
|
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
1459
1548
|
head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
|
|
1460
|
-
const chunks = [trackChunk(
|
|
1549
|
+
const chunks = [trackChunk(melodyEvents(schedule), head)];
|
|
1461
1550
|
if (typeof opts.drums === "function" || opts.drums && opts.drums.length > 0) {
|
|
1462
|
-
const drum =
|
|
1463
|
-
for (const seg of schedule.tempo) {
|
|
1464
|
-
const pattern = typeof opts.drums === "function" ? opts.drums(seg) : opts.drums;
|
|
1465
|
-
if (!pattern?.length) continue;
|
|
1466
|
-
const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
|
|
1467
|
-
for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
|
|
1468
|
-
const note = DRUM_NOTE[pattern[k % pattern.length]];
|
|
1469
|
-
const t = Math.round((seg.beat + k / seg.scale) * PPQ);
|
|
1470
|
-
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
1471
|
-
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1551
|
+
const drum = drumEvents(schedule, opts.drums);
|
|
1474
1552
|
chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
|
|
1475
1553
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
chunks.length >> 8 & 255,
|
|
1486
|
-
chunks.length & 255,
|
|
1487
|
-
PPQ >> 8 & 255,
|
|
1488
|
-
PPQ & 255
|
|
1489
|
-
];
|
|
1490
|
-
return new Uint8Array([...header, ...chunks.flat()]);
|
|
1554
|
+
return midiFile(chunks);
|
|
1555
|
+
}
|
|
1556
|
+
function exportSchedule(doc, o) {
|
|
1557
|
+
return buildSchedule(doc, { tonic: o.tonic, baseOctave: o.baseOctave, laySpeeds: o.laySpeeds, repeats: o.repeats ?? true, excerpt: o.excerpt });
|
|
1558
|
+
}
|
|
1559
|
+
function taalDrums(seg) {
|
|
1560
|
+
if (seg.taal === null) return void 0;
|
|
1561
|
+
const id = seg.taal?.id;
|
|
1562
|
+
return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
|
|
1491
1563
|
}
|
|
1492
1564
|
function safeName2(title) {
|
|
1493
1565
|
return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
|
|
1494
1566
|
}
|
|
1495
1567
|
function exportMidi(doc, options = {}) {
|
|
1496
|
-
const schedule =
|
|
1497
|
-
tonic: options.tonic,
|
|
1498
|
-
baseOctave: options.baseOctave,
|
|
1499
|
-
laySpeeds: options.laySpeeds,
|
|
1500
|
-
repeats: options.repeats ?? true
|
|
1501
|
-
});
|
|
1568
|
+
const schedule = exportSchedule(doc, options);
|
|
1502
1569
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
1503
|
-
const
|
|
1504
|
-
const id = seg.taal?.id;
|
|
1505
|
-
if (seg.taal === null) return void 0;
|
|
1506
|
-
return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
|
|
1507
|
-
} : void 0;
|
|
1508
|
-
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
|
|
1570
|
+
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums: options.drums ? taalDrums : void 0 });
|
|
1509
1571
|
return {
|
|
1510
1572
|
blob: new Blob([bytes], { type: "audio/midi" }),
|
|
1511
1573
|
filename: `${safeName2(doc.meta.title)}.mid`,
|
|
1512
1574
|
mimeType: "audio/midi"
|
|
1513
1575
|
};
|
|
1514
1576
|
}
|
|
1577
|
+
function exportMidiSet(items, name) {
|
|
1578
|
+
const melody = [];
|
|
1579
|
+
const drum = [];
|
|
1580
|
+
let beat = 0;
|
|
1581
|
+
for (const { doc, options = {} } of items) {
|
|
1582
|
+
const schedule = exportSchedule(doc, options);
|
|
1583
|
+
const tick = beat * PPQ;
|
|
1584
|
+
const usPerBeat = Math.round(6e7 / (options.bpm ?? doc.meta.bpm ?? 90));
|
|
1585
|
+
const at = (bytes2) => ({ tick, on: false, note: 0, vel: 0, ch: 0, meta: bytes2 });
|
|
1586
|
+
melody.push(at(meta(81, [usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255])));
|
|
1587
|
+
melody.push(at(meta(6, utf8(options.name ?? doc.meta.title ?? ""))), ...melodyEvents(schedule, tick));
|
|
1588
|
+
if (options.drums) drum.push(...drumEvents(schedule, taalDrums, tick));
|
|
1589
|
+
beat += Math.ceil(schedule.totalBeats) + 2;
|
|
1590
|
+
}
|
|
1591
|
+
const chunks = [trackChunk(melody, [...vlq(0), ...meta(3, utf8(name))])];
|
|
1592
|
+
if (drum.length > 0) chunks.push(trackChunk(drum, [...vlq(0), ...meta(3, utf8("Drums"))]));
|
|
1593
|
+
const bytes = midiFile(chunks);
|
|
1594
|
+
return { blob: new Blob([bytes], { type: "audio/midi" }), filename: `${safeName2(name)}.mid`, mimeType: "audio/midi" };
|
|
1595
|
+
}
|
|
1515
1596
|
|
|
1516
|
-
export { SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi };
|
|
1597
|
+
export { SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, exportMidiSet, mp3Stream };
|
|
1517
1598
|
//# sourceMappingURL=index.js.map
|
|
1518
1599
|
//# sourceMappingURL=index.js.map
|