smplr 0.19.0 → 0.21.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 +192 -72
- package/dist/index.d.mts +249 -178
- package/dist/index.d.ts +249 -178
- package/dist/index.js +479 -493
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +472 -491
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
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.21.0 release is the **1.0 candidate** — the documented surface is intended to ship unchanged into 1.0; the formal stability commitment lands when a handful of coordinated sibling tickets are in (see the 0.21.0 [CHANGELOG](https://github.com/danigb/smplr/blob/main/CHANGELOG.md) entry).
|
|
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
|
});
|
|
@@ -265,7 +307,7 @@ Events can be configured globally:
|
|
|
265
307
|
|
|
266
308
|
```js
|
|
267
309
|
const context = new AudioContext();
|
|
268
|
-
const
|
|
310
|
+
const piano = SplendidGrandPiano(context, {
|
|
269
311
|
onStart: (note) => {
|
|
270
312
|
console.log(note.time, context.currentTime);
|
|
271
313
|
},
|
|
@@ -286,20 +328,20 @@ piano.start({
|
|
|
286
328
|
|
|
287
329
|
Global callbacks will be invoked regardless of whether local events are defined.
|
|
288
330
|
|
|
289
|
-
⚠️ The invocation time of `onStart` is not exact
|
|
331
|
+
⚠️ 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
332
|
|
|
291
333
|
### Effects
|
|
292
334
|
|
|
293
335
|
#### Reverb
|
|
294
336
|
|
|
295
|
-
|
|
337
|
+
A packaged version of the [DattorroReverbNode](https://github.com/khoin/DattorroReverbNode) algorithmic reverb is included.
|
|
296
338
|
|
|
297
339
|
Use `output.addEffect(name, effect, mix)` to connect an effect using a send bus:
|
|
298
340
|
|
|
299
341
|
```js
|
|
300
342
|
import { Reverb, SplendidGrandPiano } from "smplr";
|
|
301
343
|
const reverb = new Reverb(context);
|
|
302
|
-
const piano =
|
|
344
|
+
const piano = SplendidGrandPiano(context, { volume });
|
|
303
345
|
piano.output.addEffect("reverb", reverb, 0.2);
|
|
304
346
|
```
|
|
305
347
|
|
|
@@ -309,13 +351,11 @@ To change the mix level, use `output.sendEffect(name, mix)`:
|
|
|
309
351
|
piano.output.sendEffect("reverb", 0.5);
|
|
310
352
|
```
|
|
311
353
|
|
|
312
|
-
###
|
|
313
|
-
|
|
314
|
-
#### Cache requests
|
|
354
|
+
### Cache requests
|
|
315
355
|
|
|
316
|
-
|
|
356
|
+
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
357
|
|
|
318
|
-
|
|
358
|
+
To cache samples in the browser, use a `CacheStorage` object:
|
|
319
359
|
|
|
320
360
|
```ts
|
|
321
361
|
import { SplendidGrandPiano, CacheStorage } from "smplr";
|
|
@@ -323,21 +363,21 @@ import { SplendidGrandPiano, CacheStorage } from "smplr";
|
|
|
323
363
|
const context = new AudioContext();
|
|
324
364
|
const storage = new CacheStorage();
|
|
325
365
|
// First time the instrument loads, will fetch the samples from http. Subsequent times from cache.
|
|
326
|
-
const piano =
|
|
366
|
+
const piano = SplendidGrandPiano(context, { storage });
|
|
327
367
|
```
|
|
328
368
|
|
|
329
|
-
⚠️ `CacheStorage` is based on [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) and only works in secure environments that
|
|
369
|
+
⚠️ `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
370
|
|
|
331
371
|
## Sequencer
|
|
332
372
|
|
|
333
|
-
`Sequencer` schedules notes from one or more tracks against any smplr instrument with sample-accurate timing.
|
|
373
|
+
`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
374
|
|
|
335
375
|
```js
|
|
336
376
|
import { Sequencer, SplendidGrandPiano, DrumMachine } from "smplr";
|
|
337
377
|
|
|
338
378
|
const context = new AudioContext();
|
|
339
|
-
const piano =
|
|
340
|
-
const drums =
|
|
379
|
+
const piano = SplendidGrandPiano(context);
|
|
380
|
+
const drums = DrumMachine(context, { instrument: "TR-808" });
|
|
341
381
|
|
|
342
382
|
const seq = new Sequencer(context, { bpm: 120, loop: true });
|
|
343
383
|
|
|
@@ -522,6 +562,85 @@ const seq = new Sequencer(context, {
|
|
|
522
562
|
|
|
523
563
|
---
|
|
524
564
|
|
|
565
|
+
## Export Audio
|
|
566
|
+
|
|
567
|
+
Render audio offline (faster than real-time) and export it as a WAV file. Uses `OfflineAudioContext` under the hood.
|
|
568
|
+
|
|
569
|
+
```js
|
|
570
|
+
import { renderOffline } from "smplr";
|
|
571
|
+
|
|
572
|
+
const result = await renderOffline(async (context) => {
|
|
573
|
+
const piano = await SplendidGrandPiano(context).load;
|
|
574
|
+
piano.start({ note: "C4", time: 0, duration: 1 });
|
|
575
|
+
piano.start({ note: "E4", time: 0.5, duration: 1 });
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
result.downloadWav("export.wav");
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### Options
|
|
582
|
+
|
|
583
|
+
```js
|
|
584
|
+
const result = await renderOffline(callback, {
|
|
585
|
+
duration: 10, // Total duration in seconds (auto-detected if omitted)
|
|
586
|
+
sampleRate: 48000, // Sample rate (default: 48000)
|
|
587
|
+
channels: 2, // Number of channels (default: 2)
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
When `duration` is omitted, a 60-second buffer is used and trailing silence is automatically trimmed. Pass an explicit `duration` for longer renders or to preserve trailing silence.
|
|
592
|
+
|
|
593
|
+
#### RenderResult
|
|
594
|
+
|
|
595
|
+
`renderOffline` returns a `RenderResult` object:
|
|
596
|
+
|
|
597
|
+
- `result.audioBuffer` — the raw `AudioBuffer`
|
|
598
|
+
- `result.toWav()` — encode as 32-bit float WAV `Blob` (lossless)
|
|
599
|
+
- `result.toWav16()` — encode as 16-bit integer WAV `Blob` (smaller file)
|
|
600
|
+
- `result.downloadWav(filename?)` — download as 32-bit WAV
|
|
601
|
+
- `result.downloadWav16(filename?)` — download as 16-bit WAV
|
|
602
|
+
- `result.duration` — actual duration in seconds
|
|
603
|
+
- `result.sampleRate` — sample rate used
|
|
604
|
+
|
|
605
|
+
WAV encoding is lazy — it only happens when you call `toWav()` or `toWav16()`.
|
|
606
|
+
|
|
607
|
+
#### Buffer reuse
|
|
608
|
+
|
|
609
|
+
If you already have an instrument loaded, pass the same `SampleLoader` to avoid re-fetching samples:
|
|
610
|
+
|
|
611
|
+
```js
|
|
612
|
+
import { SplendidGrandPiano, SampleLoader, renderOffline } from "smplr";
|
|
613
|
+
|
|
614
|
+
const loader = new SampleLoader(audioContext);
|
|
615
|
+
const piano = SplendidGrandPiano(audioContext, { loader });
|
|
616
|
+
await piano.load;
|
|
617
|
+
|
|
618
|
+
// Offline render reuses cached buffers — no re-fetch
|
|
619
|
+
const result = await renderOffline(async (context) => {
|
|
620
|
+
const offlinePiano = await SplendidGrandPiano(context, { loader }).load;
|
|
621
|
+
offlinePiano.start({ note: "C4", time: 0, duration: 1 });
|
|
622
|
+
});
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
#### Bug reports
|
|
626
|
+
|
|
627
|
+
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:
|
|
628
|
+
|
|
629
|
+
```js
|
|
630
|
+
const { renderOffline, SplendidGrandPiano } =
|
|
631
|
+
await import("https://esm.sh/smplr");
|
|
632
|
+
|
|
633
|
+
const result = await renderOffline(async (context) => {
|
|
634
|
+
const piano = await SplendidGrandPiano(context).load;
|
|
635
|
+
piano.start({ note: "C4", time: 0, duration: 2 });
|
|
636
|
+
});
|
|
637
|
+
result.downloadWav16("bug-report.wav");
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
This will download a WAV file you can attach to your issue or pull request.
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
525
644
|
## Instruments
|
|
526
645
|
|
|
527
646
|
### Sampler
|
|
@@ -535,7 +654,7 @@ const buffers = {
|
|
|
535
654
|
kick: "https://smpldsnds.github.io/drum-machines/808-mini/kick.m4a",
|
|
536
655
|
snare: "https://smpldsnds.github.io/drum-machines/808-mini/snare-1.m4a",
|
|
537
656
|
};
|
|
538
|
-
const sampler =
|
|
657
|
+
const sampler = Sampler(new AudioContext(), { buffers });
|
|
539
658
|
```
|
|
540
659
|
|
|
541
660
|
And then use the name of the buffer as note name:
|
|
@@ -552,7 +671,7 @@ A Soundfont player. By default it loads audio from Benjamin Gleitzman's package
|
|
|
552
671
|
```js
|
|
553
672
|
import { Soundfont, getSoundfontNames, getSoundfontKits } from "smplr";
|
|
554
673
|
|
|
555
|
-
const marimba =
|
|
674
|
+
const marimba = Soundfont(new AudioContext(), { instrument: "marimba" });
|
|
556
675
|
marimba.start({ note: "C4" });
|
|
557
676
|
```
|
|
558
677
|
|
|
@@ -562,10 +681,10 @@ It's intended to be a modern replacement of [soundfont-player](https://github.co
|
|
|
562
681
|
|
|
563
682
|
Use `getSoundfontNames` to get all available instrument names and `getSoundfontKits` to get kit names.
|
|
564
683
|
|
|
565
|
-
There are two kits available: `MusyngKite` or `FluidR3_GM`. The first one is used by default: it sounds better but samples
|
|
684
|
+
There are two kits available: `MusyngKite` or `FluidR3_GM`. The first one is used by default: it sounds better but the samples are heavier.
|
|
566
685
|
|
|
567
686
|
```js
|
|
568
|
-
const marimba =
|
|
687
|
+
const marimba = Soundfont(context, {
|
|
569
688
|
instrument: "clavinet",
|
|
570
689
|
kit: "FluidR3_GM", // "MusyngKite" is used by default if not specified
|
|
571
690
|
});
|
|
@@ -574,7 +693,7 @@ const marimba = new Soundfont(context, {
|
|
|
574
693
|
Alternatively, you can pass your custom url as the instrument. In that case, the `kit` is ignored:
|
|
575
694
|
|
|
576
695
|
```js
|
|
577
|
-
const marimba =
|
|
696
|
+
const marimba = Soundfont(context, {
|
|
578
697
|
instrumentUrl:
|
|
579
698
|
"https://gleitz.github.io/midi-js-soundfonts/MusyngKite/marimba-mp3.js",
|
|
580
699
|
});
|
|
@@ -585,7 +704,7 @@ const marimba = new Soundfont(context, {
|
|
|
585
704
|
You can enable note looping to make note names indefinitely long by loading loop data:
|
|
586
705
|
|
|
587
706
|
```js
|
|
588
|
-
const marimba =
|
|
707
|
+
const marimba = Soundfont(context, {
|
|
589
708
|
instrument: "cello",
|
|
590
709
|
loadLoopData: true,
|
|
591
710
|
});
|
|
@@ -601,7 +720,7 @@ A sampled acoustic piano. It uses Steinway samples with 4 velocity groups from
|
|
|
601
720
|
```js
|
|
602
721
|
import { SplendidGrandPiano } from "smplr";
|
|
603
722
|
|
|
604
|
-
const piano =
|
|
723
|
+
const piano = SplendidGrandPiano(new AudioContext());
|
|
605
724
|
|
|
606
725
|
piano.start({ note: "C4" });
|
|
607
726
|
```
|
|
@@ -610,7 +729,7 @@ piano.start({ note: "C4" });
|
|
|
610
729
|
|
|
611
730
|
The second argument of the constructor accepts the following options:
|
|
612
731
|
|
|
613
|
-
- `baseUrl`:
|
|
732
|
+
- `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.
|
|
614
733
|
- `detune`: global detune in cents (0 if not specified)
|
|
615
734
|
- `velocity`: default velocity (100 if not specified)
|
|
616
735
|
- `volume`: default volume (100 if not specified)
|
|
@@ -620,7 +739,7 @@ The second argument of the constructor accepts the following options:
|
|
|
620
739
|
Example:
|
|
621
740
|
|
|
622
741
|
```ts
|
|
623
|
-
const piano =
|
|
742
|
+
const piano = SplendidGrandPiano(context, {
|
|
624
743
|
detune: -20,
|
|
625
744
|
volume: 80,
|
|
626
745
|
notesToLoad: {
|
|
@@ -637,9 +756,9 @@ A sampled electric pianos. Samples from https://github.com/sfzinstruments/GregSu
|
|
|
637
756
|
```js
|
|
638
757
|
import { ElectricPiano, getElectricPianoNames } from "smplr";
|
|
639
758
|
|
|
640
|
-
const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200"]
|
|
759
|
+
const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200", "TX81Z"]
|
|
641
760
|
|
|
642
|
-
const epiano =
|
|
761
|
+
const epiano = ElectricPiano(new AudioContext(), {
|
|
643
762
|
instrument: "PianetT",
|
|
644
763
|
});
|
|
645
764
|
|
|
@@ -654,6 +773,7 @@ Available instruments:
|
|
|
654
773
|
- `CP80`: Yamaha CP80 Electric Grand Piano v1.3 (29-Sep-2004)
|
|
655
774
|
- `PianetT`: Hohner Pianet T (type 2) v1.3 (24-Sep-2004)
|
|
656
775
|
- `WurlitzerEP200`: Wurlitzer EP200 Electric Piano v1.1 (16-May-1999)
|
|
776
|
+
- `TX81Z`: Yamaha TX81Z "FM Piano" patch (from the VCSL Electrophones set)
|
|
657
777
|
|
|
658
778
|
### Mallets
|
|
659
779
|
|
|
@@ -664,7 +784,7 @@ import { Mallet, getMalletNames } from "smplr";
|
|
|
664
784
|
|
|
665
785
|
const instruments = getMalletNames();
|
|
666
786
|
|
|
667
|
-
const mallet =
|
|
787
|
+
const mallet = Mallet(new AudioContext(), {
|
|
668
788
|
instrument: instruments[0],
|
|
669
789
|
});
|
|
670
790
|
```
|
|
@@ -678,7 +798,7 @@ import { Mellotron, getMellotronNames } from "smplr";
|
|
|
678
798
|
|
|
679
799
|
const instruments = getMellotronNames();
|
|
680
800
|
|
|
681
|
-
const
|
|
801
|
+
const mellotron = Mellotron(new AudioContext(), {
|
|
682
802
|
instrument: instruments[0],
|
|
683
803
|
});
|
|
684
804
|
```
|
|
@@ -693,13 +813,13 @@ import { DrumMachine, getDrumMachineNames } from "smplr";
|
|
|
693
813
|
const instruments = getDrumMachineNames();
|
|
694
814
|
|
|
695
815
|
const context = new AudioContext();
|
|
696
|
-
const drums =
|
|
816
|
+
const drums = DrumMachine(context, { instrument: "TR-808" });
|
|
697
817
|
drums.start({ note: "kick" });
|
|
698
818
|
|
|
699
819
|
// Drum samples are grouped and can have sample variations:
|
|
700
820
|
drums.getSampleNames(); // => ['kick-1', 'kick-2', 'snare-1', 'snare-2', ...]
|
|
701
821
|
drums.getGroupNames(); // => ['kick', 'snare']
|
|
702
|
-
drums.getSampleNamesForGroup("kick")
|
|
822
|
+
drums.getSampleNamesForGroup("kick"); // => ['kick-1', 'kick-2']
|
|
703
823
|
|
|
704
824
|
// You can trigger samples by group name or specific sample
|
|
705
825
|
drums.start("kick"); // Play the first sample of the group
|
|
@@ -715,7 +835,7 @@ const instruments = getSmolkenNames(); // => Arco, Pizzicato & Switched
|
|
|
715
835
|
|
|
716
836
|
// Create an instrument
|
|
717
837
|
const context = new AudioContext();
|
|
718
|
-
const doubleBass = await
|
|
838
|
+
const doubleBass = await Smolken(context, { instrument: "Arco" }).load;
|
|
719
839
|
```
|
|
720
840
|
|
|
721
841
|
### Versilian
|
|
@@ -731,7 +851,7 @@ import { Versilian, getVersilianInstruments } from "smplr";
|
|
|
731
851
|
const instrumentNames = await getVersilianInstruments();
|
|
732
852
|
|
|
733
853
|
const context = new AudioContext();
|
|
734
|
-
const
|
|
854
|
+
const versilian = Versilian(context, { instrument: instrumentNames[0] });
|
|
735
855
|
```
|
|
736
856
|
|
|
737
857
|
### Soundfont2Sampler
|
|
@@ -743,7 +863,7 @@ import { Soundfont2Sampler } from "smplr";
|
|
|
743
863
|
import { SoundFont2 } from "soundfont2";
|
|
744
864
|
|
|
745
865
|
const context = new AudioContext();
|
|
746
|
-
const sampler =
|
|
866
|
+
const sampler = Soundfont2Sampler(context, {
|
|
747
867
|
url: "https://smpldsnds.github.io/soundfonts/soundfonts/galaxy-electric-pianos.sf2",
|
|
748
868
|
createSoundfont: (data) => new SoundFont2(data),
|
|
749
869
|
});
|