smplr 0.17.0 → 0.18.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 +172 -0
- package/dist/index.d.mts +197 -233
- package/dist/index.d.ts +197 -233
- package/dist/index.js +1068 -520
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1067 -520
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -328,6 +328,178 @@ const piano = new SplendidGrandPiano(context, { storage });
|
|
|
328
328
|
|
|
329
329
|
⚠️ `CacheStorage` is based on [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) and only works in secure environments that runs with `https`. Read your framework documentation for setup instructions. For example, in nextjs you can use https://www.npmjs.com/package/next-dev-https. For vite there's https://github.com/liuweiGL/vite-plugin-mkcert. Find the appropriate solution for your environment.
|
|
330
330
|
|
|
331
|
+
## Sequencer
|
|
332
|
+
|
|
333
|
+
`Sequencer` schedules notes from one or more tracks against any smplr instrument with sample-accurate timing.
|
|
334
|
+
|
|
335
|
+
```js
|
|
336
|
+
import { Sequencer, SplendidGrandPiano, DrumMachine } from "smplr";
|
|
337
|
+
|
|
338
|
+
const context = new AudioContext();
|
|
339
|
+
const piano = new SplendidGrandPiano(context);
|
|
340
|
+
const drums = new DrumMachine(context, { instrument: "TR-808" });
|
|
341
|
+
|
|
342
|
+
const seq = new Sequencer(context, { bpm: 120, loop: true });
|
|
343
|
+
|
|
344
|
+
seq.addTrack(piano, [
|
|
345
|
+
{ note: "C4", at: "1:1", duration: "4n" },
|
|
346
|
+
{ note: "E4", at: "1:2", duration: "4n" },
|
|
347
|
+
{ note: "G4", at: "1:3", duration: "4n" },
|
|
348
|
+
{ note: "C5", at: "1:4", duration: "2n" },
|
|
349
|
+
]);
|
|
350
|
+
|
|
351
|
+
seq.addTrack(drums, [
|
|
352
|
+
{ note: "kick", at: "1:1" },
|
|
353
|
+
{ note: "snare", at: "1:2" },
|
|
354
|
+
{ note: "kick", at: "1:3" },
|
|
355
|
+
{ note: "snare", at: "1:4" },
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
seq.loopEnd = "2:1"; // 1 bar
|
|
359
|
+
seq.start();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Time notation
|
|
363
|
+
|
|
364
|
+
Note positions and durations accept several formats:
|
|
365
|
+
|
|
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
|
+
|
|
376
|
+
#### Constructor options
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
const seq = new Sequencer(context, {
|
|
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: { timing: 0.01, velocity: 8 }, // optional randomisation
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Playback
|
|
393
|
+
|
|
394
|
+
```js
|
|
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
|
+
|
|
399
|
+
seq.state; // "stopped" | "playing" | "paused"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### Tempo and position
|
|
403
|
+
|
|
404
|
+
```js
|
|
405
|
+
seq.bpm = 140; // change BPM live, no glitch
|
|
406
|
+
seq.timeSignature = 3; // change time signature
|
|
407
|
+
|
|
408
|
+
seq.position; // current position as "bar:beat:tick" string
|
|
409
|
+
seq.position = "3:1"; // seek while playing or stopped
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### Loop
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
seq.loop = true;
|
|
416
|
+
seq.loopStart = "1:1"; // ticks or string notation
|
|
417
|
+
seq.loopEnd = "3:1"; // ticks or string notation
|
|
418
|
+
|
|
419
|
+
seq.progress; // 0..1 within the loop range
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### Pattern API
|
|
423
|
+
|
|
424
|
+
`scheduleRepeat` fires a callback at a regular musical interval, passing the exact AudioContext time:
|
|
425
|
+
|
|
426
|
+
```js
|
|
427
|
+
const cancel = seq.scheduleRepeat((time) => {
|
|
428
|
+
piano.start({ note: "C4", time, duration: 0.1 });
|
|
429
|
+
}, "8n"); // every eighth note
|
|
430
|
+
|
|
431
|
+
cancel(); // stop repeating
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
An optional third argument sets the start position:
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
seq.scheduleRepeat(callback, "4n", "2:1"); // start at bar 2
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### Events
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
seq.on("beat", (beat, time) => {
|
|
444
|
+
const delay = (time - context.currentTime) * 1000;
|
|
445
|
+
setTimeout(() => metronome.flash(), delay);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
seq.on("bar", (bar, time) => { ui.updateBar(bar); });
|
|
449
|
+
seq.on("loop", () => { console.log("looped"); });
|
|
450
|
+
seq.on("end", () => { console.log("done"); });
|
|
451
|
+
seq.on("start", () => { });
|
|
452
|
+
seq.on("stop", () => { });
|
|
453
|
+
seq.on("pause", () => { });
|
|
454
|
+
|
|
455
|
+
seq.off("beat", handler); // remove a listener
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### Note events
|
|
459
|
+
|
|
460
|
+
`noteOn` and `noteOff` events fire when the instrument's `onStart` / `onEnded` callbacks are called, so they are driven by the actual audio playback — not by the scheduling lookahead.
|
|
461
|
+
|
|
462
|
+
```js
|
|
463
|
+
seq.on("noteOn", (event) => {
|
|
464
|
+
console.log(event.noteId, event.trackIndex, event.noteIndex);
|
|
465
|
+
highlight(event.noteId);
|
|
466
|
+
});
|
|
467
|
+
seq.on("noteOff", (event) => {
|
|
468
|
+
unhighlight(event.noteId);
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
The `event` object (`NoteEvent`) contains:
|
|
473
|
+
|
|
474
|
+
| Field | Type | Description |
|
|
475
|
+
|--------------|--------------------|--------------------------------------------------|
|
|
476
|
+
| `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 |
|
|
480
|
+
|
|
481
|
+
You can set a custom `id` on any `SequencerNote` to use as `noteId`:
|
|
482
|
+
|
|
483
|
+
```js
|
|
484
|
+
seq.addTrack(piano, [
|
|
485
|
+
{ id: "intro-c", note: "C4", at: "1:1", duration: "4n" },
|
|
486
|
+
{ id: "intro-e", note: "E4", at: "1:2", duration: "4n" },
|
|
487
|
+
]);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### Humanize
|
|
491
|
+
|
|
492
|
+
Add subtle randomisation to timing (seconds) and velocity for a more natural feel:
|
|
493
|
+
|
|
494
|
+
```js
|
|
495
|
+
const seq = new Sequencer(context, {
|
|
496
|
+
bpm: 90,
|
|
497
|
+
humanize: { timing: 0.012, velocity: 8 },
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
331
503
|
## Instruments
|
|
332
504
|
|
|
333
505
|
### Sampler
|