Skip to content

Commit

Permalink
audio-source: Finish wrapping playableNode
Browse files Browse the repository at this point in the history
  • Loading branch information
timothyhale committed Mar 12, 2024
1 parent d2eea49 commit e5837a9
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 39 deletions.
17 changes: 14 additions & 3 deletions src/audio-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ export class PlayableNode {
* context is in a 'suspended' state, it unlocks the audio context before starting
* the playback.
*
* @async
* @param {Float32Array | PannerOptions} [config] - Optional configuration for audio playback.
* If not provided, the audio plays without panning.
* - If a Float32Array is provided, it is used as position coordinates for a PannerNode.
Expand Down Expand Up @@ -218,7 +217,7 @@ export class PlayableNode {
buffer: this._audioBuffer,
loop: this.loop,
});
if (config === undefined) {
if (!config) {
this._audioNode.connect(this._gainNode);
} else {
if (config instanceof Float32Array) {
Expand Down Expand Up @@ -276,6 +275,7 @@ export class PlayableNode {
* Stops the playback, and if set to destroy, removes associated audio file.
*/
stop() {
if (!this._isPlaying) return;
this._isPlaying = false;
/* This triggers the 'ended' listener and frees the resources */
this._audioNode.stop();
Expand Down Expand Up @@ -308,6 +308,13 @@ export class PlayableNode {
}, duration * 1000);
}

async changeSource(src: string) {
this.stop();
this._audioManager._remove(this._source);
this._audioBuffer = await this._audioManager._add(src);
this._source = src;
}

get emitter(): Emitter<[PlayState]> {
return this._emitter;
}
Expand Down Expand Up @@ -344,7 +351,11 @@ export class PlayableNode {
return this._volume;
}

updatePosition(dt: number, posVec: Float32Array, oriVec: Float32Array) {
updatePosition(
dt: number,
posVec: Float32Array,
oriVec: Float32Array = new Float32Array([0, 0, 1])
) {
if (!this._pannerNode) return;
const time = _audioContext.currentTime + dt;
this._pannerNode.positionX.linearRampToValueAtTime(posVec[0], time);
Expand Down
88 changes: 52 additions & 36 deletions src/audio-source.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import {Component, Emitter, WonderlandEngine} from '@wonderlandengine/api';
import {property} from '@wonderlandengine/api/decorators.js';
import {_audioContext, AudioListener} from './audio-listener.js';
import {globalAudioManager, PlayableNode, PlayState} from './audio-manager.js';
import {
AudioManager,
globalAudioManager,
PlayableNode,
PlayState,
} from './audio-manager.js';

/**
* Constants
*/
const posVec = new Float32Array(3);
const oriVec = new Float32Array(3);
const distanceModels = ['linear', 'exponential', 'inverse'];
const audioManager = new AudioManager();

/**
* Represents an audio src in the Wonderland Engine, allowing playback of audio files.
Expand All @@ -26,9 +32,8 @@ export class AudioSource extends Component {
/** Path to the audio file that should be played. */
@property.string()
src!: string;
/**
* Maximum volume a src can have. From 0 to 1 (0% to 100%).
*/

/** Volume of the audio source */
@property.float(1.0)
volume!: number;

Expand Down Expand Up @@ -83,15 +88,15 @@ export class AudioSource extends Component {
coneOuterGain!: number;

private _pannerOptions: PannerOptions = {};
private _hrtf: boolean = true;
private _playableNode!: PlayableNode;

/**
* Initializes the audio src component.
* If `autoplay` is enabled, the audio will start playing if the file is loaded.
*/
async start() {
this._playableNode = await globalAudioManager.load(this.src);
this._playableNode = await audioManager.load(this.src);
this._playableNode.volume = this.volume;
if (this.autoplay) {
this.play();
}
Expand All @@ -104,25 +109,40 @@ export class AudioSource extends Component {
*/
async play() {
const p = this._playableNode;
p.volume = this.volume;
p.loop = this.loop;
/* "+0" is necessary here to allow backwards compatability with howler,
* where spatial was either true or false */
// @ts-ignore
switch (this.spatial + 0) {
case 0:
p.play();
break;
case 1:
this._hrtf = false;
/* Fallthrough is wanted here, since the steps are the same otherwise. */
default:
this._updateSettings();
p.play(this._pannerOptions);
if (!this.isStationary) {
this.update = this._update.bind(this);
if (!this.spatial) {
p.play();
return;
}
this._updateSettings();
p.play(this._pannerOptions);
if (!this.isStationary) {
this.update = this._update.bind(this);
}
}

async playWithCrossfadeTransition(node: AudioSource | PlayableNode, duration: number) {
const source = node instanceof AudioSource ? node['_playableNode'] : node;
const p = this._playableNode;
p.loop = this.loop;
if (!this.spatial) {
p.playWithCrossfadeTransition(source, duration);
return;
}
this._updateSettings();
p.playWithCrossfadeTransition(source, duration, this._pannerOptions);
if (this.isStationary) {
return;
}
if (node instanceof AudioSource) {
/* The update function still needs to be disabled after playback */
node.emitter.once((state) => {
if (state == (PlayState.ENDED || PlayState.STOPPED)) {
node.stop();
}
});
}
this.update = this._update.bind(this);
}

/**
Expand All @@ -144,18 +164,12 @@ export class AudioSource extends Component {
return this._playableNode.emitter;
}

/**
* Sets the volume of this AudioSource gradually, while it is playing.
*
* @param v - Volume in float values 0.0 to 1.0.
* @param rampTime - Time until the volume has reached the specified value in seconds.
* @warning Setting the rampTime to 0 will produce a click. To avoid this, specify at least 0.005 sec (5 ms).
*/
changeVolumeDuringPlayback(v: number, rampTime: number) {
const p = this._playableNode;
p.volumeRampTime = rampTime;
p.volume = v;
p.volumeRampTime = 0;
changeVolumeRampTime(t: number) {
this._playableNode.volumeRampTime = t;
}

changeVolume(v: number) {
this._playableNode.volume = v;
}

/**
Expand All @@ -164,7 +178,9 @@ export class AudioSource extends Component {
* @param src Path to the audio file.
* @warning Changing the src will stop current playback.
*/
changeSource(src: string) {}
async changeSource(src: string) {
await this._playableNode.changeSource(src);
}

/**
* Called when the component is deactivated.
Expand Down Expand Up @@ -200,7 +216,7 @@ export class AudioSource extends Component {
maxDistance: this.maxDistance,
refDistance: this.refDistance,
rolloffFactor: this.rolloffFactor,
panningModel: this._hrtf ? 'HRTF' : 'equalpower',
panningModel: this.spatial == 2 ? 'HRTF' : 'equalpower',
positionX: posVec[0],
positionY: posVec[2],
positionZ: -posVec[1],
Expand Down

0 comments on commit e5837a9

Please sign in to comment.