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 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 = new Soundfont(context, { instrument: "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 dm = new DrumMachine(context);
22
- dm.start({ note: "kick" });
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
- ```js
26
- import { SplendidGrandPiano, Reverb } from "smplr";
43
+ **Render an arpeggio with reverb to a WAV file — offline, no speakers needed:**
27
44
 
28
- const context = new AudioContext();
29
- const piano = new SplendidGrandPiano(context);
30
- piano.output.addEffect("reverb", new Reverb(context), 0.2);
45
+ ```js
46
+ import { SplendidGrandPiano, Reverb, renderOffline } from "smplr";
31
47
 
32
- piano.start({ note: "C4" });
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 still under development and features are considered unstable until v 1.0
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
- Read [CHANGELOG](https://github.com/danigb/smplr/blob/main/CHANGELOG.md) for changes.
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 manger
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 marimba = new SplendidGrandPiano(context); // create and load the instrument
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
- marimba.start({ note: 60, velocity: 80 }); // play the note
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 serve as a url from a service like [unpkg](unpkg.com) or similar.
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
- All instruments follows the same pattern: `new Instrument(context, options)`. For example:
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 = new SplendidGrandPiano(context, { decayTime: 0.5 });
96
- const marimba = new Soundfont(context, { instrument: "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 new SplendidGrandPiano(context).load;
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 = new SplendidGrandPiano(context, {
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 that are passed as second argument of the constructor. As it name implies, all fields are optional:
173
+ All instruments share some configuration options, passed as the second argument to the factory. Every field is optional:
137
174
 
138
- - `volume`: A number from 0 to 127 representing the instrument global volume. 100 by default
139
- - `destination`: An `AudioNode` that is the output of the instrument. `AudioContext.destination` is used by default
140
- - `volumeToGain`: a function to convert the volume to gain. It uses MIDI standard as default.
141
- - `disableScheduler`: disable internal scheduler. `false` by default.
142
- - `scheduleLookaheadMs`: the lookahead of the scheduler. If the start time of the note is less than current time plus this lookahead time, the note will be started. 200ms by default.
143
- - `scheduleIntervalMs`: the interval of the scheduler. 50ms by default.
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`: a function that is called when starting a note. It receives the note started as parameter. Bear in mind that the time this function is called is not precise, and it's determined by lookahead.
146
- - `onEnded`: a function that is called when the note ends. It receives the started note as parameter.
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 = new SplendidGrandPiano(context);
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 = new Soundfont(context, { instrument: "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
- You can read more about this issue [here](https://github.com/chrisguttandin/standardized-audio-context/issues/897)
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
- // This will stop C4 note
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 sampler = new Sampler(audioContext, { duh: "duh-duh-ah.mp3" });
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 sampler = new Sample(context, {
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. It triggers slightly before the actual start time and is influenced by the `scheduleLookaheadMs` parameter.
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
- An packed version of [DattorroReverbNode](https://github.com/khoin/DattorroReverbNode) algorithmic reverb is included.
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 = new SplendidGrandPiano(context, { volume });
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
- ### Experimental features
313
-
314
- #### Cache requests
354
+ ### Cache requests
315
355
 
316
- If you use default samples, they are stored at github pages. Github rate limits the number of requests per second. That could be a problem, specially if you're using a development environment with hot reload (like most React frameworks).
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
- If you want to cache samples on the browser you can use a `CacheStorage` object:
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 = new SplendidGrandPiano(context, { storage });
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 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.
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 = new SplendidGrandPiano(context);
340
- const drums = new DrumMachine(context, { instrument: "TR-808" });
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 = new Sampler(new AudioContext(), { buffers });
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 = new Soundfont(new AudioContext(), { instrument: "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 weights more.
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 = new Soundfont(context, {
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 = new Soundfont(context, {
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 = new Soundfont(context, {
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 = new SplendidGrandPiano(new AudioContext());
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 = new SplendidGrandPiano(context, {
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 = new ElectricPiano(new AudioContext(), {
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 = new Mallet(new AudioContext(), {
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 mallet = new Mellotron(new AudioContext(), {
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 = new DrumMachine(context, { instrument: "TR-808" });
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") => // => ['kick-1', 'kick-2']
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 new Smolken(context, { instrument: "Arco" }).load;
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 sampler = new Versilian(context, { instrument: instrumentNames[0] });
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 = new Soundfont2Sampler(context, {
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
  });