cacophony 0.1.3 → 0.1.5
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 +2 -2
- package/package.json +1 -1
- package/src/cacophony.ts +110 -30
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ async function playSampleSound() {
|
|
|
32
32
|
const sound = await cacophony.createSound('path/to/your/audio/file.mp3');
|
|
33
33
|
|
|
34
34
|
sound.play();
|
|
35
|
-
sound.
|
|
35
|
+
sound.position = [1, 1, 1]; // Position the sound in 3D space
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
playSampleSound();
|
|
@@ -94,7 +94,7 @@ All classes representing sound sources (`Sound`, `Playback`, `Group`) offer the
|
|
|
94
94
|
- `resume()`
|
|
95
95
|
- `addFilter(filter: BiquadFilterNode)`
|
|
96
96
|
- `removeFilter(filter: BiquadFilterNode)`
|
|
97
|
-
- `
|
|
97
|
+
- `position`: A tuple `[x: number, y: number, z: number]` representing the position of the sound in 3D space, with both getter and setter.
|
|
98
98
|
- `loop(loopCount?: LoopCount)`: `LoopCount` can be a finite number or 'infinite'.
|
|
99
99
|
|
|
100
100
|
## License
|
package/package.json
CHANGED
package/src/cacophony.ts
CHANGED
|
@@ -8,7 +8,9 @@ type BiquadFilterNode = IBiquadFilterNode<AudioContext>;
|
|
|
8
8
|
type AudioBufferSourceNode = IAudioBufferSourceNode<AudioContext>;
|
|
9
9
|
type PannerNode = IPannerNode<AudioContext>;
|
|
10
10
|
|
|
11
|
-
type
|
|
11
|
+
export type Position = [number, number, number];
|
|
12
|
+
|
|
13
|
+
export type LoopCount = number | 'infinite';
|
|
12
14
|
|
|
13
15
|
export type FadeType = 'linear' | 'exponential'
|
|
14
16
|
|
|
@@ -20,9 +22,8 @@ export interface BaseSound {
|
|
|
20
22
|
resume(): void;
|
|
21
23
|
addFilter(filter: BiquadFilterNode): void;
|
|
22
24
|
removeFilter(filter: BiquadFilterNode): void;
|
|
23
|
-
moveTo(x: number, y: number, z: number): void;
|
|
24
25
|
volume: number;
|
|
25
|
-
|
|
26
|
+
position: Position;
|
|
26
27
|
loop(loopCount?: LoopCount): LoopCount;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -138,7 +139,7 @@ export class Sound extends FilterManager implements BaseSound {
|
|
|
138
139
|
context: AudioContext;
|
|
139
140
|
playbacks: Playback[] = [];
|
|
140
141
|
globalGainNode: GainNode;
|
|
141
|
-
|
|
142
|
+
private _position: Position = [0, 0, 0];
|
|
142
143
|
loopCount: LoopCount = 0;
|
|
143
144
|
|
|
144
145
|
constructor(buffer: AudioBuffer, context: AudioContext, globalGainNode: IGainNode<AudioContext>) {
|
|
@@ -146,6 +147,7 @@ export class Sound extends FilterManager implements BaseSound {
|
|
|
146
147
|
this.buffer = buffer;
|
|
147
148
|
this.context = context;
|
|
148
149
|
this.globalGainNode = globalGainNode;
|
|
150
|
+
this._position = [0, 0, 0];
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
preplay(): Playback[] {
|
|
@@ -156,14 +158,14 @@ export class Sound extends FilterManager implements BaseSound {
|
|
|
156
158
|
gainNode.connect(this.globalGainNode);
|
|
157
159
|
const playback = new Playback(source, gainNode, this.context, this.loopCount);
|
|
158
160
|
this.filters.forEach(filter => playback.addFilter(filter));
|
|
159
|
-
playback.
|
|
161
|
+
playback.position = this.position;
|
|
160
162
|
this.playbacks.push(playback);
|
|
161
163
|
return [playback];
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
play(): Playback[] {
|
|
165
167
|
const playback = this.preplay();
|
|
166
|
-
playback.forEach(p => p.source
|
|
168
|
+
playback.forEach(p => p.source!.start());
|
|
167
169
|
return playback;
|
|
168
170
|
}
|
|
169
171
|
|
|
@@ -183,9 +185,13 @@ export class Sound extends FilterManager implements BaseSound {
|
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
this.
|
|
188
|
-
this.playbacks.forEach(p => p.
|
|
188
|
+
set position(position: Position) {
|
|
189
|
+
this._position = position;
|
|
190
|
+
this.playbacks.forEach(p => p.position = this._position);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get position(): Position {
|
|
194
|
+
return this._position;
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
loop(loopCount?: LoopCount): LoopCount {
|
|
@@ -193,7 +199,7 @@ export class Sound extends FilterManager implements BaseSound {
|
|
|
193
199
|
return this.loopCount;
|
|
194
200
|
}
|
|
195
201
|
this.loopCount = loopCount;
|
|
196
|
-
this.playbacks.forEach(p => p.source
|
|
202
|
+
this.playbacks.forEach(p => p.source!.loop = true);
|
|
197
203
|
return this.loopCount;
|
|
198
204
|
}
|
|
199
205
|
|
|
@@ -219,9 +225,9 @@ export class Sound extends FilterManager implements BaseSound {
|
|
|
219
225
|
|
|
220
226
|
class Playback extends FilterManager implements BaseSound {
|
|
221
227
|
context: AudioContext;
|
|
222
|
-
source
|
|
223
|
-
gainNode
|
|
224
|
-
panner
|
|
228
|
+
source?: AudioBufferSourceNode;
|
|
229
|
+
gainNode?: GainNode;
|
|
230
|
+
panner?: PannerNode;
|
|
225
231
|
loopCount: LoopCount = 0;
|
|
226
232
|
currentLoop: number = 0;
|
|
227
233
|
buffer: IAudioBuffer | null = null;
|
|
@@ -250,36 +256,68 @@ class Playback extends FilterManager implements BaseSound {
|
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
play() {
|
|
259
|
+
if (!this.source) {
|
|
260
|
+
throw new Error('Cannot play a sound that has been cleaned up');
|
|
261
|
+
}
|
|
253
262
|
this.source.start();
|
|
254
263
|
return [this];
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
get volume(): number {
|
|
267
|
+
if (!this.gainNode) {
|
|
268
|
+
throw new Error('Cannot get volume of a sound that has been cleaned up');
|
|
269
|
+
}
|
|
258
270
|
return this.gainNode.gain.value;
|
|
259
271
|
}
|
|
260
272
|
|
|
261
273
|
set volume(v: number) {
|
|
274
|
+
if (!this.gainNode) {
|
|
275
|
+
throw new Error('Cannot set volume of a sound that has been cleaned up');
|
|
276
|
+
}
|
|
262
277
|
this.gainNode.gain.value = v;
|
|
263
278
|
}
|
|
264
279
|
|
|
265
|
-
fadeIn(time: number,
|
|
280
|
+
fadeIn(time: number, fadeType: FadeType = 'linear'): Promise<void> {
|
|
266
281
|
return new Promise(resolve => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
282
|
+
if (!this.gainNode) {
|
|
283
|
+
throw new Error('Cannot fade in a sound that has been cleaned up');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const initialVolume = this.gainNode.gain.value;
|
|
287
|
+
const targetVolume = 1; // Assuming the target volume after fade-in is 1 (full volume)
|
|
288
|
+
|
|
289
|
+
// Reset volume to 0 to start the fade-in process
|
|
290
|
+
this.gainNode.gain.value = 0;
|
|
291
|
+
|
|
292
|
+
switch (fadeType) {
|
|
293
|
+
case 'exponential':
|
|
294
|
+
// Start at a low value (0.01) because exponentialRampToValueAtTime cannot ramp from 0
|
|
295
|
+
this.gainNode.gain.setValueAtTime(0.01, this.context.currentTime);
|
|
296
|
+
this.gainNode.gain.exponentialRampToValueAtTime(targetVolume, this.context.currentTime + time);
|
|
297
|
+
break;
|
|
298
|
+
case 'linear':
|
|
299
|
+
this.gainNode.gain.linearRampToValueAtTime(targetVolume, this.context.currentTime + time);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Resolve the Promise after the fade-in time
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
// Ensure the final volume is set to the target volume
|
|
306
|
+
if (!this.gainNode) {
|
|
307
|
+
throw new Error('Cannot fade in a sound that has been cleaned up');
|
|
275
308
|
}
|
|
276
|
-
|
|
309
|
+
this.gainNode.gain.value = targetVolume;
|
|
310
|
+
resolve();
|
|
311
|
+
}, time * 1000);
|
|
277
312
|
});
|
|
278
313
|
}
|
|
279
314
|
|
|
280
315
|
fadeOut(time: number, fadeType: FadeType = 'linear'): Promise<void> {
|
|
281
316
|
return new Promise(resolve => {
|
|
282
317
|
// Storing the current gain value
|
|
318
|
+
if (!this.gainNode) {
|
|
319
|
+
throw new Error('Cannot fade out a sound that has been cleaned up');
|
|
320
|
+
}
|
|
283
321
|
const initialVolume = this.gainNode.gain.value;
|
|
284
322
|
switch (fadeType) {
|
|
285
323
|
case 'exponential':
|
|
@@ -296,7 +334,23 @@ class Playback extends FilterManager implements BaseSound {
|
|
|
296
334
|
});
|
|
297
335
|
}
|
|
298
336
|
|
|
337
|
+
cleanup(): void {
|
|
338
|
+
if (this.source) {
|
|
339
|
+
this.source.disconnect();
|
|
340
|
+
this.source = undefined;
|
|
341
|
+
}
|
|
342
|
+
if (this.gainNode) {
|
|
343
|
+
this.gainNode.disconnect();
|
|
344
|
+
this.gainNode = undefined;
|
|
345
|
+
}
|
|
346
|
+
this.filters.forEach(filter => filter.disconnect());
|
|
347
|
+
this.filters = [];
|
|
348
|
+
}
|
|
349
|
+
|
|
299
350
|
loop(loopCount?: LoopCount): LoopCount {
|
|
351
|
+
if (!this.source) {
|
|
352
|
+
throw new Error('Cannot loop a sound that has been cleaned up');
|
|
353
|
+
}
|
|
300
354
|
if (loopCount === undefined) {
|
|
301
355
|
return this.source.loop === true ? 'infinite' : 0;
|
|
302
356
|
}
|
|
@@ -307,16 +361,25 @@ class Playback extends FilterManager implements BaseSound {
|
|
|
307
361
|
}
|
|
308
362
|
|
|
309
363
|
stop(): void {
|
|
364
|
+
if (!this.source) {
|
|
365
|
+
throw new Error('Cannot stop a sound that has been cleaned up');
|
|
366
|
+
}
|
|
310
367
|
this.source.stop();
|
|
311
368
|
}
|
|
312
369
|
|
|
313
370
|
pause(): void {
|
|
371
|
+
if (!this.source) {
|
|
372
|
+
throw new Error('Cannot pause a sound that has been cleaned up');
|
|
373
|
+
}
|
|
314
374
|
if ('suspend' in this.source.context) {
|
|
315
375
|
this.source.context.suspend();
|
|
316
376
|
}
|
|
317
377
|
}
|
|
318
378
|
|
|
319
379
|
resume(): void {
|
|
380
|
+
if (!this.source) {
|
|
381
|
+
throw new Error('Cannot resume a sound that has been cleaned up');
|
|
382
|
+
}
|
|
320
383
|
if ('resume' in this.source.context) {
|
|
321
384
|
this.source.context.resume();
|
|
322
385
|
}
|
|
@@ -332,13 +395,27 @@ class Playback extends FilterManager implements BaseSound {
|
|
|
332
395
|
this.refreshFilters();
|
|
333
396
|
}
|
|
334
397
|
|
|
335
|
-
|
|
398
|
+
set position(position: Position) {
|
|
399
|
+
if (!this.panner) {
|
|
400
|
+
throw new Error('Cannot move a sound that has been cleaned up');
|
|
401
|
+
}
|
|
402
|
+
const [x, y, z] = position;
|
|
336
403
|
this.panner.positionX.value = x;
|
|
337
404
|
this.panner.positionY.value = y;
|
|
338
405
|
this.panner.positionZ.value = z;
|
|
339
406
|
}
|
|
340
407
|
|
|
408
|
+
get position(): Position {
|
|
409
|
+
if (!this.panner) {
|
|
410
|
+
throw new Error('Cannot get position of a sound that has been cleaned up');
|
|
411
|
+
}
|
|
412
|
+
return [this.panner.positionX.value, this.panner.positionY.value, this.panner.positionZ.value];
|
|
413
|
+
}
|
|
414
|
+
|
|
341
415
|
private refreshFilters(): void {
|
|
416
|
+
if (!this.source || !this.gainNode) {
|
|
417
|
+
throw new Error('Cannot update filters on a sound that has been cleaned up');
|
|
418
|
+
}
|
|
342
419
|
let connection = this.source;
|
|
343
420
|
this.source.disconnect();
|
|
344
421
|
connection = this.applyFilters(connection);
|
|
@@ -349,7 +426,7 @@ class Playback extends FilterManager implements BaseSound {
|
|
|
349
426
|
|
|
350
427
|
export class Group implements BaseSound {
|
|
351
428
|
sounds: Sound[] = [];
|
|
352
|
-
|
|
429
|
+
private _position: Position = [0, 0, 0];
|
|
353
430
|
loopCount: LoopCount = 0;
|
|
354
431
|
|
|
355
432
|
addSound(sound: Sound): void {
|
|
@@ -403,18 +480,21 @@ export class Group implements BaseSound {
|
|
|
403
480
|
this.sounds.forEach(sound => sound.removeFilter(filter));
|
|
404
481
|
}
|
|
405
482
|
|
|
406
|
-
|
|
407
|
-
this.
|
|
408
|
-
this.sounds.forEach(sound => sound.
|
|
483
|
+
set position(position: [number, number, number]) {
|
|
484
|
+
this._position = position;
|
|
485
|
+
this.sounds.forEach(sound => sound.position = this._position);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
get position(): [number, number, number] {
|
|
489
|
+
return this._position;
|
|
409
490
|
}
|
|
410
491
|
|
|
411
492
|
get volume(): number {
|
|
412
|
-
return this.sounds
|
|
493
|
+
return this.sounds.map(sound => sound.volume).reduce((a, b) => a + b, 0) / this.sounds.length;
|
|
413
494
|
}
|
|
414
495
|
|
|
415
496
|
set volume(volume: number) {
|
|
416
497
|
this.sounds.forEach(sound => sound.volume = volume);
|
|
417
498
|
}
|
|
418
|
-
|
|
419
499
|
}
|
|
420
500
|
|