@ugfoundation/swaralipi-js 0.14.0 → 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.
Files changed (39) hide show
  1. package/README.md +4 -0
  2. package/dist/{batch-D4HRATTH.js → batch-J72GGBG2.js} +4 -4
  3. package/dist/{batch-D4HRATTH.js.map → batch-J72GGBG2.js.map} +1 -1
  4. package/dist/{chunk-QQOC6LRA.js → chunk-4WDAVRET.js} +3 -3
  5. package/dist/{chunk-QQOC6LRA.js.map → chunk-4WDAVRET.js.map} +1 -1
  6. package/dist/{chunk-YBFM5NLW.js → chunk-GIOGYJMM.js} +28 -3
  7. package/dist/chunk-GIOGYJMM.js.map +1 -0
  8. package/dist/{chunk-UV5OJYT3.js → chunk-KUZSW4FN.js} +3 -3
  9. package/dist/{chunk-UV5OJYT3.js.map → chunk-KUZSW4FN.js.map} +1 -1
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +2 -2
  13. package/dist/convert/index.cjs +1 -1
  14. package/dist/convert/index.cjs.map +1 -1
  15. package/dist/convert/index.js +2 -2
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/{schedule-oJgg_bnc.d.cts → instruments-DTIuTlda.d.cts} +171 -162
  20. package/dist/{schedule-DsVESieT.d.ts → instruments-NcUb0ozc.d.ts} +171 -162
  21. package/dist/player/index.cjs +162 -62
  22. package/dist/player/index.cjs.map +1 -1
  23. package/dist/player/index.d.cts +34 -14
  24. package/dist/player/index.d.ts +34 -14
  25. package/dist/player/index.js +137 -65
  26. package/dist/player/index.js.map +1 -1
  27. package/dist/spec/index.cjs +1 -1
  28. package/dist/spec/index.cjs.map +1 -1
  29. package/dist/spec/index.d.cts +1 -1
  30. package/dist/spec/index.d.ts +1 -1
  31. package/dist/spec/index.js +1 -1
  32. package/dist/viewer/index.cjs +126 -3
  33. package/dist/viewer/index.cjs.map +1 -1
  34. package/dist/viewer/index.d.cts +62 -2
  35. package/dist/viewer/index.d.ts +62 -2
  36. package/dist/viewer/index.js +123 -4
  37. package/dist/viewer/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-YBFM5NLW.js.map +0 -1
@@ -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, E as Excerpt, S as Schedule, D as DrumVoice, T as TempoSegment } from '../schedule-oJgg_bnc.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 meendContinuations, x as nearestSampleKey, y as nearestTablaBpm, z as normalizeBase, B as probeUrl, C as quantizeLoopEnd, F as santoorUrls, H as scheduleLines, J as subsetUrls, K as tablaBpms, N as tablaUrl, O as triggerGlide } from '../schedule-oJgg_bnc.cjs';
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
 
@@ -239,6 +239,10 @@ declare class SwaralipiPlayer {
239
239
 
240
240
  type AudioFormat = 'wav' | 'mp3';
241
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;
242
246
  /** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */
243
247
  soundsBaseUrl?: string;
244
248
  /** Self-host the grand piano (see the player's `pianoBaseUrl`). */
@@ -293,6 +297,15 @@ interface AudioExportResult {
293
297
  mimeType: string;
294
298
  /** Rendered length in seconds. */
295
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
+ }[];
296
309
  }
297
310
  /**
298
311
  * Render a swaralipi doc to an audio Blob in the browser. Runs in an
@@ -308,11 +321,17 @@ declare function exportAudio(doc: Swaralipi, options?: AudioExportOptions): Prom
308
321
  */
309
322
  declare function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array;
310
323
  /**
311
- * Encode planar float channels to MP3 via lamejs. lamejs is **dynamically
312
- * imported** and an *optional* peer dependency (LGPL-3.0) — WAV needs no
313
- * dependency, so only reach for this when a compressed file is wanted. Samples
314
- * are fed in 1152-frame blocks (a multiple of 576, lamejs' preferred size).
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).
315
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. */
316
335
  declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?: number): Promise<Uint8Array>;
317
336
 
318
337
  /**
@@ -333,6 +352,8 @@ declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?:
333
352
  */
334
353
 
