Skip to content

Commit

Permalink
Introduce Audio PanningQuality preference
Browse files Browse the repository at this point in the history
WebAudio and Three.js uses HRTF panning model for Panner audios
by default. But HRTF is said to be better quality but much more
costly than another pannind model equalpower. Some users,
especially low-end or mobile device users, may think they want
to set equalpower panning model for better performance by
sacrificing the audio panning quality.

This commit introduces Audio PanningQuality preference. The high
quality corresponds to HRTF while the low quality corresponds to
equalpower.
  • Loading branch information
takahirox committed May 25, 2022
1 parent f1213d3 commit c9de6ba
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 11 deletions.
8 changes: 8 additions & 0 deletions src/components/audio-params.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ export const DistanceModelType = {
Exponential: "exponential"
};

export const PanningModelType = Object.freeze({
HRTF: "HRTF",
EqualPower: "equalpower"
});

export const AvatarAudioDefaults = Object.freeze({
audioType: AudioType.PannerNode,
distanceModel: DistanceModelType.Inverse,
panningModel: PanningModelType.HRTF,
rolloffFactor: 5,
refDistance: 5,
maxDistance: 10000,
Expand All @@ -37,6 +43,7 @@ export const AvatarAudioDefaults = Object.freeze({
export const MediaAudioDefaults = Object.freeze({
audioType: AudioType.PannerNode,
distanceModel: DistanceModelType.Inverse,
panningModel: PanningModelType.HRTF,
rolloffFactor: 5,
refDistance: 5,
maxDistance: 10000,
Expand All @@ -49,6 +56,7 @@ export const MediaAudioDefaults = Object.freeze({
export const TargetAudioDefaults = Object.freeze({
audioType: AudioType.PannerNode,
distanceModel: DistanceModelType.Inverse,
panningModel: PanningModelType.HRTF,
rolloffFactor: 5,
refDistance: 8,
maxDistance: 10000,
Expand Down
19 changes: 15 additions & 4 deletions src/components/avatar-audio-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,24 @@ AFRAME.registerComponent("avatar-audio-source", {
APP.dialog.on("stream_updated", this._onStreamUpdated, this);
this.createAudio();

let disableLeftRightPanningPref = APP.store.state.preferences.disableLeftRightPanning;
let { disableLeftRightPanning, audioPanningQuality } = APP.store.state.preferences;
this.onPreferenceChanged = () => {
const newPref = APP.store.state.preferences.disableLeftRightPanning;
const shouldRecreateAudio = disableLeftRightPanningPref !== newPref && !this.isCreatingAudio;
disableLeftRightPanningPref = newPref;
const newDisableLeftRightPanning = APP.store.state.preferences.disableLeftRightPanning;
const newAudioPanningQuality = APP.store.state.preferences.audioPanningQuality;

const shouldRecreateAudio = disableLeftRightPanning !== newDisableLeftRightPanning && !this.isCreatingAudio;
const shouldUpdateAudioSettings = audioPanningQuality !== newAudioPanningQuality;

disableLeftRightPanning = newDisableLeftRightPanning;
audioPanningQuality = newAudioPanningQuality;

if (shouldRecreateAudio) {
this.createAudio();
} else if (shouldUpdateAudioSettings) {
// updateAudioSettings() is called in this.createAudio()
// so no need to call it if shouldRecreateAudio is true.
const audio = this.el.getObject3D(this.attrName);
updateAudioSettings(this.el, audio);
}
};
APP.store.addEventListener("statechanged", this.onPreferenceChanged);
Expand Down
20 changes: 16 additions & 4 deletions src/components/media-video.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,27 @@ AFRAME.registerComponent("media-video", {
this.updatePlaybackState();
});

let disableLeftRightPanningPref = APP.store.state.preferences.disableLeftRightPanning;
let { disableLeftRightPanning, audioPanningQuality } = APP.store.state.preferences;
this.onPreferenceChanged = () => {
const newPref = APP.store.state.preferences.disableLeftRightPanning;
const shouldRecreateAudio = disableLeftRightPanningPref !== newPref && this.audio && this.mediaElementAudioSource;
disableLeftRightPanningPref = newPref;
const newDisableLeftRightPanning = APP.store.state.preferences.disableLeftRightPanning;
const newAudioPanningQuality = APP.store.state.preferences.audioPanningQuality;

const shouldRecreateAudio =
disableLeftRightPanning !== newDisableLeftRightPanning && this.audio && this.mediaElementAudioSource;
const shouldUpdateAudioSettings = audioPanningQuality !== newAudioPanningQuality;

disableLeftRightPanning = newDisableLeftRightPanning;
audioPanningQuality = newAudioPanningQuality;

if (shouldRecreateAudio) {
this.setupAudio();
} else if (shouldUpdateAudioSettings) {
// updateAudioSettings() is called in this.setupAudio()
// so no need to call it if shouldRecreateAudio is true.
updateAudioSettings(this.el, this.audio);
}
};

APP.store.addEventListener("statechanged", this.onPreferenceChanged);
this.el.addEventListener("audio_type_changed", this.setupAudio);
},
Expand Down
24 changes: 24 additions & 0 deletions src/react-components/preferences-screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ const preferenceLabels = defineMessages({
id: "preferences-screen.preference.show-audio-debug-panel",
defaultMessage: "Show Audio Debug Panel"
},
audioPanningQuality: {
id: "preferences-screen.preference.audio-panning-quality",
defaultMessage: "Panning quality"
},
enableAudioClipping: {
id: "preferences-screen.preference.enable-audio-clipping",
defaultMessage: "Enable Audio Clipping"
Expand Down Expand Up @@ -1061,6 +1065,26 @@ class PreferencesScreen extends Component {
{
key: "showAudioDebugPanel",
prefType: PREFERENCE_LIST_ITEM_TYPE.CHECK_BOX
},
{
key: "audioPanningQuality",
prefType: PREFERENCE_LIST_ITEM_TYPE.SELECT,
options: [
{
value: "High",
text: intl.formatMessage({
id: "preferences-screen.audio-panning-quality.high",
defaultMessage: "High"
})
},
{
value: "Low",
text: intl.formatMessage({
id: "preferences-screen.audio-panning-quality.low",
defaultMessage: "Low"
})
}
]
}
]
],
Expand Down
1 change: 1 addition & 0 deletions src/storage/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export const SCHEMA = {
showAudioDebugPanel: { type: "bool", default: false },
enableAudioClipping: { type: "bool", default: false },
audioClippingThreshold: { type: "number", default: 0.015 },
audioPanningQuality: { type: "string", default: "High" },
theme: { type: "string", default: undefined },
cursorSize: { type: "number", default: 1 },
nametagVisibility: { type: "string", default: "showAll" },
Expand Down
7 changes: 7 additions & 0 deletions src/systems/sound-effects-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import URL_SPEAKER_TONE from "../assets/sfx/tone.mp3";
import { setMatrixWorld } from "../utils/three-utils";
import { isSafari } from "../utils/detect-safari";
import { SourceType } from "../components/audio-params";
import { getOverriddenPanningModelType } from "../update-audio-settings";

let soundEnum = 0;
export const SOUND_HOVER_OR_GRAB = soundEnum++;
Expand Down Expand Up @@ -148,6 +149,12 @@ export class SoundEffectsSystem {
: new THREE.PositionalAudio(this.scene.audioListener);
positionalAudio.setBuffer(audioBuffer);
positionalAudio.loop = loop;
if (!disablePositionalAudio) {
const overriddenPanningModelType = getOverriddenPanningModelType();
if (overriddenPanningModelType !== null) {
positionalAudio.panner.panningModel = overriddenPanningModelType;
}
}
this.pendingPositionalAudios.push(positionalAudio);
this.scene.systems["hubs-systems"].audioSystem.addAudio({
sourceType: SourceType.SFX,
Expand Down
29 changes: 26 additions & 3 deletions src/update-audio-settings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AudioType,
SourceType,
PanningModelType,
MediaAudioDefaults,
AvatarAudioDefaults,
TargetAudioDefaults
Expand All @@ -21,23 +22,45 @@ function applySettings(audio, settings) {
audio.setRolloffFactor(settings.rolloffFactor);
audio.setRefDistance(settings.refDistance);
audio.setMaxDistance(settings.maxDistance);
audio.panner.panningModel = settings.panningModel;
audio.panner.coneInnerAngle = settings.coneInnerAngle;
audio.panner.coneOuterAngle = settings.coneOuterAngle;
audio.panner.coneOuterGain = settings.coneOuterGain;
}
audio.gain.gain.setTargetAtTime(settings.gain, audio.context.currentTime, 0.1);
}

export function getOverriddenPanningModelType() {
switch (APP.store.state.preferences.audioPanningQuality) {
case "High":
return PanningModelType.HRTF;
case "Low":
return PanningModelType.EqualPower;
default:
return null;
}
}

export function getCurrentAudioSettings(el) {
const sourceType = APP.sourceType.get(el);
const defaults = defaultSettingsForSourceType.get(sourceType);
const sceneOverrides = APP.sceneAudioDefaults.get(sourceType);
const audioOverrides = APP.audioOverrides.get(el);
const audioDebugPanelOverrides = APP.audioDebugPanelOverrides.get(sourceType);
const zoneSettings = APP.zoneOverrides.get(el);
const preferencesOverrides = APP.store.state.preferences.disableLeftRightPanning
? { audioType: AudioType.Stereo }
: {};

const preferencesOverrides = {};

const overriddenPanningModelType = getOverriddenPanningModelType();

if (overriddenPanningModelType !== null) {
preferencesOverrides.panningModel = overriddenPanningModelType;
}

if (APP.store.state.preferences.disableLeftRightPanning) {
preferencesOverrides.audioType = AudioType.Stereo;
}

const safariOverrides = isSafari() ? { audioType: AudioType.Stereo } : {};
const settings = Object.assign(
{},
Expand Down

0 comments on commit c9de6ba

Please sign in to comment.