smplr 0.20.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
 
@@ -530,7 +570,7 @@ Render audio offline (faster than real-time) and export it as a WAV file. Uses `
530
570
  import { renderOffline } from "smplr";
531
571
 
532
572
  const result = await renderOffline(async (context) => {
533
- const piano = await new SplendidGrandPiano(context).load;
573
+ const piano = await SplendidGrandPiano(context).load;
534
574
  piano.start({ note: "C4", time: 0, duration: 1 });
535
575
  piano.start({ note: "E4", time: 0.5, duration: 1 });
536
576
  });
@@ -572,12 +612,12 @@ If you already have an instrument loaded, pass the same `SampleLoader` to avoid
572
612
  import { SplendidGrandPiano, SampleLoader, renderOffline } from "smplr";
573
613
 
574
614
  const loader = new SampleLoader(audioContext);
575
- const piano = new SplendidGrandPiano(audioContext, { loader });
615
+ const piano = SplendidGrandPiano(audioContext, { loader });
576
616
  await piano.load;
577
617
 
578
618
  // Offline render reuses cached buffers — no re-fetch
579
619
  const result = await renderOffline(async (context) => {
580
- const offlinePiano = await new SplendidGrandPiano(context, { loader }).load;
620
+ const offlinePiano = await SplendidGrandPiano(context, { loader }).load;
581
621
  offlinePiano.start({ note: "C4", time: 0, duration: 1 });
582
622
  });
583
623
  ```
