orchestre-js 2.1.1 → 3.1.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
@@ -2,15 +2,27 @@
2
2
 
3
3
  ![Logo](./doc/logo.png)
4
4
 
5
- Audio tool to create adaptive and interactive music.
5
+ _Orchestre-JS_ is a web audio tool to create adaptive and interactive music.
6
6
 
7
- [View demo](https://clementrivaille.github.io/orchestre-js/)
7
+ - 🎶 **Vertical Layering:** Manage songs with several instruments and toggle them on and off
8
+ - 🎼 **Seamless Loops:** Play tracks in loop while keeping them in rhythm and preserving their decay
9
+ - 🥁 **Sync to the Beat:** Subscribe to events and trigger actions on the rhythm
10
+ - 🌊 **Syncopation:** Layer loops of different duration to create rich patterns
11
+ - ⏳️ **Scheduling:** Prepare the start and stop of instruments to make change just when you need to
12
+ - 💻️ **Developer Friendly:** Everything is done through code, no extra tool to learn
13
+ - ☁️ **Zero Dependency:** Works with all modern browsers
14
+ - 🌐 **Web Audio API Compatible:** Plug your music to any effects
8
15
 
9
- Orchestre-JS is an audio library for managing music loops with several layers. It can be used to dynamically add and remove instruments in a song, play sounds in rhythm, transitioning through verses, or call events on beats. Orchestre-JS aims to provides a simple way to create dynamic soundtracks for your web games or applications.
16
+ Orchestre-JS is the perfect solution for **managing music in your web game or application**. Its modularity gives you the basic tools to play your music and design all kind of dynamic system around it. Its goal is to be easy to use, while powerful enough to support complex compositions.
10
17
 
11
- Orchestre-JS has no external dependencies and uses only the native Web Audio API. It should be compatible with most modern browsers. You can also plug it to custom audio applications (see the [Web Audio API](#using-the-web-audio-api) section).
18
+ [See the demo](https://clementrivaille.github.io/orchestre-js/) to learn all of its features.
19
+
20
+ You can also see it in action here:
21
+
22
+ - [Starseed Harmonies](https://starseed-harmonies.clementrivaille.fr): A musical garden made with Svelte
23
+ - [Echoes Traveler](https://itooh.itch.io/echoes-traveler): Music exploration game made with Phaser
24
+ - [Blood Not Allowed](https://itooh.itch.io/blood-not-allowed): A musical story created with Twine
12
25
 
13
- If you want to see the library in action, you can check out those games: [Echoes Traveler](https://itooh.itch.io/echoes-traveler), [Blood Not Allowed](https://itooh.itch.io/blood-not-allowed) (made with Twine), and [Step Out](https://itooh.itch.io/step-out).
14
26
  If you use Orchestre-JS in your creations, I would be really glad to see them! Feel free to show them to me.
15
27
 
16
28
  ## Install
@@ -29,15 +41,13 @@ Download the [latest release](https://github.com/ClementRivaille/orchestre-js/re
29
41
 
30
42
  ```javascript
31
43
  import { Orchestre } from 'orchestre-js';
32
- // or
33
- const { Orchestre } = require('orchestre-js');
34
44
  ```
35
45
 
36
- ## How to use
46
+ ## Basic Usage
37
47
 
38
48
  ### Create an orchestra
39
49
 
40
- The first thing you need is to create an _“orchestre”_ (French for orchestra, if you hadn't figured it out yet). The only thing it needs is the song's BPM (beats per minute).
50
+ The first thing you need is to create an _“Orchestre”_ (French for orchestra, as you've probably already figured). The only thing it needs is the song's BPM (beats per minute).
41
51
 
42
52
  ```javascript
43
53
  const orchestra = new Orchestre(120);
@@ -45,29 +55,23 @@ const orchestra = new Orchestre(120);
45
55
 
46
56
  ### Add players
47
57
 
48
- Then, you will need to add some players. Each player corresponds to one track. For one player, you need:
58
+ Then, you will need to register your song's separate tracks. In Orchestre-JS, those are called _Players_. Each player needs:
49
59
 
50
60
  - A unique **name** that will identify it
51
61
  - The **URL** of the sound file it will play
52
62
  - The **length** in beats of the track
53
- - An optional **absolute** boolean if you want the track to be played on bars
54
63
 
55
64
  _Be aware that you need a local server to request files_.
56
65
 
57
- For example, in a 4/4 signature, a track of one bar would have a length of 4, two bars would be 8, etc… But you can also use a track of one bar and three beats (7) and make it phase as it loops!
58
-
59
- What does the **absolute** option means? By default, a player is relative, which means that it will play from its track beginning when it starts, no matter where we are in the song. Absolute players, on the other hand, will calculate their offset relatively from the start of the song. Which mean that every absolute players will always play together. This is useful for players that set the chords or main melodies, generally playing several bars.
60
-
61
- Here is a diagram to better understand what absolute means. Each player here has a length of 4 beats, and are activated at the same time. See how the relative one starts right on the first beat, while the absolute one starts from the second beat.
62
- ![Absolute vs relative diagram](doc/absolute-diagram.png)
66
+ The _length_ is the number of fourth notes in the track. For example, in a 4/4 signature, a track of one bar would have a length of 4, one of two bars would have 8, etc… You can also use a track of one bar and three beats (which gives a total of 7) and make it phases as it loops!
63
67
 
64
68
  To add a single player, use:
65
69
 
66
70
  ```javascript
67
- await orchestra.addPlayer('bass', './assets/music/bass.ogg', 16, true);
71
+ await orchestra.addPlayer('bass', './assets/music/bass.ogg', 16);
68
72
  ```
69
73
 
70
- `addPlayer` returns a promise that resolves once the sound file has been fetched. A player can't be used until being fully loaded.
74
+ `addPlayer` returns a promise that resolves once the sound file has been fetched. A player can't be used until it is fully loaded.
71
75
 
72
76
  However, you might want to use more than a single player! Therefore, you should use the `addPlayers` function, which takes an array of player configurations, and load them all:
73
77
 
@@ -77,13 +81,11 @@ const players = [
77
81
  name: 'chords',
78
82
  url: './assets/music/chords.ogg',
79
83
  length: 16,
80
- absolute: true,
81
84
  },
82
85
  {
83
86
  name: 'bass',
84
87
  url: './assets/music/bass.ogg',
85
88
  length: 16,
86
- absolute: true,
87
89
  },
88
90
  {
89
91
  name: 'guitar',
@@ -105,9 +107,9 @@ Speaking of which, here is how it's done:
105
107
  orchestra.start();
106
108
  ```
107
109
 
108
- This won't play any sound yet. But it will initiate a metronome, that will set the beginning of the music, and count each beat based on the BPM.
110
+ This won't play any sound yet. But it will initiate a _metronome_, that will set the beginning of the music, and count each beat based on the BPM.
109
111
 
110
- However if you want to start with some tracks immediately, you can call `start` with an array of player names as parameter.
112
+ If you want to start with some tracks immediately, you can call `start` with an array of player names as parameter.
111
113
 
112
114
  ```javascript
113
115
  orchestra.start(['bass', 'chords']);
@@ -121,30 +123,69 @@ Once the orchestra has been loaded, you can activate your players:
121
123
  orchestra.play('guitar');
122
124
  ```
123
125
 
124
- And to stop them:
126
+ To stop them, use:
125
127
 
126
128
  ```javascript
127
129
  orchestra.stop('guitar');
128
130
  ```
129
131
 
130
- Players will start and stop on the next beat, and automatically stay in rhythm, according to their type (relative or absolute). It's as simple as that!
132
+ Players will start and stop **on the next beat**. They will then stay in rhythm according to their type (relative or absolute). It's as simple as that!
131
133
 
132
134
  You don't have to worry if your player is already playing or not. If you call `play` when a player is already active, or stop when it isn't, nothing will happen. Thus you can use `play` and `stop` to make sure the player is in the right state.
133
135
 
134
- You can also call the function `toggle`, that just changes the player position between play and stop.
136
+ You can also call the function `toggle`, that just changes the player's state between play and stop.
135
137
 
136
138
  ```javascript
137
139
  orchestra.toggle('guitar');
138
140
  ```
139
141
 
142
+ See below for more [playing options](#playing-options)
143
+
144
+ You now know the basics of Orchestre-JS. But it provides more tools to design a dynamic music system! Let's explore them.
145
+
146
+ ## Features
147
+
148
+ ### Absolute & relative player position
149
+
150
+ When adding a player, you can configure its **position** as either _"absolute"_ or _"relative"_. By default, it's _absolute_.
151
+
152
+ ```javascript
153
+ await orchestra.addPlayer('melody', './assets/music/melody.ogg', 8, 'relative');
154
+ // or
155
+ await orchestra.addPlayers([
156
+ {
157
+ name: 'melody',
158
+ url: './assets/music/melody.ogg',
159
+ length: 8,
160
+ position: 'relative',
161
+ },
162
+ ]);
163
+ ```
164
+
165
+ What does this position mean? When you call the `play` method, **an absolute players will start with an offset so that it is always aligned in the song**. It's as if they were already playing silently and they volume has just been turned up. It's probably what you expect for an adaptive song.
166
+
167
+ **A relative player however will always start from its beginning**, no matter when you're calling `play`. It will still be on beat and in rhythm. But they won't be aligned on global a global sheet: they can play at any beat of any bar (with any length even).
168
+
169
+ Here is a diagram to better understand how it works. Each player here has a length of 4 beats, and are activated at the same time. See how the absolute player starts already in sync with the bar, while the relative one starts on its first beat.
170
+
171
+ ![Absolute vs relative diagram](doc/absolute-diagram.png)
172
+
173
+ Most of the time, you will want **absolute** players. They are guaranteed to stay aligned with each other, as they are playing on the bars of your song.
174
+
175
+ **Relative** players are useful for _stingers_: small melodic phrases played only once on an event (with the `once` option). They can also be used for complex generative compositions.
176
+
177
+ ### <a id="playing-options"></a>Playing options
178
+
140
179
  `play`, `stop` and `toggle` can take a second parameter _options_, which is an object that allows you to define some of those properties:
141
180
 
142
181
  - **fade** _(float)_: time constant in seconds for a fade in or fade out. The length of fading is approximately equal to 1.6 times your constant. See [setTargetAtTime](https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setTargetAtTime) for more details.
143
- - **now** _(bool)_: if true, sound will start / stop immediately instead of waiting for next beat. This is better used with fading.
144
- - **once** _(bool)_: for _play_ only. Play sound only once (instead of a loop), then stop.
145
- - **keep** _(bool)_: for _stop_ only. Keep playing the sound until its completion then stop looping.
182
+ - **now** _(bool)_: if true, sound will start / stop _immediately_ instead of waiting for next beat. This is better used with a fading.
183
+ - **once** _(bool)_: for _play_ only. Play the track only once (instead of a loop).
184
+ - **keep** _(bool)_: for _stop_ only. Keep playing the track until its completion, then stop looping.
146
185
 
147
- Finally, you can schedule an action on a player several beats in advance with the following method:
186
+ ### Scheduling
187
+
188
+ You can schedule a play/stop action on a player several beats in advance with the following method:
148
189
 
149
190
  ```javascript
150
191
  orchestra.schedule('bass', 4, 'toggle'); // bass will be toggled after the next 4 beats
@@ -153,51 +194,53 @@ orchestra.schedule('guitar', 8, 'play', { absolute: true }); // guitar will play
153
194
 
154
195
  _Warning:_ Once an action has been scheduled, it can't be cancelled.
155
196
 
156
- ### Trigger events
197
+ ### Events
198
+
199
+ Orchestre-JS provide several methods for subscribing to the beat of the music.
157
200
 
158
- To wait one or more beat before executing an event, you can use the `wait` method:
201
+ To **subscribe to a beat interval**, use `addListener`. It takes a _callback_ and the _length_ of the interval in beats.
159
202
 
160
203
  ```javascript
161
- await orchestra.wait(2); // Waits 2 beats
204
+ // Called every beat
205
+ const listenerId = orchestra.addListener(() => {
206
+ /* Do something */
207
+ }, 1);
208
+ // Called every 4 beats (starting from now)
209
+ const listenerId2 = orchestra.addListener(() => {
210
+ /* Do something */
211
+ }, 4);
162
212
  ```
163
213
 
164
- `wait` takes also a second _options_ parameter:
214
+ `addListener` takes also a second _options_ parameter:
165
215
 
166
- - **absolute** _(bool)_: wait for the next bar of n beats
216
+ - **absolute** _(bool)_: listen to absolute bars of n beats
167
217
  - **offset** _(number)_: use with absolute to set a position in the bar
168
218
 
169
- If you want to regularly perform an action, use `addListener`. It takes a callback, the length of the interval in beats, and the same options as _wait_. Here is how to call an event at the third note of every bar of 4 beats:
219
+ For example, `addListener(cb, 4, { absolute: true })`, will trigger on every bar of 4 beat, while `addListener(cb, 4, { absolute: true, offset: 1 })` will trigger on the second beat of the bars (index "1").
220
+
221
+ To remove a listener, use `removeListener` with its id:
170
222
 
171
223
  ```javascript
172
- const listenerId = orchestra.addListener(
173
- () => {
174
- /* Do something */
175
- },
176
- 4,
177
- {
178
- absolute: true,
179
- offset: 2,
180
- }
181
- );
224
+ orchestra.removeListener(listenerId);
182
225
  ```
183
226
 
184
- To remove a listener, use `removeListener` with its id:
227
+ If you want to **trigger an event just once**, you can use the `wait` method. It takes the number of beats to wait, and the same options as `addListener`. It then returns a Promise that resolves once the beat is reached.
185
228
 
186
229
  ```javascript
187
- orchestra.removeListener(listenerId);
230
+ await orchestra.wait(2); // Waits 2 beats
231
+ await orchestra.wait(4, { absolute: true }); // Waits for next bar of 4
232
+ await orchestra.wait(4, { absolute: true, offset: 2 }); // Waits for next 3rd beat in a bar
188
233
  ```
189
234
 
190
235
  ### Stop
191
236
 
192
- Once you are done with your song, you can call `fullStop` on the orchestra to immediately stop all the instruments, and stop the metronome.
237
+ Once you are done with your song, you can call `fullStop` on the orchestra to immediately stop all the instruments as well as its metronome.
193
238
 
194
239
  ```javascript
195
240
  orchestra.fullStop();
196
241
  ```
197
242
 
198
- Note that the orchestra will need to be started to be used again.
199
-
200
- That's it! You know all the basics of Orchestre-JS.
243
+ The orchestra will need to be started to be used again.
201
244
 
202
245
  ## Accessibility
203
246
 
@@ -208,18 +251,17 @@ In order for your application to be accessible to anyone (including users with s
208
251
 
209
252
  Orchestre-JS provides some functions that you can use in that order.
210
253
 
211
- You can pause your orchestra by calling `orchestre.suspend()`, and start it again with `orchestre.resume()`. This will immediately interrupt all players and the metronome. Calling `resume` will make it start just where it stopped.
254
+ You can **pause the orchestra** by calling `orchestre.suspend()`, and start it again with `orchestre.resume()`. This will immediately interrupt all players and the metronome. Calling `resume` will make it start just where it was.
212
255
 
213
- You can change the volume of the whole orchestre with `orchestre.setVolume(value)`, where value is a float between 0 and 1 (or higher, but this is at your own risk). _Do not use this method for a fade out_ or any other effect. It has been intended for giving users a way to change volume, and therefore is applied immediately.
256
+ You can **change the volume** of the whole orchestre with `orchestre.setVolume(value)`, where _value_ is a float between 0 and 1 (or higher, but this is at your own risk). _Do not use this method for a fade out_ or any other effect. It has been intended for giving users a way to change volume, and therefore is applied immediately.
214
257
 
215
258
  ## Advanced
216
259
 
217
260
  ### Using the Web Audio API
218
261
 
219
- Orchestre-JS uses the _Web Audio API_. You don't need to know how to use it to use Orchestre-JS, but as always, it can help.
220
- If you're more advanced with the Web Audio API, you might want to have some more complex usage of Orchestre-JS. Here are some options at your disposition.
262
+ Orchestre-JS uses the _Web Audio API_. You don't need to have experience with it to use Orchestre-JS. But knowing some if its basics can allow you to extend the possibilities that the lib offers. Here are some connecting options at your disposition.
221
263
 
222
- By default, every new orchestra creates its own audio context. But you can pass your own as a second argument.
264
+ By default, every new Orchestre creates its own audio context. But you can pass your own as a second argument.
223
265
 
224
266
  ```javascript
225
267
  const context = new (window.AudioContext || window.webkitAudioContext)();
@@ -228,15 +270,15 @@ const orchestra = new Orchestre(120, context);
228
270
 
229
271
  You can also access the audio context from the `context` property of the orchestra.
230
272
 
231
- Players are by default connected to the orchestra's master gain (`orchestre.master`), which is connected to the context's destination. But if you want to connect them to your own node (for example to add a reverb effect), you can change that with the `destination` parameter.
273
+ Players are by default connected to the orchestra's master gain (`orchestre.master`), which is connected to the context's destination. But if you want to **connect players to your own nodes**, you can change that with the `destination` parameter.
232
274
 
233
275
  ```javascript
234
276
  await orchestra.addPlayer(
235
277
  'bass',
236
278
  './assets/music/bass.ogg',
237
279
  16,
238
- true,
239
- myAudioNode
280
+ 'absolute',
281
+ myAudioNode,
240
282
  );
241
283
  // Or
242
284
  await orchestra.addPlayers([
@@ -249,7 +291,9 @@ await orchestra.addPlayers([
249
291
  ]);
250
292
  ```
251
293
 
252
- Alternatively, you can connect or disconnect players dynamically:
294
+ This allows you to **add effects on individual players** (like panning or reverb) or analyse their output.
295
+
296
+ Alternatively, you can also connect or disconnect players after they have been added:
253
297
 
254
298
  ```javascript
255
299
  orchestra.connect('bass', myAudioNode);
@@ -257,7 +301,7 @@ orchestra.disconnect('bass', myAudioNode);
257
301
  orchestra.disconnect('bass'); // Will disconnect from every nodes
258
302
  ```
259
303
 
260
- _Warning_: If a player is not connected to master, it is no longer affected by the `setVolume` method. The best practice is to connect your final node to `orchestre.master` so that it can be affected by the orchestra's volume.
304
+ _Warning_: If a player is not connected to `orchestre.master`, it is no longer affected by the `setVolume` method. The best practice is to connect your final node to `orchestre.master` so that it can be affected by the orchestra's volume.
261
305
 
262
306
  ```javascript
263
307
  orchestra.connect('bass', myAudioNode);
@@ -266,24 +310,42 @@ myAudioNode.connect(orchestra.master);
266
310
 
267
311
  ### Metronome
268
312
 
269
- Orchestre-JS orchestra uses a metronome to sync all tracks. In most use cases, you don't need to interact with it. You can still access it from the `metronome` property of a created Orchestre.
313
+ Orchestre-JS orchestra uses a metronome to sync all tracks. In most use cases, you don't need to interact with it. But you can still access it from the `metronome` property of a created Orchestre.
270
314
 
271
- The metronome gives you access to the property `beatsLength`, which is the length of a beat in seconds. _Beats_ are the tiniest unit of time calculated. If you want to be more precise, the better is to adapt your BPM (like setting it to the double of the actual BPM to count eighth notes).
315
+ The metronome gives you access to the property `beatsLength`, which is the length of a beat in seconds. _Beats_ are the tiniest unit of time calculated. By default, they correspond to fourth notes. If you want to be more precise, **the better is to multiply your BPM** (doubling the BPM for example will align beats to eighth notes).
272
316
 
273
317
  Here are some metronome's methods you can use :
274
318
 
275
319
  - `getNextBeatTime(): float` gives you the time, in second, of the next beat
276
320
  - `getNextNthBeatTime(beats: number): float` gives you the time, in second, of the next nth beat
277
321
  - `getOffset(time: float): float` gives in seconds the offset of the given time relatively to the closest beat before it
322
+ - `getTimeBeforeBeat(beat: number = 1): float` gives in seconds the time remaining before the next nth beat
278
323
  - `getBeatPosition(time: float, barSize: number): number` for absolute bars of _barSize_ beats, gives the position of the given time. For example, for a bar of 4 beat, results may go from 0 (first beat) to 3 (last beat).
324
+ - `getBeatsToBar(barSize: number, bars: number = 1): number` gives the beats remaining before the next nth bar of the given size
279
325
 
280
326
  For the simple tasks though (such as counting the position in a bar), I would advise not to use these functions and instead use the `addListener` method on the orchestra to manage your own counters.
281
327
 
328
+ ### Duplicating an Orchestre
329
+
330
+ Sometime you might need another instance of an Orchestre, with the same players. This can be useful for horizontal layering. For example: starting the second Orchestre when the first one reaches its 8th bar, or maybe even inserting a transition track between them. But in this case, recreating a new Orchestre and loading all the players _again_ isn't ideal. This would duplicates the queries for the sound files.
331
+
332
+ For that purpose, you can create a **copy** of an Orchestre from an existing instance, with the static method `from`:
333
+
334
+ ```javascript
335
+ const copy = Orchestre.from(orchestre);
336
+ ```
337
+
338
+ The copy will have the **same players as the originals** and share the **same audio context**. You can still add new players afterward. On creation, it will be stopped, even if the original is playing. You can start it when you want with `.start`. It is a truly independent new Orchestre, with its own metronome and volume.
339
+
340
+ Players' destinations are also preserved _if_ you specified it in their configuration (in `addPlayer` or `addPlayers`). But otherwise, if you used `.connect` after their creation, the copy's players won't be linked to these new targets. Likewise, the Orchestre copy is directly connected to `context.destination`, regardless of the original's master target.
341
+
342
+ Subscriptions made with `addListeners` are also not included in the copy. They will still be triggered by the first Orchestre. To keep the alignment with the copy (if the first Orchestre is silent or stopped), the best is to recreate the listeners once the copy is started.
343
+
282
344
  ## API
283
345
 
284
346
  [API Documentation](doc/api.md)
285
347
 
286
348
  ## Troubleshooting
287
349
 
288
- - **My players loop too early / too late**: Make sure that the BPM you provided to your Orchestre matches your song's one, and that you wrote the correct number of beats in your loop in the player's _length_. For example, 4 bars in 4/4 will have a length of 16. Orchestre will use these values to loop your tracks, and ignore their actual length. This allows not only to keep them synchronized to the rhythm, but also to make them overlap if they have reverb or delay at the end.
350
+ - **My players loop too early / too late**: Make sure that the BPM you provided to your Orchestre matches your song's one, and that you wrote the correct number of beats in your loop in the player's _length_. For example, 4 bars in 4/4 will have a length of 16. Orchestre will use these values to loop your tracks, regardless of the audio file's actual length. This allows not only to keep them synchronized to the rhythm, but also to make them overlap if they have reverb or delay at the end.
289
351
  - **My audio files don't play:** Make sure that you wrote the correct folder and name in the _url_ property of the player, and that this file is accessible. You can check its download in your browser's devtools. If you see another error in the console, refer to the Web Audio API documentation. Some browser might not accept all formats! You should be safe with .ogg, .wav or .mp3 though.