@ugfoundation/swaralipi-js 0.14.2 → 0.15.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 +3 -1
- package/dist/{batch-QEIW6XAA.js → batch-SYLHDJJL.js} +4 -4
- package/dist/{batch-QEIW6XAA.js.map → batch-SYLHDJJL.js.map} +1 -1
- package/dist/{chunk-CJI7A6AM.js → chunk-J5JYJL5N.js} +3 -3
- package/dist/{chunk-CJI7A6AM.js.map → chunk-J5JYJL5N.js.map} +1 -1
- package/dist/{chunk-WEXTZRRB.js → chunk-SPZO4573.js} +35 -7
- package/dist/chunk-SPZO4573.js.map +1 -0
- package/dist/{chunk-6AKH2NLW.js → chunk-WBYJUYI6.js} +3 -3
- package/dist/{chunk-6AKH2NLW.js.map → chunk-WBYJUYI6.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.d.cts +1 -1
- package/dist/convert/index.d.ts +1 -1
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{instruments-CfHu4bSQ.d.cts → instruments-C-KtHQiW.d.cts} +12 -9
- package/dist/{instruments-C3CbtYdm.d.ts → instruments-SoUQBZNd.d.ts} +12 -9
- package/dist/{lay-CFLy_wd_.d.ts → lay-CWlEhImP.d.ts} +1 -1
- package/dist/{lay-Bi87MnkT.d.cts → lay-CXhC-HRO.d.cts} +1 -1
- package/dist/player/index.cjs +93 -31
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +25 -5
- package/dist/player/index.d.ts +25 -5
- package/dist/player/index.js +62 -28
- package/dist/player/index.js.map +1 -1
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +12 -12
- package/dist/spec/index.d.ts +12 -12
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +43 -10
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +6 -4
- package/dist/viewer/index.d.ts +6 -4
- package/dist/viewer/index.js +10 -5
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-WEXTZRRB.js.map +0 -1
- package/dist/{schema-DyL0bMCf.d.ts → schema-DZag9qkF.d.cts} +36 -36
- package/dist/{schema-DyL0bMCf.d.cts → schema-DZag9qkF.d.ts} +36 -36
package/dist/player/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-
|
|
2
|
-
import { a as LaySpeeds, T as TaalId } from '../lay-
|
|
3
|
-
export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-
|
|
4
|
-
import { I as InstrumentId, a as InstrumentDef, E as Excerpt, S as Schedule, D as DrumVoice, T as TempoSegment } from '../instruments-
|
|
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-
|
|
1
|
+
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DZag9qkF.cjs';
|
|
2
|
+
import { a as LaySpeeds, T as TaalId } from '../lay-CXhC-HRO.cjs';
|
|
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 '../instruments-C-KtHQiW.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-C-KtHQiW.cjs';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import 'tone';
|
|
8
8
|
|
|
@@ -55,6 +55,13 @@ interface PlayerOptions {
|
|
|
55
55
|
* sam/tali/khaali structure — a genuine "drum atlas"-style translation,
|
|
56
56
|
* not a loop; works for every taal (no per-taal sample requirement). */
|
|
57
57
|
rhythm?: RhythmMode;
|
|
58
|
+
/** Rhythm first, for a song that opens on a pickup (অনাগত): the tabla /
|
|
59
|
+
* drums / metronome play the rest of that cycle and the melody comes in on
|
|
60
|
+
* its own beat — অতীত-গ্রহ, the tāla before the song. Default false: the
|
|
61
|
+
* melody starts at once and the rhythm comes in on the সম — অনাগত-গ্রহ,
|
|
62
|
+
* the song before the tāla (Kohala's Tālalakṣaṇa). The tanpura sounds from
|
|
63
|
+
* the start either way, and a song that opens on sam plays the same. */
|
|
64
|
+
countIn?: boolean;
|
|
58
65
|
/** Play the tanpura drone. Default true. */
|
|
59
66
|
tanpura?: boolean;
|
|
60
67
|
/** Sing the song's form: each `{ }` twice, `( )` only the first time, the
|
|
@@ -140,6 +147,8 @@ declare class SwaralipiPlayer {
|
|
|
140
147
|
get loop(): LineRange | null;
|
|
141
148
|
/** Whether each repeat is followed by a silent pass for the learner. */
|
|
142
149
|
get echo(): boolean;
|
|
150
|
+
/** Whether the rhythm leads a pickup in (see PlayerOptions.countIn). */
|
|
151
|
+
get countIn(): boolean;
|
|
143
152
|
/** Set the document to play. Stops any current playback. With `excerpt`,
|
|
144
153
|
* only the notes singing those stretches of the song's text play, in order
|
|
145
154
|
* (see `alignLyrics`); the length, seeks and repeats all keep to them. */
|
|
@@ -204,6 +213,8 @@ declare class SwaralipiPlayer {
|
|
|
204
213
|
setPreviewLimit(sec: number): void;
|
|
205
214
|
setInstrument(instrument: InstrumentId): void;
|
|
206
215
|
setRhythm(rhythm: RhythmMode): void;
|
|
216
|
+
/** Rhythm first (true) or the melody first (false) — see PlayerOptions.countIn. */
|
|
217
|
+
setCountIn(on: boolean): void;
|
|
207
218
|
setTanpura(on: boolean): void;
|
|
208
219
|
/** Override the taal (null restores auto-detection from `meta.taal`). */
|
|
209
220
|
setTaal(id: TaalId | null): void;
|
|
@@ -270,6 +281,10 @@ interface AudioExportOptions {
|
|
|
270
281
|
/** Taal for the rhythm: an explicit id, or `auto` (from `meta.taal`). Default
|
|
271
282
|
* `auto`. Only used when `rhythm` isn't `none`. */
|
|
272
283
|
taal?: TaalId | 'auto';
|
|
284
|
+
/** Rhythm first for a song that opens on a pickup, as the player's
|
|
285
|
+
* `countIn`. Default false: the melody starts at once and the rhythm comes
|
|
286
|
+
* in on the সম. */
|
|
287
|
+
countIn?: boolean;
|
|
273
288
|
/** Include the tanpura drone. Default false. */
|
|
274
289
|
tanpura?: boolean;
|
|
275
290
|
/** Sing the song's form (repeats, returns to the asthayi), as the player
|
|
@@ -368,6 +383,10 @@ interface MidiExportOptions {
|
|
|
368
383
|
name?: string;
|
|
369
384
|
/** Also export the taal's drumset pattern as a separate GM channel-10 track. */
|
|
370
385
|
drums?: boolean;
|
|
386
|
+
/** With `drums`, a song that opens on a pickup starts with the drums' count-in
|
|
387
|
+
* and the melody after it, as the player's `countIn`. Default false: the
|
|
388
|
+
* melody starts at once and the drums come in on the সম. */
|
|
389
|
+
countIn?: boolean;
|
|
371
390
|
/** Sing the song's form (repeats, returns to the asthayi), as the player
|
|
372
391
|
* does. Default true; false keeps the written order. */
|
|
373
392
|
repeats?: boolean;
|
|
@@ -382,6 +401,7 @@ declare function encodeMidi(schedule: Schedule, opts: {
|
|
|
382
401
|
bpm: number;
|
|
383
402
|
name?: string;
|
|
384
403
|
drums?: DrumVoice[] | ((seg: TempoSegment) => DrumVoice[] | undefined);
|
|
404
|
+
countIn?: boolean;
|
|
385
405
|
}): Uint8Array;
|
|
386
406
|
/**
|
|
387
407
|
* Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs
|
package/dist/player/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-
|
|
2
|
-
import { a as LaySpeeds, T as TaalId } from '../lay-
|
|
3
|
-
export { b as LAYS, c as LAY_SPEEDS, L as Lay, p as printedLay, r as resolveLaySpeeds, d as rowLay } from '../lay-
|
|
4
|
-
import { I as InstrumentId, a as InstrumentDef, E as Excerpt, S as Schedule, D as DrumVoice, T as TempoSegment } from '../instruments-
|
|
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-
|
|
1
|
+
import { S as Swaralipi, T as Taal, O as Octave, a as SvaraLetter, N as Note } from '../schema-DZag9qkF.js';
|
|
2
|
+
import { a as LaySpeeds, T as TaalId } from '../lay-CWlEhImP.js';
|
|
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 '../instruments-SoUQBZNd.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-SoUQBZNd.js';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import 'tone';
|
|
8
8
|
|
|
@@ -55,6 +55,13 @@ interface PlayerOptions {
|
|
|
55
55
|
* sam/tali/khaali structure — a genuine "drum atlas"-style translation,
|
|
56
56
|
* not a loop; works for every taal (no per-taal sample requirement). */
|
|
57
57
|
rhythm?: RhythmMode;
|
|
58
|
+
/** Rhythm first, for a song that opens on a pickup (অনাগত): the tabla /
|
|
59
|
+
* drums / metronome play the rest of that cycle and the melody comes in on
|
|
60
|
+
* its own beat — অতীত-গ্রহ, the tāla before the song. Default false: the
|
|
61
|
+
* melody starts at once and the rhythm comes in on the সম — অনাগত-গ্রহ,
|
|
62
|
+
* the song before the tāla (Kohala's Tālalakṣaṇa). The tanpura sounds from
|
|
63
|
+
* the start either way, and a song that opens on sam plays the same. */
|
|
64
|
+
countIn?: boolean;
|
|
58
65
|
/** Play the tanpura drone. Default true. */
|
|
59
66
|
tanpura?: boolean;
|
|
60
67
|
/** Sing the song's form: each `{ }` twice, `( )` only the first time, the
|
|
@@ -140,6 +147,8 @@ declare class SwaralipiPlayer {
|
|
|
140
147
|
get loop(): LineRange | null;
|
|
141
148
|
/** Whether each repeat is followed by a silent pass for the learner. */
|
|
142
149
|
get echo(): boolean;
|
|
150
|
+
/** Whether the rhythm leads a pickup in (see PlayerOptions.countIn). */
|
|
151
|
+
get countIn(): boolean;
|
|
143
152
|
/** Set the document to play. Stops any current playback. With `excerpt`,
|
|
144
153
|
* only the notes singing those stretches of the song's text play, in order
|
|
145
154
|
* (see `alignLyrics`); the length, seeks and repeats all keep to them. */
|
|
@@ -204,6 +213,8 @@ declare class SwaralipiPlayer {
|
|
|
204
213
|
setPreviewLimit(sec: number): void;
|
|
205
214
|
setInstrument(instrument: InstrumentId): void;
|
|
206
215
|
setRhythm(rhythm: RhythmMode): void;
|
|
216
|
+
/** Rhythm first (true) or the melody first (false) — see PlayerOptions.countIn. */
|
|
217
|
+
setCountIn(on: boolean): void;
|
|
207
218
|
setTanpura(on: boolean): void;
|
|
208
219
|
/** Override the taal (null restores auto-detection from `meta.taal`). */
|
|
209
220
|
setTaal(id: TaalId | null): void;
|
|
@@ -270,6 +281,10 @@ interface AudioExportOptions {
|
|
|
270
281
|
/** Taal for the rhythm: an explicit id, or `auto` (from `meta.taal`). Default
|
|
271
282
|
* `auto`. Only used when `rhythm` isn't `none`. */
|
|
272
283
|
taal?: TaalId | 'auto';
|
|
284
|
+
/** Rhythm first for a song that opens on a pickup, as the player's
|
|
285
|
+
* `countIn`. Default false: the melody starts at once and the rhythm comes
|
|
286
|
+
* in on the সম. */
|
|
287
|
+
countIn?: boolean;
|
|
273
288
|
/** Include the tanpura drone. Default false. */
|
|
274
289
|
tanpura?: boolean;
|
|
275
290
|
/** Sing the song's form (repeats, returns to the asthayi), as the player
|
|
@@ -368,6 +383,10 @@ interface MidiExportOptions {
|
|
|
368
383
|
name?: string;
|
|
369
384
|
/** Also export the taal's drumset pattern as a separate GM channel-10 track. */
|
|
370
385
|
drums?: boolean;
|
|
386
|
+
/** With `drums`, a song that opens on a pickup starts with the drums' count-in
|
|
387
|
+
* and the melody after it, as the player's `countIn`. Default false: the
|
|
388
|
+
* melody starts at once and the drums come in on the সম. */
|
|
389
|
+
countIn?: boolean;
|
|
371
390
|
/** Sing the song's form (repeats, returns to the asthayi), as the player
|
|
372
391
|
* does. Default true; false keeps the written order. */
|
|
373
392
|
repeats?: boolean;
|
|
@@ -382,6 +401,7 @@ declare function encodeMidi(schedule: Schedule, opts: {
|
|
|
382
401
|
bpm: number;
|
|
383
402
|
name?: string;
|
|
384
403
|
drums?: DrumVoice[] | ((seg: TempoSegment) => DrumVoice[] | undefined);
|
|
404
|
+
countIn?: boolean;
|
|
385
405
|
}): Uint8Array;
|
|
386
406
|
/**
|
|
387
407
|
* Export a swaralipi doc to a `.mid` Blob (melody voice only). No browser APIs
|
package/dist/player/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_SOUNDS_BASE, INSTRUMENTS, SAMPLED_TAALS, buildSchedule, eventAt, lineAt, byRow, lineFor, quantizeLoopEnd, instrumentSampleUrl, probeUrl, scheduleLines, GRACE_STEP, normalizeBase, nearestTablaBpm, tablaBpms, graceTouches, midiToSampleName, glideTargets, triggerGlide, createSampler, createBuffers, tablaShiftSemitones, tablaUrl, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, createDrumKit, drumPattern, createMetronome, TANPURA_PCS, createTanpura, parseTonic, lyricCues, alignLyrics, MEEND_HOLD, sCurve } from '../chunk-
|
|
2
|
-
export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_VARIATIONS, TONEJS_INSTRUMENTS_BASE, 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-
|
|
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-SPZO4573.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-SPZO4573.js';
|
|
3
3
|
import '../chunk-BSZ3WQUP.js';
|
|
4
4
|
import { TAALS } from '../chunk-K73Y46WU.js';
|
|
5
5
|
export { LAYS, LAY_SPEEDS, printedLay, resolveLaySpeeds, rowLay } from '../chunk-K73Y46WU.js';
|
|
@@ -30,8 +30,9 @@ function midiRange(schedule) {
|
|
|
30
30
|
}
|
|
31
31
|
return lo <= hi ? { lo, hi } : void 0;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
var leadIn = (schedule, spb, rhythm, countIn) => countIn && rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
34
|
+
function playDurationSec(schedule, spb, rhythm, countIn) {
|
|
35
|
+
return schedule.totalBeats * spb + leadIn(schedule, spb, rhythm, countIn) + spb;
|
|
35
36
|
}
|
|
36
37
|
async function loadedOrWarn(Tone) {
|
|
37
38
|
try {
|
|
@@ -107,6 +108,7 @@ var SwaralipiPlayer = class {
|
|
|
107
108
|
instrument: options.instrument ?? "piano",
|
|
108
109
|
instruments: options.instruments ?? Object.keys(INSTRUMENTS),
|
|
109
110
|
rhythm: options.rhythm ?? "tabla",
|
|
111
|
+
countIn: options.countIn ?? false,
|
|
110
112
|
tanpura: options.tanpura ?? true,
|
|
111
113
|
repeats: options.repeats ?? true,
|
|
112
114
|
previewLimitSec: options.previewLimitSec ?? Infinity,
|
|
@@ -158,7 +160,12 @@ var SwaralipiPlayer = class {
|
|
|
158
160
|
get durationSec() {
|
|
159
161
|
if (this.#taal !== void 0) return Infinity;
|
|
160
162
|
if (this.#doc === void 0) return 0;
|
|
161
|
-
const full = playDurationSec(
|
|
163
|
+
const full = playDurationSec(
|
|
164
|
+
buildSchedule(this.#doc, this.#scheduleOpts()),
|
|
165
|
+
60 / this.#effectiveBpm(),
|
|
166
|
+
this.#opts.rhythm,
|
|
167
|
+
this.#opts.countIn
|
|
168
|
+
);
|
|
162
169
|
return Math.min(full, this.#opts.previewLimitSec);
|
|
163
170
|
}
|
|
164
171
|
/** Seconds played so far, off the Transport's own clock — so it holds while
|
|
@@ -178,6 +185,10 @@ var SwaralipiPlayer = class {
|
|
|
178
185
|
get echo() {
|
|
179
186
|
return this.#echo;
|
|
180
187
|
}
|
|
188
|
+
/** Whether the rhythm leads a pickup in (see PlayerOptions.countIn). */
|
|
189
|
+
get countIn() {
|
|
190
|
+
return this.#opts.countIn;
|
|
191
|
+
}
|
|
181
192
|
/* ----------------------------------------------------------------- */
|
|
182
193
|
/* Loading + transport control */
|
|
183
194
|
/* ----------------------------------------------------------------- */
|
|
@@ -486,6 +497,11 @@ var SwaralipiPlayer = class {
|
|
|
486
497
|
this.#opts.rhythm = rhythm;
|
|
487
498
|
this.#reapply();
|
|
488
499
|
}
|
|
500
|
+
/** Rhythm first (true) or the melody first (false) — see PlayerOptions.countIn. */
|
|
501
|
+
setCountIn(on) {
|
|
502
|
+
this.#opts.countIn = on;
|
|
503
|
+
this.#reapply();
|
|
504
|
+
}
|
|
489
505
|
setTanpura(on) {
|
|
490
506
|
this.#opts.tanpura = on;
|
|
491
507
|
this.#reapply();
|
|
@@ -833,9 +849,10 @@ var SwaralipiPlayer = class {
|
|
|
833
849
|
const melody = this.#buildMelody(Tone, id, base, range);
|
|
834
850
|
this.#voice = melody;
|
|
835
851
|
const glide = schedule.events.some((ev) => ev.glide === true) ? this.#buildGlide(Tone, id, base, range) : void 0;
|
|
836
|
-
const shift = this.#opts.rhythm
|
|
852
|
+
const shift = leadIn(schedule, spb, this.#opts.rhythm, this.#opts.countIn);
|
|
853
|
+
const entry = this.#opts.countIn ? 0 : schedule.samBeat * spb;
|
|
837
854
|
this.#timeline = { schedule, lines, spb, shift };
|
|
838
|
-
this.#buildRhythm(Tone, transport, base, spb, schedule.tempo, shift);
|
|
855
|
+
this.#buildRhythm(Tone, transport, base, spb, schedule.tempo, shift, entry);
|
|
839
856
|
this.#buildTanpura(Tone, transport, base, spb, schedule.tempo, shift);
|
|
840
857
|
await loadedOrWarn(Tone);
|
|
841
858
|
this.#quantizeTablaLoops();
|
|
@@ -899,7 +916,7 @@ var SwaralipiPlayer = class {
|
|
|
899
916
|
}, at);
|
|
900
917
|
});
|
|
901
918
|
this.#applyLoop();
|
|
902
|
-
const endAt = playDurationSec(schedule, spb, this.#opts.rhythm);
|
|
919
|
+
const endAt = playDurationSec(schedule, spb, this.#opts.rhythm, this.#opts.countIn);
|
|
903
920
|
transport.scheduleOnce((time) => {
|
|
904
921
|
this.#opts.onEvent?.(null);
|
|
905
922
|
draw.schedule(() => this.#emitHighlight(null), time);
|
|
@@ -922,7 +939,7 @@ var SwaralipiPlayer = class {
|
|
|
922
939
|
const bpm = this.#effectiveBpm();
|
|
923
940
|
transport.bpm.value = bpm;
|
|
924
941
|
const spb = 60 / bpm;
|
|
925
|
-
this.#buildRhythm(Tone, transport, base, spb, TAAL_MODE_TEMPO, 0);
|
|
942
|
+
this.#buildRhythm(Tone, transport, base, spb, TAAL_MODE_TEMPO, 0, 0);
|
|
926
943
|
await loadedOrWarn(Tone);
|
|
927
944
|
this.#quantizeTablaLoops();
|
|
928
945
|
const draw = Tone.getDraw();
|
|
@@ -975,9 +992,10 @@ var SwaralipiPlayer = class {
|
|
|
975
992
|
* click / drum / drone patterns re-space their interval. A তালফেরতা swaps the
|
|
976
993
|
* theka the same way: each segment loops its own taal's file (or pattern, or
|
|
977
994
|
* click cycle), and a মুক্তছন্দ segment rests. A document with neither mark
|
|
978
|
-
* has exactly one segment, so this is the old single-loop path.
|
|
995
|
+
* has exactly one segment, so this is the old single-loop path. Nothing
|
|
996
|
+
* sounds before `entry` (transport seconds): the সম a pickup leads into.
|
|
979
997
|
*/
|
|
980
|
-
#buildRhythm(Tone, transport, base, spb, segments, shift) {
|
|
998
|
+
#buildRhythm(Tone, transport, base, spb, segments, shift, entry) {
|
|
981
999
|
this.#disposeTabla();
|
|
982
1000
|
const master = this.#ensureMaster(Tone);
|
|
983
1001
|
const rhythm = this.#opts.rhythm;
|
|
@@ -986,8 +1004,9 @@ var SwaralipiPlayer = class {
|
|
|
986
1004
|
const own = this.#segmentTaal(seg);
|
|
987
1005
|
if (own === null) return;
|
|
988
1006
|
const taalId = own !== void 0 && own.id in TAALS ? own.id : void 0;
|
|
989
|
-
const from = i === 0 ? 0 : seg.beat * spb + shift;
|
|
1007
|
+
const from = Math.max(i === 0 ? 0 : seg.beat * spb + shift, entry);
|
|
990
1008
|
const to = i === segments.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
1009
|
+
if (from >= to) return;
|
|
991
1010
|
const beatSec = spb / seg.scale;
|
|
992
1011
|
const span = to === Infinity ? void 0 : to - from;
|
|
993
1012
|
const beatAt = (time) => Math.round((transport.getSecondsAtTime(time) - from) / beatSec);
|
|
@@ -1176,7 +1195,9 @@ async function exportAudio(doc, options = {}) {
|
|
|
1176
1195
|
const spb = 60 / bpm;
|
|
1177
1196
|
const id = options.instrument ?? "piano";
|
|
1178
1197
|
const def = pickDef(id, options);
|
|
1179
|
-
const
|
|
1198
|
+
const countIn = options.countIn ?? false;
|
|
1199
|
+
const shift = countIn && rhythm !== "none" ? schedule.pickupBeats * spb : 0;
|
|
1200
|
+
const entry = countIn ? 0 : schedule.samBeat * spb;
|
|
1180
1201
|
const durationSec = Math.max(schedule.totalBeats * spb + shift + (def.release ?? 1) + fadeOutSec + 0.5, 0.5);
|
|
1181
1202
|
const rendered = await renderOffline(
|
|
1182
1203
|
Tone,
|
|
@@ -1229,8 +1250,9 @@ async function exportAudio(doc, options = {}) {
|
|
|
1229
1250
|
const own = pinned ?? seg.taal;
|
|
1230
1251
|
if (own === null) return;
|
|
1231
1252
|
const segId = own !== void 0 && own.id in TAALS ? own.id : void 0;
|
|
1232
|
-
const from = i === 0 ? 0 : seg.beat * spb + shift;
|
|
1253
|
+
const from = Math.max(i === 0 ? 0 : seg.beat * spb + shift, entry);
|
|
1233
1254
|
const to = i === schedule.tempo.length - 1 ? Infinity : seg.endBeat * spb + shift;
|
|
1255
|
+
if (from >= to) return;
|
|
1234
1256
|
const beatSec = spb / seg.scale;
|
|
1235
1257
|
const span = to === Infinity ? void 0 : to - from;
|
|
1236
1258
|
if (rhythm === "tabla" && segId !== void 0 && SAMPLED_TAALS.has(segId)) {
|
|
@@ -1518,19 +1540,23 @@ function melodyEvents(schedule, offset = 0) {
|
|
|
1518
1540
|
});
|
|
1519
1541
|
return melody;
|
|
1520
1542
|
}
|
|
1521
|
-
|
|
1543
|
+
var leadIn2 = (schedule, countIn) => countIn ? schedule.pickupBeats : 0;
|
|
1544
|
+
function drumEvents(schedule, drums, offset = 0, countIn = false) {
|
|
1522
1545
|
const drum = [];
|
|
1523
|
-
|
|
1546
|
+
const shift = leadIn2(schedule, countIn);
|
|
1547
|
+
const entry = countIn ? 0 : schedule.samBeat;
|
|
1548
|
+
schedule.tempo.forEach((seg, i) => {
|
|
1524
1549
|
const pattern = typeof drums === "function" ? drums(seg) : drums;
|
|
1525
|
-
if (!pattern?.length)
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1550
|
+
if (!pattern?.length) return;
|
|
1551
|
+
const from = Math.max(i === 0 ? 0 : seg.beat + shift, entry);
|
|
1552
|
+
const end = (i === schedule.tempo.length - 1 ? Math.ceil(schedule.totalBeats) : seg.endBeat) + shift;
|
|
1553
|
+
for (let k = 0; from + k / seg.scale < end - 1e-6; k++) {
|
|
1528
1554
|
const note = DRUM_NOTE[pattern[k % pattern.length]];
|
|
1529
|
-
const t = Math.round((
|
|
1555
|
+
const t = Math.round((from + k / seg.scale) * PPQ) + offset;
|
|
1530
1556
|
drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
|
|
1531
1557
|
drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
|
|
1532
1558
|
}
|
|
1533
|
-
}
|
|
1559
|
+
});
|
|
1534
1560
|
return drum;
|
|
1535
1561
|
}
|
|
1536
1562
|
function midiFile(chunks) {
|
|
@@ -1546,9 +1572,11 @@ function encodeMidi(schedule, opts) {
|
|
|
1546
1572
|
const head = [];
|
|
1547
1573
|
if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
|
|
1548
1574
|
head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1575
|
+
const withDrums = typeof opts.drums === "function" || opts.drums !== void 0 && opts.drums.length > 0;
|
|
1576
|
+
const lead = withDrums ? leadIn2(schedule, opts.countIn) : 0;
|
|
1577
|
+
const chunks = [trackChunk(melodyEvents(schedule, Math.round(lead * PPQ)), head)];
|
|
1578
|
+
if (withDrums) {
|
|
1579
|
+
const drum = drumEvents(schedule, opts.drums, 0, opts.countIn);
|
|
1552
1580
|
chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
|
|
1553
1581
|
}
|
|
1554
1582
|
return midiFile(chunks);
|
|
@@ -1567,7 +1595,12 @@ function safeName2(title) {
|
|
|
1567
1595
|
function exportMidi(doc, options = {}) {
|
|
1568
1596
|
const schedule = exportSchedule(doc, options);
|
|
1569
1597
|
const bpm = options.bpm ?? doc.meta.bpm ?? 90;
|
|
1570
|
-
const bytes = encodeMidi(schedule, {
|
|
1598
|
+
const bytes = encodeMidi(schedule, {
|
|
1599
|
+
bpm,
|
|
1600
|
+
name: options.name ?? doc.meta.title,
|
|
1601
|
+
drums: options.drums ? taalDrums : void 0,
|
|
1602
|
+
countIn: options.countIn
|
|
1603
|
+
});
|
|
1571
1604
|
return {
|
|
1572
1605
|
blob: new Blob([bytes], { type: "audio/midi" }),
|
|
1573
1606
|
filename: `${safeName2(doc.meta.title)}.mid`,
|
|
@@ -1584,9 +1617,10 @@ function exportMidiSet(items, name) {
|
|
|
1584
1617
|
const usPerBeat = Math.round(6e7 / (options.bpm ?? doc.meta.bpm ?? 90));
|
|
1585
1618
|
const at = (bytes2) => ({ tick, on: false, note: 0, vel: 0, ch: 0, meta: bytes2 });
|
|
1586
1619
|
melody.push(at(meta(81, [usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255])));
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1620
|
+
const lead = options.drums ? leadIn2(schedule, options.countIn) : 0;
|
|
1621
|
+
melody.push(at(meta(6, utf8(options.name ?? doc.meta.title ?? ""))), ...melodyEvents(schedule, tick + Math.round(lead * PPQ)));
|
|
1622
|
+
if (options.drums) drum.push(...drumEvents(schedule, taalDrums, tick, options.countIn));
|
|
1623
|
+
beat += Math.ceil(schedule.totalBeats + lead) + 2;
|
|
1590
1624
|
}
|
|
1591
1625
|
const chunks = [trackChunk(melody, [...vlq(0), ...meta(3, utf8(name))])];
|
|
1592
1626
|
if (drum.length > 0) chunks.push(trackChunk(drum, [...vlq(0), ...meta(3, utf8("Drums"))]));
|