@@ -587,10 +627,11 @@ const result = await renderOffline(async (context) => {
587
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:
588
628
 
589
629
  ```js
590
- const { renderOffline, SplendidGrandPiano } = await import("https://esm.sh/smplr");
630
+ const { renderOffline, SplendidGrandPiano } =
631
+ await import("https://esm.sh/smplr");
591
632
 
592
633
  const result = await renderOffline(async (context) => {
593
- const piano = await new SplendidGrandPiano(context).load;
634
+ const piano = await SplendidGrandPiano(context).load;
594
635
  piano.start({ note: "C4", time: 0, duration: 2 });
595
636
  });
596
637
  result.downloadWav16("bug-report.wav");
@@ -613,7 +654,7 @@ const buffers = {
613
654
  kick: "https://smpldsnds.github.io/drum-machines/808-mini/kick.m4a",
614
655
  snare: "https://smpldsnds.github.io/drum-machines/808-mini/snare-1.m4a",
615
656
  };
616
- const sampler = new Sampler(new AudioContext(), { buffers });
657
+ const sampler = Sampler(new AudioContext(), { buffers });
617
658
  ```
618
659
 
619
660
  And then use the name of the buffer as note name:
@@ -630,7 +671,7 @@ A Soundfont player. By default it loads audio from Benjamin Gleitzman's package
630
671
  ```js
631
672
  import { Soundfont, getSoundfontNames, getSoundfontKits } from "smplr";
632
673
 
633
- const marimba = new Soundfont(new AudioContext(), { instrument: "marimba" });
674
+ const marimba = Soundfont(new AudioContext(), { instrument: "marimba" });
634
675
  marimba.start({ note: "C4" });
635
676
  ```
636
677
 
@@ -640,10 +681,10 @@ It's intended to be a modern replacement of [soundfont-player](https://github.co
640
681
 
641
682
  Use `getSoundfontNames` to get all available instrument names and `getSoundfontKits` to get kit names.
642
683
 
643
- 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.
644
685
 
645
686
  ```js
646
- const marimba = new Soundfont(context, {
687
+ const marimba = Soundfont(context, {
647
688
  instrument: "clavinet",
648
689
  kit: "FluidR3_GM", // "MusyngKite" is used by default if not specified
649
690
  });
@@ -652,7 +693,7 @@ const marimba = new Soundfont(context, {
652
693
  Alternatively, you can pass your custom url as the instrument. In that case, the `kit` is ignored:
653
694
 
654
695
  ```js
655
- const marimba = new Soundfont(context, {
696
+ const marimba = Soundfont(context, {
656
697
  instrumentUrl:
657
698
  "https://gleitz.github.io/midi-js-soundfonts/MusyngKite/marimba-mp3.js",
658
699
  });
@@ -663,7 +704,7 @@ const marimba = new Soundfont(context, {
663
704
  You can enable note looping to make note names indefinitely long by loading loop data:
664
705
 
665
706
  ```js
666
- const marimba = new Soundfont(context, {
707
+ const marimba = Soundfont(context, {
667
708
  instrument: "cello",
668
709
  loadLoopData: true,
669
710
  });
@@ -679,7 +720,7 @@ A sampled acoustic piano. It uses Steinway samples with 4 velocity groups from
679
720
  ```js
680
721
  import { SplendidGrandPiano } from "smplr";
681
722
 
682
- const piano = new SplendidGrandPiano(new AudioContext());
723
+ const piano = SplendidGrandPiano(new AudioContext());
683
724
 
684
725
  piano.start({ note: "C4" });
685
726
  ```
@@ -688,7 +729,7 @@ piano.start({ note: "C4" });
688
729
 
689
730
  The second argument of the constructor accepts the following options:
690
731
 
691
- - `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.
692
733
  - `detune`: global detune in cents (0 if not specified)
693
734
  - `velocity`: default velocity (100 if not specified)
694
735
  - `volume`: default volume (100 if not specified)
@@ -698,7 +739,7 @@ The second argument of the constructor accepts the following options:
698
739
  Example:
699
740
 
700
741
  ```ts
701
- const piano = new SplendidGrandPiano(context, {
742
+ const piano = SplendidGrandPiano(context, {
702
743
  detune: -20,
703
744
  volume: 80,
704
745
  notesToLoad: {
@@ -715,9 +756,9 @@ A sampled electric pianos. Samples from https://github.com/sfzinstruments/GregSu
715
756
  ```js
716
757
  import { ElectricPiano, getElectricPianoNames } from "smplr";
717
758
 
718
- const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200"]
759
+ const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200", "TX81Z"]
719
760
 
720
- const epiano = new ElectricPiano(new AudioContext(), {
761
+ const epiano = ElectricPiano(new AudioContext(), {
721
762
  instrument: "PianetT",
722
763
  });
723
764
 
@@ -732,6 +773,7 @@ Available instruments:
732
773
  - `CP80`: Yamaha CP80 Electric Grand Piano v1.3 (29-Sep-2004)
733
774
  - `PianetT`: Hohner Pianet T (type 2) v1.3 (24-Sep-2004)
734
775
  - `WurlitzerEP200`: Wurlitzer EP200 Electric Piano v1.1 (16-May-1999)
776
+ - `TX81Z`: Yamaha TX81Z "FM Piano" patch (from the VCSL Electrophones set)
735
777
 
736
778
  ### Mallets
737
779
 
@@ -742,7 +784,7 @@ import { Mallet, getMalletNames } from "smplr";
742
784
 
743
785
  const instruments = getMalletNames();
744
786
 
745
- const mallet = new Mallet(new AudioContext(), {
787
+ const mallet = Mallet(new AudioContext(), {
746
788
  instrument: instruments[0],
747
789
  });
748
790
  ```
@@ -756,7 +798,7 @@ import { Mellotron, getMellotronNames } from "smplr";
756
798
 
757
799
  const instruments = getMellotronNames();
758
800
 
759
- const mallet = new Mellotron(new AudioContext(), {
801
+ const mellotron = Mellotron(new AudioContext(), {
760
802
  instrument: instruments[0],
761
803
  });
762
804
  ```
@@ -771,13 +813,13 @@ import { DrumMachine, getDrumMachineNames } from "smplr";
771
813
  const instruments = getDrumMachineNames();
772
814
 
773
815
  const context = new AudioContext();
774
- const drums = new DrumMachine(context, { instrument: "TR-808" });
816
+ const drums = DrumMachine(context, { instrument: "TR-808" });
775
817
  drums.start({ note: "kick" });
776
818
 
777
819
  // Drum samples are grouped and can have sample variations:
778
820
  drums.getSampleNames(); // => ['kick-1', 'kick-2', 'snare-1', 'snare-2', ...]
779
821
  drums.getGroupNames(); // => ['kick', 'snare']
780
- drums.getSampleNamesForGroup("kick") => // => ['kick-1', 'kick-2']
822
+ drums.getSampleNamesForGroup("kick"); // => ['kick-1', 'kick-2']
781
823
 
782
824
  // You can trigger samples by group name or specific sample
783
825
  drums.start("kick"); // Play the first sample of the group
@@ -793,7 +835,7 @@ const instruments = getSmolkenNames(); // => Arco, Pizzicato & Switched
793
835
 
794
836
  // Create an instrument
795
837
  const context = new AudioContext();
796
- const doubleBass = await new Smolken(context, { instrument: "Arco" }).load;
838
+ const doubleBass = await Smolken(context, { instrument: "Arco" }).load;
797
839
  ```
798
840
 
799
841
  ### Versilian
@@ -809,7 +851,7 @@ import { Versilian, getVersilianInstruments } from "smplr";
809
851
  const instrumentNames = await getVersilianInstruments();
810
852
 
811
853
  const context = new AudioContext();
812
- const sampler = new Versilian(context, { instrument: instrumentNames[0] });
854
+ const versilian = Versilian(context, { instrument: instrumentNames[0] });
813
855
  ```
814
856
 
815
857
  ### Soundfont2Sampler
@@ -821,7 +863,7 @@ import { Soundfont2Sampler } from "smplr";
821
863
  import { SoundFont2 } from "soundfont2";
822
864
 
823
865
  const context = new AudioContext();
824
- const sampler = new Soundfont2Sampler(context, {
866
+ const sampler = Soundfont2Sampler(context, {
825
867
  url: "https://smpldsnds.github.io/soundfonts/soundfonts/galaxy-electric-pianos.sf2",
826
868
  createSoundfont: (data) => new SoundFont2(data),
827
869
  });