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 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.moveTo(1, 1, 1); // Position the sound in 3D space
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
- - `moveTo(x: number, y: number, z: number)`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cacophony",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Typescript audio library with caching",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
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 LoopCount = number | 'infinite';
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
- position: number[] = [0, 0, 0];
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.moveTo(this.position[0], this.position[1], this.position[2]);
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.start());
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
- moveTo(x: number, y: number, z: number): void {
187
- this.position = [x, y, z];
188
- this.playbacks.forEach(p => p.moveTo(x, y, z));
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.loop = true);
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: AudioBufferSourceNode;
223
- gainNode: GainNode;
224
- panner: PannerNode;
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, FfadeType: FadeType = 'linear'): Promise<void> {
280
+ fadeIn(time: number, fadeType: FadeType = 'linear'): Promise<void> {
266
281
  return new Promise(resolve => {
267
- let volume = 0;
268
- const increment = this.gainNode.gain.value / (time * 60); // Assuming time is in seconds
269
- const interval = setInterval(() => {
270
- volume += increment;
271
- this.gainNode.gain.value = volume;
272
- if (volume >= this.gainNode.gain.value) {
273
- clearInterval(interval);
274
- resolve();
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
- }, 1000 / 60); // 60 times per second
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
- moveTo(x: number, y: number, z: number): void {
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
- position: number[] = [0, 0, 0];
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
- moveTo(x: number, y: number, z: number): void {
407
- this.position = [x, y, z];
408
- this.sounds.forEach(sound => sound.moveTo(x, y, z));
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[0].volume;
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