335
354
  interface MidiExportOptions {
355
+ /** Play only the notes singing these stretches of the text (see alignLyrics). */
356
+ excerpt?: Excerpt;
336
357
  /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
337
358
  tonic?: string;
338
359
  /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
@@ -357,13 +378,6 @@ interface MidiExportResult {
357
378
  filename: string;
358
379
  mimeType: string;
359
380
  }
360
- /**
361
- * Encode a schedule to a Standard MIDI File. Melody rides channel 0; when
362
- * `drums` is given, its per-matra voices ride a second GM channel-10 track
363
- * (the file becomes format 1). `drums` is one pattern for the whole song, or a
364
- * function picking each লয় / তাল segment's own (তালফেরতা; `undefined` = rest).
365
- * Pure — no Tone, no browser — directly testable.
366
- */
367
381
  declare function encodeMidi(schedule: Schedule, opts: {
368
382
  bpm: number;
369
383
  name?: string;
@@ -374,6 +388,12 @@ declare function encodeMidi(schedule: Schedule, opts: {
374
388
  * beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.
375
389
  */
376
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;
377
397
 
378
398
  /** Octave register offset in octaves, relative to madhyam (middle). */
379
399
  declare const OCTAVE_OFFSET: Record<Octave, number>;
@@ -421,4 +441,4 @@ declare function sampleFileName(midi: number): string;
421
441
  */
422
442
  declare function sCurve(x: number, steepness?: number): number;
423
443
 
424
- export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, Excerpt, 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 };
@@ -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, E as Excerpt, S as Schedule, D as DrumVoice, T as TempoSegment } from '../schedule-DsVESieT.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 meendContinuations, x as nearestSampleKey, y as nearestTablaBpm, z as normalizeBase, B as probeUrl, C as quantizeLoopEnd, F as santoorUrls, H as scheduleLines, J as subsetUrls, K as tablaBpms, N as tablaUrl, O as triggerGlide } from '../schedule-DsVESieT.js';
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
 
@@ -239,6 +239,10 @@ declare class SwaralipiPlayer {
239
239
 
240
240
  type AudioFormat = 'wav' | 'mp3';
241
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;
242
246
  /** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */
243
247
  soundsBaseUrl?: string;
244
248
  /** Self-host the grand piano (see the player's `pianoBaseUrl`). */
@@ -293,6 +297,15 @@ interface AudioExportResult {
293
297
  mimeType: string;
294
298
  /** Rendered length in seconds. */
295
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
+ }[];
296
309
  }
297
310
  /**
298
311
  * Render a swaralipi doc to an audio Blob in the browser. Runs in an
@@ -308,11 +321,17 @@ declare function exportAudio(doc: Swaralipi, options?: AudioExportOptions): Prom
308
321
  */
309
322
  declare function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array;
310
323
  /**
311
- * Encode planar float channels to MP3 via lamejs. lamejs is **dynamically
312
- * imported** and an *optional* peer dependency (LGPL-3.0) — WAV needs no
313
- * dependency, so only reach for this when a compressed file is wanted. Samples
314
- * are fed in 1152-frame blocks (a multiple of 576, lamejs' preferred size).
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).
315
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. */
316
335
  declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?: number): Promise<Uint8Array>;
317
336
 
318
337
  /**
@@ -333,6 +352,8 @@ declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?:
333
352
  */
334
353
 
335
354
  interface MidiExportOptions {
355
+ /** Play only the notes singing these stretches of the text (see alignLyrics). */
356
+ excerpt?: Excerpt;
336
357
  /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
337
358
  tonic?: string;
338
359
  /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
@@ -357,13 +378,6 @@ interface MidiExportResult {
357
378
  filename: string;
358
379
  mimeType: string;
359
380
  }
360
- /**
361
- * Encode a schedule to a Standard MIDI File. Melody rides channel 0; when
362
- * `drums` is given, its per-matra voices ride a second GM channel-10 track
363
- * (the file becomes format 1). `drums` is one pattern for the whole song, or a
364
- * function picking each লয় / তাল segment's own (তালফেরতা; `undefined` = rest).
365
- * Pure — no Tone, no browser — directly testable.
366
- */
367
381
  declare function encodeMidi(schedule: Schedule, opts: {
368
382
  bpm: number;
369
383
  name?: string;
@@ -374,6 +388,12 @@ declare function encodeMidi(schedule: Schedule, opts: {
374
388
  * beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.
375
389
  */
376
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;
377
397
 
378
398
  /** Octave register offset in octaves, relative to madhyam (middle). */
379
399
  declare const OCTAVE_OFFSET: Record<Octave, number>;
@@ -421,4 +441,4 @@ declare function sampleFileName(midi: number): string;
421
441
  */
422
442
  declare function sCurve(x: number, steepness?: number): number;
423
443
 
424
- export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, Excerpt, 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 };
@@ -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-YBFM5NLW.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, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, scheduleLines, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-YBFM5NLW.js';
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';
@@ -1104,6 +1104,38 @@ function midiRange2(schedule) {
1104
1104
  }
1105
1105
  return lo <= hi ? { lo, hi } : void 0;
1106
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
+ }
1107
1139
  function pickDef(id, o) {
1108
1140
  const def = o.customInstruments?.[id] ?? INSTRUMENTS[id];
1109
1141
  if (def === void 0) return INSTRUMENTS.harmonium;
@@ -1132,7 +1164,8 @@ async function exportAudio(doc, options = {}) {
1132
1164
  tonic: options.tonic,
1133
1165
  baseOctave: options.baseOctave,
1134
1166
  laySpeeds: options.laySpeeds,
1135
- repeats: options.repeats ?? true
1167
+ repeats: options.repeats ?? true,
1168
+ excerpt: options.excerpt
1136
1169
  });
1137
1170
  const range = midiRange2(schedule);
1138
1171
  const taalId = resolveTaal(doc, options.taal);
@@ -1145,7 +1178,8 @@ async function exportAudio(doc, options = {}) {
1145
1178
  const def = pickDef(id, options);
1146
1179
  const shift = rhythm !== "none" ? schedule.pickupBeats * spb : 0;
1147
1180
  const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
1148
- const rendered = await Tone.Offline(
1181
+ const rendered = await renderOffline(
1182
+ Tone,
1149
1183
  async ({ transport }) => {
1150
1184
  transport.bpm.value = bpm;
1151
1185
  const master = new Tone.Gain(1).toDestination();
@@ -1292,11 +1326,16 @@ async function exportAudio(doc, options = {}) {
1292
1326
  for (let c = 0; c < rendered.numberOfChannels; c++) chans.push(rendered.getChannelData(c));
1293
1327
  const bytes = format === "mp3" ? await encodeMp3(chans, rendered.sampleRate, options.mp3Kbps) : encodeWav(chans, rendered.sampleRate);
1294
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
+ }));
1295
1333
  return {
1296
1334
  blob: new Blob([bytes], { type: mimeType }),
1297
1335
  filename: `${safeName(doc.meta.title)}.${format}`,
1298
1336
  mimeType,
1299
- durationSec: rendered.duration
1337
+ durationSec: rendered.duration,
1338
+ ...cues === void 0 ? {} : { cues }
1300
1339
  };
1301
1340
  }
1302
1341
  function toInt16(f) {
@@ -1363,22 +1402,32 @@ async function loadLame() {
1363
1402
  );
1364
1403
  }
1365
1404
  }
1366
- async function encodeMp3(channels, sampleRate, kbps = 192) {
1405
+ async function mp3Stream(sampleRate = 44100, kbps = 192, channels = 1) {
1367
1406
  const Mp3Encoder = await loadLame();
1368
- const numCh = Math.min(channels.length, 2);
1369
- const enc = new Mp3Encoder(numCh, sampleRate, kbps);
1370
- const left = floatToInt16(channels[0] ?? new Float32Array());
1371
- const right = numCh > 1 ? floatToInt16(channels[1]) : void 0;
1372
- const block = 1152;
1407
+ const enc = new Mp3Encoder(channels, sampleRate, kbps);
1373
1408
  const out = [];
1374
- for (let i = 0; i < left.length; i += block) {
1375
- const l = left.subarray(i, i + block);
1376
- const chunk = right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l);
1409
+ const keep = (chunk) => {
1377
1410
  if (chunk.length > 0) out.push(new Uint8Array(chunk));
1378
- }
1379
- const tail = enc.flush();
1380
- if (tail.length > 0) out.push(new Uint8Array(tail));
1381
- return concatBytes(out);
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());
1382
1431
  }
1383
1432
 
1384
1433
  // src/player/midi.ts
@@ -1424,6 +1473,11 @@ function trackChunk(raw, head) {
1424
1473
  const track = [...head];
1425
1474
  let last = 0;
1426
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
+ }
1427
1481
  const status = e.status !== void 0 ? e.status | e.ch & 15 : (e.on ? 144 : 128) | e.ch & 15;
1428
1482
  track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
1429
1483
  last = e.tick;
@@ -1431,22 +1485,22 @@ function trackChunk(raw, head) {
1431
1485
  track.push(...vlq(0), 255, 47, 0);
1432
1486
  return [77, 84, 114, 107, ...u32be(track.length), ...track];
1433
1487
  }
1434
- function encodeMidi(schedule, opts) {
1488
+ function melodyEvents(schedule, offset = 0) {
1435
1489
  const melody = [];
1436
1490
  const hasGlide = schedule.events.some((ev) => ev.glide === true);
1437
1491
  if (hasGlide) melody.push(...bendRangeSetup());
1438
1492
  schedule.events.forEach((ev, i) => {
1439
1493
  if (ev.glide === true) return;
1440
1494
  for (const g of graceTouches(ev)) {
1441
- const on = Math.max(Math.round(g.beat * PPQ), 0);
1495
+ const on = Math.max(Math.round(g.beat * PPQ) + offset, 0);
1442
1496
  melody.push({ tick: on, on: true, note: g.midi, vel: 70, ch: 0 });
1443
- 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 });
1444
1498
  }
1445
1499
  if (ev.midi === null) return;
1446
- const start = Math.round(ev.beat * PPQ);
1500
+ const start = Math.round(ev.beat * PPQ) + offset;
1447
1501
  const targets = glideTargets(schedule.events, i);
1448
1502
  const last = targets[targets.length - 1] ?? ev;
1449
- 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);
1450
1504
  melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
1451
1505
  melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
1452
1506
  let from = ev;
@@ -1456,72 +1510,90 @@ function encodeMidi(schedule, opts) {
1456
1510
  const u = n / BEND_STEPS;
1457
1511
  const beat = glideStart + (to.beat - glideStart) * u;
1458
1512
  const semis = from.midi - ev.midi + (to.midi - from.midi) * sCurve(u);
1459
- melody.push(bendEvent(Math.round(beat * PPQ), semis));
1513
+ melody.push(bendEvent(Math.round(beat * PPQ) + offset, semis));
1460
1514
  }
1461
1515
  from = to;
1462
1516
  }
1463
1517
  if (targets.length > 0) melody.push(bendEvent(end, 0));
1464
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) {
1465
1545
  const usPerBeat = Math.round(6e7 / opts.bpm);
1466
1546
  const head = [];
1467
1547
  if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
1468
1548
  head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
1469
- const chunks = [trackChunk(melody, head)];
1549
+ const chunks = [trackChunk(melodyEvents(schedule), head)];
1470
1550
  if (typeof opts.drums === "function" || opts.drums && opts.drums.length > 0) {
1471
- const drum = [];
1472
- for (const seg of schedule.tempo) {
1473
- const pattern = typeof opts.drums === "function" ? opts.drums(seg) : opts.drums;
1474
- if (!pattern?.length) continue;
1475
- const end = seg === schedule.tempo[schedule.tempo.length - 1] ? Math.ceil(schedule.totalBeats) : seg.endBeat;
1476
- for (let k = 0; seg.beat + k / seg.scale < end - 1e-6; k++) {
1477
- const note = DRUM_NOTE[pattern[k % pattern.length]];
1478
- const t = Math.round((seg.beat + k / seg.scale) * PPQ);
1479
- drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
1480
- drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
1481
- }
1482
- }
1551
+ const drum = drumEvents(schedule, opts.drums);
1483
1552
  chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
1484
1553
  }
1485
- const format = chunks.length > 1 ? 1 : 0;
1486
- const header = [
1487
- 77,
1488
- 84,
1489
- 104,
1490
- 100,
1491
- ...u32be(6),
1492
- 0,
1493
- format,
1494
- chunks.length >> 8 & 255,
1495
- chunks.length & 255,
1496
- PPQ >> 8 & 255,
1497
- PPQ & 255
1498
- ];
1499
- 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 });
1500
1563
  }
1501
1564
  function safeName2(title) {
1502
1565
  return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
1503
1566
  }
1504
1567
  function exportMidi(doc, options = {}) {
1505
- const schedule = buildSchedule(doc, {
1506
- tonic: options.tonic,
1507
- baseOctave: options.baseOctave,
1508
- laySpeeds: options.laySpeeds,
1509
- repeats: options.repeats ?? true
1510
- });
1568
+ const schedule = exportSchedule(doc, options);
1511
1569
  const bpm = options.bpm ?? doc.meta.bpm ?? 90;
1512
- const drums = options.drums ? (seg) => {
1513
- const id = seg.taal?.id;
1514
- if (seg.taal === null) return void 0;
1515
- return drumPattern(id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 });
1516
- } : void 0;
1517
- 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 });
1518
1571
  return {
1519
1572
  blob: new Blob([bytes], { type: "audio/midi" }),
1520
1573
  filename: `${safeName2(doc.meta.title)}.mid`,
1521
1574
  mimeType: "audio/midi"
1522
1575
  };
1523
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
+ }
1524
1596
 
1525
- export { SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi };
1597
+ export { SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, exportMidiSet, mp3Stream };
1526
1598
  //# sourceMappingURL=index.js.map
1527
1599
  //# sourceMappingURL=index.js.map