smplr 0.20.0 → 0.22.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 +168 -80
- package/dist/index.d.mts +205 -179
- package/dist/index.d.ts +205 -179
- package/dist/index.js +373 -503
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +371 -501
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -6,37 +6,60 @@
|
|
|
6
6
|
|
|
7
7
|
Examples:
|
|
8
8
|
|
|
9
|
+
**Play a note from a General MIDI soundfont:**
|
|
10
|
+
|
|
9
11
|
```js
|
|
10
12
|
import { Soundfont } from "smplr";
|
|
11
13
|
|
|
12
14
|
const context = new AudioContext();
|
|
13
|
-
const marimba =
|
|
15
|
+
const marimba = Soundfont(context, { instrument: "marimba" });
|
|
14
16
|
marimba.start({ note: 60, velocity: 80 });
|
|
15
17
|
```
|
|
16
18
|
|
|
19
|
+
**Sequence a beat with a drum machine and a piano on the same clock:**
|
|
20
|
+
|
|
17
21
|
```js
|
|
18
|
-
import { DrumMachine } from "smplr";
|
|
22
|
+
import { Sequencer, SplendidGrandPiano, DrumMachine } from "smplr";
|
|
19
23
|
|
|
20
24
|
const context = new AudioContext();
|
|
21
|
-
const
|
|
22
|
-
|
|
25
|
+
const piano = SplendidGrandPiano(context);
|
|
26
|
+
const drums = DrumMachine(context, { instrument: "TR-808" });
|
|
27
|
+
|
|
28
|
+
const seq = new Sequencer(context, { bpm: 110, loop: true });
|
|
29
|
+
seq.addTrack(piano, [
|
|
30
|
+
{ note: "C4", at: "1:1", duration: "4n" },
|
|
31
|
+
{ note: "E4", at: "1:2", duration: "4n" },
|
|
32
|
+
{ note: "G4", at: "1:3", duration: "4n" },
|
|
33
|
+
]);
|
|
34
|
+
seq.addTrack(drums, [
|
|
35
|
+
{ note: "kick", at: "1:1" },
|
|
36
|
+
{ note: "snare", at: "1:2" },
|
|
37
|
+
{ note: "kick", at: "1:3" },
|
|
38
|
+
{ note: "snare", at: "1:4" },
|
|
39
|
+
]);
|
|
40
|
+
seq.start();
|
|
23
41
|
```
|
|
24
42
|
|
|
25
|
-
|
|
26
|
-
import { SplendidGrandPiano, Reverb } from "smplr";
|
|
43
|
+
**Render an arpeggio with reverb to a WAV file — offline, no speakers needed:**
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
piano.output.addEffect("reverb", new Reverb(context), 0.2);
|
|
45
|
+
```js
|
|
46
|
+
import { SplendidGrandPiano, Reverb, renderOffline } from "smplr";
|
|
31
47
|
|
|
32
|
-
|
|
48
|
+
const wav = await renderOffline(async (context) => {
|
|
49
|
+
const piano = await SplendidGrandPiano(context).load;
|
|
50
|
+
piano.output.addEffect("reverb", new Reverb(context), 0.3);
|
|
51
|
+
["C4", "E4", "G4", "C5"].forEach((note, i) => {
|
|
52
|
+
piano.start({ note, time: i * 0.4, duration: 0.4 });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
wav.downloadWav("arpeggio.wav");
|
|
33
56
|
```
|
|
34
57
|
|
|
35
58
|
See demo: https://danigb.github.io/smplr/
|
|
36
59
|
|
|
37
|
-
`smplr` is
|
|
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)).
|
|
38
61
|
|
|
39
|
-
|
|
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`.
|
|
40
63
|
|
|
41
64
|
#### Library goals
|
|
42
65
|
|
|
@@ -50,7 +73,7 @@ You can install the library with a package manager or use it directly by importi
|
|
|
50
73
|
|
|
51
74
|
Samples are stored at https://github.com/smpldsnds and there is no need to download them. Kudos to all _samplerist_ 🙌
|
|
52
75
|
|
|
53
|
-
#### Using a package
|
|
76
|
+
#### Using a package manager
|
|
54
77
|
|
|
55
78
|
Use npm or your favourite package manager to install the library to use it in your project:
|
|
56
79
|
|
|
@@ -70,32 +93,42 @@ You can import directly from the browser. For example:
|
|
|
70
93
|
<script type="module">
|
|
71
94
|
import { SplendidGrandPiano } from "https://unpkg.com/smplr/dist/index.mjs"; // needs to be a url
|
|
72
95
|
const context = new AudioContext(); // create the audio context
|
|
73
|
-
const
|
|
96
|
+
const piano = SplendidGrandPiano(context); // create and load the instrument
|
|
74
97
|
|
|
75
98
|
document.getElementById("btn").onclick = () => {
|
|
76
99
|
context.resume(); // enable audio context after a user interaction
|
|
77
|
-
|
|
100
|
+
piano.start({ note: 60, velocity: 80 }); // play the note
|
|
78
101
|
};
|
|
79
102
|
</script>
|
|
80
103
|
</html>
|
|
81
104
|
```
|
|
82
105
|
|
|
83
|
-
The package needs to be
|
|
106
|
+
The package needs to be served as a URL from a service like [unpkg](https://unpkg.com) or similar.
|
|
107
|
+
|
|
108
|
+
> To author your own instrument or publish a third-party package, see the [Defining an instrument](./AUTHORING.md) guide.
|
|
84
109
|
|
|
85
110
|
## Documentation
|
|
86
111
|
|
|
112
|
+
### Defining an instrument
|
|
113
|
+
|
|
114
|
+
`smplr` ships ten instruments out of the box — `SplendidGrandPiano`, `Soundfont`, `DrumMachine`, `ElectricPiano`, `Mallet`, `Mellotron`, `Smolken`, `Versilian`, `Sampler`, `Soundfont2Sampler`. If none of them fit your use case, you can author your own with the `Instrument` builder and the `Smplr` interface.
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
87
118
|
### Create and load an instrument
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
Every smplr instrument is a factory function: call it with an `AudioContext` and an options object to get back an instance.
|
|
90
121
|
|
|
91
122
|
```js
|
|
92
123
|
import { SplendidGrandPiano, Soundfont } from "smplr";
|
|
93
124
|
|
|
94
125
|
const context = new AudioContext();
|
|
95
|
-
const piano =
|
|
96
|
-
const marimba =
|
|
126
|
+
const piano = SplendidGrandPiano(context, { decayTime: 0.5 });
|
|
127
|
+
const marimba = Soundfont(context, { instrument: "marimba" });
|
|
97
128
|
```
|
|
98
129
|
|
|
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
|
+
|
|
99
132
|
#### Wait for audio loading
|
|
100
133
|
|
|
101
134
|
You can start playing notes as soon as one audio is loaded. But if you want to wait for all of them, you can use the `load` property that returns a promise:
|
|
@@ -109,9 +142,13 @@ piano.load.then(() => {
|
|
|
109
142
|
Since the promise returns the instrument instance, you can create and wait in a single line:
|
|
110
143
|
|
|
111
144
|
```js
|
|
112
|
-
const piano = await
|
|
145
|
+
const piano = await SplendidGrandPiano(context).load;
|
|
113
146
|
```
|
|
114
147
|
|
|
148
|
+
The pre-1.0 `new`-prefixed form continues to work — `const piano = await new SplendidGrandPiano(context).load` resolves to the same instrument. This is the documented backward-compat path for code from earlier `smplr` versions.
|
|
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
|
+
|
|
115
152
|
⚠️ In versions lower than 0.8.0 a `loaded()` function was exposed instead.
|
|
116
153
|
|
|
117
154
|
#### Load progress
|
|
@@ -119,7 +156,7 @@ const piano = await new SplendidGrandPiano(context).load;
|
|
|
119
156
|
Track how many samples have loaded via the `onLoadProgress` option or the `loadProgress` getter:
|
|
120
157
|
|
|
121
158
|
```js
|
|
122
|
-
const piano =
|
|
159
|
+
const piano = SplendidGrandPiano(context, {
|
|
123
160
|
onLoadProgress: ({ loaded, total }) => {
|
|
124
161
|
console.log(`${loaded} / ${total} samples loaded`);
|
|
125
162
|
},
|
|
@@ -133,17 +170,19 @@ console.log(piano.loadProgress); // { loaded: 12, total: 48 }
|
|
|
133
170
|
|
|
134
171
|
#### Shared configuration options
|
|
135
172
|
|
|
136
|
-
All instruments share some configuration options
|
|
173
|
+
All instruments share some configuration options, passed as the second argument to the factory. Every field is optional:
|
|
137
174
|
|
|
138
|
-
- `volume`:
|
|
139
|
-
- `
|
|
140
|
-
- `
|
|
141
|
-
- `
|
|
142
|
-
- `
|
|
143
|
-
- `
|
|
175
|
+
- `volume`: a number from 0 to 127 representing the instrument's global volume. 100 by default.
|
|
176
|
+
- `velocity`: default note velocity (0–127) when not specified per note. 100 by default.
|
|
177
|
+
- `pan`: stereo pan, -1 (full left) to +1 (full right). 0 by default.
|
|
178
|
+
- `destination`: the `AudioNode` the instrument writes to. `AudioContext.destination` by default.
|
|
179
|
+
- `volumeToGain`: a function to map MIDI volume to a linear gain. Uses the MIDI standard curve by default.
|
|
180
|
+
- `storage`: a [storage backend](#cache-requests) used to fetch sample buffers. `HttpStorage` by default.
|
|
181
|
+
- `loader`: a shared `SampleLoader` instance. Pass the same loader to multiple instruments to cache buffers across them (see [Buffer reuse](#buffer-reuse)).
|
|
182
|
+
- `scheduler`: a shared `Scheduler` instance. Construct your own to tune scheduling — for example, `new Scheduler(context, { lookaheadMs: 100, intervalMs: 25 })` — or omit to get a per-instrument default.
|
|
144
183
|
- `onLoadProgress`: a function called after each sample buffer is decoded. Receives `{ loaded, total }` where `total` is the full count known before loading starts.
|
|
145
|
-
- `onStart`:
|
|
146
|
-
- `onEnded`:
|
|
184
|
+
- `onStart`: called when a note is dispatched to the audio engine. Receives the started note. See ⚠️ note under [Events](#events) on timing precision.
|
|
185
|
+
- `onEnded`: called when each voice's audio node ends. Receives the started note.
|
|
147
186
|
|
|
148
187
|
#### Usage with standardized-audio-context
|
|
149
188
|
|
|
@@ -153,7 +192,7 @@ This package should be compatible with [standardized-audio-context](https://gith
|
|
|
153
192
|
import { AudioContext } from "standardized-audio-context";
|
|
154
193
|
|
|
155
194
|
const context = new AudioContext();
|
|
156
|
-
const piano =
|
|
195
|
+
const piano = SplendidGrandPiano(context);
|
|
157
196
|
```
|
|
158
197
|
|
|
159
198
|
However, if you are using Typescript, you might need to "force cast" the types:
|
|
@@ -163,7 +202,7 @@ import { Soundfont } from "smplr";
|
|
|
163
202
|
import { AudioContext as StandardizedAudioContext } from "standardized-audio-context";
|
|
164
203
|
|
|
165
204
|
const context = new StandardizedAudioContext() as unknown as AudioContext;
|
|
166
|
-
const marimba =
|
|
205
|
+
const marimba = Soundfont(context, { instrument: "marimba" });
|
|
167
206
|
```
|
|
168
207
|
|
|
169
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:
|
|
@@ -176,12 +215,12 @@ import {
|
|
|
176
215
|
} from "standardized-audio-context";
|
|
177
216
|
|
|
178
217
|
window.AudioWorkletNode = AudioWorkletNode as any;
|
|
179
|
-
const context = new StandardizedAudioContext() as unknown AudioContext;
|
|
218
|
+
const context = new StandardizedAudioContext() as unknown as AudioContext;
|
|
180
219
|
|
|
181
220
|
// ... rest of the code
|
|
182
221
|
```
|
|
183
222
|
|
|
184
|
-
|
|
223
|
+
See [standardized-audio-context issue #897](https://github.com/chrisguttandin/standardized-audio-context/issues/897) for background on why the cast is required.
|
|
185
224
|
|
|
186
225
|
### Play
|
|
187
226
|
|
|
@@ -211,11 +250,11 @@ Instruments have a global `stop` function that can be used to stop all notes:
|
|
|
211
250
|
piano.stop();
|
|
212
251
|
```
|
|
213
252
|
|
|
214
|
-
Or stop the specified one
|
|
253
|
+
Or stop the specified one. The argument is a `stopId` — by default the same value you passed as `note`, but you can override it via `start({ note, stopId })`:
|
|
215
254
|
|
|
216
255
|
```js
|
|
217
|
-
//
|
|
218
|
-
piano.stop(60);
|
|
256
|
+
piano.stop("C4"); // stop the note(s) started with `note: "C4"`
|
|
257
|
+
piano.stop(60); // stop the note(s) started with `note: 60`
|
|
219
258
|
```
|
|
220
259
|
|
|
221
260
|
#### Schedule notes
|
|
@@ -236,10 +275,13 @@ const now = context.currentTime;
|
|
|
236
275
|
You can loop a note by using `loop`, `loopStart` and `loopEnd`:
|
|
237
276
|
|
|
238
277
|
```js
|
|
239
|
-
const
|
|
278
|
+
const context = new AudioContext();
|
|
279
|
+
const sampler = Sampler(context, {
|
|
280
|
+
buffers: { duh: "https://example.com/duh-duh-ah.mp3" },
|
|
281
|
+
});
|
|
240
282
|
sampler.start({
|
|
241
|
-
note: "duh"
|
|
242
|
-
loop: true
|
|
283
|
+
note: "duh",
|
|
284
|
+
loop: true,
|
|
243
285
|
loopStart: 1.0,
|
|
244
286
|
loopEnd: 9.0,
|
|
245
287
|
});
|
|
@@ -249,14 +291,42 @@ If `loop` is true but `loopStart` or `loopEnd` are not specified, 0 and total du
|
|
|
249
291
|
|
|
250
292
|
#### Change volume
|
|
251
293
|
|
|
252
|
-
Instrument `output` attribute represents the main output of the instrument. `output.
|
|
294
|
+
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:
|
|
253
295
|
|
|
254
296
|
```js
|
|
255
|
-
piano.output.
|
|
297
|
+
piano.output.volume = 80;
|
|
298
|
+
piano.output.volume; // => 80
|
|
256
299
|
```
|
|
257
300
|
|
|
301
|
+
`output.setVolume(n)` is kept as a deprecated alias and continues to work.
|
|
302
|
+
|
|
258
303
|
⚠️ `volume` is global to the instrument, but `velocity` is specific for each note.
|
|
259
304
|
|
|
305
|
+
#### MIDI CC
|
|
306
|
+
|
|
307
|
+
Set and read MIDI Control Change values on the instrument:
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
piano.setCC(64, 127); // sustain pedal on
|
|
311
|
+
piano.getCC(64); // => 127
|
|
312
|
+
piano.setCC(64, 0); // sustain pedal off
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Unset CCs default to `0` (matches MIDI's "undefined controller defaults to 0" convention).
|
|
316
|
+
|
|
317
|
+
#### Disposing
|
|
318
|
+
|
|
319
|
+
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.
|
|
320
|
+
|
|
321
|
+
```js
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
const piano = SplendidGrandPiano(context);
|
|
324
|
+
return () => piano.dispose();
|
|
325
|
+
}, []);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`disconnect()` is kept as a deprecated alias and continues to work.
|
|
329
|
+
|
|
260
330
|
#### Events
|
|
261
331
|
|
|
262
332
|
Two events are supported `onStart` and `onEnded`. Both callbacks will receive as parameter started note.
|
|
@@ -265,7 +335,7 @@ Events can be configured globally:
|
|
|
265
335
|
|
|
266
336
|
```js
|
|
267
337
|
const context = new AudioContext();
|
|
268
|
-
const
|
|
338
|
+
const piano = SplendidGrandPiano(context, {
|
|
269
339
|
onStart: (note) => {
|
|
270
340
|
console.log(note.time, context.currentTime);
|
|
271
341
|
},
|
|
@@ -286,36 +356,36 @@ piano.start({
|
|
|
286
356
|
|
|
287
357
|
Global callbacks will be invoked regardless of whether local events are defined.
|
|
288
358
|
|
|
289
|
-
⚠️ The invocation time of `onStart` is not exact
|
|
359
|
+
⚠️ 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)).
|
|
290
360
|
|
|
291
361
|
### Effects
|
|
292
362
|
|
|
293
363
|
#### Reverb
|
|
294
364
|
|
|
295
|
-
|
|
365
|
+
A packaged version of the [DattorroReverbNode](https://github.com/khoin/DattorroReverbNode) algorithmic reverb is included.
|
|
296
366
|
|
|
297
367
|
Use `output.addEffect(name, effect, mix)` to connect an effect using a send bus:
|
|
298
368
|
|
|
299
369
|
```js
|
|
300
370
|
import { Reverb, SplendidGrandPiano } from "smplr";
|
|
301
371
|
const reverb = new Reverb(context);
|
|
302
|
-
const piano =
|
|
372
|
+
const piano = SplendidGrandPiano(context, { volume });
|
|
303
373
|
piano.output.addEffect("reverb", reverb, 0.2);
|
|
304
374
|
```
|
|
305
375
|
|
|
306
|
-
To change the mix level, use `output.
|
|
376
|
+
To change the mix level, use `output.setEffectMix(name, mix)`:
|
|
307
377
|
|
|
308
378
|
```js
|
|
309
|
-
piano.output.
|
|
379
|
+
piano.output.setEffectMix("reverb", 0.5);
|
|
310
380
|
```
|
|
311
381
|
|
|
312
|
-
|
|
382
|
+
`output.sendEffect(name, mix)` is kept as a deprecated alias and continues to work.
|
|
313
383
|
|
|
314
|
-
|
|
384
|
+
### Cache requests
|
|
315
385
|
|
|
316
|
-
|
|
386
|
+
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).
|
|
317
387
|
|
|
318
|
-
|
|
388
|
+
To cache samples in the browser, use a `CacheStorage` object:
|
|
319
389
|
|
|
320
390
|
```ts
|
|
321
391
|
import { SplendidGrandPiano, CacheStorage } from "smplr";
|
|
@@ -323,21 +393,21 @@ import { SplendidGrandPiano, CacheStorage } from "smplr";
|
|
|
323
393
|
const context = new AudioContext();
|
|
324
394
|
const storage = new CacheStorage();
|
|
325
395
|
// First time the instrument loads, will fetch the samples from http. Subsequent times from cache.
|
|
326
|
-
const piano =
|
|
396
|
+
const piano = SplendidGrandPiano(context, { storage });
|
|
327
397
|
```
|
|
328
398
|
|
|
329
|
-
⚠️ `CacheStorage` is based on [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) and only works in secure environments that
|
|
399
|
+
⚠️ `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.
|
|
330
400
|
|
|
331
401
|
## Sequencer
|
|
332
402
|
|
|
333
|
-
`Sequencer` schedules notes from one or more tracks against any smplr instrument with sample-accurate timing.
|
|
403
|
+
`Sequencer` schedules notes from one or more tracks against any smplr instrument with sample-accurate timing. Unlike instruments, it's a regular class — always constructed with `new Sequencer(context, opts)`.
|
|
334
404
|
|
|
335
405
|
```js
|
|
336
406
|
import { Sequencer, SplendidGrandPiano, DrumMachine } from "smplr";
|
|
337
407
|
|
|
338
408
|
const context = new AudioContext();
|
|
339
|
-
const piano =
|
|
340
|
-
const drums =
|
|
409
|
+
const piano = SplendidGrandPiano(context);
|
|
410
|
+
const drums = DrumMachine(context, { instrument: "TR-808" });
|
|
341
411
|
|
|
342
412
|
const seq = new Sequencer(context, { bpm: 120, loop: true });
|
|
343
413
|
|
|
@@ -530,7 +600,7 @@ Render audio offline (faster than real-time) and export it as a WAV file. Uses `
|
|
|
530
600
|
import { renderOffline } from "smplr";
|
|
531
601
|
|
|
532
602
|
const result = await renderOffline(async (context) => {
|
|
533
|
-
const piano = await
|
|
603
|
+
const piano = await SplendidGrandPiano(context).load;
|
|
534
604
|
piano.start({ note: "C4", time: 0, duration: 1 });
|
|
535
605
|
piano.start({ note: "E4", time: 0.5, duration: 1 });
|
|
536
606
|
});
|
|
@@ -572,12 +642,12 @@ If you already have an instrument loaded, pass the same `SampleLoader` to avoid
|
|
|
572
642
|
import { SplendidGrandPiano, SampleLoader, renderOffline } from "smplr";
|
|
573
643
|
|
|
574
644
|
const loader = new SampleLoader(audioContext);
|
|
575
|
-
const piano =
|
|
645
|
+
const piano = SplendidGrandPiano(audioContext, { loader });
|
|
576
646
|
await piano.load;
|
|
577
647
|
|
|
578
648
|
// Offline render reuses cached buffers — no re-fetch
|
|
579
649
|
const result = await renderOffline(async (context) => {
|
|
580
|
-
const offlinePiano = await
|
|
650
|
+
const offlinePiano = await SplendidGrandPiano(context, { loader }).load;
|
|
581
651
|
offlinePiano.start({ note: "C4", time: 0, duration: 1 });
|
|
582
652
|
});
|
|
583
653
|
```
|
|
@@ -587,10 +657,11 @@ const result = await renderOffline(async (context) => {
|
|
|
587
657
|
Use offline rendering to generate reproducible audio files for issue reports. No install needed — just open your browser's DevTools console on any page and paste:
|
|
588
658
|
|
|
589
659
|
```js
|
|
590
|
-
const { renderOffline, SplendidGrandPiano } =
|
|
660
|
+
const { renderOffline, SplendidGrandPiano } =
|
|
661
|
+
await import("https://esm.sh/smplr");
|
|
591
662
|
|
|
592
663
|
const result = await renderOffline(async (context) => {
|
|
593
|
-
const piano = await
|
|
664
|
+
const piano = await SplendidGrandPiano(context).load;
|
|
594
665
|
piano.start({ note: "C4", time: 0, duration: 2 });
|
|
595
666
|
});
|
|
596
667
|
result.downloadWav16("bug-report.wav");
|
|
@@ -602,6 +673,22 @@ This will download a WAV file you can attach to your issue or pull request.
|
|
|
602
673
|
|
|
603
674
|
## Instruments
|
|
604
675
|
|
|
676
|
+
### Available instruments
|
|
677
|
+
|
|
678
|
+
Each instrument family exposes a synchronous helper that returns the names you can pass to its factory:
|
|
679
|
+
|
|
680
|
+
| Factory | Names helper |
|
|
681
|
+
|---|---|
|
|
682
|
+
| `Soundfont` | `getSoundfontNames(): string[]` |
|
|
683
|
+
| `ElectricPiano` | `getElectricPianoNames(): string[]` |
|
|
684
|
+
| `Mallet` | `getMalletNames(): string[]` |
|
|
685
|
+
| `Mellotron` | `getMellotronNames(): string[]` |
|
|
686
|
+
| `DrumMachine` | `getDrumMachineNames(): string[]` |
|
|
687
|
+
| `Smolken` | `getSmolkenNames(): string[]` |
|
|
688
|
+
| `Versilian` | `getVersilianInstruments(): Promise<string[]>` |
|
|
689
|
+
|
|
690
|
+
`getVersilianInstruments` is async because the catalog is fetched from the network on first call (cached thereafter).
|
|
691
|
+
|
|
605
692
|
### Sampler
|
|
606
693
|
|
|
607
694
|
An audio buffer sampler. Pass a `buffers` object with the files to be load:
|
|
@@ -613,7 +700,7 @@ const buffers = {
|
|
|
613
700
|
kick: "https://smpldsnds.github.io/drum-machines/808-mini/kick.m4a",
|
|
614
701
|
snare: "https://smpldsnds.github.io/drum-machines/808-mini/snare-1.m4a",
|
|
615
702
|
};
|
|
616
|
-
const sampler =
|
|
703
|
+
const sampler = Sampler(new AudioContext(), { buffers });
|
|
617
704
|
```
|
|
618
705
|
|
|
619
706
|
And then use the name of the buffer as note name:
|
|
@@ -630,7 +717,7 @@ A Soundfont player. By default it loads audio from Benjamin Gleitzman's package
|
|
|
630
717
|
```js
|
|
631
718
|
import { Soundfont, getSoundfontNames, getSoundfontKits } from "smplr";
|
|
632
719
|
|
|
633
|
-
const marimba =
|
|
720
|
+
const marimba = Soundfont(new AudioContext(), { instrument: "marimba" });
|
|
634
721
|
marimba.start({ note: "C4" });
|
|
635
722
|
```
|
|
636
723
|
|
|
@@ -640,10 +727,10 @@ It's intended to be a modern replacement of [soundfont-player](https://github.co
|
|
|
640
727
|
|
|
641
728
|
Use `getSoundfontNames` to get all available instrument names and `getSoundfontKits` to get kit names.
|
|
642
729
|
|
|
643
|
-
There are two kits available: `MusyngKite` or `FluidR3_GM`. The first one is used by default: it sounds better but samples
|
|
730
|
+
There are two kits available: `MusyngKite` or `FluidR3_GM`. The first one is used by default: it sounds better but the samples are heavier.
|
|
644
731
|
|
|
645
732
|
```js
|
|
646
|
-
const marimba =
|
|
733
|
+
const marimba = Soundfont(context, {
|
|
647
734
|
instrument: "clavinet",
|
|
648
735
|
kit: "FluidR3_GM", // "MusyngKite" is used by default if not specified
|
|
649
736
|
});
|
|
@@ -652,7 +739,7 @@ const marimba = new Soundfont(context, {
|
|
|
652
739
|
Alternatively, you can pass your custom url as the instrument. In that case, the `kit` is ignored:
|
|
653
740
|
|
|
654
741
|
```js
|
|
655
|
-
const marimba =
|
|
742
|
+
const marimba = Soundfont(context, {
|
|
656
743
|
instrumentUrl:
|
|
657
744
|
"https://gleitz.github.io/midi-js-soundfonts/MusyngKite/marimba-mp3.js",
|
|
658
745
|
});
|
|
@@ -663,7 +750,7 @@ const marimba = new Soundfont(context, {
|
|
|
663
750
|
You can enable note looping to make note names indefinitely long by loading loop data:
|
|
664
751
|
|
|
665
752
|
```js
|
|
666
|
-
const marimba =
|
|
753
|
+
const marimba = Soundfont(context, {
|
|
667
754
|
instrument: "cello",
|
|
668
755
|
loadLoopData: true,
|
|
669
756
|
});
|
|
@@ -679,7 +766,7 @@ A sampled acoustic piano. It uses Steinway samples with 4 velocity groups from
|
|
|
679
766
|
```js
|
|
680
767
|
import { SplendidGrandPiano } from "smplr";
|
|
681
768
|
|
|
682
|
-
const piano =
|
|
769
|
+
const piano = SplendidGrandPiano(new AudioContext());
|
|
683
770
|
|
|
684
771
|
piano.start({ note: "C4" });
|
|
685
772
|
```
|
|
@@ -688,7 +775,7 @@ piano.start({ note: "C4" });
|
|
|
688
775
|
|
|
689
776
|
The second argument of the constructor accepts the following options:
|
|
690
777
|
|
|
691
|
-
- `baseUrl`:
|
|
778
|
+
- `baseUrl`: where the piano samples are fetched from. Defaults to the public hosted set on `smpldsnds.github.io`; override only if you mirror the samples yourself.
|
|
692
779
|
- `detune`: global detune in cents (0 if not specified)
|
|
693
780
|
- `velocity`: default velocity (100 if not specified)
|
|
694
781
|
- `volume`: default volume (100 if not specified)
|
|
@@ -698,7 +785,7 @@ The second argument of the constructor accepts the following options:
|
|
|
698
785
|
Example:
|
|
699
786
|
|
|
700
787
|
```ts
|
|
701
|
-
const piano =
|
|
788
|
+
const piano = SplendidGrandPiano(context, {
|
|
702
789
|
detune: -20,
|
|
703
790
|
volume: 80,
|
|
704
791
|
notesToLoad: {
|
|
@@ -715,9 +802,9 @@ A sampled electric pianos. Samples from https://github.com/sfzinstruments/GregSu
|
|
|
715
802
|
```js
|
|
716
803
|
import { ElectricPiano, getElectricPianoNames } from "smplr";
|
|
717
804
|
|
|
718
|
-
const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200"]
|
|
805
|
+
const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200", "TX81Z"]
|
|
719
806
|
|
|
720
|
-
const epiano =
|
|
807
|
+
const epiano = ElectricPiano(new AudioContext(), {
|
|
721
808
|
instrument: "PianetT",
|
|
722
809
|
});
|
|
723
810
|
|
|
@@ -732,6 +819,7 @@ Available instruments:
|
|
|
732
819
|
- `CP80`: Yamaha CP80 Electric Grand Piano v1.3 (29-Sep-2004)
|
|
733
820
|
- `PianetT`: Hohner Pianet T (type 2) v1.3 (24-Sep-2004)
|
|
734
821
|
- `WurlitzerEP200`: Wurlitzer EP200 Electric Piano v1.1 (16-May-1999)
|
|
822
|
+
- `TX81Z`: Yamaha TX81Z "FM Piano" patch (from the VCSL Electrophones set)
|
|
735
823
|
|
|
736
824
|
### Mallets
|
|
737
825
|
|
|
@@ -742,7 +830,7 @@ import { Mallet, getMalletNames } from "smplr";
|
|
|
742
830
|
|
|
743
831
|
const instruments = getMalletNames();
|
|
744
832
|
|
|
745
|
-
const mallet =
|
|
833
|
+
const mallet = Mallet(new AudioContext(), {
|
|
746
834
|
instrument: instruments[0],
|
|
747
835
|
});
|
|
748
836
|
```
|
|
@@ -756,7 +844,7 @@ import { Mellotron, getMellotronNames } from "smplr";
|
|
|
756
844
|
|
|
757
845
|
const instruments = getMellotronNames();
|
|
758
846
|
|
|
759
|
-
const
|
|
847
|
+
const mellotron = Mellotron(new AudioContext(), {
|
|
760
848
|
instrument: instruments[0],
|
|
761
849
|
});
|
|
762
850
|
```
|
|
@@ -771,13 +859,13 @@ import { DrumMachine, getDrumMachineNames } from "smplr";
|
|
|
771
859
|
const instruments = getDrumMachineNames();
|
|
772
860
|
|
|
773
861
|
const context = new AudioContext();
|
|
774
|
-
const drums =
|
|
862
|
+
const drums = DrumMachine(context, { instrument: "TR-808" });
|
|
775
863
|
drums.start({ note: "kick" });
|
|
776
864
|
|
|
777
865
|
// Drum samples are grouped and can have sample variations:
|
|
778
866
|
drums.getSampleNames(); // => ['kick-1', 'kick-2', 'snare-1', 'snare-2', ...]
|
|
779
867
|
drums.getGroupNames(); // => ['kick', 'snare']
|
|
780
|
-
drums.getSampleNamesForGroup("kick")
|
|
868
|
+
drums.getSampleNamesForGroup("kick"); // => ['kick-1', 'kick-2']
|
|
781
869
|
|
|
782
870
|
// You can trigger samples by group name or specific sample
|
|
783
871
|
drums.start("kick"); // Play the first sample of the group
|
|
@@ -793,7 +881,7 @@ const instruments = getSmolkenNames(); // => Arco, Pizzicato & Switched
|
|
|
793
881
|
|
|
794
882
|
// Create an instrument
|
|
795
883
|
const context = new AudioContext();
|
|
796
|
-
const doubleBass = await
|
|
884
|
+
const doubleBass = await Smolken(context, { instrument: "Arco" }).load;
|
|
797
885
|
```
|
|
798
886
|
|
|
799
887
|
### Versilian
|
|
@@ -809,7 +897,7 @@ import { Versilian, getVersilianInstruments } from "smplr";
|
|
|
809
897
|
const instrumentNames = await getVersilianInstruments();
|
|
810
898
|
|
|
811
899
|
const context = new AudioContext();
|
|
812
|
-
const
|
|
900
|
+
const versilian = Versilian(context, { instrument: instrumentNames[0] });
|
|
813
901
|
```
|
|
814
902
|
|
|
815
903
|
### Soundfont2Sampler
|
|
@@ -821,7 +909,7 @@ import { Soundfont2Sampler } from "smplr";
|
|
|
821
909
|
import { SoundFont2 } from "soundfont2";
|
|
822
910
|
|
|
823
911
|
const context = new AudioContext();
|
|
824
|
-
const sampler =
|
|
912
|
+
const sampler = Soundfont2Sampler(context, {
|
|
825
913
|
url: "https://smpldsnds.github.io/soundfonts/soundfonts/galaxy-electric-pianos.sf2",
|
|
826
914
|
createSoundfont: (data) => new SoundFont2(data),
|
|
827
915
|
});
|