smplr 0.18.0 → 0.19.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 +67 -45
- package/dist/index.d.mts +217 -33
- package/dist/index.d.ts +217 -33
- package/dist/index.js +130 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +127 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -349,9 +349,9 @@ seq.addTrack(piano, [
|
|
|
349
349
|
]);
|
|
350
350
|
|
|
351
351
|
seq.addTrack(drums, [
|
|
352
|
-
{ note: "kick",
|
|
352
|
+
{ note: "kick", at: "1:1" },
|
|
353
353
|
{ note: "snare", at: "1:2" },
|
|
354
|
-
{ note: "kick",
|
|
354
|
+
{ note: "kick", at: "1:3" },
|
|
355
355
|
{ note: "snare", at: "1:4" },
|
|
356
356
|
]);
|
|
357
357
|
|
|
@@ -363,60 +363,68 @@ seq.start();
|
|
|
363
363
|
|
|
364
364
|
Note positions and durations accept several formats:
|
|
365
365
|
|
|
366
|
-
| Format
|
|
367
|
-
|
|
368
|
-
| `"4n"`
|
|
369
|
-
| `"8n"`
|
|
370
|
-
| `"4n."`
|
|
371
|
-
| `"1m"`
|
|
372
|
-
| `"2:1"`
|
|
373
|
-
| `"2:3:48"`
|
|
374
|
-
| `96`
|
|
366
|
+
| Format | Meaning |
|
|
367
|
+
| ---------- | ------------------------------ |
|
|
368
|
+
| `"4n"` | quarter note |
|
|
369
|
+
| `"8n"` | eighth note |
|
|
370
|
+
| `"4n."` | dotted quarter (1.5×) |
|
|
371
|
+
| `"1m"` | one measure |
|
|
372
|
+
| `"2:1"` | bar 2, beat 1 (1-indexed) |
|
|
373
|
+
| `"2:3:48"` | bar 2, beat 3, +48 ticks |
|
|
374
|
+
| `96` | raw ticks (number passthrough) |
|
|
375
375
|
|
|
376
376
|
#### Constructor options
|
|
377
377
|
|
|
378
378
|
```js
|
|
379
379
|
const seq = new Sequencer(context, {
|
|
380
|
-
bpm: 120,
|
|
381
|
-
ppq: 480,
|
|
382
|
-
timeSignature: 4,
|
|
383
|
-
loop: false,
|
|
384
|
-
loopStart: 0,
|
|
385
|
-
loopEnd: "2:1",
|
|
386
|
-
lookaheadMs: 200,
|
|
387
|
-
intervalMs: 50,
|
|
388
|
-
humanize: {
|
|
380
|
+
bpm: 120, // default 120
|
|
381
|
+
ppq: 480, // pulses per quarter note, default 480
|
|
382
|
+
timeSignature: 4, // beats per bar, default 4
|
|
383
|
+
loop: false, // default false
|
|
384
|
+
loopStart: 0, // loop start position (ticks or string)
|
|
385
|
+
loopEnd: "2:1", // loop end position; defaults to end of longest track
|
|
386
|
+
lookaheadMs: 200, // scheduling lookahead, default 200
|
|
387
|
+
intervalMs: 50, // flush interval, default 50
|
|
388
|
+
humanize: { timingMs: 10, velocity: 8 }, // optional randomisation
|
|
389
389
|
});
|
|
390
390
|
```
|
|
391
391
|
|
|
392
392
|
#### Playback
|
|
393
393
|
|
|
394
394
|
```js
|
|
395
|
-
seq.start();
|
|
396
|
-
seq.pause();
|
|
397
|
-
seq.stop();
|
|
395
|
+
seq.start(); // start from beginning (or resume from pause if no offset given)
|
|
396
|
+
seq.pause(); // freeze position
|
|
397
|
+
seq.stop(); // stop and reset to 0
|
|
398
|
+
seq.togglePlayPause(); // pause if playing, start/resume otherwise
|
|
398
399
|
|
|
399
|
-
seq.state;
|
|
400
|
+
seq.state; // "stopped" | "playing" | "paused"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Individual sequenced notes can be stopped by their id:
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
seq.stopNote("intro-c"); // stop immediately
|
|
407
|
+
seq.stopNote("intro-c", time); // stop at a scheduled time
|
|
400
408
|
```
|
|
401
409
|
|
|
402
410
|
#### Tempo and position
|
|
403
411
|
|
|
404
412
|
```js
|
|
405
|
-
seq.bpm = 140;
|
|
406
|
-
seq.timeSignature = 3;
|
|
413
|
+
seq.bpm = 140; // change BPM live, no glitch
|
|
414
|
+
seq.timeSignature = 3; // change time signature
|
|
407
415
|
|
|
408
|
-
seq.position;
|
|
409
|
-
seq.position = "3:1";
|
|
416
|
+
seq.position; // current position as "bar:beat:tick" string
|
|
417
|
+
seq.position = "3:1"; // seek while playing or stopped
|
|
410
418
|
```
|
|
411
419
|
|
|
412
420
|
#### Loop
|
|
413
421
|
|
|
414
422
|
```js
|
|
415
423
|
seq.loop = true;
|
|
416
|
-
seq.loopStart = "1:1";
|
|
417
|
-
seq.loopEnd
|
|
424
|
+
seq.loopStart = "1:1"; // ticks or string notation
|
|
425
|
+
seq.loopEnd = "3:1"; // ticks or string notation
|
|
418
426
|
|
|
419
|
-
seq.progress;
|
|
427
|
+
seq.progress; // 0..1 within the loop range
|
|
420
428
|
```
|
|
421
429
|
|
|
422
430
|
#### Pattern API
|
|
@@ -440,19 +448,30 @@ seq.scheduleRepeat(callback, "4n", "2:1"); // start at bar 2
|
|
|
440
448
|
#### Events
|
|
441
449
|
|
|
442
450
|
```js
|
|
451
|
+
seq.on("statechange", (state) => {
|
|
452
|
+
// state: "playing" | "paused" | "stopped"
|
|
453
|
+
setSeqState(state);
|
|
454
|
+
});
|
|
455
|
+
|
|
443
456
|
seq.on("beat", (beat, time) => {
|
|
444
457
|
const delay = (time - context.currentTime) * 1000;
|
|
445
458
|
setTimeout(() => metronome.flash(), delay);
|
|
446
459
|
});
|
|
447
460
|
|
|
448
|
-
seq.on("bar",
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
seq.on("
|
|
452
|
-
|
|
453
|
-
|
|
461
|
+
seq.on("bar", (bar, time) => {
|
|
462
|
+
ui.updateBar(bar);
|
|
463
|
+
});
|
|
464
|
+
seq.on("loop", () => {
|
|
465
|
+
console.log("looped");
|
|
466
|
+
});
|
|
467
|
+
seq.on("end", () => {
|
|
468
|
+
console.log("done");
|
|
469
|
+
});
|
|
470
|
+
seq.on("start", () => {});
|
|
471
|
+
seq.on("stop", () => {});
|
|
472
|
+
seq.on("pause", () => {});
|
|
454
473
|
|
|
455
|
-
seq.off("beat", handler);
|
|
474
|
+
seq.off("beat", handler); // remove a listener
|
|
456
475
|
```
|
|
457
476
|
|
|
458
477
|
#### Note events
|
|
@@ -471,12 +490,12 @@ seq.on("noteOff", (event) => {
|
|
|
471
490
|
|
|
472
491
|
The `event` object (`NoteEvent`) contains:
|
|
473
492
|
|
|
474
|
-
| Field | Type | Description
|
|
475
|
-
|
|
493
|
+
| Field | Type | Description |
|
|
494
|
+
| ------------ | ------------------ | ------------------------------------------------------ |
|
|
476
495
|
| `noteId` | `string \| number` | The note's `id` if provided, otherwise its array index |
|
|
477
|
-
| `trackIndex` | `number` | Index of the track in the order it was added
|
|
478
|
-
| `noteIndex` | `number` | Index of the note within its track's notes array
|
|
479
|
-
| `note` | `SequencerNote` | The original note object
|
|
496
|
+
| `trackIndex` | `number` | Index of the track in the order it was added |
|
|
497
|
+
| `noteIndex` | `number` | Index of the note within its track's notes array |
|
|
498
|
+
| `note` | `SequencerNote` | The original note object |
|
|
480
499
|
|
|
481
500
|
You can set a custom `id` on any `SequencerNote` to use as `noteId`:
|
|
482
501
|
|
|
@@ -489,15 +508,18 @@ seq.addTrack(piano, [
|
|
|
489
508
|
|
|
490
509
|
#### Humanize
|
|
491
510
|
|
|
492
|
-
Add subtle randomisation to timing
|
|
511
|
+
Add subtle randomisation to timing and velocity for a more natural feel:
|
|
493
512
|
|
|
494
513
|
```js
|
|
495
514
|
const seq = new Sequencer(context, {
|
|
496
515
|
bpm: 90,
|
|
497
|
-
humanize: {
|
|
516
|
+
humanize: { timingMs: 12, velocity: 8 },
|
|
498
517
|
});
|
|
499
518
|
```
|
|
500
519
|
|
|
520
|
+
- `timingMs`: maximum random offset in milliseconds (±). Default 0.
|
|
521
|
+
- `velocity`: maximum random offset in MIDI velocity units (±). Default 0.
|
|
522
|
+
|
|
501
523
|
---
|
|
502
524
|
|
|
503
525
|
## Instruments
|
package/dist/index.d.mts
CHANGED
|
@@ -7,6 +7,7 @@ type ChannelConfig = {
|
|
|
7
7
|
destination: AudioNode;
|
|
8
8
|
volume: number;
|
|
9
9
|
volumeToGain: (volume: number) => number;
|
|
10
|
+
pan?: number;
|
|
10
11
|
};
|
|
11
12
|
type OutputChannel = Omit<Channel, "input">;
|
|
12
13
|
/**
|
|
@@ -19,6 +20,8 @@ declare class Channel {
|
|
|
19
20
|
readonly setVolume: (vol: number) => void;
|
|
20
21
|
readonly input: AudioNode;
|
|
21
22
|
constructor(context: BaseAudioContext, options?: Partial<ChannelConfig>);
|
|
23
|
+
get pan(): number;
|
|
24
|
+
set pan(value: number);
|
|
22
25
|
addInsert(effect: AudioNode | AudioInsert): void;
|
|
23
26
|
addEffect(name: string, effect: AudioNode | {
|
|
24
27
|
input: AudioNode;
|
|
@@ -58,6 +61,7 @@ type PlaybackParams = {
|
|
|
58
61
|
loop?: boolean;
|
|
59
62
|
loopStart?: number;
|
|
60
63
|
loopEnd?: number;
|
|
64
|
+
reverse?: boolean;
|
|
61
65
|
};
|
|
62
66
|
/**
|
|
63
67
|
* An individual sample region. Maps a sample to a range of notes and velocities.
|
|
@@ -122,7 +126,7 @@ type SmplrJson = {
|
|
|
122
126
|
/**
|
|
123
127
|
* A note event passed to Smplr.start(). Can be a full object, a note name, or a MIDI number.
|
|
124
128
|
*/
|
|
125
|
-
type NoteEvent
|
|
129
|
+
type NoteEvent = {
|
|
126
130
|
note: string | number;
|
|
127
131
|
velocity?: number;
|
|
128
132
|
time?: number;
|
|
@@ -132,8 +136,9 @@ type NoteEvent$1 = {
|
|
|
132
136
|
loop?: boolean;
|
|
133
137
|
ampRelease?: number;
|
|
134
138
|
stopId?: string | number;
|
|
135
|
-
onStart?: (event: NoteEvent
|
|
136
|
-
onEnded?: (event: NoteEvent
|
|
139
|
+
onStart?: (event: NoteEvent) => void;
|
|
140
|
+
onEnded?: (event: NoteEvent) => void;
|
|
141
|
+
reverse?: boolean;
|
|
137
142
|
} | string | number;
|
|
138
143
|
/**
|
|
139
144
|
* Target for Smplr.stop(). Can be a full object, a stopId, or a MIDI number.
|
|
@@ -153,6 +158,166 @@ type LoadProgress = {
|
|
|
153
158
|
loaded: number;
|
|
154
159
|
total: number;
|
|
155
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* Fully resolved playback parameters for a single Voice.
|
|
163
|
+
* Output of resolveParams() — all fields are required, no optionals except ampVelCurve/loopAuto.
|
|
164
|
+
*/
|
|
165
|
+
type VoiceParams = {
|
|
166
|
+
detune: number;
|
|
167
|
+
velocity: number;
|
|
168
|
+
volume: number;
|
|
169
|
+
ampRelease: number;
|
|
170
|
+
ampAttack: number;
|
|
171
|
+
lpfCutoffHz: number;
|
|
172
|
+
offset: number;
|
|
173
|
+
loop: boolean;
|
|
174
|
+
loopStart: number;
|
|
175
|
+
loopEnd: number;
|
|
176
|
+
ampVelCurve?: [number, number];
|
|
177
|
+
/** If set, loop points are computed from buffer.duration at play time. */
|
|
178
|
+
loopAuto?: {
|
|
179
|
+
startRatio: number;
|
|
180
|
+
endRatio: number;
|
|
181
|
+
};
|
|
182
|
+
reverse?: boolean;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Loads and caches AudioBuffers for all samples referenced in a SmplrJson.
|
|
187
|
+
*
|
|
188
|
+
* The cache is keyed by resolved URL, so the same audio file is never fetched
|
|
189
|
+
* or decoded twice. Multiple Smplr instances can share one SampleLoader by
|
|
190
|
+
* passing it via SmplrOptions.loader.
|
|
191
|
+
*/
|
|
192
|
+
declare class SampleLoader {
|
|
193
|
+
#private;
|
|
194
|
+
constructor(context: BaseAudioContext, options?: {
|
|
195
|
+
storage?: Storage;
|
|
196
|
+
});
|
|
197
|
+
/**
|
|
198
|
+
* Load all samples referenced in `json`. Returns a Map of sample name →
|
|
199
|
+
* AudioBuffer. Progress is reported via `onProgress` callback or via
|
|
200
|
+
* options object.
|
|
201
|
+
*
|
|
202
|
+
* - `buffers` in options: pre-loaded buffers — skips fetch for these names.
|
|
203
|
+
* - All samples load in parallel. Failed samples are silently omitted.
|
|
204
|
+
*/
|
|
205
|
+
load(json: SmplrJson, onProgressOrOptions?: ((loaded: number, total: number) => void) | {
|
|
206
|
+
buffers?: Map<string, AudioBuffer>;
|
|
207
|
+
onProgress?: (loaded: number, total: number) => void;
|
|
208
|
+
}): Promise<Map<string, AudioBuffer>>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Standalone scheduler. Dispatches NoteEvents immediately when they fall within the
|
|
213
|
+
* lookahead window, or queues them for future dispatch via a self-managing interval.
|
|
214
|
+
*
|
|
215
|
+
* Multiple Smplr instances can share a single Scheduler for coordinated timing.
|
|
216
|
+
*/
|
|
217
|
+
declare class Scheduler {
|
|
218
|
+
#private;
|
|
219
|
+
constructor(context: BaseAudioContext, options?: {
|
|
220
|
+
lookaheadMs?: number;
|
|
221
|
+
intervalMs?: number;
|
|
222
|
+
});
|
|
223
|
+
/**
|
|
224
|
+
* Schedule a callback for a NoteEvent.
|
|
225
|
+
*
|
|
226
|
+
* - If the event's time falls within the lookahead window (or has no time), the
|
|
227
|
+
* callback is called synchronously and a no-op StopFn is returned.
|
|
228
|
+
* - Otherwise the event is queued, the interval is started if needed, and a StopFn
|
|
229
|
+
* is returned that removes the event from the queue before it is dispatched.
|
|
230
|
+
*/
|
|
231
|
+
schedule(event: NoteEvent, callback: (event: NoteEvent) => void): StopFn;
|
|
232
|
+
/**
|
|
233
|
+
* Clear all queued (not-yet-dispatched) events and stop the interval.
|
|
234
|
+
* Does not affect voices that are already playing.
|
|
235
|
+
*/
|
|
236
|
+
stop(): void;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
type SmplrOptions = {
|
|
240
|
+
/** Custom storage backend for sample fetching (e.g. CacheStorage). */
|
|
241
|
+
storage?: Storage;
|
|
242
|
+
/** Destination audio node. Defaults to context.destination. */
|
|
243
|
+
destination?: AudioNode;
|
|
244
|
+
/** Master volume (0–127 MIDI scale). Defaults to 100. */
|
|
245
|
+
volume?: number;
|
|
246
|
+
/** Custom volume-to-gain mapping function. Defaults to midiVelToGain. */
|
|
247
|
+
volumeToGain?: (volume: number) => number;
|
|
248
|
+
/** Stereo pan position (-1 = full left, 0 = centre, +1 = full right). Defaults to 0. */
|
|
249
|
+
pan?: number;
|
|
250
|
+
/** Default note velocity when not specified in NoteEvent (0–127). Defaults to 100. */
|
|
251
|
+
velocity?: number;
|
|
252
|
+
/** Shared SampleLoader instance. If omitted, a private one is created. */
|
|
253
|
+
loader?: SampleLoader;
|
|
254
|
+
/** Shared Scheduler instance. If omitted, a private one is created. */
|
|
255
|
+
scheduler?: Scheduler;
|
|
256
|
+
/** Called after each buffer is loaded (or served from cache). */
|
|
257
|
+
onLoadProgress?: (progress: LoadProgress) => void;
|
|
258
|
+
/** Called when a note is dispatched to the audio engine (slightly before playback). */
|
|
259
|
+
onStart?: (event: NoteEvent) => void;
|
|
260
|
+
/** Called when each voice's audio node ends. */
|
|
261
|
+
onEnded?: (event: NoteEvent) => void;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* The main sampler class. Loads samples described by a SmplrJson descriptor,
|
|
265
|
+
* matches notes to regions, and plays them through a Channel.
|
|
266
|
+
*
|
|
267
|
+
* Multiple Smplr instances can share a SampleLoader (shared cache) and/or a
|
|
268
|
+
* Scheduler (coordinated timing) by passing them via SmplrOptions.
|
|
269
|
+
*
|
|
270
|
+
* Pattern A — json provided at construction:
|
|
271
|
+
* `new Smplr(context, json, options?)`
|
|
272
|
+
*
|
|
273
|
+
* Pattern B — json loaded later via loadInstrument():
|
|
274
|
+
* `new Smplr(context, options?)` then `smplr.loadInstrument(json)`
|
|
275
|
+
*/
|
|
276
|
+
declare class Smplr {
|
|
277
|
+
#private;
|
|
278
|
+
/** Resolves with `this` once all sample buffers are loaded. */
|
|
279
|
+
readonly load: Promise<Smplr>;
|
|
280
|
+
/** The AudioContext passed to the constructor. */
|
|
281
|
+
readonly context: AudioContext;
|
|
282
|
+
constructor(context: AudioContext, json: SmplrJson, options?: SmplrOptions);
|
|
283
|
+
constructor(context: AudioContext, options?: SmplrOptions);
|
|
284
|
+
/**
|
|
285
|
+
* Load (or replace) the instrument descriptor. Creates a new RegionMatcher
|
|
286
|
+
* and fetches all sample buffers. Pre-loaded buffers (e.g. base64-decoded)
|
|
287
|
+
* can be passed via the `buffers` parameter — those skip the fetch step.
|
|
288
|
+
*
|
|
289
|
+
* Returns a Promise that resolves when all samples are ready.
|
|
290
|
+
*/
|
|
291
|
+
loadInstrument(json: SmplrJson, buffers?: Map<string, AudioBuffer>): Promise<void>;
|
|
292
|
+
/** Current loading progress snapshot. `total` is known before loading starts. */
|
|
293
|
+
get loadProgress(): LoadProgress;
|
|
294
|
+
/** The output channel — use to add effects, adjust volume, or route audio. */
|
|
295
|
+
get output(): OutputChannel;
|
|
296
|
+
/**
|
|
297
|
+
* Set a MIDI CC value. Affects region matching for groups/regions that have
|
|
298
|
+
* ccRange constraints (e.g. CC64 sustain pedal).
|
|
299
|
+
*/
|
|
300
|
+
setCC(cc: number, value: number): void;
|
|
301
|
+
/**
|
|
302
|
+
* Start playing a note. Returns a StopFn that cancels the note if it hasn't
|
|
303
|
+
* played yet, or stops the resulting voices if it has.
|
|
304
|
+
*/
|
|
305
|
+
start(event: NoteEvent): StopFn;
|
|
306
|
+
/**
|
|
307
|
+
* Stop voices.
|
|
308
|
+
*
|
|
309
|
+
* - No argument → stop all active voices
|
|
310
|
+
* - String or number → stop all voices with that stopId
|
|
311
|
+
* - `{ stopId }` → stop voices with that stopId, optionally at a future time
|
|
312
|
+
* - `{ time }` (no stopId) → stop all voices at a future time
|
|
313
|
+
*/
|
|
314
|
+
stop(target?: StopTarget): void;
|
|
315
|
+
/**
|
|
316
|
+
* Stop all voices, disconnect the output channel, and stop the scheduler.
|
|
317
|
+
* The instance should not be used after this call.
|
|
318
|
+
*/
|
|
319
|
+
disconnect(): void;
|
|
320
|
+
}
|
|
156
321
|
|
|
157
322
|
/**
|
|
158
323
|
* Given a list of [midi, sampleName] pairs, return one entry per sample with
|
|
@@ -188,6 +353,7 @@ type DrumMachineConfig = {
|
|
|
188
353
|
type DrumMachineOptions = Partial<DrumMachineConfig & {
|
|
189
354
|
destination?: AudioNode;
|
|
190
355
|
volume?: number;
|
|
356
|
+
pan?: number;
|
|
191
357
|
velocity?: number;
|
|
192
358
|
onLoadProgress?: (progress: LoadProgress) => void;
|
|
193
359
|
}>;
|
|
@@ -199,7 +365,7 @@ declare class DrumMachine {
|
|
|
199
365
|
getSampleNames(): string[];
|
|
200
366
|
getGroupNames(): string[];
|
|
201
367
|
getSampleNamesForGroup(groupName: string): string[];
|
|
202
|
-
start(sample: NoteEvent
|
|
368
|
+
start(sample: NoteEvent): StopFn;
|
|
203
369
|
stop(sample?: StopTarget): void;
|
|
204
370
|
disconnect(): void;
|
|
205
371
|
/** @deprecated */
|
|
@@ -246,6 +412,8 @@ type SequencerNote = {
|
|
|
246
412
|
/** Note duration: ticks, "4n", "8n", etc. Omit for a one-shot trigger. */
|
|
247
413
|
duration?: string | number;
|
|
248
414
|
velocity?: number;
|
|
415
|
+
/** Probability (0–100) that this note fires on each pass. Default 100 (always). */
|
|
416
|
+
chance?: number;
|
|
249
417
|
};
|
|
250
418
|
/**
|
|
251
419
|
* Any instrument the Sequencer can drive.
|
|
@@ -259,16 +427,12 @@ type SequencerInstrument = {
|
|
|
259
427
|
duration?: number;
|
|
260
428
|
velocity?: number;
|
|
261
429
|
noteId?: string | number;
|
|
262
|
-
onStart?: (event:
|
|
263
|
-
|
|
264
|
-
}) => void;
|
|
265
|
-
onEnded?: (event: {
|
|
266
|
-
noteId?: string | number;
|
|
267
|
-
}) => void;
|
|
430
|
+
onStart?: (event: unknown) => void;
|
|
431
|
+
onEnded?: (event: unknown) => void;
|
|
268
432
|
}): unknown;
|
|
269
433
|
};
|
|
270
434
|
/** Emitted with "noteOn" and "noteOff" events. */
|
|
271
|
-
type
|
|
435
|
+
type SequencerNoteEvent = {
|
|
272
436
|
noteId: string | number;
|
|
273
437
|
trackIndex: number;
|
|
274
438
|
noteIndex: number;
|
|
@@ -285,17 +449,20 @@ type SequencerOptions = {
|
|
|
285
449
|
lookaheadMs?: number;
|
|
286
450
|
/** How often (ms) the flush loop runs. Default 50. */
|
|
287
451
|
intervalMs?: number;
|
|
288
|
-
/** Randomise timing (
|
|
452
|
+
/** Randomise timing (ms) and velocity per note for a human feel. */
|
|
289
453
|
humanize?: {
|
|
290
|
-
|
|
454
|
+
timingMs?: number;
|
|
291
455
|
velocity?: number;
|
|
292
456
|
};
|
|
457
|
+
/** Emit a "step" event at this interval. Accepts musical notation or ticks: "16n", "8n", ticks, etc. */
|
|
458
|
+
stepSize?: string | number;
|
|
293
459
|
};
|
|
294
460
|
declare class Sequencer {
|
|
295
461
|
private readonly _context;
|
|
296
462
|
private readonly _clock;
|
|
297
463
|
private readonly _ppq;
|
|
298
464
|
private _timeSignature;
|
|
465
|
+
private _stepTicks;
|
|
299
466
|
private _tracks;
|
|
300
467
|
private _repeatEvents;
|
|
301
468
|
private _listeners;
|
|
@@ -313,6 +480,8 @@ declare class Sequencer {
|
|
|
313
480
|
private _totalTicks;
|
|
314
481
|
/** Guards against scheduling the auto-stop setTimeout more than once. */
|
|
315
482
|
private _endScheduled;
|
|
483
|
+
/** Active voices keyed by noteId, so individual notes can be stopped. */
|
|
484
|
+
private _activeVoices;
|
|
316
485
|
constructor(context: BaseAudioContext, options?: SequencerOptions);
|
|
317
486
|
addTrack(instrument: SequencerInstrument, notes: SequencerNote[]): this;
|
|
318
487
|
removeTrack(instrument: SequencerInstrument): this;
|
|
@@ -324,6 +493,16 @@ declare class Sequencer {
|
|
|
324
493
|
start(offsetTick?: number): this;
|
|
325
494
|
pause(): this;
|
|
326
495
|
stop(): this;
|
|
496
|
+
/**
|
|
497
|
+
* Stop a single note that was scheduled by the sequencer.
|
|
498
|
+
* @param noteId The id of the note (from SequencerNote.id or auto-assigned index).
|
|
499
|
+
* @param time Optional AudioContext time to schedule the stop.
|
|
500
|
+
*/
|
|
501
|
+
stopNote(noteId: string | number, time?: number): this;
|
|
502
|
+
/**
|
|
503
|
+
* Toggle between playing and paused. If stopped, starts from the beginning.
|
|
504
|
+
*/
|
|
505
|
+
togglePlayPause(): this;
|
|
327
506
|
get bpm(): number;
|
|
328
507
|
set bpm(value: number);
|
|
329
508
|
get timeSignature(): number;
|
|
@@ -359,17 +538,19 @@ declare class Sequencer {
|
|
|
359
538
|
/**
|
|
360
539
|
* Listen to a sequencer event.
|
|
361
540
|
*
|
|
362
|
-
* | Event
|
|
363
|
-
*
|
|
364
|
-
* | "
|
|
365
|
-
* | "
|
|
366
|
-
* | "
|
|
367
|
-
* | "
|
|
368
|
-
* | "
|
|
369
|
-
* | "
|
|
370
|
-
* | "
|
|
371
|
-
* | "
|
|
372
|
-
* | "
|
|
541
|
+
* | Event | Args |
|
|
542
|
+
* |----------------|---------------------------------------------------|
|
|
543
|
+
* | "statechange" | (state: "playing" \| "paused" \| "stopped") |
|
|
544
|
+
* | "start" | |
|
|
545
|
+
* | "stop" | |
|
|
546
|
+
* | "pause" | |
|
|
547
|
+
* | "end" | |
|
|
548
|
+
* | "loop" | |
|
|
549
|
+
* | "beat" | (beat: number, time: number) |
|
|
550
|
+
* | "bar" | (bar: number, time: number) |
|
|
551
|
+
* | "step" | (stepIndex: number, time: number) |
|
|
552
|
+
* | "noteOn" | (event: SequencerNoteEvent) |
|
|
553
|
+
* | "noteOff" | (event: SequencerNoteEvent) |
|
|
373
554
|
*/
|
|
374
555
|
on(event: string, callback: (...args: any[]) => void): this;
|
|
375
556
|
off(event: string, callback: (...args: any[]) => void): this;
|
|
@@ -377,8 +558,11 @@ declare class Sequencer {
|
|
|
377
558
|
private _stopLoop;
|
|
378
559
|
private _flush;
|
|
379
560
|
private _scheduleWindow;
|
|
561
|
+
private _emitStepsInWindow;
|
|
380
562
|
private _emitBeatsInWindow;
|
|
381
563
|
private _emit;
|
|
564
|
+
/** Emit both the specific state event ("start"/"pause"/"stop") and the unified "statechange" event. */
|
|
565
|
+
private _emitStateChange;
|
|
382
566
|
/** Recompute _totalTicks from all track notes (at + duration). */
|
|
383
567
|
private _recomputeTotalTicks;
|
|
384
568
|
/** Format a raw tick count as "bar:beat:tick" (all 1-indexed). */
|
|
@@ -413,7 +597,7 @@ declare class ElectricPiano {
|
|
|
413
597
|
});
|
|
414
598
|
get output(): OutputChannel;
|
|
415
599
|
get loadProgress(): LoadProgress;
|
|
416
|
-
start(sample: NoteEvent
|
|
600
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
417
601
|
stop(target?: StopTarget): void;
|
|
418
602
|
disconnect(): void;
|
|
419
603
|
}
|
|
@@ -442,7 +626,7 @@ declare class Versilian {
|
|
|
442
626
|
constructor(context: BaseAudioContext, options?: VersilianOptions);
|
|
443
627
|
get output(): OutputChannel;
|
|
444
628
|
get loadProgress(): LoadProgress;
|
|
445
|
-
start(sample: NoteEvent
|
|
629
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
446
630
|
stop(target?: StopTarget): void;
|
|
447
631
|
disconnect(): void;
|
|
448
632
|
}
|
|
@@ -473,7 +657,7 @@ declare class Mellotron {
|
|
|
473
657
|
constructor(context: BaseAudioContext, options?: MellotronOptions);
|
|
474
658
|
get output(): OutputChannel;
|
|
475
659
|
get loadProgress(): LoadProgress;
|
|
476
|
-
start(sample: NoteEvent
|
|
660
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
477
661
|
stop(target?: StopTarget): void;
|
|
478
662
|
disconnect(): void;
|
|
479
663
|
}
|
|
@@ -531,7 +715,7 @@ declare class Sampler {
|
|
|
531
715
|
constructor(context: AudioContext, options?: Partial<SamplerConfig>);
|
|
532
716
|
loaded(): Promise<this>;
|
|
533
717
|
get output(): OutputChannel;
|
|
534
|
-
start(sample: NoteEvent
|
|
718
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
535
719
|
stop(sample?: StopTarget | string | number): void;
|
|
536
720
|
disconnect(): void;
|
|
537
721
|
}
|
|
@@ -571,7 +755,7 @@ declare class Smolken {
|
|
|
571
755
|
constructor(context: BaseAudioContext, options?: SmolkenOptions);
|
|
572
756
|
get output(): OutputChannel;
|
|
573
757
|
get loadProgress(): LoadProgress;
|
|
574
|
-
start(sample: NoteEvent
|
|
758
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
575
759
|
stop(target?: StopTarget): void;
|
|
576
760
|
disconnect(): void;
|
|
577
761
|
}
|
|
@@ -605,7 +789,7 @@ declare class Soundfont {
|
|
|
605
789
|
get output(): OutputChannel;
|
|
606
790
|
loaded(): Promise<this>;
|
|
607
791
|
disconnect(): void;
|
|
608
|
-
start(sample: NoteEvent
|
|
792
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
609
793
|
stop(sample?: StopTarget | string | number): void;
|
|
610
794
|
}
|
|
611
795
|
/**
|
|
@@ -664,7 +848,7 @@ declare class Soundfont2Sampler {
|
|
|
664
848
|
get instrumentNames(): string[];
|
|
665
849
|
get output(): OutputChannel;
|
|
666
850
|
loadInstrument(instrumentName: string): Promise<void> | undefined;
|
|
667
|
-
start(sample: NoteEvent
|
|
851
|
+
start(sample: NoteEvent | string | number): StopFn;
|
|
668
852
|
stop(sample?: StopTarget | string | number): void;
|
|
669
853
|
disconnect(): void;
|
|
670
854
|
}
|
|
@@ -704,7 +888,7 @@ declare class SplendidGrandPiano {
|
|
|
704
888
|
get loadProgress(): LoadProgress;
|
|
705
889
|
/** @deprecated Use `load` instead. */
|
|
706
890
|
loaded(): Promise<this>;
|
|
707
|
-
start(event: NoteEvent
|
|
891
|
+
start(event: NoteEvent): StopFn;
|
|
708
892
|
stop(target?: StopTarget): void;
|
|
709
893
|
disconnect(): void;
|
|
710
894
|
}
|
|
@@ -733,4 +917,4 @@ declare const LAYERS: ({
|
|
|
733
917
|
cutoff?: undefined;
|
|
734
918
|
})[];
|
|
735
919
|
|
|
736
|
-
export { CacheStorage, DrumMachine, type DrumMachineOptions, ElectricPiano, type ElectricPianoOptions, HttpStorage, LAYERS, Mallet, Mellotron, type MellotronConfig, type MellotronOptions, NAME_TO_PATH, type NoteEvent, Reverb, Sampler, type SamplerConfig, Sequencer, type SequencerInstrument, type SequencerNote, type SequencerOptions, Smolken, type SmolkenConfig, type SmolkenOptions, Soundfont, type Soundfont2Options, Soundfont2Sampler, type SoundfontOptions, SplendidGrandPiano, type SplendidGrandPianoConfig, type Storage, type StorageResponse, Versilian, type VersilianConfig, type VersilianOptions, drumMachineToSmplrJson, getDrumMachineNames, getElectricPianoNames, getMalletNames, getMellotronNames, getSmolkenNames, getSoundfontKits, getSoundfontNames, getVersilianInstruments, mellotronToSmplrJson, pianoToSmplrJson, samplerToSmplrJson, sf2InstrumentToSmplrJson, soundfontToSmplrJson, spreadKeyRanges };
|
|
920
|
+
export { CacheStorage, DrumMachine, type DrumMachineOptions, ElectricPiano, type ElectricPianoOptions, HttpStorage, LAYERS, type LoadProgress, Mallet, Mellotron, type MellotronConfig, type MellotronOptions, NAME_TO_PATH, type NoteEvent, type PlaybackParams, Reverb, SampleLoader, Sampler, type SamplerConfig, Scheduler, Sequencer, type SequencerInstrument, type SequencerNote, type SequencerNoteEvent, type SequencerOptions, Smolken, type SmolkenConfig, type SmolkenOptions, Smplr, type SmplrGroup, type SmplrJson, type SmplrOptions, type SmplrRegion, type SmplrSamples, Soundfont, type Soundfont2Options, Soundfont2Sampler, type SoundfontOptions, SplendidGrandPiano, type SplendidGrandPianoConfig, type SpreadResult, type StopFn, type StopTarget, type Storage, type StorageResponse, Versilian, type VersilianConfig, type VersilianOptions, type VoiceParams, drumMachineToSmplrJson, getDrumMachineNames, getElectricPianoNames, getMalletNames, getMellotronNames, getSmolkenNames, getSoundfontKits, getSoundfontNames, getVersilianInstruments, mellotronToSmplrJson, pianoToSmplrJson, samplerToSmplrJson, sf2InstrumentToSmplrJson, soundfontToSmplrJson, spreadKeyRanges };
|