@ugfoundation/swaralipi-js 0.7.7 → 0.8.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/dist/{batch-6MCPHHKF.js → batch-NJMT3M5Z.js} +5 -5
- package/dist/{batch-6MCPHHKF.js.map → batch-NJMT3M5Z.js.map} +1 -1
- package/dist/{chunk-ALN4SWMS.js → chunk-4HMHTXDE.js} +15 -4
- package/dist/chunk-4HMHTXDE.js.map +1 -0
- package/dist/{chunk-QM4CXKW2.js → chunk-4HNT6OAT.js} +24 -3
- package/dist/chunk-4HNT6OAT.js.map +1 -0
- package/dist/{chunk-TKTQCUKD.js → chunk-4IVUPMRK.js} +27 -9
- package/dist/chunk-4IVUPMRK.js.map +1 -0
- package/dist/{chunk-QQC6WVLJ.js → chunk-YFVAVICF.js} +4 -4
- package/dist/{chunk-QQC6WVLJ.js.map → chunk-YFVAVICF.js.map} +1 -1
- package/dist/cli.cjs +13 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/convert/index.cjs +12 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.d.cts +1 -1
- package/dist/convert/index.d.ts +1 -1
- package/dist/convert/index.js +3 -3
- package/dist/index.cjs +39 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{taals-CjzBuZ35.d.ts → lay-Csa4CD46.d.cts} +38 -1
- package/dist/{taals-CjzBuZ35.d.cts → lay-Ct9ky_EE.d.ts} +38 -1
- package/dist/player/index.cjs +276 -117
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +22 -9
- package/dist/player/index.d.ts +22 -9
- package/dist/player/index.js +230 -114
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-BpBYGXPL.d.cts → schedule-B_UaluBi.d.cts} +27 -3
- package/dist/{schedule-Tzk01FKj.d.ts → schedule-CdILTNsk.d.ts} +27 -3
- package/dist/{schema-DqBXPGkL.d.cts → schema-DGA_bNVZ.d.cts} +97 -1
- package/dist/{schema-DqBXPGkL.d.ts → schema-DGA_bNVZ.d.ts} +97 -1
- package/dist/spec/index.cjs +39 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +16 -4
- package/dist/spec/index.d.ts +16 -4
- package/dist/spec/index.js +2 -2
- package/dist/viewer/index.cjs +114 -12
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +10 -5
- package/dist/viewer/index.d.ts +10 -5
- package/dist/viewer/index.js +43 -7
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ALN4SWMS.js.map +0 -1
- package/dist/chunk-QM4CXKW2.js.map +0 -1
- package/dist/chunk-TKTQCUKD.js.map +0 -1
package/dist/player/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-
|
|
2
|
-
import { T as TaalId } from '../
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DGA_bNVZ.cjs';
|
|
2
|
+
import { a as LaySpeeds, T as TaalId } from '../lay-Csa4CD46.cjs';
|
|
3
|
+
export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-Csa4CD46.cjs';
|
|
4
|
+
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-B_UaluBi.cjs';
|
|
5
|
+
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as TempoSegment, k as ToneModule, l as buildSchedule, m as createBuffers, n as drumPattern, o as glideTargets, p as instrumentSampleUrl, q as isArcRaw, r as meendContinuations, s as nearestSampleKey, t as nearestTablaBpm, u as normalizeBase, v as probeUrl, w as quantizeLoopEnd, x as santoorUrls, y as subsetUrls, z as tablaBpms, B as tablaUrl, C as triggerGlide } from '../schedule-B_UaluBi.cjs';
|
|
5
6
|
import 'zod';
|
|
6
7
|
import 'tone';
|
|
7
8
|
|
|
@@ -17,8 +18,13 @@ interface PlayerOptions {
|
|
|
17
18
|
pianoBaseUrl?: string;
|
|
18
19
|
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
19
20
|
tonic?: string;
|
|
20
|
-
/** Tempo in matra/min
|
|
21
|
+
/** Tempo in matra/min — the **দ্রুত** (fast) tempo; লয় marks scale down from
|
|
22
|
+
* it. Default 90. Snapped to the 15-grid while tabla plays. */
|
|
21
23
|
bpm?: number;
|
|
24
|
+
/** Override the লয় tempo factors, e.g. `{ vilambit: 0.6 }`. Defaults come
|
|
25
|
+
* from the document's `meta.laySpeeds`, else `LAY_SPEEDS` (drut 1, madhya
|
|
26
|
+
* 0.75, vilambit 0.5). */
|
|
27
|
+
laySpeeds?: LaySpeeds;
|
|
22
28
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
23
29
|
baseOctave?: number;
|
|
24
30
|
/** Active melody instrument id (a built-in or a `customInstruments` key).
|
|
@@ -168,9 +174,12 @@ interface AudioExportOptions {
|
|
|
168
174
|
customInstruments?: Record<string, InstrumentDef>;
|
|
169
175
|
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
170
176
|
tonic?: string;
|
|
171
|
-
/** Tempo in matra/min
|
|
172
|
-
*
|
|
177
|
+
/** Tempo in matra/min — the **দ্রুত** (fast) tempo; লয় marks scale down from
|
|
178
|
+
* it. Default: `meta.bpm`, else 90. Snapped to the tabla grid when a sampled
|
|
179
|
+
* tabla actually plays. */
|
|
173
180
|
bpm?: number;
|
|
181
|
+
/** Override the লয় tempo factors (see LAY_SPEEDS / meta.laySpeeds). */
|
|
182
|
+
laySpeeds?: LaySpeeds;
|
|
174
183
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
175
184
|
baseOctave?: number;
|
|
176
185
|
/** Melody instrument id. Default `piano`. */
|
|
@@ -252,8 +261,12 @@ interface MidiExportOptions {
|
|
|
252
261
|
tonic?: string;
|
|
253
262
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
254
263
|
baseOctave?: number;
|
|
255
|
-
/** Tempo in matra/min written as the file's tempo
|
|
264
|
+
/** Tempo in matra/min written as the file's tempo — the **দ্রুত** tempo;
|
|
265
|
+
* লয় marks come out as longer note values against it, not as tempo changes.
|
|
266
|
+
* Default `meta.bpm`, else 90. */
|
|
256
267
|
bpm?: number;
|
|
268
|
+
/** Override the লয় tempo factors (see LAY_SPEEDS / meta.laySpeeds). */
|
|
269
|
+
laySpeeds?: LaySpeeds;
|
|
257
270
|
/** Track name meta-event. Default the song title. */
|
|
258
271
|
name?: string;
|
|
259
272
|
/** Also export the taal's drumset pattern as a separate GM channel-10 track. */
|
|
@@ -327,4 +340,4 @@ declare function sampleFileName(midi: number): string;
|
|
|
327
340
|
*/
|
|
328
341
|
declare function sCurve(x: number, steepness?: number): number;
|
|
329
342
|
|
|
330
|
-
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
|
343
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
package/dist/player/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-
|
|
2
|
-
import { T as TaalId } from '../
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DGA_bNVZ.js';
|
|
2
|
+
import { a as LaySpeeds, T as TaalId } from '../lay-Ct9ky_EE.js';
|
|
3
|
+
export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-Ct9ky_EE.js';
|
|
4
|
+
import { I as InstrumentId, a as InstrumentDef, S as Schedule, D as DrumVoice } from '../schedule-CdILTNsk.js';
|
|
5
|
+
export { A as ARC_RAW_RE, b as DEFAULT_SOUNDS_BASE, c as DRUM_KIT_BASE, G as GlideLeg, d as INSTRUMENTS, M as MEEND_HOLD, P as PlayEvent, e as SALAMANDER_BASE, f as SAMPLED_TAALS, g as ScheduleOptions, T as TABLA_BPMS, h as TABLA_VARIATIONS, i as TONEJS_INSTRUMENTS_BASE, j as TempoSegment, k as ToneModule, l as buildSchedule, m as createBuffers, n as drumPattern, o as glideTargets, p as instrumentSampleUrl, q as isArcRaw, r as meendContinuations, s as nearestSampleKey, t as nearestTablaBpm, u as normalizeBase, v as probeUrl, w as quantizeLoopEnd, x as santoorUrls, y as subsetUrls, z as tablaBpms, B as tablaUrl, C as triggerGlide } from '../schedule-CdILTNsk.js';
|
|
5
6
|
import 'zod';
|
|
6
7
|
import 'tone';
|
|
7
8
|
|
|
@@ -17,8 +18,13 @@ interface PlayerOptions {
|
|
|
17
18
|
pianoBaseUrl?: string;
|
|
18
19
|
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
19
20
|
tonic?: string;
|
|
20
|
-
/** Tempo in matra/min
|
|
21
|
+
/** Tempo in matra/min — the **দ্রুত** (fast) tempo; লয় marks scale down from
|
|
22
|
+
* it. Default 90. Snapped to the 15-grid while tabla plays. */
|
|
21
23
|
bpm?: number;
|
|
24
|
+
/** Override the লয় tempo factors, e.g. `{ vilambit: 0.6 }`. Defaults come
|
|
25
|
+
* from the document's `meta.laySpeeds`, else `LAY_SPEEDS` (drut 1, madhya
|
|
26
|
+
* 0.75, vilambit 0.5). */
|
|
27
|
+
laySpeeds?: LaySpeeds;
|
|
22
28
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
23
29
|
baseOctave?: number;
|
|
24
30
|
/** Active melody instrument id (a built-in or a `customInstruments` key).
|
|
@@ -168,9 +174,12 @@ interface AudioExportOptions {
|
|
|
168
174
|
customInstruments?: Record<string, InstrumentDef>;
|
|
169
175
|
/** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: doc tonic, else `C`. */
|
|
170
176
|
tonic?: string;
|
|
171
|
-
/** Tempo in matra/min
|
|
172
|
-
*
|
|
177
|
+
/** Tempo in matra/min — the **দ্রুত** (fast) tempo; লয় marks scale down from
|
|
178
|
+
* it. Default: `meta.bpm`, else 90. Snapped to the tabla grid when a sampled
|
|
179
|
+
* tabla actually plays. */
|
|
173
180
|
bpm?: number;
|
|
181
|
+
/** Override the লয় tempo factors (see LAY_SPEEDS / meta.laySpeeds). */
|
|
182
|
+
laySpeeds?: LaySpeeds;
|
|
174
183
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
175
184
|
baseOctave?: number;
|
|
176
185
|
/** Melody instrument id. Default `piano`. */
|
|
@@ -252,8 +261,12 @@ interface MidiExportOptions {
|
|
|
252
261
|
tonic?: string;
|
|
253
262
|
/** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
|
|
254
263
|
baseOctave?: number;
|
|
255
|
-
/** Tempo in matra/min written as the file's tempo
|
|
264
|
+
/** Tempo in matra/min written as the file's tempo — the **দ্রুত** tempo;
|
|
265
|
+
* লয় marks come out as longer note values against it, not as tempo changes.
|
|
266
|
+
* Default `meta.bpm`, else 90. */
|
|
256
267
|
bpm?: number;
|
|
268
|
+
/** Override the লয় tempo factors (see LAY_SPEEDS / meta.laySpeeds). */
|
|
269
|
+
laySpeeds?: LaySpeeds;
|
|
257
270
|
/** Track name meta-event. Default the song title. */
|
|
258
271
|
name?: string;
|
|
259
272
|
/** Also export the taal's drumset pattern as a separate GM channel-10 track. */
|
|
@@ -327,4 +340,4 @@ declare function sampleFileName(midi: number): string;
|
|
|
327
340
|
*/
|
|
328
341
|
declare function sCurve(x: number, steepness?: number): number;
|
|
329
342
|
|
|
330
|
-
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
|
343
|
+
export { type AudioExportOptions, type AudioExportResult, type AudioFormat, type BindSheetOptions, DrumVoice, InstrumentDef, InstrumentId, LaySpeeds, type MidiExportOptions, type MidiExportResult, OCTAVE_OFFSET, type PlayerOptions, type PlayerState, type RhythmMode, SHUDDHA, Schedule, SwaralipiPlayer, encodeMidi, encodeMp3, encodeWav, exportAudio, exportMidi, midiToSampleName, parseTonic, sCurve, sampleFileName, svaraSemitone, svaraToMidi };
|
package/dist/player/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms,
|
|
2
|
-
export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, glideTargets, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-
|
|
3
|
-
import { TAALS } from '../chunk-
|
|
1
|
+
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, quantizeLoopEnd, instrumentSampleUrl, probeUrl, normalizeBase, nearestTablaBpm, tablaBpms, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, drumPattern, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, createMetronome, TANPURA_PCS, createTanpura, parseTonic, MEEND_HOLD, sCurve } from '../chunk-4IVUPMRK.js';
|
|
2
|
+
export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, buildSchedule, createBuffers, drumPattern, glideTargets, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaUrl, triggerGlide } from '../chunk-4IVUPMRK.js';
|
|
3
|
+
import { TAALS } from '../chunk-4HNT6OAT.js';
|
|
4
|
+
export { LAYS, LAY_SPEEDS, printedLay, resolveLaySpeeds, rowLay } from '../chunk-4HNT6OAT.js';
|
|
4
5
|
|
|
5
6
|
// src/player/player.ts
|
|
6
7
|
var STOP_FADE_SEC = 0.5;
|
|
8
|
+
var LAY_TAIL_SEC = 0.12;
|
|
9
|
+
var TAAL_MODE_TEMPO = [
|
|
10
|
+
{ beat: 0, endBeat: Infinity, scale: 1, lay: "drut" }
|
|
11
|
+
];
|
|
7
12
|
var MEDIA_LEAD_SEC = 0.25;
|
|
8
13
|
function midiRange(schedule) {
|
|
9
14
|
let lo = Infinity;
|
|
@@ -48,8 +53,11 @@ var SwaralipiPlayer = class {
|
|
|
48
53
|
#tanpura;
|
|
49
54
|
#tanpuraKey = "";
|
|
50
55
|
#metronome;
|
|
51
|
-
/** The looping tabla —
|
|
52
|
-
|
|
56
|
+
/** The looping tabla — one loop per লয় segment, each a `GrainPlayer` when
|
|
57
|
+
* the tonic needs retuning or the segment's tempo needs stretching.
|
|
58
|
+
* `fileSpb` is the recorded loop's OWN beat length, which is the unit
|
|
59
|
+
* `quantizeLoopEnd` must snap in (loop points are buffer time). */
|
|
60
|
+
#tabla = [];
|
|
53
61
|
#drums;
|
|
54
62
|
#scheduleIds = [];
|
|
55
63
|
#fadeTimer;
|
|
@@ -60,6 +68,7 @@ var SwaralipiPlayer = class {
|
|
|
60
68
|
customInstruments: options.customInstruments,
|
|
61
69
|
tonic: options.tonic,
|
|
62
70
|
bpm: options.bpm,
|
|
71
|
+
laySpeeds: options.laySpeeds,
|
|
63
72
|
baseOctave: options.baseOctave ?? 4,
|
|
64
73
|
instrument: options.instrument ?? "piano",
|
|
65
74
|
instruments: options.instruments ?? Object.keys(INSTRUMENTS),
|
|
@@ -113,8 +122,7 @@ var SwaralipiPlayer = class {
|
|
|
113
122
|
get durationSec() {
|
|
114
123
|
if (this.#taal !== void 0) return Infinity;
|
|
115
124
|
if (this.#doc === void 0) return 0;
|
|
116
|
-
const
|
|
117
|
-
const full = playDurationSec(schedule, 60 / this.#effectiveBpm(), this.#opts.rhythm);
|
|
125
|
+
const full = playDurationSec(buildSchedule(this.#doc, this.#scheduleOpts()), 60 / this.#effectiveBpm(), this.#opts.rhythm);
|
|
118
126
|
return Math.min(full, this.#opts.previewLimitSec);
|
|
119
127
|
}
|
|
120
128
|
/** Seconds played so far, off the Transport's own clock — so it holds while
|
|
@@ -218,8 +226,7 @@ var SwaralipiPlayer = class {
|
|
|
218
226
|
transport.cancel(0);
|
|
219
227
|
transport.position = 0;
|
|
220
228
|
}
|
|
221
|
-
this.#
|
|
222
|
-
this.#tabla = void 0;
|
|
229
|
+
this.#disposeTabla();
|
|
223
230
|
this.#restoreMasterGain();
|
|
224
231
|
this.#scheduleIds = [];
|
|
225
232
|
this.#emitHighlight(null);
|
|
@@ -237,6 +244,17 @@ var SwaralipiPlayer = class {
|
|
|
237
244
|
this.#cancelFadeTimer();
|
|
238
245
|
this.#restoreMasterGain();
|
|
239
246
|
}
|
|
247
|
+
#disposeTabla() {
|
|
248
|
+
for (const { loop } of this.#tabla) loop.dispose();
|
|
249
|
+
this.#tabla = [];
|
|
250
|
+
}
|
|
251
|
+
/** Snap each loop to a whole number of ITS OWN beats now the buffer length is
|
|
252
|
+
* known, so a mistrimmed file phase-locks instead of drifting. */
|
|
253
|
+
#quantizeTablaLoops() {
|
|
254
|
+
for (const { loop, fileSpb } of this.#tabla) {
|
|
255
|
+
if (loop.buffer?.loaded) loop.loopEnd = quantizeLoopEnd(loop.buffer.duration, fileSpb);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
240
258
|
#restoreMasterGain() {
|
|
241
259
|
if (this.#master !== void 0 && this.#tone !== void 0) {
|
|
242
260
|
this.#master.gain.cancelScheduledValues(this.#tone.now());
|
|
@@ -322,10 +340,10 @@ var SwaralipiPlayer = class {
|
|
|
322
340
|
this.#glide.clear();
|
|
323
341
|
this.#tanpura?.dispose();
|
|
324
342
|
this.#metronome?.dispose();
|
|
325
|
-
this.#
|
|
343
|
+
this.#disposeTabla();
|
|
326
344
|
this.#drums?.dispose();
|
|
327
345
|
this.#master?.dispose();
|
|
328
|
-
this.#tanpura = this.#metronome = this.#
|
|
346
|
+
this.#tanpura = this.#metronome = this.#drums = this.#master = void 0;
|
|
329
347
|
}
|
|
330
348
|
/* ----------------------------------------------------------------- */
|
|
331
349
|
/* Internals */
|
|
@@ -380,6 +398,13 @@ var SwaralipiPlayer = class {
|
|
|
380
398
|
if (def.local === true && !this.#opts.soundsBaseUrl) return this.#fallbackInstrument();
|
|
381
399
|
return id;
|
|
382
400
|
}
|
|
401
|
+
#scheduleOpts() {
|
|
402
|
+
return {
|
|
403
|
+
tonic: this.#opts.tonic,
|
|
404
|
+
baseOctave: this.#opts.baseOctave,
|
|
405
|
+
laySpeeds: this.#opts.laySpeeds
|
|
406
|
+
};
|
|
407
|
+
}
|
|
383
408
|
/** Effective tempo: snapped to the taal's available tabla tempos while a
|
|
384
409
|
* tabla actually plays (so it never snaps to a bpm that taal lacks). */
|
|
385
410
|
#effectiveBpm() {
|
|
@@ -417,23 +442,18 @@ var SwaralipiPlayer = class {
|
|
|
417
442
|
const bpm = this.#effectiveBpm();
|
|
418
443
|
transport.bpm.value = bpm;
|
|
419
444
|
const spb = 60 / bpm;
|
|
420
|
-
const schedule = buildSchedule(this.#doc,
|
|
421
|
-
tonic: this.#opts.tonic,
|
|
422
|
-
baseOctave: this.#opts.baseOctave
|
|
423
|
-
});
|
|
445
|
+
const schedule = buildSchedule(this.#doc, this.#scheduleOpts());
|
|
424
446
|
const range = midiRange(schedule);
|
|
425
447
|
const id = this.#effectiveInstrument();
|
|
426
448
|
const def = this.#resolveDef(id) ?? INSTRUMENTS.harmonium;
|
|
427
449
|
const melody = this.#buildMelody(Tone, id, base, range);
|
|
428
450
|
const glide = schedule.events.some((ev) => ev.glide === true) ? this.#buildGlide(Tone, id, base, range) : void 0;
|
|
429
|
-
this.#
|
|
430
|
-
this.#
|
|
451
|
+
const shift = this.#opts.rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
452
|
+
this.#buildRhythm(Tone, transport, base, spb, schedule.tempo, shift);
|
|
453
|
+
this.#buildTanpura(Tone, transport, base, spb, schedule.tempo, shift);
|
|
431
454
|
await loadedOrWarn(Tone);
|
|
432
|
-
|
|
433
|
-
this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
|
|
434
|
-
}
|
|
455
|
+
this.#quantizeTablaLoops();
|
|
435
456
|
const draw = Tone.getDraw();
|
|
436
|
-
const shift = this.#opts.rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
437
457
|
const mediaLead = Math.min(MEDIA_LEAD_SEC, spb);
|
|
438
458
|
schedule.events.forEach((ev, i) => {
|
|
439
459
|
const at = ev.beat * spb + shift;
|
|
@@ -496,11 +516,9 @@ var SwaralipiPlayer = class {
|
|
|
496
516
|
const bpm = this.#effectiveBpm();
|
|
497
517
|
transport.bpm.value = bpm;
|
|
498
518
|
const spb = 60 / bpm;
|
|
499
|
-
this.#buildRhythm(Tone, transport, base, spb);
|
|
519
|
+
this.#buildRhythm(Tone, transport, base, spb, TAAL_MODE_TEMPO, 0);
|
|
500
520
|
await loadedOrWarn(Tone);
|
|
501
|
-
|
|
502
|
-
this.#tabla.loopEnd = quantizeLoopEnd(this.#tabla.buffer.duration, spb);
|
|
503
|
-
}
|
|
521
|
+
this.#quantizeTablaLoops();
|
|
504
522
|
const draw = Tone.getDraw();
|
|
505
523
|
const n = Math.max(1, this.#taal.numBeats);
|
|
506
524
|
let beat = 0;
|
|
@@ -542,52 +560,92 @@ var SwaralipiPlayer = class {
|
|
|
542
560
|
this.#glide.set(id, entry);
|
|
543
561
|
return entry;
|
|
544
562
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Accompaniment, one লয় segment at a time. A tempo change under the melody
|
|
565
|
+
* is a real tempo change under the drum: the tabla loop is rebuilt at the
|
|
566
|
+
* segment's own bpm — its own recorded file when the set has one at that
|
|
567
|
+
* tempo, else the nearest file time-stretched by `GrainPlayer` (which
|
|
568
|
+
* stretches *without* transposing, unlike a plain playbackRate) — and the
|
|
569
|
+
* click / drum / drone patterns re-space their interval. A document with no
|
|
570
|
+
* লয় marks has exactly one segment, so this is the old single-loop path.
|
|
571
|
+
*/
|
|
572
|
+
#buildRhythm(Tone, transport, base, spb, segments, shift) {
|
|
573
|
+
this.#disposeTabla();
|
|
548
574
|
const master = this.#ensureMaster(Tone);
|
|
549
575
|
const taalId = this.#taalId();
|
|
550
576
|
const wantsTabla = this.#opts.rhythm === "tabla" && taalId !== void 0 && SAMPLED_TAALS.has(taalId) && base !== void 0;
|
|
551
|
-
if (wantsTabla)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
this.#tabla = (shift === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
555
|
-
url,
|
|
556
|
-
loop: true,
|
|
557
|
-
detune: shift * 100,
|
|
558
|
-
grainSize: TABLA_GRAIN_SEC,
|
|
559
|
-
overlap: TABLA_GRAIN_OVERLAP_SEC
|
|
560
|
-
})).connect(master);
|
|
561
|
-
this.#tabla.sync().start(0);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
if (this.#opts.rhythm === "drums") {
|
|
565
|
-
this.#drums ??= createDrumKit(Tone).connect(master);
|
|
566
|
-
const drums = this.#drums;
|
|
567
|
-
const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };
|
|
568
|
-
const pattern = drumPattern(taal);
|
|
569
|
-
let beat2 = 0;
|
|
570
|
-
const id2 = transport.scheduleRepeat((time) => {
|
|
571
|
-
const voice = pattern[beat2 % pattern.length];
|
|
572
|
-
if (drums.has(voice)) drums.player(voice).start(time);
|
|
573
|
-
beat2 += 1;
|
|
574
|
-
}, spb, 0);
|
|
575
|
-
this.#scheduleIds.push(id2);
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
if (this.#opts.rhythm === "none" || base === void 0) return;
|
|
579
|
-
this.#metronome ??= createMetronome(Tone, base).connect(master);
|
|
580
|
-
const metro = this.#metronome;
|
|
577
|
+
if (!wantsTabla && this.#opts.rhythm !== "drums" && (this.#opts.rhythm === "none" || base === void 0)) return;
|
|
578
|
+
const taal = this.#taal ?? (taalId !== void 0 ? TAALS[taalId] : this.#doc?.meta.taal) ?? { numBeats: 4, sam: 0 };
|
|
579
|
+
const pattern = this.#opts.rhythm === "drums" ? drumPattern(taal) : [];
|
|
581
580
|
const cycle = this.#taal?.numBeats ?? this.#doc?.meta.taal?.numBeats ?? 4;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
581
|
+
segments.forEach((seg, i) => {
|
|
582
|
+
const from = i === 0 ? 0 : seg.beat * spb + shift;
|
|
583
|
+
const to = i === segments.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
584
|
+
const beatSec = spb / seg.scale;
|
|
585
|
+
const span = to === Infinity ? void 0 : to - from;
|
|
586
|
+
if (wantsTabla) {
|
|
587
|
+
const bpm = 60 / beatSec;
|
|
588
|
+
const fileBpm = nearestTablaBpm(bpm, tablaBpms(taalId));
|
|
589
|
+
const rate = bpm / fileBpm;
|
|
590
|
+
const semis = tablaShiftSemitones(this.#tonicSemitone());
|
|
591
|
+
const url = tablaUrl(base, taalId, fileBpm, this.#variation());
|
|
592
|
+
const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
593
|
+
url,
|
|
594
|
+
loop: true,
|
|
595
|
+
playbackRate: rate,
|
|
596
|
+
detune: semis * 100,
|
|
597
|
+
grainSize: TABLA_GRAIN_SEC,
|
|
598
|
+
overlap: TABLA_GRAIN_OVERLAP_SEC
|
|
599
|
+
})).connect(master);
|
|
600
|
+
loop.sync().start(from);
|
|
601
|
+
if (span !== void 0) {
|
|
602
|
+
this.#scheduleIds.push(
|
|
603
|
+
transport.scheduleOnce((t) => {
|
|
604
|
+
loop.volume.setValueAtTime(0, t);
|
|
605
|
+
loop.volume.linearRampToValueAtTime(-40, t + LAY_TAIL_SEC);
|
|
606
|
+
}, to)
|
|
607
|
+
);
|
|
608
|
+
loop.stop(to + LAY_TAIL_SEC);
|
|
609
|
+
}
|
|
610
|
+
this.#tabla.push({ loop, fileSpb: 60 / fileBpm });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (this.#opts.rhythm === "drums") {
|
|
614
|
+
this.#drums ??= createDrumKit(Tone).connect(master);
|
|
615
|
+
const drums = this.#drums;
|
|
616
|
+
let beat2 = 0;
|
|
617
|
+
this.#scheduleIds.push(
|
|
618
|
+
transport.scheduleRepeat(
|
|
619
|
+
(time) => {
|
|
620
|
+
const voice = pattern[beat2 % pattern.length];
|
|
621
|
+
if (drums.has(voice)) drums.player(voice).start(time);
|
|
622
|
+
beat2 += 1;
|
|
623
|
+
},
|
|
624
|
+
beatSec,
|
|
625
|
+
from,
|
|
626
|
+
span
|
|
627
|
+
)
|
|
628
|
+
);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
this.#metronome ??= createMetronome(Tone, base).connect(master);
|
|
632
|
+
const metro = this.#metronome;
|
|
633
|
+
let beat = 0;
|
|
634
|
+
this.#scheduleIds.push(
|
|
635
|
+
transport.scheduleRepeat(
|
|
636
|
+
(time) => {
|
|
637
|
+
metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
|
|
638
|
+
metro.start(time);
|
|
639
|
+
beat += 1;
|
|
640
|
+
},
|
|
641
|
+
beatSec,
|
|
642
|
+
from,
|
|
643
|
+
span
|
|
644
|
+
)
|
|
645
|
+
);
|
|
646
|
+
});
|
|
589
647
|
}
|
|
590
|
-
#buildTanpura(Tone, transport, base, spb) {
|
|
648
|
+
#buildTanpura(Tone, transport, base, spb, segments, shift) {
|
|
591
649
|
if (!this.#opts.tanpura || base === void 0) return;
|
|
592
650
|
const tonicPc = (this.#tonicSemitone() % 12 + 12) % 12;
|
|
593
651
|
const sa = TANPURA_PCS[tonicPc];
|
|
@@ -601,13 +659,23 @@ var SwaralipiPlayer = class {
|
|
|
601
659
|
const players = this.#tanpura;
|
|
602
660
|
players.volume.value = -14;
|
|
603
661
|
const pattern = [pa, sa, sa, sa];
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
662
|
+
segments.forEach((seg, k) => {
|
|
663
|
+
const from = k === 0 ? 0 : seg.beat * spb + shift;
|
|
664
|
+
const to = k === segments.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
665
|
+
let i = 0;
|
|
666
|
+
this.#scheduleIds.push(
|
|
667
|
+
transport.scheduleRepeat(
|
|
668
|
+
(time) => {
|
|
669
|
+
const stem = pattern[i % pattern.length];
|
|
670
|
+
if (players.has(stem)) players.player(stem).start(time);
|
|
671
|
+
i += 1;
|
|
672
|
+
},
|
|
673
|
+
spb * 2 / seg.scale,
|
|
674
|
+
from,
|
|
675
|
+
to === Infinity ? void 0 : to - from
|
|
676
|
+
)
|
|
677
|
+
);
|
|
678
|
+
});
|
|
611
679
|
}
|
|
612
680
|
#tonicSemitone() {
|
|
613
681
|
return parseTonic(this.#opts.tonic ?? this.#doc?.meta.tonic);
|
|
@@ -615,6 +683,7 @@ var SwaralipiPlayer = class {
|
|
|
615
683
|
};
|
|
616
684
|
|
|
617
685
|
// src/player/export.ts
|
|
686
|
+
var LAY_TAIL_SEC2 = 0.12;
|
|
618
687
|
function midiRange2(schedule) {
|
|
619
688
|
let lo = Infinity;
|
|
620
689
|
let hi = -Infinity;
|
|
@@ -652,7 +721,11 @@ async function exportAudio(doc, options = {}) {
|
|
|
652
721
|
const rhythm = options.rhythm ?? "none";
|
|
653
722
|
const withMelody = options.melody ?? true;
|
|
654
723
|
const withTanpura = options.tanpura ?? false;
|
|
655
|
-
const schedule = buildSchedule(doc, {
|
|
724
|
+
const schedule = buildSchedule(doc, {
|
|
725
|
+
tonic: options.tonic,
|
|
726
|
+
baseOctave: options.baseOctave,
|
|
727
|
+
laySpeeds: options.laySpeeds
|
|
728
|
+
});
|
|
656
729
|
const range = midiRange2(schedule);
|
|
657
730
|
const taalId = resolveTaal(doc, options.taal);
|
|
658
731
|
const variation = options.taal === void 0 || options.taal === "auto" ? doc.meta.taal?.variation : void 0;
|
|
@@ -705,39 +778,67 @@ async function exportAudio(doc, options = {}) {
|
|
|
705
778
|
}
|
|
706
779
|
});
|
|
707
780
|
}
|
|
708
|
-
|
|
709
|
-
if (rhythm
|
|
710
|
-
const drums = createDrumKit(Tone).connect(master);
|
|
781
|
+
const tablas = [];
|
|
782
|
+
if (rhythm !== "none" && base) {
|
|
711
783
|
const taal = taalId !== void 0 ? TAALS[taalId] : doc.meta.taal ?? { numBeats: 4, sam: 0 };
|
|
712
|
-
const pattern = drumPattern(taal);
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
784
|
+
const pattern = rhythm === "drums" ? drumPattern(taal) : [];
|
|
785
|
+
const drums = rhythm === "drums" ? createDrumKit(Tone).connect(master) : void 0;
|
|
786
|
+
const metro = rhythm !== "drums" && !usesTabla ? createMetronome(Tone, base).connect(master) : void 0;
|
|
787
|
+
const cycle = taalId !== void 0 ? TAALS[taalId].numBeats : doc.meta.taal?.numBeats ?? 4;
|
|
788
|
+
const semis = tablaShiftSemitones(parseTonic(options.tonic ?? doc.meta.tonic));
|
|
789
|
+
schedule.tempo.forEach((seg, i) => {
|
|
790
|
+
const from = i === 0 ? 0 : seg.beat * spb + shift;
|
|
791
|
+
const to = i === schedule.tempo.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
792
|
+
const beatSec = spb / seg.scale;
|
|
793
|
+
const span = to === Infinity ? void 0 : to - from;
|
|
794
|
+
if (usesTabla && taalId) {
|
|
795
|
+
const segBpm = 60 / beatSec;
|
|
796
|
+
const fileBpm = nearestTablaBpm(segBpm, tablaBpms(taalId));
|
|
797
|
+
const rate = segBpm / fileBpm;
|
|
798
|
+
const url = tablaUrl(base, taalId, fileBpm, variation);
|
|
799
|
+
const loop = (rate === 1 && semis === 0 ? new Tone.Player({ url, loop: true }) : new Tone.GrainPlayer({
|
|
800
|
+
url,
|
|
801
|
+
loop: true,
|
|
802
|
+
playbackRate: rate,
|
|
803
|
+
detune: semis * 100,
|
|
804
|
+
grainSize: TABLA_GRAIN_SEC,
|
|
805
|
+
overlap: TABLA_GRAIN_OVERLAP_SEC
|
|
806
|
+
})).connect(master);
|
|
807
|
+
loop.sync().start(from);
|
|
808
|
+
if (span !== void 0) {
|
|
809
|
+
transport.scheduleOnce((t) => {
|
|
810
|
+
loop.volume.setValueAtTime(0, t);
|
|
811
|
+
loop.volume.linearRampToValueAtTime(-40, t + LAY_TAIL_SEC2);
|
|
812
|
+
}, to);
|
|
813
|
+
loop.stop(to + LAY_TAIL_SEC2);
|
|
814
|
+
}
|
|
815
|
+
tablas.push({ loop, fileSpb: 60 / fileBpm });
|
|
816
|
+
} else if (drums !== void 0) {
|
|
817
|
+
let beat = 0;
|
|
818
|
+
transport.scheduleRepeat(
|
|
819
|
+
(time) => {
|
|
820
|
+
const voice = pattern[beat % pattern.length];
|
|
821
|
+
if (drums.has(voice)) drums.player(voice).start(time);
|
|
822
|
+
beat += 1;
|
|
823
|
+
},
|
|
824
|
+
beatSec,
|
|
825
|
+
from,
|
|
826
|
+
span
|
|
827
|
+
);
|
|
828
|
+
} else if (metro !== void 0) {
|
|
829
|
+
let beat = 0;
|
|
830
|
+
transport.scheduleRepeat(
|
|
831
|
+
(time) => {
|
|
832
|
+
metro.volume.setValueAtTime(beat % cycle === 0 ? 0 : -8, time);
|
|
833
|
+
metro.start(time);
|
|
834
|
+
beat += 1;
|
|
835
|
+
},
|
|
836
|
+
beatSec,
|
|
837
|
+
from,
|
|
838
|
+
span
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
741
842
|
}
|
|
742
843
|
if (withTanpura && base) {
|
|
743
844
|
const tonicPc = (parseTonic(options.tonic ?? doc.meta.tonic) % 12 + 12) % 12;
|
|
@@ -746,15 +847,26 @@ async function exportAudio(doc, options = {}) {
|
|
|
746
847
|
const players = createTanpura(Tone, base, [sa, pa]).connect(master);
|
|
747
848
|
players.volume.value = -14;
|
|
748
849
|
const pattern = [pa, sa, sa, sa];
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
850
|
+
schedule.tempo.forEach((seg, k) => {
|
|
851
|
+
const from = k === 0 ? 0 : seg.beat * spb + shift;
|
|
852
|
+
const to = k === schedule.tempo.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
853
|
+
let i = 0;
|
|
854
|
+
transport.scheduleRepeat(
|
|
855
|
+
(time) => {
|
|
856
|
+
const stem = pattern[i % pattern.length];
|
|
857
|
+
if (players.has(stem)) players.player(stem).start(time);
|
|
858
|
+
i += 1;
|
|
859
|
+
},
|
|
860
|
+
spb * 2 / seg.scale,
|
|
861
|
+
from,
|
|
862
|
+
to === Infinity ? void 0 : to - from
|
|
863
|
+
);
|
|
864
|
+
});
|
|
755
865
|
}
|
|
756
866
|
await Tone.loaded();
|
|
757
|
-
|
|
867
|
+
for (const { loop, fileSpb } of tablas) {
|
|
868
|
+
if (loop.buffer?.loaded) loop.loopEnd = quantizeLoopEnd(loop.buffer.duration, fileSpb);
|
|
869
|
+
}
|
|
758
870
|
if (fadeOutSec > 0) {
|
|
759
871
|
master.gain.setValueAtTime(1, Math.max(durationSec - fadeOutSec, 0));
|
|
760
872
|
master.gain.linearRampToValueAtTime(0, durationSec);
|
|
@@ -975,7 +1087,11 @@ function safeName2(title) {
|
|
|
975
1087
|
return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
|
|
976
1088
|
}
|
|
977
1089
|
function exportMidi(doc, options = {}) {
|
|
978
|
-
const schedule = buildSchedule(doc, {
|
|
1090
|
+
const schedule = buildSchedule(doc, {
|
|
1091
|
+
tonic: options.tonic,
|
|
1092
|
+
baseOctave: options.baseOctave,
|
|
1093
|
+
laySpeeds: options.laySpeeds
|
|
1094
|
+
});
|
|
979
1095
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
980
1096
|
const taalId = doc.meta.taal?.id;
|
|
981
1097
|
const taal = taalId !== void 0 && taalId in TAALS ? TAALS[taalId] : { numBeats: 4, sam: 0 };
|