@ugfoundation/swaralipi-js 0.1.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/LICENSE +347 -0
- package/README.md +257 -0
- package/dist/batch-NF2Q2CFS.js +47 -0
- package/dist/batch-NF2Q2CFS.js.map +1 -0
- package/dist/chunk-C6O7DYU5.js +115 -0
- package/dist/chunk-C6O7DYU5.js.map +1 -0
- package/dist/chunk-GWXI2HJA.js +14 -0
- package/dist/chunk-GWXI2HJA.js.map +1 -0
- package/dist/chunk-N3NNWAOW.js +593 -0
- package/dist/chunk-N3NNWAOW.js.map +1 -0
- package/dist/cli.cjs +913 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +91 -0
- package/dist/cli.js.map +1 -0
- package/dist/convert/index.cjs +720 -0
- package/dist/convert/index.cjs.map +1 -0
- package/dist/convert/index.d.cts +270 -0
- package/dist/convert/index.d.ts +270 -0
- package/dist/convert/index.js +5 -0
- package/dist/convert/index.js.map +1 -0
- package/dist/index.cjs +143 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/player/index.cjs +775 -0
- package/dist/player/index.cjs.map +1 -0
- package/dist/player/index.d.cts +285 -0
- package/dist/player/index.d.ts +285 -0
- package/dist/player/index.js +743 -0
- package/dist/player/index.js.map +1 -0
- package/dist/schema-gxhG45OK.d.cts +467 -0
- package/dist/schema-gxhG45OK.d.ts +467 -0
- package/dist/spec/index.cjs +143 -0
- package/dist/spec/index.cjs.map +1 -0
- package/dist/spec/index.d.cts +70 -0
- package/dist/spec/index.d.ts +70 -0
- package/dist/spec/index.js +4 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/taals-DguYW0wf.d.cts +60 -0
- package/dist/taals-DguYW0wf.d.ts +60 -0
- package/dist/viewer/index.cjs +998 -0
- package/dist/viewer/index.cjs.map +1 -0
- package/dist/viewer/index.d.cts +285 -0
- package/dist/viewer/index.d.ts +285 -0
- package/dist/viewer/index.js +814 -0
- package/dist/viewer/index.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { S as Swaralipi, O as Octave, a as SvaraLetter, N as Note } from '../schema-gxhG45OK.js';
|
|
2
|
+
import { T as TaalId } from '../taals-DguYW0wf.js';
|
|
3
|
+
import * as tone from 'tone';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/** The dynamically-imported Tone module, typed without importing it. */
|
|
7
|
+
type ToneModule = typeof tone;
|
|
8
|
+
/** A melodic instrument: a Tone.Sampler note→filename map + where to find it. */
|
|
9
|
+
interface InstrumentDef {
|
|
10
|
+
/** Human label for UIs. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Tone.Sampler `urls`: note name (`C#4`) → filename (`Cs4.mp3`). */
|
|
13
|
+
urls: Record<string, string>;
|
|
14
|
+
/** Sample directory: an absolute URL, or — when `local` — a path segment
|
|
15
|
+
* appended to the player's `soundsBaseUrl`. */
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
/** `baseUrl` is relative to `soundsBaseUrl` (needs it set). */
|
|
18
|
+
local?: boolean;
|
|
19
|
+
/** Sampler release time in seconds. */
|
|
20
|
+
release?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Built-in melody instrument ids (consumers may add their own via the
|
|
23
|
+
* player's `customInstruments`, so the runtime id type stays open). */
|
|
24
|
+
type InstrumentId = string;
|
|
25
|
+
/**
|
|
26
|
+
* Default host for the project's samples (santoor / tabla / tanpura /
|
|
27
|
+
* metronome) — CC-BY, redistribution-verified and CORS-enabled, so the player
|
|
28
|
+
* works with zero config. Override via the player's `soundsBaseUrl` to
|
|
29
|
+
* self-host. (Melody defaults to the CDN-hosted piano, so it needs no host.)
|
|
30
|
+
*/
|
|
31
|
+
declare const DEFAULT_SOUNDS_BASE = "https://cdn.daserve.in/swaralipijs-instruments/";
|
|
32
|
+
/** CC-BY Salamander Grand Piano (Tone's de-facto piano), CDN-hosted. */
|
|
33
|
+
declare const SALAMANDER_BASE = "https://tonejs.github.io/audio/salamander/";
|
|
34
|
+
/** CC-BY tonejs-instruments sample CDN (harmonium/violin/flute). */
|
|
35
|
+
declare const TONEJS_INSTRUMENTS_BASE = "https://nbrosowsky.github.io/tonejs-instruments/samples/";
|
|
36
|
+
/** Built-in melody instruments. santoor is local; the rest are CDN-hosted
|
|
37
|
+
* (CC-BY) so they need only network — `pianoBaseUrl`/`customInstruments`
|
|
38
|
+
* override for self-hosting. */
|
|
39
|
+
declare const INSTRUMENTS: Record<string, InstrumentDef>;
|
|
40
|
+
/** Taals with recorded tabla loops (everything else → metronome fallback). */
|
|
41
|
+
declare const SAMPLED_TAALS: ReadonlySet<TaalId>;
|
|
42
|
+
/** Available tabla loop tempos (file suffix `…<bpm>bpm.mp3`). */
|
|
43
|
+
declare const TABLA_BPMS: readonly number[];
|
|
44
|
+
/** Ensure exactly one trailing slash. */
|
|
45
|
+
declare function normalizeBase(url: string): string;
|
|
46
|
+
/** The 37 santoor samples, as a Tone.Sampler `urls` map (`C4` → `c4.mp3`). */
|
|
47
|
+
declare function santoorUrls(): Record<string, string>;
|
|
48
|
+
/**
|
|
49
|
+
* Restrict a Sampler `urls` map to just the samples needed to cover MIDI
|
|
50
|
+
* `[lo, hi]`: every sample inside the range, plus the nearest one below and
|
|
51
|
+
* above it (so Tone interpolates the edges without extrapolating). Cuts a
|
|
52
|
+
* typical song from ~37 santoor files to a handful — a big win on mobile and
|
|
53
|
+
* never worse on fast links (Tone fetches what's left in parallel). The
|
|
54
|
+
* Sampler pitch-shifts from the nearest sample, so audio is unchanged.
|
|
55
|
+
*/
|
|
56
|
+
declare function subsetUrls(urls: Record<string, string>, lo: number, hi: number): Record<string, string>;
|
|
57
|
+
/** Resolve a representative sample URL for an instrument (its first sample),
|
|
58
|
+
* used to probe availability. undefined when a local instrument has no
|
|
59
|
+
* `soundsBaseUrl` or the def has no samples. */
|
|
60
|
+
declare function instrumentSampleUrl(def: InstrumentDef, soundsBaseUrl?: string): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Liveness check for a sample URL: a single tiny ranged GET (one byte), the
|
|
63
|
+
* same CORS-enabled fetch Tone uses to load the buffer — so a pass means Tone
|
|
64
|
+
* can actually load it. Resolves false on any error/timeout/non-2xx, and false
|
|
65
|
+
* (no throw) when `fetch` is unavailable (Node/SSR). `Range: bytes=0-0` is a
|
|
66
|
+
* CORS-safelisted header, so no preflight.
|
|
67
|
+
*/
|
|
68
|
+
declare function probeUrl(url: string, timeoutMs?: number): Promise<boolean>;
|
|
69
|
+
/** Snap an arbitrary bpm to the nearest available tabla tempo. */
|
|
70
|
+
declare function nearestTablaBpm(bpm: number): number;
|
|
71
|
+
/** URL of the tabla loop for a taal at a (snapped) bpm. */
|
|
72
|
+
declare function tablaUrl(base: string, taalId: string, bpm: number): string;
|
|
73
|
+
|
|
74
|
+
type PlayerState = 'idle' | 'loading' | 'ready' | 'playing' | 'paused';
|
|
75
|
+
type RhythmMode = 'tabla' | 'metronome' | 'none';
|
|
76
|
+
interface PlayerOptions {
|
|
77
|
+
/** Base URL for the project's samples (santoor/tabla/tanpura/metronome).
|
|
78
|
+
* Defaults to `DEFAULT_SOUNDS_BASE` (the project's CORS-enabled CDN), so the
|
|
79
|
+
* player works with zero config; override to self-host. */
|
|
80
|
+
soundsBaseUrl?: string;
|
|
81
|
+
/** Grand-piano sample host. Default: the Salamander CDN (see SALAMANDER_BASE).
|
|
82
|
+
* Set this to self-host the piano under your own URL. */
|
|
83
|
+
pianoBaseUrl?: string;
|
|
84
|
+
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
85
|
+
tonic?: string;
|
|
86
|
+
/** Tempo in matra/min. Default 90. Snapped to the 15-grid while tabla plays. */
|
|
87
|
+
bpm?: number;
|
|
88
|
+
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
89
|
+
baseOctave?: number;
|
|
90
|
+
/** Active melody instrument id (a built-in or a `customInstruments` key).
|
|
91
|
+
* Default `piano` (CDN-hosted, zero-config). Built-ins: piano, santoor,
|
|
92
|
+
* harmonium, violin, flute. */
|
|
93
|
+
instrument?: InstrumentId;
|
|
94
|
+
/** Instrument ids the UI may expose. Default: all built-ins. Narrow it to
|
|
95
|
+
* e.g. `['santoor','harmonium']` to limit the picker. */
|
|
96
|
+
instruments?: InstrumentId[];
|
|
97
|
+
/** Extra melody instruments, keyed by id — e.g. a self-hosted sitar/sarod.
|
|
98
|
+
* (True sampled sitar/sarod/sarangi/esraj have no free web set; supply your
|
|
99
|
+
* own Tone.Sampler `urls` + `baseUrl` here and they slot into the picker.) */
|
|
100
|
+
customInstruments?: Record<string, InstrumentDef>;
|
|
101
|
+
/** Rhythm accompaniment. Default `tabla` (→ metronome for unsampled taals). */
|
|
102
|
+
rhythm?: RhythmMode;
|
|
103
|
+
/** Play the tanpura drone. Default true. */
|
|
104
|
+
tanpura?: boolean;
|
|
105
|
+
/** Stop playback after this many seconds (a policy-neutral preview cap).
|
|
106
|
+
* Default `Infinity` (unlimited). The library only enforces the number it is
|
|
107
|
+
* handed; who is capped / the value / the upsell is the consumer's policy. */
|
|
108
|
+
previewLimitSec?: number;
|
|
109
|
+
/** Called when the preview cap stops playback (so the consumer can show an
|
|
110
|
+
* upsell). Not called on a normal stop()/end. */
|
|
111
|
+
onLimit?: () => void;
|
|
112
|
+
/** Called with the active note's `part.row.note` path, or null when idle. */
|
|
113
|
+
onHighlight?: (path: string | null) => void;
|
|
114
|
+
/** Called on every state transition. */
|
|
115
|
+
onState?: (state: PlayerState) => void;
|
|
116
|
+
}
|
|
117
|
+
/** Minimal shape of the viewer element the player drives (avoids a hard
|
|
118
|
+
* dependency on the viewer module). `highlight` returns the highlighted node
|
|
119
|
+
* (or null) so bindSheet can optionally scroll it into view. */
|
|
120
|
+
interface HighlightTarget {
|
|
121
|
+
highlight(path: string): HTMLElement | null;
|
|
122
|
+
clearHighlights(): void;
|
|
123
|
+
}
|
|
124
|
+
interface BindSheetOptions {
|
|
125
|
+
/** Keep the active note visible while playing by calling `scrollIntoView`
|
|
126
|
+
* on it. `true` → `'center'` (keeps the note's lyric, rendered just below it,
|
|
127
|
+
* on screen too); or pass an explicit block position (`'nearest'`, `'start'`,
|
|
128
|
+
* …). Default off so it never fights a host that manages its own scrolling.
|
|
129
|
+
* Works through the shadow boundary, so it scrolls the page, not just the
|
|
130
|
+
* sheet. */
|
|
131
|
+
autoScroll?: boolean | ScrollLogicalPosition;
|
|
132
|
+
}
|
|
133
|
+
declare class SwaralipiPlayer {
|
|
134
|
+
#private;
|
|
135
|
+
constructor(options?: PlayerOptions);
|
|
136
|
+
get state(): PlayerState;
|
|
137
|
+
get bpm(): number;
|
|
138
|
+
/** The preview cap in seconds (`Infinity` = unlimited). */
|
|
139
|
+
get previewLimitSec(): number;
|
|
140
|
+
get instrument(): InstrumentId;
|
|
141
|
+
get instruments(): InstrumentId[];
|
|
142
|
+
/** The taal currently driving the rhythm (auto from the doc, or an override). */
|
|
143
|
+
get taalId(): TaalId | undefined;
|
|
144
|
+
/** Whether the effective rhythm will actually be a sampled tabla. */
|
|
145
|
+
get usesTabla(): boolean;
|
|
146
|
+
/** Set the document to play. Stops any current playback. */
|
|
147
|
+
load(doc: Swaralipi): void;
|
|
148
|
+
/** Start (or resume) playback. Must be triggered from a user gesture so the
|
|
149
|
+
* browser permits `AudioContext` resume. */
|
|
150
|
+
play(): Promise<void>;
|
|
151
|
+
/** Pause; `play()` resumes from the same position. */
|
|
152
|
+
pause(): void;
|
|
153
|
+
/**
|
|
154
|
+
* Stop and rewind to the start. When sounding, the accompaniment (tabla /
|
|
155
|
+
* metronome / tanpura, which otherwise loop) is gently faded out over
|
|
156
|
+
* STOP_FADE_SEC before the transport is halted, so the rhythm doesn't keep
|
|
157
|
+
* playing or cut abruptly; otherwise it halts immediately.
|
|
158
|
+
*/
|
|
159
|
+
stop(): void;
|
|
160
|
+
setTonic(tonic: string): void;
|
|
161
|
+
setBpm(bpm: number): void;
|
|
162
|
+
/** Update the preview cap (`Infinity` lifts it). Takes effect on next play. */
|
|
163
|
+
setPreviewLimit(sec: number): void;
|
|
164
|
+
setInstrument(instrument: InstrumentId): void;
|
|
165
|
+
setRhythm(rhythm: RhythmMode): void;
|
|
166
|
+
setTanpura(on: boolean): void;
|
|
167
|
+
/** Override the taal (null restores auto-detection from `meta.taal`). */
|
|
168
|
+
setTaal(id: TaalId | null): void;
|
|
169
|
+
/** Wire highlight output to a `<swaralipi-sheet>` (or any highlight target).
|
|
170
|
+
* Pass `{ autoScroll }` to keep the active note in view while playing. */
|
|
171
|
+
bindSheet(target: HighlightTarget, opts?: BindSheetOptions): void;
|
|
172
|
+
/**
|
|
173
|
+
* Check whether an instrument's samples are reachable (its CDN is up / the
|
|
174
|
+
* local files exist), via a one-byte ranged fetch of a representative sample.
|
|
175
|
+
* A pass means Tone can actually load it. `false` for unknown ids, local
|
|
176
|
+
* instruments with no `soundsBaseUrl`, or any network failure/timeout.
|
|
177
|
+
*/
|
|
178
|
+
checkInstrument(id: string, timeoutMs?: number): Promise<boolean>;
|
|
179
|
+
/**
|
|
180
|
+
* Probe several instruments at once (default: the exposed `instruments`),
|
|
181
|
+
* returning `{ id: available }`. Use it to disable picker options whose CDN
|
|
182
|
+
* or files are down. Probes run concurrently; a check never throws.
|
|
183
|
+
*/
|
|
184
|
+
checkInstruments(ids?: string[], timeoutMs?: number): Promise<Record<string, boolean>>;
|
|
185
|
+
/** Release all audio resources. The instance is unusable afterward. */
|
|
186
|
+
dispose(): void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Pure spec-doc → playback timeline. No Tone, no audio — the player turns
|
|
191
|
+
* this `Schedule` into Transport events, but it is fully unit-testable on its
|
|
192
|
+
* own and mirrors the viewer's spec-driven (not raw-driven) reading.
|
|
193
|
+
*
|
|
194
|
+
* Timing unit is the মাত্রা (matra / beat). One global beat cursor runs across
|
|
195
|
+
* every part and row: NLTR "systems" (rows) are wraps of one continuous line,
|
|
196
|
+
* and parts (asthayi → antara …) play back-to-back, so the music never pauses
|
|
197
|
+
* at a row or part boundary.
|
|
198
|
+
*
|
|
199
|
+
* Each audible column carries the same `data-note` path the viewer emits
|
|
200
|
+
* (`part.row.note`, note = absolute index into `row.notes`; see
|
|
201
|
+
* src/viewer/render.ts), so `SwaralipiPlayer` can drive `highlight()` directly.
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
interface PlayEvent {
|
|
205
|
+
/** Onset in matra from the start of the document. */
|
|
206
|
+
beat: number;
|
|
207
|
+
/** Ring length in matra (extended by following আ-কার holds). */
|
|
208
|
+
durationBeats: number;
|
|
209
|
+
/** Pitch as MIDI, or null for a rest / আ-কার hold column. */
|
|
210
|
+
midi: number | null;
|
|
211
|
+
/** Grace-note MIDI struck just before the main attack, when present. */
|
|
212
|
+
kan: number | null;
|
|
213
|
+
/** `part.row.note` highlight path. */
|
|
214
|
+
path: string;
|
|
215
|
+
kind: 'note' | 'rest' | 'sustain';
|
|
216
|
+
}
|
|
217
|
+
interface Schedule {
|
|
218
|
+
events: PlayEvent[];
|
|
219
|
+
/** Total length in matra (the cursor's final value). */
|
|
220
|
+
totalBeats: number;
|
|
221
|
+
}
|
|
222
|
+
interface ScheduleOptions {
|
|
223
|
+
/** Tonic; accepts `C`, `C#`, `cs`… (see parseTonic). Default C. */
|
|
224
|
+
tonic?: string;
|
|
225
|
+
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
226
|
+
baseOctave?: number;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Walk a document into a flat, time-ordered list of play events.
|
|
230
|
+
*
|
|
231
|
+
* - svara note → an attack at the cursor; advances by `durationMatra ?? 1`.
|
|
232
|
+
* - rest → a silent column that still advances the cursor.
|
|
233
|
+
* - sustain **with** svara (melisma) → a fresh attack (the pitch moves under
|
|
234
|
+
* a held vowel); advances.
|
|
235
|
+
* - sustain **without** svara (আ-কার hold) → no attack, but the previous
|
|
236
|
+
* audible note's ring is extended; still emitted (midi null) so the lyric
|
|
237
|
+
* column highlights; advances.
|
|
238
|
+
* - bar / bracket-only / carrier / raw-only → ignored (no event, no advance).
|
|
239
|
+
*/
|
|
240
|
+
declare function buildSchedule(doc: Swaralipi, opts?: ScheduleOptions): Schedule;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Pure svara → pitch math for the player. No Tone, no audio — unit-testable
|
|
244
|
+
* in plain Node and shared by the scheduler.
|
|
245
|
+
*
|
|
246
|
+
* The model: a tonic (Sa) pitch-class + a per-note scale degree + octave
|
|
247
|
+
* register + komal/tivra/microtone accidentals → an absolute MIDI number,
|
|
248
|
+
* which the Tone.Sampler pitch-shifts from the nearest santoor sample.
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/** Octave register offset in octaves, relative to madhyam (middle). */
|
|
252
|
+
declare const OCTAVE_OFFSET: Record<Octave, number>;
|
|
253
|
+
/**
|
|
254
|
+
* Shuddha (natural) scale degrees in semitones above Sa — the Bilawal/major
|
|
255
|
+
* thaat that Rabindrasangeet swaralipi notates against. `d` = dha, `n` = ni
|
|
256
|
+
* (standard sargam letter convention).
|
|
257
|
+
*/
|
|
258
|
+
declare const SHUDDHA: Record<SvaraLetter, number>;
|
|
259
|
+
/**
|
|
260
|
+
* Parse a tonic string to a pitch-class 0–11. Accepts `C`, `C#`, `Db`, and
|
|
261
|
+
* the player/sample lowercase-`s` sharp form (`cs`, `Cs`); case-insensitive.
|
|
262
|
+
* Unknown input falls back to 0 (C).
|
|
263
|
+
*/
|
|
264
|
+
declare function parseTonic(tonic: string | undefined): number;
|
|
265
|
+
/**
|
|
266
|
+
* Semitone of a note relative to Sa (degree + accidentals), ignoring octave.
|
|
267
|
+
* Returns null when the note carries no pitch (rest/sustain-hold/bar).
|
|
268
|
+
*
|
|
269
|
+
* Microtones (অল্পকোমল/অতিকোমল) are approximated to the komal pitch — a
|
|
270
|
+
* documented v1 limitation (the sampler can't bend a sub-komal microtone
|
|
271
|
+
* cleanly per note); matches the viewer's placeholder treatment.
|
|
272
|
+
*/
|
|
273
|
+
declare function svaraSemitone(note: Note): number | null;
|
|
274
|
+
/**
|
|
275
|
+
* Absolute MIDI for a note, or null when it has no pitch. madhyam Sa lands on
|
|
276
|
+
* the tonic in octave `baseOctave` (C4 = MIDI 60). Out-of-sample registers
|
|
277
|
+
* are produced by the Sampler pitch-shifting from the nearest recorded note.
|
|
278
|
+
*/
|
|
279
|
+
declare function svaraToMidi(note: Note, tonicPc: number, baseOctave?: number): number | null;
|
|
280
|
+
/** MIDI → Tone note name (`C#4`). */
|
|
281
|
+
declare function midiToSampleName(midi: number): string;
|
|
282
|
+
/** MIDI → santoor sample filename stem (`cs4`): lowercase, `#` → `s`. */
|
|
283
|
+
declare function sampleFileName(midi: number): string;
|
|
284
|
+
|
|
285
|
+
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, TONEJS_INSTRUMENTS_BASE, type ToneModule, buildSchedule, instrumentSampleUrl, midiToSampleName, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaUrl };
|