smplr 0.24.0 → 0.26.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 +197 -147
- package/dist/index.d.mts +117 -46
- package/dist/index.d.ts +117 -46
- package/dist/index.js +510 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +503 -48
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
> `smplr` is a collection of sampled instruments for Web Audio API ready to be used with no setup required.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Quick start
|
|
8
8
|
|
|
9
9
|
**Play a note from a General MIDI soundfont:**
|
|
10
10
|
|
|
@@ -57,17 +57,13 @@ wav.downloadWav("arpeggio.wav");
|
|
|
57
57
|
|
|
58
58
|
See demo: https://danigb.github.io/smplr/
|
|
59
59
|
|
|
60
|
-
`smplr` is approaching 1.0. The 0.22.0 release lands the final batch of pre-1.0 API work — every documented `new X(ctx, opts)` keeps working, and the documented surface is intended to ship unchanged into 1.0. The formal stability commitment lands once the narrow `loader`/`scheduler` public interfaces sibling ticket is in (see [CHANGELOG](https://github.com/danigb/smplr/blob/main/CHANGELOG.md)).
|
|
61
|
-
|
|
62
|
-
> **Upgrading from an earlier 0.x?** No code changes are required — every documented `new X(ctx, opts)` keeps working. New code should drop the `new` (`X(ctx, opts)`) and prefer `await x.ready` over `await x.load`.
|
|
63
|
-
|
|
64
60
|
#### Library goals
|
|
65
61
|
|
|
66
62
|
- No setup: specifically, all samples are online, so no need for a server.
|
|
67
63
|
- Easy to use: everything should be intuitive for non-experienced developers
|
|
68
64
|
- Decent sounding: uses high quality open source samples. For better or worse, it is sample based 🤷
|
|
69
65
|
|
|
70
|
-
##
|
|
66
|
+
## Installation
|
|
71
67
|
|
|
72
68
|
You can install the library with a package manager or use it directly by importing from the browser.
|
|
73
69
|
|
|
@@ -75,7 +71,7 @@ Samples are stored at https://github.com/smpldsnds and there is no need to downl
|
|
|
75
71
|
|
|
76
72
|
#### Using a package manager
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
Install with npm or your favourite package manager:
|
|
79
75
|
|
|
80
76
|
```
|
|
81
77
|
npm i smplr
|
|
@@ -105,17 +101,33 @@ You can import directly from the browser. For example:
|
|
|
105
101
|
|
|
106
102
|
The package needs to be served as a URL from a service like [unpkg](https://unpkg.com) or similar.
|
|
107
103
|
|
|
108
|
-
|
|
104
|
+
## Available instruments
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
`smplr` ships eleven instruments out of the box. Pick one and jump to its section in the [Instrument reference](#instrument-reference) for setup details.
|
|
111
107
|
|
|
112
|
-
|
|
108
|
+
| Instrument | Description | Names helper |
|
|
109
|
+
| ------------------------------------------- | ----------------------------------------- | ---------------------------- |
|
|
110
|
+
| [`Sampler`](#sampler) | Your own buffers or SFZ-style preset | — |
|
|
111
|
+
| [`Soundfont`](#soundfont) | General MIDI soundfonts | `getSoundfontNames()` |
|
|
112
|
+
| [`SplendidGrandPiano`](#splendidgrandpiano) | Sampled Steinway grand, 4 velocity layers | — |
|
|
113
|
+
| [`ElectricPiano`](#electric-piano) | CP80, PianetT, Wurlitzer, TX81Z | `getElectricPianoNames()` |
|
|
114
|
+
| [`DrumMachine`](#drum-machines) | Classic drum machines (TR-808, …) | `getDrumMachineNames()` |
|
|
115
|
+
| [`DrumAbuse`](#drumabuse) | ~210 machines (Synthabuse collection) | `getDrumAbuseMachineNames()` |
|
|
116
|
+
| [`Mallet`](#mallets) | VCSL mallets | `getMalletNames()` |
|
|
117
|
+
| [`Mellotron`](#mellotron) | Mellotron archive samples | `getMellotronNames()` |
|
|
118
|
+
| [`Smolken`](#smolken-double-bass) | Smolken double bass (Arco/Pizz/Switched) | `getSmolkenNames()` |
|
|
119
|
+
| [`Versilian`](#versilian) | VCSL multi-instrument (partial support) | `getVersilianInstruments()` |
|
|
120
|
+
| [`Soundfont2`](#soundfont2) | Reads .sf2 files directly | — |
|
|
113
121
|
|
|
114
|
-
|
|
122
|
+
Each names helper returns strings to pass as the factory's `instrument` option. `getVersilianInstruments` is async (the catalog is fetched once and cached).
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
To build your own instrument, see [Defining your own instrument](#defining-your-own-instrument).
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
## Using an instrument
|
|
127
|
+
|
|
128
|
+
The shared API below applies to every instrument. Instrument-specific options live in the [Instrument reference](#instrument-reference).
|
|
129
|
+
|
|
130
|
+
### Create and load
|
|
119
131
|
|
|
120
132
|
Every smplr instrument is a factory function: call it with an `AudioContext` and an options object to get back an instance.
|
|
121
133
|
|
|
@@ -127,29 +139,18 @@ const piano = SplendidGrandPiano(context, { decayTime: 0.5 });
|
|
|
127
139
|
const marimba = Soundfont(context, { instrument: "marimba" });
|
|
128
140
|
```
|
|
129
141
|
|
|
130
|
-
> **Compatibility note:** All factories also support the `new` keyword — `new SplendidGrandPiano(context)` produces the same instance as `SplendidGrandPiano(context)`. Code from earlier `smplr` versions keeps working unchanged. Editors will mark the `new` form as `@deprecated` to nudge new code toward the call form; both remain supported throughout the 1.x line.
|
|
131
|
-
|
|
132
142
|
#### Wait for audio loading
|
|
133
143
|
|
|
134
|
-
You can start playing notes as soon as one
|
|
144
|
+
You can start playing notes as soon as one sample is loaded. To wait for all of them, await either:
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
piano.load
|
|
138
|
-
// now the piano is fully loaded
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Since the promise returns the instrument instance, you can create and wait in a single line:
|
|
146
|
+
- `piano.ready` — resolves to `void` (preferred for new code).
|
|
147
|
+
- `piano.load` — resolves to the instrument itself, so you can create and await in one line:
|
|
143
148
|
|
|
144
149
|
```js
|
|
145
150
|
const piano = await SplendidGrandPiano(context).load;
|
|
146
151
|
```
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
> **New in 1.0:** prefer `await piano.ready` for new code. It resolves to `void` (not the instrument) and won't be removed — `.load` is kept as a deprecated alias for compatibility.
|
|
151
|
-
|
|
152
|
-
⚠️ In versions lower than 0.8.0 a `loaded()` function was exposed instead.
|
|
153
|
+
> Upgrading from older versions? See [MIGRATE.md](./MIGRATE.md).
|
|
153
154
|
|
|
154
155
|
#### Load progress
|
|
155
156
|
|
|
@@ -168,7 +169,7 @@ console.log(piano.loadProgress); // { loaded: 12, total: 48 }
|
|
|
168
169
|
|
|
169
170
|
`total` is known before loading starts, so you can display a determinate progress bar.
|
|
170
171
|
|
|
171
|
-
|
|
172
|
+
### Shared configuration options
|
|
172
173
|
|
|
173
174
|
All instruments share some configuration options, passed as the second argument to the factory. Every field is optional:
|
|
174
175
|
|
|
@@ -177,62 +178,24 @@ All instruments share some configuration options, passed as the second argument
|
|
|
177
178
|
- `pan`: stereo pan, -1 (full left) to +1 (full right). 0 by default.
|
|
178
179
|
- `destination`: the `AudioNode` the instrument writes to. `AudioContext.destination` by default.
|
|
179
180
|
- `volumeToGain`: a function to map MIDI volume to a linear gain. Uses the MIDI standard curve by default.
|
|
180
|
-
- `storage`: a [storage backend](#
|
|
181
|
+
- `storage`: a [storage backend](#caching-samples) used to fetch sample buffers. `HttpStorage` by default.
|
|
181
182
|
- `loader`: a shared `SampleLoader` instance. Pass the same loader to multiple instruments to cache buffers across them (see [Buffer reuse](#buffer-reuse)).
|
|
182
183
|
- `scheduler`: a shared `Scheduler` instance. Construct your own to tune scheduling — for example, `Scheduler(context, { lookaheadMs: 100, intervalMs: 25 })` — or omit to get a per-instrument default.
|
|
183
184
|
- `onLoadProgress`: a function called after each sample buffer is decoded. Receives `{ loaded, total }` where `total` is the full count known before loading starts.
|
|
184
185
|
- `onStart`: called when a note is dispatched to the audio engine. Receives the started note. See ⚠️ note under [Events](#events) on timing precision.
|
|
185
186
|
- `onEnded`: called when each voice's audio node ends. Receives the started note.
|
|
186
187
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
This package should be compatible with [standardized-audio-context](https://github.com/chrisguttandin/standardized-audio-context):
|
|
190
|
-
|
|
191
|
-
```js
|
|
192
|
-
import { AudioContext } from "standardized-audio-context";
|
|
193
|
-
|
|
194
|
-
const context = new AudioContext();
|
|
195
|
-
const piano = SplendidGrandPiano(context);
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
However, if you are using Typescript, you might need to "force cast" the types:
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
import { Soundfont } from "smplr";
|
|
202
|
-
import { AudioContext as StandardizedAudioContext } from "standardized-audio-context";
|
|
203
|
-
|
|
204
|
-
const context = new StandardizedAudioContext() as unknown as AudioContext;
|
|
205
|
-
const marimba = Soundfont(context, { instrument: "marimba" });
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
In case you need to use the `Reverb` module (or any other module that needs `AudioWorkletNode`) you need to enforce to use the one from `standardized-audio-context` package. Here is how:
|
|
209
|
-
|
|
210
|
-
```ts
|
|
211
|
-
import {
|
|
212
|
-
AudioWorkletNode,
|
|
213
|
-
IAudioContext,
|
|
214
|
-
AudioContext as StandardizedAudioContext,
|
|
215
|
-
} from "standardized-audio-context";
|
|
216
|
-
|
|
217
|
-
window.AudioWorkletNode = AudioWorkletNode as any;
|
|
218
|
-
const context = new StandardizedAudioContext() as unknown as AudioContext;
|
|
219
|
-
|
|
220
|
-
// ... rest of the code
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
See [standardized-audio-context issue #897](https://github.com/chrisguttandin/standardized-audio-context/issues/897) for background on why the cast is required.
|
|
224
|
-
|
|
225
|
-
### Play
|
|
188
|
+
### Play notes
|
|
226
189
|
|
|
227
190
|
#### Start and stop notes
|
|
228
191
|
|
|
229
|
-
The `start` function accepts
|
|
192
|
+
The `start` function accepts these options:
|
|
230
193
|
|
|
231
194
|
```js
|
|
232
195
|
piano.start({ note: "C4", velocity: 80, time: 5, duration: 1 });
|
|
233
196
|
```
|
|
234
197
|
|
|
235
|
-
|
|
198
|
+
`velocity` (0–127) represents how hard the key is pressed: louder at higher values, and on some instruments it also changes timbre.
|
|
236
199
|
|
|
237
200
|
The `start` function returns a `stop` function for the given note:
|
|
238
201
|
|
|
@@ -259,9 +222,9 @@ piano.stop(60); // stop the note(s) started with `note: 60`
|
|
|
259
222
|
|
|
260
223
|
#### Schedule notes
|
|
261
224
|
|
|
262
|
-
|
|
225
|
+
Schedule notes via the `time` and `duration` properties (both in seconds). `time` is measured against `audioContext.currentTime`.
|
|
263
226
|
|
|
264
|
-
|
|
227
|
+
This plays a C major arpeggio, one note per second:
|
|
265
228
|
|
|
266
229
|
```js
|
|
267
230
|
const now = context.currentTime;
|
|
@@ -289,7 +252,9 @@ sampler.start({
|
|
|
289
252
|
|
|
290
253
|
If `loop` is true but `loopStart` or `loopEnd` are not specified, 0 and total duration will be used by default, respectively.
|
|
291
254
|
|
|
292
|
-
|
|
255
|
+
### Output
|
|
256
|
+
|
|
257
|
+
#### Volume
|
|
293
258
|
|
|
294
259
|
Instrument `output` attribute represents the main output of the instrument. The `output.volume` getter/setter accepts a number where 0 means no volume, and 127 is max volume without amplification:
|
|
295
260
|
|
|
@@ -298,8 +263,6 @@ piano.output.volume = 80;
|
|
|
298
263
|
piano.output.volume; // => 80
|
|
299
264
|
```
|
|
300
265
|
|
|
301
|
-
`output.setVolume(n)` is kept as a deprecated alias and continues to work.
|
|
302
|
-
|
|
303
266
|
⚠️ `volume` is global to the instrument, but `velocity` is specific for each note.
|
|
304
267
|
|
|
305
268
|
#### Pan, detune, and reverse
|
|
@@ -330,24 +293,30 @@ piano.setCC(64, 0); // sustain pedal off
|
|
|
330
293
|
|
|
331
294
|
Unset CCs default to `0` (matches MIDI's "undefined controller defaults to 0" convention).
|
|
332
295
|
|
|
333
|
-
|
|
296
|
+
### Effects
|
|
334
297
|
|
|
335
|
-
|
|
298
|
+
#### Reverb
|
|
299
|
+
|
|
300
|
+
A packaged version of the [DattorroReverbNode](https://github.com/khoin/DattorroReverbNode) algorithmic reverb is included.
|
|
301
|
+
|
|
302
|
+
Use `output.addEffect(name, effect, mix)` to connect an effect using a send bus:
|
|
336
303
|
|
|
337
304
|
```js
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
305
|
+
import { Reverb, SplendidGrandPiano } from "smplr";
|
|
306
|
+
const reverb = Reverb(context);
|
|
307
|
+
const piano = SplendidGrandPiano(context, { volume });
|
|
308
|
+
piano.output.addEffect("reverb", reverb, 0.2);
|
|
342
309
|
```
|
|
343
310
|
|
|
344
|
-
|
|
311
|
+
To change the mix level, use `output.setEffectMix(name, mix)`:
|
|
345
312
|
|
|
346
|
-
|
|
313
|
+
```js
|
|
314
|
+
piano.output.setEffectMix("reverb", 0.5);
|
|
315
|
+
```
|
|
347
316
|
|
|
348
|
-
|
|
317
|
+
### Events
|
|
349
318
|
|
|
350
|
-
|
|
319
|
+
Two events are available: `onStart` and `onEnded`. Both callbacks receive the started note as a parameter, and can be configured globally:
|
|
351
320
|
|
|
352
321
|
```js
|
|
353
322
|
const context = new AudioContext();
|
|
@@ -374,30 +343,18 @@ Global callbacks will be invoked regardless of whether local events are defined.
|
|
|
374
343
|
|
|
375
344
|
⚠️ The invocation time of `onStart` is not exact: it fires slightly before the audio actually starts, by up to the scheduler's lookahead window (200ms by default; configurable via the `scheduler` option — see [Shared configuration options](#shared-configuration-options)).
|
|
376
345
|
|
|
377
|
-
###
|
|
378
|
-
|
|
379
|
-
#### Reverb
|
|
380
|
-
|
|
381
|
-
A packaged version of the [DattorroReverbNode](https://github.com/khoin/DattorroReverbNode) algorithmic reverb is included.
|
|
346
|
+
### Dispose
|
|
382
347
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
```js
|
|
386
|
-
import { Reverb, SplendidGrandPiano } from "smplr";
|
|
387
|
-
const reverb = Reverb(context);
|
|
388
|
-
const piano = SplendidGrandPiano(context, { volume });
|
|
389
|
-
piano.output.addEffect("reverb", reverb, 0.2);
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
To change the mix level, use `output.setEffectMix(name, mix)`:
|
|
348
|
+
When you're done with an instrument, call `dispose()` to stop all voices, tear down the audio graph, and stop the scheduler. The instance must not be used after this call.
|
|
393
349
|
|
|
394
350
|
```js
|
|
395
|
-
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
const piano = SplendidGrandPiano(context);
|
|
353
|
+
return () => piano.dispose();
|
|
354
|
+
}, []);
|
|
396
355
|
```
|
|
397
356
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
### Cache requests
|
|
357
|
+
### Caching samples
|
|
401
358
|
|
|
402
359
|
The default sample sets are hosted on GitHub Pages, which rate-limits requests per second. That can be a problem, especially in a development environment with hot reload (most React frameworks).
|
|
403
360
|
|
|
@@ -414,9 +371,47 @@ const piano = SplendidGrandPiano(context, { storage });
|
|
|
414
371
|
|
|
415
372
|
⚠️ `CacheStorage` is based on the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) and only works in secure environments that run over `https`. Check your framework's documentation for local-HTTPS setup — for example [next-dev-https](https://www.npmjs.com/package/next-dev-https) for Next.js or [vite-plugin-mkcert](https://github.com/liuweiGL/vite-plugin-mkcert) for Vite.
|
|
416
373
|
|
|
374
|
+
### Using with standardized-audio-context
|
|
375
|
+
|
|
376
|
+
This package should be compatible with [standardized-audio-context](https://github.com/chrisguttandin/standardized-audio-context):
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
import { AudioContext } from "standardized-audio-context";
|
|
380
|
+
|
|
381
|
+
const context = new AudioContext();
|
|
382
|
+
const piano = SplendidGrandPiano(context);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
However, if you are using Typescript, you might need to "force cast" the types:
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { Soundfont } from "smplr";
|
|
389
|
+
import { AudioContext as StandardizedAudioContext } from "standardized-audio-context";
|
|
390
|
+
|
|
391
|
+
const context = new StandardizedAudioContext() as unknown as AudioContext;
|
|
392
|
+
const marimba = Soundfont(context, { instrument: "marimba" });
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
If you use `Reverb` (or anything else that needs `AudioWorkletNode`), force the `standardized-audio-context` version:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import {
|
|
399
|
+
AudioWorkletNode,
|
|
400
|
+
IAudioContext,
|
|
401
|
+
AudioContext as StandardizedAudioContext,
|
|
402
|
+
} from "standardized-audio-context";
|
|
403
|
+
|
|
404
|
+
window.AudioWorkletNode = AudioWorkletNode as any;
|
|
405
|
+
const context = new StandardizedAudioContext() as unknown as AudioContext;
|
|
406
|
+
|
|
407
|
+
// ... rest of the code
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
See [standardized-audio-context issue #897](https://github.com/chrisguttandin/standardized-audio-context/issues/897) for background on why the cast is required.
|
|
411
|
+
|
|
417
412
|
## Sequencer
|
|
418
413
|
|
|
419
|
-
`Sequencer` schedules notes from one or more tracks against any smplr instrument with sample-accurate timing.
|
|
414
|
+
`Sequencer` schedules notes from one or more tracks against any smplr instrument with sample-accurate timing.
|
|
420
415
|
|
|
421
416
|
```js
|
|
422
417
|
import { Sequencer, SplendidGrandPiano, DrumMachine } from "smplr";
|
|
@@ -489,13 +484,13 @@ seq.clearTracks(); // remove every track
|
|
|
489
484
|
|
|
490
485
|
`addTrack`'s third argument accepts:
|
|
491
486
|
|
|
492
|
-
| Field | Type
|
|
493
|
-
| ---------- |
|
|
494
|
-
| `id` | `string`
|
|
495
|
-
| `humanize` | `{ timingMs?: number; velocity?: number }`
|
|
496
|
-
| `volume` | `number`
|
|
497
|
-
| `muted` | `boolean`
|
|
498
|
-
| `solo` | `boolean`
|
|
487
|
+
| Field | Type | Description |
|
|
488
|
+
| ---------- | ------------------------------------------ | -------------------------------------------------------------------- |
|
|
489
|
+
| `id` | `string` | Stable id for `setTrackVolume` / `muteTrack` / `soloTrack`. |
|
|
490
|
+
| `humanize` | `{ timingMs?: number; velocity?: number }` | Per-track humanize. Overrides the sequencer-level setting when set. |
|
|
491
|
+
| `volume` | `number` | Multiplicative velocity scalar (default 1). `0.5` halves velocities. |
|
|
492
|
+
| `muted` | `boolean` | When true, this track does not dispatch notes. |
|
|
493
|
+
| `solo` | `boolean` | When true, only soloed tracks play. |
|
|
499
494
|
|
|
500
495
|
After `setPatterns` is called (see [Pattern chain](#pattern-chain-song-mode)), `addTrack` / `removeTrack` / `clearTracks` throw — the chain is owned by the patterns array.
|
|
501
496
|
|
|
@@ -664,22 +659,28 @@ seq.addTrack(piano, notes, { humanize: { timingMs: 0, velocity: 0 } });
|
|
|
664
659
|
|
|
665
660
|
#### SequencerNote fields
|
|
666
661
|
|
|
667
|
-
| Field | Type | Description
|
|
668
|
-
| ---------------------- | ------------------- |
|
|
669
|
-
| `note` | `string \| number` | Note name or MIDI number.
|
|
670
|
-
| `at` | `string \| number` | Musical position (ticks or `"bar:beat[.frac][:ticks]"` / `"4n"` / `"1m"`).
|
|
671
|
-
| `duration` | `string \| number?` | Duration; omit for a one-shot trigger.
|
|
672
|
-
| `velocity` | `number?` | Velocity 0–127. Default 100.
|
|
673
|
-
| `id` | `string \| number?` | Used as `noteId` in `noteOn` / `noteOff` events. Default: array index.
|
|
662
|
+
| Field | Type | Description |
|
|
663
|
+
| ---------------------- | ------------------- | ----------------------------------------------------------------------------- |
|
|
664
|
+
| `note` | `string \| number` | Note name or MIDI number. |
|
|
665
|
+
| `at` | `string \| number` | Musical position (ticks or `"bar:beat[.frac][:ticks]"` / `"4n"` / `"1m"`). |
|
|
666
|
+
| `duration` | `string \| number?` | Duration; omit for a one-shot trigger. |
|
|
667
|
+
| `velocity` | `number?` | Velocity 0–127. Default 100. |
|
|
668
|
+
| `id` | `string \| number?` | Used as `noteId` in `noteOn` / `noteOff` events. Default: array index. |
|
|
674
669
|
| `chance` | `number?` | Probability 0–100 that this note fires on each pass. Re-rolled on every loop. |
|
|
675
|
-
| `ratchet` | `number?` | Expand into N sub-notes over `duration` (requires `duration`).
|
|
676
|
-
| `ratchetVelocityDecay` | `number?` | Per-step velocity decay; each sub-note scaled by `(1 - decay)^i`.
|
|
670
|
+
| `ratchet` | `number?` | Expand into N sub-notes over `duration` (requires `duration`). |
|
|
671
|
+
| `ratchetVelocityDecay` | `number?` | Per-step velocity decay; each sub-note scaled by `(1 - decay)^i`. |
|
|
677
672
|
|
|
678
673
|
Example:
|
|
679
674
|
|
|
680
675
|
```js
|
|
681
676
|
seq.addTrack(drums, [
|
|
682
|
-
{
|
|
677
|
+
{
|
|
678
|
+
note: "hat",
|
|
679
|
+
at: "1:4",
|
|
680
|
+
duration: "8n",
|
|
681
|
+
ratchet: 4,
|
|
682
|
+
ratchetVelocityDecay: 0.2,
|
|
683
|
+
},
|
|
683
684
|
{ note: "snare", at: "1:2", chance: 50 }, // fires 50% of the time
|
|
684
685
|
]);
|
|
685
686
|
```
|
|
@@ -698,7 +699,7 @@ seq.setPatterns([
|
|
|
698
699
|
]);
|
|
699
700
|
|
|
700
701
|
seq.chainOrder = [0, 1, 2, 1, 2]; // intro, verse, chorus, verse, chorus
|
|
701
|
-
seq.loop = true;
|
|
702
|
+
seq.loop = true; // loop the whole chain
|
|
702
703
|
seq.start();
|
|
703
704
|
|
|
704
705
|
seq.on("patternChange", (idx) => ui.highlightPattern(idx));
|
|
@@ -712,7 +713,7 @@ seq.on("patternChange", (idx) => ui.highlightPattern(idx));
|
|
|
712
713
|
|
|
713
714
|
---
|
|
714
715
|
|
|
715
|
-
##
|
|
716
|
+
## Offline rendering
|
|
716
717
|
|
|
717
718
|
Render audio offline (faster than real-time) and export it as a WAV file. Uses `OfflineAudioContext` under the hood.
|
|
718
719
|
|
|
@@ -791,27 +792,13 @@ This will download a WAV file you can attach to your issue or pull request.
|
|
|
791
792
|
|
|
792
793
|
---
|
|
793
794
|
|
|
794
|
-
##
|
|
795
|
-
|
|
796
|
-
### Available instruments
|
|
795
|
+
## Instrument reference
|
|
797
796
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
| Factory | Names helper |
|
|
801
|
-
| --------------- | ---------------------------------------------- |
|
|
802
|
-
| `Soundfont` | `getSoundfontNames(): string[]` |
|
|
803
|
-
| `ElectricPiano` | `getElectricPianoNames(): string[]` |
|
|
804
|
-
| `Mallet` | `getMalletNames(): string[]` |
|
|
805
|
-
| `Mellotron` | `getMellotronNames(): string[]` |
|
|
806
|
-
| `DrumMachine` | `getDrumMachineNames(): string[]` |
|
|
807
|
-
| `Smolken` | `getSmolkenNames(): string[]` |
|
|
808
|
-
| `Versilian` | `getVersilianInstruments(): Promise<string[]>` |
|
|
809
|
-
|
|
810
|
-
`getVersilianInstruments` is async because the catalog is fetched from the network on first call (cached thereafter).
|
|
797
|
+
Detailed configuration for each bundled instrument. For the shared API (load, play, output, effects, events), see [Using an instrument](#using-an-instrument).
|
|
811
798
|
|
|
812
799
|
### Sampler
|
|
813
800
|
|
|
814
|
-
An audio buffer sampler. Pass a `buffers`
|
|
801
|
+
An audio buffer sampler. Pass a `buffers` map of name → URL:
|
|
815
802
|
|
|
816
803
|
#### Buffers mode
|
|
817
804
|
|
|
@@ -831,7 +818,7 @@ And then use the name of the buffer as note name:
|
|
|
831
818
|
sampler.start({ note: "kick" });
|
|
832
819
|
```
|
|
833
820
|
|
|
834
|
-
####
|
|
821
|
+
#### Preset mode
|
|
835
822
|
|
|
836
823
|
For advanced use cases (per-region pitch/velocity/round-robin, SFZ-like multi-sample instruments, runtime swaps), pass a `SmplrPreset` directly:
|
|
837
824
|
|
|
@@ -858,7 +845,7 @@ sampler.start({ note: 60 });
|
|
|
858
845
|
await sampler.reload(kitB);
|
|
859
846
|
```
|
|
860
847
|
|
|
861
|
-
The full `SmplrPreset` schema is documented in [
|
|
848
|
+
The full `SmplrPreset` schema is documented in [PRESET_SCHEMA.md](./PRESET_SCHEMA.md). Note: `buffers` and `preset` are mutually exclusive on construction — pass exactly one.
|
|
862
849
|
|
|
863
850
|
`sampler.reload(input)` accepts either shape (flat buffers record or full `SmplrPreset`), regardless of which mode was used at construction.
|
|
864
851
|
|
|
@@ -1025,6 +1012,61 @@ drums.start("kick"); // Play the first sample of the group
|
|
|
1025
1012
|
drums.start("kick-1"); // Play this specific sample
|
|
1026
1013
|
```
|
|
1027
1014
|
|
|
1015
|
+
### DrumAbuse
|
|
1016
|
+
|
|
1017
|
+
Sampled instrument for the [Synthabuse](https://www.youtube.com/watch?v=Ay-U9eYKmGA) drum-machine collection — 5 packs covering ~210 classic drum machines and synths. Samples hosted at `smpldsnds.github.io/drum-abuse-{pack}/`.
|
|
1018
|
+
|
|
1019
|
+
Two source modes: load a single machine's full kit, or load a cross-machine instrument list from a pack.
|
|
1020
|
+
|
|
1021
|
+
#### Machine mode
|
|
1022
|
+
|
|
1023
|
+
```js
|
|
1024
|
+
import { DrumAbuse, getDrumAbuseMachineNames } from "smplr";
|
|
1025
|
+
|
|
1026
|
+
const machines = getDrumAbuseMachineNames(); // ~210 machine ids
|
|
1027
|
+
|
|
1028
|
+
const context = new AudioContext();
|
|
1029
|
+
const drums = DrumAbuse(context, {
|
|
1030
|
+
source: { kind: "machine", machine: "roland-tr-808" },
|
|
1031
|
+
});
|
|
1032
|
+
await drums.load;
|
|
1033
|
+
|
|
1034
|
+
drums.start({ note: "kick" });
|
|
1035
|
+
|
|
1036
|
+
// Samples are grouped by instrument name, like DrumMachine:
|
|
1037
|
+
drums.getGroupNames(); // => ["kick", "snare", "hi-hat", ...]
|
|
1038
|
+
drums.getSampleNamesForGroup("kick"); // => ["kick/1", "kick/2", ...]
|
|
1039
|
+
drums.start({ note: "kick" }); // first sample in the group
|
|
1040
|
+
drums.start({ note: "kick/1" }); // a specific sample
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
If a machine has more than one sample set, pass `set` to pick a specific one. Omit to load the first set.
|
|
1044
|
+
|
|
1045
|
+
```js
|
|
1046
|
+
const drums = DrumAbuse(context, {
|
|
1047
|
+
source: { kind: "machine", machine: "roland-tr-808", set: "kit-a" },
|
|
1048
|
+
});
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
#### Pack mode
|
|
1052
|
+
|
|
1053
|
+
A pack is a cross-machine catalog of named instruments (e.g. all the kicks across `vol1`). Pass `source: { kind: "pack", pack, instrument }`:
|
|
1054
|
+
|
|
1055
|
+
```js
|
|
1056
|
+
import {
|
|
1057
|
+
DrumAbuse,
|
|
1058
|
+
getDrumAbusePackNames,
|
|
1059
|
+
getDrumAbuseMachinesForPack,
|
|
1060
|
+
} from "smplr";
|
|
1061
|
+
|
|
1062
|
+
getDrumAbusePackNames(); // => ["vol1", "vol2", "vol3", "vol4", "vol5"]
|
|
1063
|
+
getDrumAbuseMachinesForPack("vol1"); // => machine ids in vol1
|
|
1064
|
+
|
|
1065
|
+
const drums = DrumAbuse(new AudioContext(), {
|
|
1066
|
+
source: { kind: "pack", pack: "vol1", instrument: "bass-drum" },
|
|
1067
|
+
});
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1028
1070
|
### Smolken double bass
|
|
1029
1071
|
|
|
1030
1072
|
```js
|
|
@@ -1039,7 +1081,7 @@ const doubleBass = await Smolken(context, { instrument: "Arco" }).load;
|
|
|
1039
1081
|
|
|
1040
1082
|
### Versilian
|
|
1041
1083
|
|
|
1042
|
-
|
|
1084
|
+
Plays instruments from the [Versilian Community Sample Library](https://github.com/sgossner/VCSL).
|
|
1043
1085
|
|
|
1044
1086
|
⚠️ Not all features are implemented. Some instruments may sound incorrect ⚠️
|
|
1045
1087
|
|
|
@@ -1055,7 +1097,7 @@ const versilian = Versilian(context, { instrument: instrumentNames[0] });
|
|
|
1055
1097
|
|
|
1056
1098
|
### Soundfont2
|
|
1057
1099
|
|
|
1058
|
-
Sampler capable of reading .sf2 files directly.
|
|
1100
|
+
Sampler capable of reading .sf2 files directly.
|
|
1059
1101
|
|
|
1060
1102
|
```ts
|
|
1061
1103
|
import { Soundfont2 } from "smplr";
|
|
@@ -1076,7 +1118,15 @@ sampler.load.then(() => {
|
|
|
1076
1118
|
});
|
|
1077
1119
|
```
|
|
1078
1120
|
|
|
1079
|
-
|
|
1121
|
+
## Defining your own instrument
|
|
1122
|
+
|
|
1123
|
+
If none of the bundled instruments fits your use case, you can author your own with the `Instrument` builder and the `Smplr` interface.
|
|
1124
|
+
|
|
1125
|
+
See **[Defining an instrument](./AUTHORING.md)** for the full authoring guide — sync and async examples, third-party package layout, and how to use `Smplr` as a TypeScript type for generic helpers.
|
|
1126
|
+
|
|
1127
|
+
## Upgrading
|
|
1128
|
+
|
|
1129
|
+
`smplr` is approaching 1.0; pre-1.0 APIs keep working as deprecated aliases. See [MIGRATE.md](./MIGRATE.md) for the full compatibility table and [CHANGELOG](https://github.com/danigb/smplr/blob/main/CHANGELOG.md) for per-release detail.
|
|
1080
1130
|
|
|
1081
1131
|
## License
|
|
1082
1132
|
|