@ugfoundation/swaralipi-js 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/{batch-KKRTXF52.js → batch-CIAXVKP5.js} +5 -5
- package/dist/{batch-KKRTXF52.js.map → batch-CIAXVKP5.js.map} +1 -1
- package/dist/{chunk-HTTURJ7A.js → chunk-2NBNF6JP.js} +4 -4
- package/dist/{chunk-HTTURJ7A.js.map → chunk-2NBNF6JP.js.map} +1 -1
- package/dist/{chunk-R6QU5CXY.js → chunk-H6HC44PZ.js} +5 -3
- package/dist/chunk-H6HC44PZ.js.map +1 -0
- package/dist/{chunk-5PLGGEPK.js → chunk-SH2INDZ2.js} +3 -3
- package/dist/{chunk-5PLGGEPK.js.map → chunk-SH2INDZ2.js.map} +1 -1
- package/dist/cli.cjs +4 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/convert/index.cjs +4 -2
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +3 -3
- package/dist/index.cjs +4 -2
- 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 +2 -2
- package/dist/player/index.cjs +271 -1
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +118 -2
- package/dist/player/index.d.ts +118 -2
- package/dist/player/index.js +265 -2
- package/dist/player/index.js.map +1 -1
- package/dist/spec/index.cjs +4 -2
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +2 -2
- package/dist/spec/index.d.ts +2 -2
- package/dist/spec/index.js +2 -2
- package/dist/{taals-DZU242TR.d.cts → taals-FmEulsjY.d.cts} +7 -0
- package/dist/{taals-DZU242TR.d.ts → taals-FmEulsjY.d.ts} +7 -0
- package/dist/viewer/index.cjs +4 -2
- package/dist/viewer/index.cjs.map +1 -1
- package/package.json +10 -1
- package/dist/chunk-R6QU5CXY.js.map +0 -1
package/dist/player/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { S as Swaralipi, O as Octave, a as SvaraLetter, N as Note } from '../schema-D3wyNRqM.cjs';
|
|
2
|
-
import { T as TaalId } from '../taals-
|
|
2
|
+
import { T as TaalId } from '../taals-FmEulsjY.cjs';
|
|
3
3
|
import * as tone from 'tone';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
@@ -217,6 +217,77 @@ declare class SwaralipiPlayer {
|
|
|
217
217
|
dispose(): void;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
type AudioFormat = 'wav' | 'mp3';
|
|
221
|
+
interface AudioExportOptions {
|
|
222
|
+
/** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */
|
|
223
|
+
soundsBaseUrl?: string;
|
|
224
|
+
/** Self-host the grand piano (see the player's `pianoBaseUrl`). */
|
|
225
|
+
pianoBaseUrl?: string;
|
|
226
|
+
/** Extra melody instruments, keyed by id (see the player's option). */
|
|
227
|
+
customInstruments?: Record<string, InstrumentDef>;
|
|
228
|
+
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
229
|
+
tonic?: string;
|
|
230
|
+
/** Tempo in matra/min. Default: `meta.bpm`, else 90. Snapped to the tabla grid
|
|
231
|
+
* when a sampled tabla actually plays. */
|
|
232
|
+
bpm?: number;
|
|
233
|
+
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
234
|
+
baseOctave?: number;
|
|
235
|
+
/** Melody instrument id. Default `piano`. */
|
|
236
|
+
instrument?: InstrumentId;
|
|
237
|
+
/** Include the notation (melody) voice. Default true. */
|
|
238
|
+
melody?: boolean;
|
|
239
|
+
/** Rhythm accompaniment: `tabla` (→ metronome for unsampled taals),
|
|
240
|
+
* `metronome`, or `none`. Default `none` — the base export is notation only. */
|
|
241
|
+
rhythm?: RhythmMode;
|
|
242
|
+
/** Taal for the rhythm: an explicit id, or `auto` (from `meta.taal`). Default
|
|
243
|
+
* `auto`. Only used when `rhythm` isn't `none`. */
|
|
244
|
+
taal?: TaalId | 'auto';
|
|
245
|
+
/** Include the tanpura drone. Default false. */
|
|
246
|
+
tanpura?: boolean;
|
|
247
|
+
/** Output format. Default `wav` (lossless, zero-dependency). `mp3` needs the
|
|
248
|
+
* optional peer dep `@breezystack/lamejs`. */
|
|
249
|
+
format?: AudioFormat;
|
|
250
|
+
/** MP3 bitrate in kbps. Default 192. */
|
|
251
|
+
mp3Kbps?: number;
|
|
252
|
+
/** Output channels. Default 1 (the content is monophonic and centred, so mono
|
|
253
|
+
* halves the file with no audible loss); set 2 for stereo. */
|
|
254
|
+
channels?: 1 | 2;
|
|
255
|
+
/** Sample rate. Default 44100. */
|
|
256
|
+
sampleRate?: number;
|
|
257
|
+
/** Fade the last N seconds so a looped tabla/tanpura doesn't hard-cut at the
|
|
258
|
+
* render boundary. Default 0.4. */
|
|
259
|
+
fadeOutSec?: number;
|
|
260
|
+
}
|
|
261
|
+
interface AudioExportResult {
|
|
262
|
+
/** The encoded audio, ready to download or upload. */
|
|
263
|
+
blob: Blob;
|
|
264
|
+
/** Suggested `<title>.<ext>` download filename. */
|
|
265
|
+
filename: string;
|
|
266
|
+
mimeType: string;
|
|
267
|
+
/** Rendered length in seconds. */
|
|
268
|
+
durationSec: number;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Render a swaralipi doc to an audio Blob in the browser. Runs in an
|
|
272
|
+
* OfflineAudioContext — no user gesture and no server needed. Throws in an
|
|
273
|
+
* environment without Web Audio (Node/SSR) or, for `format: 'mp3'`, when the
|
|
274
|
+
* optional `@breezystack/lamejs` peer dependency is not installed.
|
|
275
|
+
*/
|
|
276
|
+
declare function exportAudio(doc: Swaralipi, options?: AudioExportOptions): Promise<AudioExportResult>;
|
|
277
|
+
/**
|
|
278
|
+
* Encode planar float channels to a 16-bit PCM WAV (RIFF). Pure — no Web Audio
|
|
279
|
+
* — so it runs (and tests) in Node. 16-bit PCM is lossless CD quality and plays
|
|
280
|
+
* everywhere; float/24-bit skipped (add if mastering-grade output is needed).
|
|
281
|
+
*/
|
|
282
|
+
declare function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array;
|
|
283
|
+
/**
|
|
284
|
+
* Encode planar float channels to MP3 via lamejs. lamejs is **dynamically
|
|
285
|
+
* imported** and an *optional* peer dependency (LGPL-3.0) — WAV needs no
|
|
286
|
+
* dependency, so only reach for this when a compressed file is wanted. Samples
|
|
287
|
+
* are fed in 1152-frame blocks (a multiple of 576, lamejs' preferred size).
|
|
288
|
+
*/
|
|
289
|
+
declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?: number): Promise<Uint8Array>;
|
|
290
|
+
|
|
220
291
|
/**
|
|
221
292
|
* Pure spec-doc → playback timeline. No Tone, no audio — the player turns
|
|
222
293
|
* this `Schedule` into Transport events, but it is fully unit-testable on its
|
|
@@ -270,6 +341,51 @@ interface ScheduleOptions {
|
|
|
270
341
|
*/
|
|
271
342
|
declare function buildSchedule(doc: Swaralipi, opts?: ScheduleOptions): Schedule;
|
|
272
343
|
|
|
344
|
+
/**
|
|
345
|
+
* MIDI export — write a swaralipi doc to a downloadable Standard MIDI File.
|
|
346
|
+
*
|
|
347
|
+
* Unlike the audio export this needs **no Tone and no AudioContext**: MIDI is
|
|
348
|
+
* just note events, and `buildSchedule` already gives us (midi, beat,
|
|
349
|
+
* durationBeats) per note. So the whole thing is pure bytes — it runs (and
|
|
350
|
+
* tests) in plain Node, and carries no dependency (the SMF format is trivial),
|
|
351
|
+
* which keeps it clear of any redistribution/license question.
|
|
352
|
+
*
|
|
353
|
+
* Scope = the **melody voice only**. Tabla is prerecorded audio loops with no
|
|
354
|
+
* per-stroke note data, so there's nothing to turn into MIDI notes; tanpura is
|
|
355
|
+
* a fixed background drone. A `.mid` here is the notation line — the user opens
|
|
356
|
+
* it in any DAW / notation editor and assigns whatever instrument they like.
|
|
357
|
+
*/
|
|
358
|
+
|
|
359
|
+
interface MidiExportOptions {
|
|
360
|
+
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
361
|
+
tonic?: string;
|
|
362
|
+
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
363
|
+
baseOctave?: number;
|
|
364
|
+
/** Tempo in matra/min written as the file's tempo. Default `meta.bpm`, else 90. */
|
|
365
|
+
bpm?: number;
|
|
366
|
+
/** Track name meta-event. Default the song title. */
|
|
367
|
+
name?: string;
|
|
368
|
+
}
|
|
369
|
+
interface MidiExportResult {
|
|
370
|
+
blob: Blob;
|
|
371
|
+
/** Suggested `<title>.mid` filename. */
|
|
372
|
+
filename: string;
|
|
373
|
+
mimeType: string;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Encode a schedule to a format-0 Standard MIDI File (one track, one channel).
|
|
377
|
+
* Pure — no Tone, no browser — so it's directly unit-testable.
|
|
378
|
+
*/
|
|
379
|
+
declare function encodeMidi(schedule: Schedule, opts: {
|
|
380
|
+
bpm: number;
|
|
381
|
+
name?: string;
|
|
382
|
+
}): Uint8Array;
|
|
383
|
+
/**
|
|
384
|
+
* Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs
|
|
385
|
+
* beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.
|
|
386
|
+
*/
|
|
387
|
+
declare function exportMidi(doc: Swaralipi, options?: MidiExportOptions): MidiExportResult;
|
|
388
|
+
|
|
273
389
|
/**
|
|
274
390
|
* Pure svara → pitch math for the player. No Tone, no audio — unit-testable
|
|
275
391
|
* in plain Node and shared by the scheduler.
|
|
@@ -313,4 +429,4 @@ declare function midiToSampleName(midi: number): string;
|
|
|
313
429
|
/** MIDI → santoor sample filename stem (`cs4`): lowercase, `#` → `s`. */
|
|
314
430
|
declare function sampleFileName(midi: number): string;
|
|
315
431
|
|
|
316
|
-
export { type BindSheetOptions, DEFAULT_SOUNDS_BASE, INSTRUMENTS, type InstrumentDef, type InstrumentId, OCTAVE_OFFSET, type PlayEvent, type PlayerOptions, type PlayerState, type RhythmMode, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, type Schedule, type ScheduleOptions, SwaralipiPlayer, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, type ToneModule, buildSchedule, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl };
|
|
432
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DEFAULT_SOUNDS_BASE, INSTRUMENTS, type InstrumentDef, type InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayEvent, type PlayerOptions, type PlayerState, type RhythmMode, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, type Schedule, type ScheduleOptions, SwaralipiPlayer, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, type ToneModule, buildSchedule, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl };
|
package/dist/player/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { S as Swaralipi, O as Octave, a as SvaraLetter, N as Note } from '../schema-D3wyNRqM.js';
|
|
2
|
-
import { T as TaalId } from '../taals-
|
|
2
|
+
import { T as TaalId } from '../taals-FmEulsjY.js';
|
|
3
3
|
import * as tone from 'tone';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
@@ -217,6 +217,77 @@ declare class SwaralipiPlayer {
|
|
|
217
217
|
dispose(): void;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
type AudioFormat = 'wav' | 'mp3';
|
|
221
|
+
interface AudioExportOptions {
|
|
222
|
+
/** Samples host (santoor/tabla/tanpura/metronome). Default the project CDN. */
|
|
223
|
+
soundsBaseUrl?: string;
|
|
224
|
+
/** Self-host the grand piano (see the player's `pianoBaseUrl`). */
|
|
225
|
+
pianoBaseUrl?: string;
|
|
226
|
+
/** Extra melody instruments, keyed by id (see the player's option). */
|
|
227
|
+
customInstruments?: Record<string, InstrumentDef>;
|
|
228
|
+
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
229
|
+
tonic?: string;
|
|
230
|
+
/** Tempo in matra/min. Default: `meta.bpm`, else 90. Snapped to the tabla grid
|
|
231
|
+
* when a sampled tabla actually plays. */
|
|
232
|
+
bpm?: number;
|
|
233
|
+
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
234
|
+
baseOctave?: number;
|
|
235
|
+
/** Melody instrument id. Default `piano`. */
|
|
236
|
+
instrument?: InstrumentId;
|
|
237
|
+
/** Include the notation (melody) voice. Default true. */
|
|
238
|
+
melody?: boolean;
|
|
239
|
+
/** Rhythm accompaniment: `tabla` (→ metronome for unsampled taals),
|
|
240
|
+
* `metronome`, or `none`. Default `none` — the base export is notation only. */
|
|
241
|
+
rhythm?: RhythmMode;
|
|
242
|
+
/** Taal for the rhythm: an explicit id, or `auto` (from `meta.taal`). Default
|
|
243
|
+
* `auto`. Only used when `rhythm` isn't `none`. */
|
|
244
|
+
taal?: TaalId | 'auto';
|
|
245
|
+
/** Include the tanpura drone. Default false. */
|
|
246
|
+
tanpura?: boolean;
|
|
247
|
+
/** Output format. Default `wav` (lossless, zero-dependency). `mp3` needs the
|
|
248
|
+
* optional peer dep `@breezystack/lamejs`. */
|
|
249
|
+
format?: AudioFormat;
|
|
250
|
+
/** MP3 bitrate in kbps. Default 192. */
|
|
251
|
+
mp3Kbps?: number;
|
|
252
|
+
/** Output channels. Default 1 (the content is monophonic and centred, so mono
|
|
253
|
+
* halves the file with no audible loss); set 2 for stereo. */
|
|
254
|
+
channels?: 1 | 2;
|
|
255
|
+
/** Sample rate. Default 44100. */
|
|
256
|
+
sampleRate?: number;
|
|
257
|
+
/** Fade the last N seconds so a looped tabla/tanpura doesn't hard-cut at the
|
|
258
|
+
* render boundary. Default 0.4. */
|
|
259
|
+
fadeOutSec?: number;
|
|
260
|
+
}
|
|
261
|
+
interface AudioExportResult {
|
|
262
|
+
/** The encoded audio, ready to download or upload. */
|
|
263
|
+
blob: Blob;
|
|
264
|
+
/** Suggested `<title>.<ext>` download filename. */
|
|
265
|
+
filename: string;
|
|
266
|
+
mimeType: string;
|
|
267
|
+
/** Rendered length in seconds. */
|
|
268
|
+
durationSec: number;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Render a swaralipi doc to an audio Blob in the browser. Runs in an
|
|
272
|
+
* OfflineAudioContext — no user gesture and no server needed. Throws in an
|
|
273
|
+
* environment without Web Audio (Node/SSR) or, for `format: 'mp3'`, when the
|
|
274
|
+
* optional `@breezystack/lamejs` peer dependency is not installed.
|
|
275
|
+
*/
|
|
276
|
+
declare function exportAudio(doc: Swaralipi, options?: AudioExportOptions): Promise<AudioExportResult>;
|
|
277
|
+
/**
|
|
278
|
+
* Encode planar float channels to a 16-bit PCM WAV (RIFF). Pure — no Web Audio
|
|
279
|
+
* — so it runs (and tests) in Node. 16-bit PCM is lossless CD quality and plays
|
|
280
|
+
* everywhere; float/24-bit skipped (add if mastering-grade output is needed).
|
|
281
|
+
*/
|
|
282
|
+
declare function encodeWav(channels: Float32Array[], sampleRate: number): Uint8Array;
|
|
283
|
+
/**
|
|
284
|
+
* Encode planar float channels to MP3 via lamejs. lamejs is **dynamically
|
|
285
|
+
* imported** and an *optional* peer dependency (LGPL-3.0) — WAV needs no
|
|
286
|
+
* dependency, so only reach for this when a compressed file is wanted. Samples
|
|
287
|
+
* are fed in 1152-frame blocks (a multiple of 576, lamejs' preferred size).
|
|
288
|
+
*/
|
|
289
|
+
declare function encodeMp3(channels: Float32Array[], sampleRate: number, kbps?: number): Promise<Uint8Array>;
|
|
290
|
+
|
|
220
291
|
/**
|
|
221
292
|
* Pure spec-doc → playback timeline. No Tone, no audio — the player turns
|
|
222
293
|
* this `Schedule` into Transport events, but it is fully unit-testable on its
|
|
@@ -270,6 +341,51 @@ interface ScheduleOptions {
|
|
|
270
341
|
*/
|
|
271
342
|
declare function buildSchedule(doc: Swaralipi, opts?: ScheduleOptions): Schedule;
|
|
272
343
|
|
|
344
|
+
/**
|
|
345
|
+
* MIDI export — write a swaralipi doc to a downloadable Standard MIDI File.
|
|
346
|
+
*
|
|
347
|
+
* Unlike the audio export this needs **no Tone and no AudioContext**: MIDI is
|
|
348
|
+
* just note events, and `buildSchedule` already gives us (midi, beat,
|
|
349
|
+
* durationBeats) per note. So the whole thing is pure bytes — it runs (and
|
|
350
|
+
* tests) in plain Node, and carries no dependency (the SMF format is trivial),
|
|
351
|
+
* which keeps it clear of any redistribution/license question.
|
|
352
|
+
*
|
|
353
|
+
* Scope = the **melody voice only**. Tabla is prerecorded audio loops with no
|
|
354
|
+
* per-stroke note data, so there's nothing to turn into MIDI notes; tanpura is
|
|
355
|
+
* a fixed background drone. A `.mid` here is the notation line — the user opens
|
|
356
|
+
* it in any DAW / notation editor and assigns whatever instrument they like.
|
|
357
|
+
*/
|
|
358
|
+
|
|
359
|
+
interface MidiExportOptions {
|
|
360
|
+
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
361
|
+
tonic?: string;
|
|
362
|
+
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
363
|
+
baseOctave?: number;
|
|
364
|
+
/** Tempo in matra/min written as the file's tempo. Default `meta.bpm`, else 90. */
|
|
365
|
+
bpm?: number;
|
|
366
|
+
/** Track name meta-event. Default the song title. */
|
|
367
|
+
name?: string;
|
|
368
|
+
}
|
|
369
|
+
interface MidiExportResult {
|
|
370
|
+
blob: Blob;
|
|
371
|
+
/** Suggested `<title>.mid` filename. */
|
|
372
|
+
filename: string;
|
|
373
|
+
mimeType: string;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Encode a schedule to a format-0 Standard MIDI File (one track, one channel).
|
|
377
|
+
* Pure — no Tone, no browser — so it's directly unit-testable.
|
|
378
|
+
*/
|
|
379
|
+
declare function encodeMidi(schedule: Schedule, opts: {
|
|
380
|
+
bpm: number;
|
|
381
|
+
name?: string;
|
|
382
|
+
}): Uint8Array;
|
|
383
|
+
/**
|
|
384
|
+
* Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs
|
|
385
|
+
* beyond `Blob` (present in Node 20+ too), so it works client-side or server-side.
|
|
386
|
+
*/
|
|
387
|
+
declare function exportMidi(doc: Swaralipi, options?: MidiExportOptions): MidiExportResult;
|
|
388
|
+
|
|
273
389
|
/**
|
|
274
390
|
* Pure svara → pitch math for the player. No Tone, no audio — unit-testable
|
|
275
391
|
* in plain Node and shared by the scheduler.
|
|
@@ -313,4 +429,4 @@ declare function midiToSampleName(midi: number): string;
|
|
|
313
429
|
/** MIDI → santoor sample filename stem (`cs4`): lowercase, `#` → `s`. */
|
|
314
430
|
declare function sampleFileName(midi: number): string;
|
|
315
431
|
|
|
316
|
-
export { type BindSheetOptions, DEFAULT_SOUNDS_BASE, INSTRUMENTS, type InstrumentDef, type InstrumentId, OCTAVE_OFFSET, type PlayEvent, type PlayerOptions, type PlayerState, type RhythmMode, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, type Schedule, type ScheduleOptions, SwaralipiPlayer, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, type ToneModule, buildSchedule, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl };
|
|
432
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DEFAULT_SOUNDS_BASE, INSTRUMENTS, type InstrumentDef, type InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayEvent, type PlayerOptions, type PlayerState, type RhythmMode, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, type Schedule, type ScheduleOptions, SwaralipiPlayer, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, type ToneModule, buildSchedule, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl };
|
package/dist/player/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TAALS } from '../chunk-
|
|
1
|
+
import { TAALS } from '../chunk-H6HC44PZ.js';
|
|
2
2
|
|
|
3
3
|
// src/player/pitch.ts
|
|
4
4
|
var OCTAVE_OFFSET = {
|
|
@@ -773,6 +773,269 @@ var SwaralipiPlayer = class {
|
|
|
773
773
|
}
|
|
774
774
|
};
|
|
775
775
|
|
|
776
|
-
export
|
|
776
|
+
// src/player/export.ts
|
|
777
|
+
function midiRange2(schedule) {
|
|
778
|
+
let lo = Infinity;
|
|
779
|
+
let hi = -Infinity;
|
|
780
|
+
for (const ev of schedule.events) {
|
|
781
|
+
for (const m of [ev.midi, ev.kan]) {
|
|
782
|
+
if (m !== null) {
|
|
783
|
+
if (m < lo) lo = m;
|
|
784
|
+
if (m > hi) hi = m;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return lo <= hi ? { lo, hi } : void 0;
|
|
789
|
+
}
|
|
790
|
+
function pickDef(id, o) {
|
|
791
|
+
const def = o.customInstruments?.[id] ?? INSTRUMENTS[id];
|
|
792
|
+
if (def === void 0) return INSTRUMENTS.harmonium;
|
|
793
|
+
if (id === "piano" && o.pianoBaseUrl) return { ...def, baseUrl: normalizeBase(o.pianoBaseUrl) };
|
|
794
|
+
if (def.local === true && !o.soundsBaseUrl) return INSTRUMENTS.harmonium;
|
|
795
|
+
return def;
|
|
796
|
+
}
|
|
797
|
+
function resolveTaal(doc, taal) {
|
|
798
|
+
const id = taal !== void 0 && taal !== "auto" ? taal : doc.meta.taal?.id;
|
|
799
|
+
return id !== void 0 && id in TAALS ? id : void 0;
|
|
800
|
+
}
|
|
801
|
+
function safeName(title) {
|
|
802
|
+
return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
|
|
803
|
+
}
|
|
804
|
+
async function exportAudio(doc, options = {}) {
|
|
805
|
+
const Tone = await import('tone');
|
|
806
|
+
const base = options.soundsBaseUrl ?? DEFAULT_SOUNDS_BASE;
|
|
807
|
+
const format = options.format ?? "wav";
|
|
808
|
+
const channels = options.channels ?? 1;
|
|
809
|
+
const sampleRate = options.sampleRate ?? 44100;
|
|
810
|
+
const fadeOutSec = options.fadeOutSec ?? 0.4;
|
|
811
|
+
const rhythm = options.rhythm ?? "none";
|
|
812
|
+
const withMelody = options.melody ?? true;
|
|
813
|
+
const withTanpura = options.tanpura ?? false;
|
|
814
|
+
const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });
|
|
815
|
+
const range = midiRange2(schedule);
|
|
816
|
+
const taalId = resolveTaal(doc, options.taal);
|
|
817
|
+
const variation = options.taal === void 0 || options.taal === "auto" ? doc.meta.taal?.variation : void 0;
|
|
818
|
+
const usesTabla = rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && !!base;
|
|
819
|
+
const reqBpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
820
|
+
const bpm = usesTabla && taalId ? nearestTablaBpm(reqBpm, tablaBpms(taalId)) : reqBpm;
|
|
821
|
+
const spb = 60 / bpm;
|
|
822
|
+
const id = options.instrument ?? "piano";
|
|
823
|
+
const def = pickDef(id, options);
|
|
824
|
+
const durationSec = Math.max(schedule.totalBeats * spb + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
|
|
825
|
+
const rendered = await Tone.Offline(
|
|
826
|
+
async ({ transport }) => {
|
|
827
|
+
transport.bpm.value = bpm;
|
|
828
|
+
const master = new Tone.Gain(1).toDestination();
|
|
829
|
+
if (withMelody) {
|
|
830
|
+
const sampler = createSampler(Tone, def, base, range).connect(master);
|
|
831
|
+
for (const ev of schedule.events) {
|
|
832
|
+
if (ev.midi === null) continue;
|
|
833
|
+
const at = ev.beat * spb;
|
|
834
|
+
const dur = Math.max(ev.durationBeats * spb, 0.05);
|
|
835
|
+
if (ev.kan !== null) {
|
|
836
|
+
const name2 = midiToSampleName(ev.kan);
|
|
837
|
+
transport.scheduleOnce((t) => sampler.triggerAttackRelease(name2, spb * 0.4, t), Math.max(at - spb * 0.12, 0));
|
|
838
|
+
}
|
|
839
|
+
const name = midiToSampleName(ev.midi);
|
|
840
|
+
transport.scheduleOnce((t) => sampler.triggerAttackRelease(name, dur, t), at);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
let tabla;
|
|
844
|
+
if (rhythm !== "none" && base) {
|
|
845
|
+
if (usesTabla && taalId) {
|
|
846
|
+
const url = tablaUrl(base, taalId, bpm, variation);
|
|
847
|
+
tabla = new Tone.Player({ url, loop: true }).connect(master);
|
|
848
|
+
tabla.sync().start(0);
|
|
849
|
+
} else {
|
|
850
|
+
const metro = createMetronome(Tone, base).connect(master);
|
|
851
|
+
const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : 4;
|
|
852
|
+
let beat = 0;
|
|
853
|
+
transport.scheduleRepeat((time) => {
|
|
854
|
+
metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
|
|
855
|
+
metro.start(time);
|
|
856
|
+
beat += 1;
|
|
857
|
+
}, spb, 0);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (withTanpura && base) {
|
|
861
|
+
const tonicPc = (parseTonic(options.tonic ?? doc.meta.tonic) % 12 + 12) % 12;
|
|
862
|
+
const sa = TANPURA_PCS[tonicPc];
|
|
863
|
+
const pa = TANPURA_PCS[(tonicPc + 7) % 12];
|
|
864
|
+
const players = createTanpura(Tone, base, [sa, pa]).connect(master);
|
|
865
|
+
players.volume.value = -14;
|
|
866
|
+
const pattern = [pa, sa, sa, sa];
|
|
867
|
+
let i = 0;
|
|
868
|
+
transport.scheduleRepeat((time) => {
|
|
869
|
+
const stem = pattern[i % pattern.length];
|
|
870
|
+
if (players.has(stem)) players.player(stem).start(time);
|
|
871
|
+
i += 1;
|
|
872
|
+
}, spb * 2, 0);
|
|
873
|
+
}
|
|
874
|
+
await Tone.loaded();
|
|
875
|
+
if (tabla?.buffer?.loaded) tabla.loopEnd = quantizeLoopEnd(tabla.buffer.duration, spb);
|
|
876
|
+
if (fadeOutSec > 0) {
|
|
877
|
+
master.gain.setValueAtTime(1, Math.max(durationSec - fadeOutSec, 0));
|
|
878
|
+
master.gain.linearRampToValueAtTime(0, durationSec);
|
|
879
|
+
}
|
|
880
|
+
transport.start(0);
|
|
881
|
+
},
|
|
882
|
+
durationSec,
|
|
883
|
+
channels,
|
|
884
|
+
sampleRate
|
|
885
|
+
);
|
|
886
|
+
const chans = [];
|
|
887
|
+
for (let c = 0; c < rendered.numberOfChannels; c++) chans.push(rendered.getChannelData(c));
|
|
888
|
+
const bytes = format === "mp3" ? await encodeMp3(chans, rendered.sampleRate, options.mp3Kbps) : encodeWav(chans, rendered.sampleRate);
|
|
889
|
+
const mimeType = format === "mp3" ? "audio/mpeg" : "audio/wav";
|
|
890
|
+
return {
|
|
891
|
+
blob: new Blob([bytes], { type: mimeType }),
|
|
892
|
+
filename: `${safeName(doc.meta.title)}.${format}`,
|
|
893
|
+
mimeType,
|
|
894
|
+
durationSec: rendered.duration
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function toInt16(f) {
|
|
898
|
+
const s = f < -1 ? -1 : f > 1 ? 1 : f;
|
|
899
|
+
return s < 0 ? s * 32768 : s * 32767;
|
|
900
|
+
}
|
|
901
|
+
function encodeWav(channels, sampleRate) {
|
|
902
|
+
const numCh = channels.length;
|
|
903
|
+
const frames = channels[0]?.length ?? 0;
|
|
904
|
+
const bytesPerSample = 2;
|
|
905
|
+
const blockAlign = numCh * bytesPerSample;
|
|
906
|
+
const dataSize = frames * blockAlign;
|
|
907
|
+
const buf = new ArrayBuffer(44 + dataSize);
|
|
908
|
+
const view = new DataView(buf);
|
|
909
|
+
const str = (off2, s) => {
|
|
910
|
+
for (let i = 0; i < s.length; i++) view.setUint8(off2 + i, s.charCodeAt(i));
|
|
911
|
+
};
|
|
912
|
+
str(0, "RIFF");
|
|
913
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
914
|
+
str(8, "WAVE");
|
|
915
|
+
str(12, "fmt ");
|
|
916
|
+
view.setUint32(16, 16, true);
|
|
917
|
+
view.setUint16(20, 1, true);
|
|
918
|
+
view.setUint16(22, numCh, true);
|
|
919
|
+
view.setUint32(24, sampleRate, true);
|
|
920
|
+
view.setUint32(28, sampleRate * blockAlign, true);
|
|
921
|
+
view.setUint16(32, blockAlign, true);
|
|
922
|
+
view.setUint16(34, bytesPerSample * 8, true);
|
|
923
|
+
str(36, "data");
|
|
924
|
+
view.setUint32(40, dataSize, true);
|
|
925
|
+
let off = 44;
|
|
926
|
+
for (let f = 0; f < frames; f++) {
|
|
927
|
+
for (let c = 0; c < numCh; c++) {
|
|
928
|
+
view.setInt16(off, toInt16(channels[c][f]), true);
|
|
929
|
+
off += 2;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return new Uint8Array(buf);
|
|
933
|
+
}
|
|
934
|
+
function floatToInt16(f) {
|
|
935
|
+
const out = new Int16Array(f.length);
|
|
936
|
+
for (let i = 0; i < f.length; i++) out[i] = toInt16(f[i]);
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
function concatBytes(chunks) {
|
|
940
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
941
|
+
const out = new Uint8Array(total);
|
|
942
|
+
let off = 0;
|
|
943
|
+
for (const c of chunks) {
|
|
944
|
+
out.set(c, off);
|
|
945
|
+
off += c.length;
|
|
946
|
+
}
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
async function loadLame() {
|
|
950
|
+
try {
|
|
951
|
+
const mod = await import('@breezystack/lamejs');
|
|
952
|
+
const Mp3Encoder = mod.Mp3Encoder ?? mod.default?.Mp3Encoder;
|
|
953
|
+
if (!Mp3Encoder) throw new Error("no Mp3Encoder export");
|
|
954
|
+
return Mp3Encoder;
|
|
955
|
+
} catch {
|
|
956
|
+
throw new Error(
|
|
957
|
+
'MP3 export needs the optional peer dependency "@breezystack/lamejs" \u2014 install it, or export as WAV (format: "wav").'
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
async function encodeMp3(channels, sampleRate, kbps = 192) {
|
|
962
|
+
const Mp3Encoder = await loadLame();
|
|
963
|
+
const numCh = Math.min(channels.length, 2);
|
|
964
|
+
const enc = new Mp3Encoder(numCh, sampleRate, kbps);
|
|
965
|
+
const left = floatToInt16(channels[0] ?? new Float32Array());
|
|
966
|
+
const right = numCh > 1 ? floatToInt16(channels[1]) : void 0;
|
|
967
|
+
const block = 1152;
|
|
968
|
+
const out = [];
|
|
969
|
+
for (let i = 0; i < left.length; i += block) {
|
|
970
|
+
const l = left.subarray(i, i + block);
|
|
971
|
+
const chunk = right ? enc.encodeBuffer(l, right.subarray(i, i + block)) : enc.encodeBuffer(l);
|
|
972
|
+
if (chunk.length > 0) out.push(new Uint8Array(chunk));
|
|
973
|
+
}
|
|
974
|
+
const tail = enc.flush();
|
|
975
|
+
if (tail.length > 0) out.push(new Uint8Array(tail));
|
|
976
|
+
return concatBytes(out);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/player/midi.ts
|
|
980
|
+
var PPQ = 480;
|
|
981
|
+
function vlq(n) {
|
|
982
|
+
const out = [n & 127];
|
|
983
|
+
n >>>= 7;
|
|
984
|
+
while (n > 0) {
|
|
985
|
+
out.unshift(n & 127 | 128);
|
|
986
|
+
n >>>= 7;
|
|
987
|
+
}
|
|
988
|
+
return out;
|
|
989
|
+
}
|
|
990
|
+
function utf8(s) {
|
|
991
|
+
return [...new TextEncoder().encode(s)];
|
|
992
|
+
}
|
|
993
|
+
function u32be(n) {
|
|
994
|
+
return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
|
|
995
|
+
}
|
|
996
|
+
function encodeMidi(schedule, opts) {
|
|
997
|
+
const raw = [];
|
|
998
|
+
for (const ev of schedule.events) {
|
|
999
|
+
if (ev.midi === null) continue;
|
|
1000
|
+
const start = Math.round(ev.beat * PPQ);
|
|
1001
|
+
const end = Math.max(Math.round((ev.beat + ev.durationBeats) * PPQ), start + 1);
|
|
1002
|
+
if (ev.kan !== null) {
|
|
1003
|
+
const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);
|
|
1004
|
+
raw.push({ tick: kOn, on: true, note: ev.kan, vel: 70 });
|
|
1005
|
+
raw.push({ tick: start, on: false, note: ev.kan, vel: 0 });
|
|
1006
|
+
}
|
|
1007
|
+
raw.push({ tick: start, on: true, note: ev.midi, vel: 100 });
|
|
1008
|
+
raw.push({ tick: end, on: false, note: ev.midi, vel: 0 });
|
|
1009
|
+
}
|
|
1010
|
+
raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));
|
|
1011
|
+
const track = [];
|
|
1012
|
+
if (opts.name) track.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
1013
|
+
const usPerBeat = Math.round(6e7 / opts.bpm);
|
|
1014
|
+
track.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
|
|
1015
|
+
let last = 0;
|
|
1016
|
+
for (const e of raw) {
|
|
1017
|
+
track.push(...vlq(e.tick - last), e.on ? 144 : 128, e.note & 127, e.vel & 127);
|
|
1018
|
+
last = e.tick;
|
|
1019
|
+
}
|
|
1020
|
+
track.push(...vlq(0), 255, 47, 0);
|
|
1021
|
+
const header = [77, 84, 104, 100, ...u32be(6), 0, 0, 0, 1, PPQ >> 8 & 255, PPQ & 255];
|
|
1022
|
+
const trackChunk = [77, 84, 114, 107, ...u32be(track.length), ...track];
|
|
1023
|
+
return new Uint8Array([...header, ...trackChunk]);
|
|
1024
|
+
}
|
|
1025
|
+
function safeName2(title) {
|
|
1026
|
+
return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
|
|
1027
|
+
}
|
|
1028
|
+
function exportMidi(doc, options = {}) {
|
|
1029
|
+
const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });
|
|
1030
|
+
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
1031
|
+
const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title });
|
|
1032
|
+
return {
|
|
1033
|
+
blob: new Blob([bytes], { type: "audio/midi" }),
|
|
1034
|
+
filename: `${safeName2(doc.meta.title)}.mid`,
|
|
1035
|
+
mimeType: "audio/midi"
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
export { DEFAULT_SOUNDS_BASE, INSTRUMENTS, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, SwaralipiPlayer, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl };
|
|
777
1040
|
//# sourceMappingURL=index.js.map
|
|
778
1041
|
//# sourceMappingURL=index.js.map
|