smplr 0.1.0 → 0.4.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
@@ -1,25 +1,263 @@
1
- # smplr [![npm](https://img.shields.io/npm/v/smplr.svg)](https://www.npmjs.com/package/smplr)
1
+ # smplr
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/smplr)](https://www.npmjs.com/package/smplr)
3
4
 
4
- A web audio sampler:
5
+ > `smplr` is a collection of sampled instruments for Web Audio API ready to be used with no setup required.
6
+
7
+ Examples:
8
+
9
+ ```js
10
+ import { Soundfont } from "smplr";
11
+
12
+ const context = new AudioContext();
13
+ const marimba = new Soundfont(context, { instrument: "marimba" });
14
+ marimba.start({ note: 60, velocity: 80 });
15
+ ```
16
+
17
+ ```js
18
+ import { DrumMachine } from "smplr";
19
+
20
+ const context = new AudioContext();
21
+ const dm = new DrumMachine(context);
22
+ dm.start({ note: "kick" });
23
+ ```
24
+
25
+ ```js
26
+ import { SplendidGrandPiano, Reverb } from "smplr";
27
+
28
+ const context = new AudioContext();
29
+ const piano = new SplendidGrandPiano(context);
30
+ piano.output.addEffect("reverb", new Reverb(context), 0.2);
31
+
32
+ piano.start({ note: "C4" });
33
+ ```
34
+
35
+ See demo: https://danigb.github.io/smplr/
36
+
37
+ #### Library goals
38
+
39
+ - No setup: specifically, all samples are online, so no need for a server.
40
+ - Easy to use: everything should be intuitive for non-experienced developers
41
+ - Decent sounding: use high quality open source samples. For good or worst, is sample based 🤷
42
+
43
+ #### Installation
44
+
45
+ Install with npm or your favourite package manager:
46
+
47
+ ```
48
+ npm i smplr
49
+ ```
50
+
51
+ Samples are published at: https://github.com/danigb/samples
52
+
53
+ ## Documentation
54
+
55
+ ### Create an instrument
56
+
57
+ All instruments follows the same pattern: `new Instrument(context, options)`. For example:
58
+
59
+ ```js
60
+ import { SplendidGrandPiano, Soundfont } from "smplr";
61
+
62
+ const context = new AudioContext();
63
+ const piano = new SplendidGrandPiano(context, { decayTime: 0.5 });
64
+ const marimba = new Soundfont(context, { instrument: "marimba" });
65
+ ```
66
+
67
+ ### Wait for audio loading
68
+
69
+ 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 `loaded()` function that returns a promise:
70
+
71
+ ```js
72
+ piano.loaded().then(() => {
73
+ // now the piano is fully loaded
74
+ });
75
+ ```
76
+
77
+ Since the promise returns the instrument instance, you can create and wait in a single line:
78
+
79
+ ```js
80
+ const piano = await new SplendidGrandPiano(context).loaded();
81
+ ```
82
+
83
+ ### Start and stop notes
84
+
85
+ The `start` function accepts a bunch of options:
86
+
87
+ ```js
88
+ piano.start({ note: "C4", velocity: 80, time: 5, duration: 1 });
89
+ ```
90
+
91
+ The `velocity` is a number between 0 and 127 the represents at which velocity the key is pressed. The bigger the number, louder the sound. But `velocity` not only controls the loudness. In some instruments, it also affects the timbre.
92
+
93
+ The `start` function returns a `stop` function for the given note:
94
+
95
+ ```js
96
+ const stopNote = piano.start({ note: 60 });
97
+ stopNote({ time: 10 });
98
+ ```
99
+
100
+ Bear in mind that you may need to call [`context.resume()` before playing a note](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Best_practices#autoplay_policy)
101
+
102
+ Instruments have a global `stop` function that can be used to stop all notes:
103
+
104
+ ```js
105
+ // This will stop all notes
106
+ piano.stop();
107
+ ```
108
+
109
+ Or stop the specified one:
110
+
111
+ ```js
112
+ // This will stop C4 note
113
+ piano.stop(60);
114
+ ```
115
+
116
+ ### Schedule notes
117
+
118
+ You can schedule notes using `time` and `duration` properties. Both are measured in seconds, and time is the number of seconds since the AudioContext was created.
119
+
120
+ For example, next example plays a C major arpeggio, one note per second:
121
+
122
+ ```js
123
+ const now = context.currentTime;
124
+ ["C4", "E4", "G4", "C5"].forEach((note, i) => {
125
+ piano.start({ note, time: now + i, duration: 0.5 });
126
+ });
127
+ ```
128
+
129
+ ### Change volume
130
+
131
+ `setVolume` uses a scale where 0 means no volume, and 127 is max volume without amplification:
132
+
133
+ ```js
134
+ piano.setVolume(80);
135
+ ```
136
+
137
+ Bear in mind that `volume` is global to the instrument, but `velocity` is specific for each note.
138
+
139
+ ### Effects
140
+
141
+ An packed version of [DattorroReverbNode](https://github.com/khoin/DattorroReverbNode) algorithmic reverb is included.
142
+
143
+ Use `output.addEffect(name, effect, mix)` to connect an effect using a send bus:
144
+
145
+ ```js
146
+ import { Reverb, SplendidGrandPiano } from "smplr";
147
+ const reverb = new Reverb(context);
148
+ const piano = new SplendidGrandPiano(context, { volume });
149
+ piano.output.addEffect("reverb", reverb, 0.2);
150
+ ```
151
+
152
+ To change the mix level, use `output.sendEffect(name, mix)`:
153
+
154
+ ```js
155
+ piano.output.sendEffect("reverb", 0.5);
156
+ ```
157
+
158
+ ## Instruments
159
+
160
+ ### Sampler
161
+
162
+ An audio buffer sampler.
163
+
164
+ ```js
165
+ import { Sampler } from "smplr";
166
+
167
+ const samples = {
168
+ kick: "https://danigb.github.io/samples/drum-machines/808-mini/kick.m4a",
169
+ snare: "https://danigb.github.io/samples/drum-machines/808-mini/snare-1.m4a",
170
+ };
171
+ const sampler = new Sampler(new AudioContext(), { samples });
172
+ sampler.start({ note: "kick" });
173
+ ```
174
+
175
+ ### Soundfont
176
+
177
+ A Soundfont player. By default it loads audio from Benjamin Gleitzman's package of
178
+ [pre-rendered sound fonts](https://github.com/gleitz/midi-js-soundfonts).
179
+
180
+ ```js
181
+ import { Soundfont } from "smplr";
182
+
183
+ const marimba = new Soundfont(new AudioContext(), { instrument: "marimba" });
184
+ marimba.start({ note: "C4" });
185
+ ```
186
+
187
+ It's intended to be a modern replacement of [soundfont-player](https://github.com/danigb/soundfont-player)
188
+
189
+ ### Piano
190
+
191
+ A sampled acoustic piano. It uses Steinway samples with 4 velocity layers from
192
+ [SplendidGrandPiano](https://github.com/sfzinstruments/SplendidGrandPiano)
193
+
194
+ ```js
195
+ import { SplendidGrandPiano } from "smplr";
196
+
197
+ const piano = new SplendidGrandPiano(new AudioContext());
198
+
199
+ piano.start({ note: "C4" });
200
+ ```
201
+
202
+ ### Electric Piano
203
+
204
+ A sampled electric pianos. Samples from https://github.com/sfzinstruments/GregSullivan.E-Pianos
205
+
206
+ ```js
207
+ import { ElectricPiano, getElectricPianoNames } from "smplr";
208
+
209
+ const instruments = getElectricPianoNames(); // => ["CP80", "PianetT", "WurlitzerEP200"]
210
+
211
+ const epiano = new ElectricPiano(new AudioContext(), {
212
+ instrument: "PianetT",
213
+ });
214
+
215
+ epiano.start({ note: "C4" });
216
+
217
+ // Includes a (basic) tremolo effect:
218
+ epiano.tremolo.level(30);
219
+ ```
220
+
221
+ Available instruments:
222
+
223
+ - `CP80`: Yamaha CP80 Electric Grand Piano v1.3 (29-Sep-2004)
224
+ - `PianetT`: Hohner Pianet T (type 2) v1.3 (24-Sep-2004)
225
+ - `WurlitzerEP200`: Wurlitzer EP200 Electric Piano v1.1 (16-May-1999)
226
+
227
+ ### Mallets
228
+
229
+ Samples from [The Versilian Community Sample Library](https://github.com/sgossner/VCSL)
5
230
 
6
231
  ```js
7
- var ac = new AudioContext()
8
- var sampler = require('smplr')(ac)
9
- sampler.load('@drum-machines/maestro').then(function (maestro) {
10
- var now = ac.currentTime
11
- maestro('kick').start(now)
12
- maestro('snare').start(now + 0.2)
13
- })
232
+ import { Mallet, getMalletNames } from "smplr";
233
+
234
+ const instruments = getMalletNames();
235
+
236
+ const mallet = new Mallet(new AudioContext(), {
237
+ instrument: instruments[0],
238
+ });
14
239
  ```
15
240
 
16
- ## Install
241
+ ### Drum Machines
17
242
 
18
- Via npm: `npm i --save smplr` or grab the [browser ready file](https://raw.githubusercontent.com/danigb/smplr/master/packages/smplr/dist/smplr.min.js) (4kb) which exports `loader` as window globals.
243
+ Sampled drum machines. Samples from different sources:
19
244
 
20
- ## User guide
245
+ ```js
246
+ import { DrumMachine, getDrumMachineNames } from "smplr";
21
247
 
248
+ const instruments = getDrumMachineNames();
249
+
250
+ const context = new AudioContext();
251
+ const drums = new DrumMachine(context, { instrument: "TR-808" });
252
+ drums.start({ note: "kick" });
253
+
254
+ // Drum samples could have variations:
255
+ const now = context.currentTime;
256
+ drums.getVariations("kick").forEach((variation, index) => {
257
+ drums.start({ note: variation, time: now + index });
258
+ });
259
+ ```
22
260
 
23
- # License
261
+ ## License
24
262
 
25
263
  MIT License
@@ -0,0 +1,284 @@
1
+ type AudioInsert = {
2
+ input: AudioNode;
3
+ output: AudioNode;
4
+ };
5
+
6
+ type ChannelOptions = {
7
+ destination: AudioNode;
8
+ volume: number;
9
+ volumeToGain: (volume: number) => number;
10
+ };
11
+ /**
12
+ * @private
13
+ */
14
+ declare class Channel {
15
+ #private;
16
+ readonly context: AudioContext;
17
+ readonly setVolume: (vol: number) => void;
18
+ readonly input: AudioNode;
19
+ constructor(context: AudioContext, options: Partial<ChannelOptions>);
20
+ addInsert(effect: AudioNode | AudioInsert): void;
21
+ addEffect(name: string, effect: AudioNode | {
22
+ input: AudioNode;
23
+ }, mixValue: number): void;
24
+ sendEffect(name: string, mix: number): void;
25
+ disconnect(): void;
26
+ }
27
+
28
+ type AudioBuffers = Record<string | number, AudioBuffer>;
29
+
30
+ type StopSample = {
31
+ stopId?: string | number;
32
+ time?: number;
33
+ };
34
+
35
+ /**
36
+ * A function that downloads audio
37
+ */
38
+ type SamplerAudioLoader = (context: AudioContext, buffers: AudioBuffers) => Promise<void>;
39
+ type SamplerConfig = {
40
+ detune: number;
41
+ volume: number;
42
+ velocity: number;
43
+ decayTime?: number;
44
+ lpfCutoffHz?: number;
45
+ destination: AudioNode;
46
+ buffers: Record<string | number, string | AudioBuffers> | SamplerAudioLoader;
47
+ volumeToGain: (volume: number) => number;
48
+ noteToSample: (note: SamplerNote, buffers: AudioBuffers, config: SamplerConfig) => [string | number, number];
49
+ };
50
+ type SamplerNote = {
51
+ note: string | number;
52
+ stopId?: string | number;
53
+ time?: number;
54
+ duration?: number;
55
+ decayTime?: number;
56
+ detune?: number;
57
+ velocity?: number;
58
+ lpfCutoffHz?: number;
59
+ };
60
+ /**
61
+ * A Sampler instrument
62
+ *
63
+ * @private
64
+ */
65
+ declare class Sampler {
66
+ #private;
67
+ readonly context: AudioContext;
68
+ readonly output: Omit<Channel, "input">;
69
+ readonly buffers: AudioBuffers;
70
+ constructor(context: AudioContext, options: Partial<SamplerConfig>);
71
+ loaded(): Promise<this>;
72
+ start(note: SamplerNote | string | number): (time?: number) => void;
73
+ stop(note?: StopSample | string | number): void;
74
+ }
75
+
76
+ declare function getDrumMachineNames(): string[];
77
+ type DrumMachineConfig = {
78
+ instrument: string;
79
+ destination: AudioNode;
80
+ detune: number;
81
+ volume: number;
82
+ velocity: number;
83
+ decayTime?: number;
84
+ lpfCutoffHz?: number;
85
+ };
86
+ declare class DrumMachine extends Sampler {
87
+ #private;
88
+ constructor(context: AudioContext, options: Partial<DrumMachineConfig>);
89
+ get sampleNames(): string[];
90
+ getVariations(name: string): string[];
91
+ }
92
+
93
+ type SfzInstrument = {
94
+ name: string;
95
+ formats?: string[];
96
+ baseUrl?: string;
97
+ websfzUrl: string;
98
+ tags?: string[];
99
+ };
100
+
101
+ type Websfz = {
102
+ global: Record<string, string | number>;
103
+ groups: WebsfzGroup[];
104
+ meta: {
105
+ name?: string;
106
+ description?: string;
107
+ license?: string;
108
+ source?: string;
109
+ baseUrl?: string;
110
+ websfzUrl?: string;
111
+ formats?: string[];
112
+ tags?: string[];
113
+ };
114
+ };
115
+ type WebsfzGroup = {
116
+ group_label?: string;
117
+ group?: number;
118
+ hikey?: number;
119
+ hivel?: number;
120
+ lokey?: number;
121
+ lovel?: number;
122
+ off_by?: number;
123
+ off_mode?: "normal";
124
+ pitch_keycenter?: number;
125
+ regions: WebsfzRegion[];
126
+ seq_length?: number;
127
+ trigger?: "first" | "legato";
128
+ volume?: number;
129
+ amp_velcurve_83?: number;
130
+ locc64?: number;
131
+ hicc64?: number;
132
+ hicc107?: number;
133
+ locc107?: number;
134
+ pan_oncc122?: number;
135
+ tune_oncc123?: number;
136
+ eg06_time1_oncc109?: number;
137
+ ampeg_attack_oncc100?: number;
138
+ };
139
+ type WebsfzRegion = {
140
+ end?: number;
141
+ group?: number;
142
+ hivel?: number;
143
+ lovel?: number;
144
+ hikey?: number;
145
+ key?: number;
146
+ lokey?: number;
147
+ off_by?: number;
148
+ pitch_keycenter?: number;
149
+ region_label?: number;
150
+ sample: string;
151
+ seq_position?: number;
152
+ trigger?: "first" | "legato";
153
+ volume?: number;
154
+ locc64?: number;
155
+ hicc64?: number;
156
+ ampeg_attack_oncc100?: number;
157
+ eg06_time1_oncc109?: number;
158
+ pan_oncc122?: number;
159
+ tune_oncc123?: number;
160
+ };
161
+
162
+ /**
163
+ * Splendid Grand Piano options
164
+ */
165
+ type SfzSamplerConfig = {
166
+ instrument: SfzInstrument | Websfz | string;
167
+ destination: AudioNode;
168
+ volume: number;
169
+ velocity: number;
170
+ detune: number;
171
+ decayTime: number;
172
+ lpfCutoffHz?: number;
173
+ };
174
+ declare class SfzSampler {
175
+ #private;
176
+ readonly context: AudioContext;
177
+ readonly output: Omit<Channel, "input">;
178
+ readonly buffers: AudioBuffers;
179
+ constructor(context: AudioContext, options: Partial<SfzSamplerConfig> & Pick<SfzSamplerConfig, "instrument">);
180
+ loaded(): Promise<this>;
181
+ start(note: SamplerNote | string | number): (time?: number) => void;
182
+ stop(note?: StopSample | string | number): void;
183
+ disconnect(): void;
184
+ }
185
+
186
+ declare function getElectricPianoNames(): string[];
187
+ declare class ElectricPiano extends SfzSampler {
188
+ readonly tremolo: Readonly<{
189
+ level: (value: number) => void;
190
+ }>;
191
+ constructor(context: AudioContext, options: Partial<SfzSamplerConfig> & {
192
+ instrument: string;
193
+ });
194
+ }
195
+
196
+ declare function getMalletNames(): ("Balafon - Hard Mallet" | "Balafon - Keyswitch" | "Balafon - Soft Mallet" | "Balafon - Traditional Mallet" | "Tubular Bells 1" | "Tubular Bells 2" | "Vibraphone - Hard Mallets" | "Vibraphone - Keyswitch" | "Vibraphone - Soft Mallets" | "Xylophone - Hard Mallets" | "Xylophone - Keyswitch" | "Xylophone - Medium Mallets" | "Xylophone - Soft Mallets")[];
197
+ declare class Mallet extends SfzSampler {
198
+ constructor(context: AudioContext, options: Partial<SfzSamplerConfig> & {
199
+ instrument: string;
200
+ });
201
+ }
202
+ declare const DATA: {
203
+ readonly "Balafon - Hard Mallet": "Struck Idiophones/balafon-hard-mallet";
204
+ readonly "Balafon - Keyswitch": "Struck Idiophones/balafon-keyswitch";
205
+ readonly "Balafon - Soft Mallet": "Struck Idiophones/balafon-soft-mallet";
206
+ readonly "Balafon - Traditional Mallet": "Struck Idiophones/balafon-traditional-mallet";
207
+ readonly "Tubular Bells 1": "Struck Idiophones/tubular-bells-1";
208
+ readonly "Tubular Bells 2": "Struck Idiophones/tubular-bells-2";
209
+ readonly "Vibraphone - Hard Mallets": "Struck Idiophones/vibraphone-hard-mallets";
210
+ readonly "Vibraphone - Keyswitch": "Struck Idiophones/vibraphone-keyswitch";
211
+ readonly "Vibraphone - Soft Mallets": "Struck Idiophones/vibraphone-soft-mallets";
212
+ readonly "Xylophone - Hard Mallets": "Struck Idiophones/xylophone-hard-mallets";
213
+ readonly "Xylophone - Keyswitch": "Struck Idiophones/xylophone-keyswitch";
214
+ readonly "Xylophone - Medium Mallets": "Struck Idiophones/xylophone-medium-mallets";
215
+ readonly "Xylophone - Soft Mallets": "Struck Idiophones/xylophone-soft-mallets";
216
+ };
217
+
218
+ declare const PARAMS: readonly ["preDelay", "bandwidth", "inputDiffusion1", "inputDiffusion2", "decay", "decayDiffusion1", "decayDiffusion2", "damping", "excursionRate", "excursionDepth", "wet", "dry"];
219
+ declare class Reverb {
220
+ #private;
221
+ readonly input: AudioNode;
222
+ constructor(context: AudioContext);
223
+ get paramNames(): readonly ["preDelay", "bandwidth", "inputDiffusion1", "inputDiffusion2", "decay", "decayDiffusion1", "decayDiffusion2", "damping", "excursionRate", "excursionDepth", "wet", "dry"];
224
+ getParam(name: (typeof PARAMS)[number]): AudioParam | undefined;
225
+ get isReady(): boolean;
226
+ ready(): Promise<this>;
227
+ connect(output: AudioNode): void;
228
+ }
229
+
230
+ type SoundfontConfig = {
231
+ library: SoundfontLibrary | LibraryUrlBuilder;
232
+ instrument: string;
233
+ destination: AudioNode;
234
+ detune: number;
235
+ volume: number;
236
+ velocity: number;
237
+ decayTime?: number;
238
+ lpfCutoffHz?: number;
239
+ extraGain?: number;
240
+ };
241
+ declare class Soundfont extends Sampler {
242
+ constructor(context: AudioContext, options: Partial<SoundfontConfig> & {
243
+ instrument: string;
244
+ });
245
+ }
246
+ declare function gleitzKitUrl(kit: "FluidR3_GM" | "MusyngKite", name: string, format: string): string;
247
+ type LibraryUrlBuilder = (instrument: string, format?: string) => string;
248
+ type SoundfontLibrary = {
249
+ name: string;
250
+ url: (name: string, format?: "ogg" | "mp3") => string;
251
+ instruments: string[];
252
+ };
253
+ declare const FluidR3: SoundfontLibrary;
254
+ declare const MusyngKite: SoundfontLibrary;
255
+ declare const SoundfontLibraries: Record<string, SoundfontLibrary>;
256
+
257
+ /**
258
+ * Splendid Grand Piano options
259
+ */
260
+ type SplendidGrandPianoConfig = {
261
+ baseUrl: string;
262
+ destination: AudioNode;
263
+ detune: number;
264
+ volume: number;
265
+ velocity: number;
266
+ decayTime?: number;
267
+ lpfCutoffHz?: number;
268
+ };
269
+ declare class SplendidGrandPiano extends Sampler {
270
+ constructor(context: AudioContext, options: Partial<SplendidGrandPianoConfig>);
271
+ }
272
+ declare const LAYERS: ({
273
+ name: string;
274
+ vel_range: number[];
275
+ cutoff: number;
276
+ samples: (string | number)[][];
277
+ } | {
278
+ name: string;
279
+ vel_range: number[];
280
+ samples: (string | number)[][];
281
+ cutoff?: undefined;
282
+ })[];
283
+
284
+ export { DATA, DrumMachine, DrumMachineConfig, ElectricPiano, FluidR3, LAYERS, LibraryUrlBuilder, Mallet, MusyngKite, Reverb, Sampler, SamplerAudioLoader, SamplerConfig, SamplerNote, Soundfont, SoundfontConfig, SoundfontLibraries, SoundfontLibrary, SplendidGrandPiano, SplendidGrandPianoConfig, getDrumMachineNames, getElectricPianoNames, getMalletNames, gleitzKitUrl };