mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 03:17:06 +02:00
Merge pull request #9403 from humbletim/21118
Code Review for Job #21118 - Add on/off switch to ambisonic/ambient sound emitter
This commit is contained in:
commit
d60d183c8c
2 changed files with 196 additions and 47 deletions
19
scripts/developer/tests/ambientSoundTest.js
Normal file
19
scripts/developer/tests/ambientSoundTest.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
var WAVE = 'http://cdn.rawgit.com/ambisonictoolkit/atk-sounds/aa31005c/stereo/Aurora_Surgit-Lux_Aeterna.wav';
|
||||||
|
var uuid = Entities.addEntity({
|
||||||
|
type: "Shape",
|
||||||
|
shape: "Icosahedron",
|
||||||
|
dimensions: Vec3.HALF,
|
||||||
|
script: Script.resolvePath('../../tutorials/entity_scripts/ambientSound.js'),
|
||||||
|
position: Vec3.sum(Vec3.multiply(5, Quat.getFront(MyAvatar.orientation)), MyAvatar.position),
|
||||||
|
userData: JSON.stringify({
|
||||||
|
soundURL: WAVE,
|
||||||
|
maxVolume: 0.1,
|
||||||
|
range: 25,
|
||||||
|
disabled: true,
|
||||||
|
grabbableKey: { wantsTrigger: true },
|
||||||
|
}),
|
||||||
|
lifetime: 600,
|
||||||
|
});
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
Entities.deleteEntity(uuid);
|
||||||
|
});
|
|
@ -6,7 +6,8 @@
|
||||||
// In the userData section for the entity, add/edit three values:
|
// In the userData section for the entity, add/edit three values:
|
||||||
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
|
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
|
||||||
// userData.range should be an integer for the max distance away from the entity where the sound will be audible.
|
// userData.range should be an integer for the max distance away from the entity where the sound will be audible.
|
||||||
// userData.volume is the max volume at which the clip should play. Defaults to 1.0 full volume)
|
// userData.maxVolume is the max volume at which the clip should play. Defaults to 1.0 full volume.
|
||||||
|
// userData.disabled is an optionanl boolean flag which can be used to disable the ambient sound. Defaults to false.
|
||||||
//
|
//
|
||||||
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
|
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
|
||||||
// direction in-which a certain sound comes from.
|
// direction in-which a certain sound comes from.
|
||||||
|
@ -26,9 +27,22 @@
|
||||||
var DEFAULT_URL = "http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav";
|
var DEFAULT_URL = "http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav";
|
||||||
var DEFAULT_VOLUME = 1.0;
|
var DEFAULT_VOLUME = 1.0;
|
||||||
|
|
||||||
|
var DEFAULT_USERDATA = {
|
||||||
|
soundURL: DEFAULT_URL,
|
||||||
|
range: DEFAULT_RANGE,
|
||||||
|
maxVolume: DEFAULT_VOLUME,
|
||||||
|
disabled: true,
|
||||||
|
grabbableKey: { wantsTrigger: true },
|
||||||
|
};
|
||||||
|
|
||||||
var soundURL = "";
|
var soundURL = "";
|
||||||
|
var soundOptions = {
|
||||||
|
loop: true,
|
||||||
|
localOnly: true,
|
||||||
|
};
|
||||||
var range = DEFAULT_RANGE;
|
var range = DEFAULT_RANGE;
|
||||||
var maxVolume = DEFAULT_VOLUME;
|
var maxVolume = DEFAULT_VOLUME;
|
||||||
|
var disabled = false;
|
||||||
var UPDATE_INTERVAL_MSECS = 100;
|
var UPDATE_INTERVAL_MSECS = 100;
|
||||||
var rotation;
|
var rotation;
|
||||||
|
|
||||||
|
@ -39,14 +53,13 @@
|
||||||
var checkTimer = false;
|
var checkTimer = false;
|
||||||
var _this;
|
var _this;
|
||||||
|
|
||||||
var WANT_COLOR_CHANGE = false;
|
|
||||||
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
|
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
|
||||||
var COLOR_ON = { red: 255, green: 0, blue: 0 };
|
var COLOR_ON = { red: 255, green: 0, blue: 0 };
|
||||||
|
|
||||||
var WANT_DEBUG = false;
|
var WANT_DEBUG = false;
|
||||||
function debugPrint(string) {
|
function debugPrint(string) {
|
||||||
if (WANT_DEBUG) {
|
if (WANT_DEBUG) {
|
||||||
print(string);
|
print("ambientSound | " + string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,42 +68,154 @@
|
||||||
var oldSoundURL = soundURL;
|
var oldSoundURL = soundURL;
|
||||||
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||||
if (props.userData) {
|
if (props.userData) {
|
||||||
|
try {
|
||||||
var data = JSON.parse(props.userData);
|
var data = JSON.parse(props.userData);
|
||||||
|
} catch(e) {
|
||||||
|
debugPrint("unable to parse userData JSON string: " + props.userData);
|
||||||
|
this.cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data.soundURL && !(soundURL === data.soundURL)) {
|
if (data.soundURL && !(soundURL === data.soundURL)) {
|
||||||
soundURL = data.soundURL;
|
soundURL = data.soundURL;
|
||||||
debugPrint("Read ambient sound URL: " + soundURL);
|
debugPrint("Read ambient sound URL: " + soundURL);
|
||||||
} else if (!data.soundURL) {
|
|
||||||
soundURL = DEFAULT_URL;
|
|
||||||
}
|
}
|
||||||
if (data.range && !(range === data.range)) {
|
if (data.range && !(range === data.range)) {
|
||||||
range = data.range;
|
range = data.range;
|
||||||
debugPrint("Read ambient sound range: " + range);
|
debugPrint("Read ambient sound range: " + range);
|
||||||
}
|
}
|
||||||
if (data.volume && !(maxVolume === data.volume)) {
|
// Check known aliases for the "volume" setting (which allows for inplace upgrade of existing marketplace entities)
|
||||||
maxVolume = data.volume;
|
data.maxVolume = data.maxVolume || data.soundVolume || data.volume;
|
||||||
|
if (data.maxVolume && !(maxVolume === data.maxVolume)) {
|
||||||
|
maxVolume = data.maxVolume;
|
||||||
debugPrint("Read ambient sound volume: " + maxVolume);
|
debugPrint("Read ambient sound volume: " + maxVolume);
|
||||||
}
|
}
|
||||||
|
if ("disabled" in data && !(disabled === data.disabled)) {
|
||||||
|
disabled = data.disabled;
|
||||||
|
debugPrint("Read ambient disabled state: " + disabled);
|
||||||
|
this._updateColor(disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (disabled) {
|
||||||
|
this.cleanup();
|
||||||
|
soundURL = "";
|
||||||
|
return;
|
||||||
|
} else if (!checkTimer) {
|
||||||
|
checkTimer = Script.setInterval(_this.maybeUpdate, UPDATE_INTERVAL_MSECS);
|
||||||
}
|
}
|
||||||
if (!(soundURL === oldSoundURL) || (soundURL === "")) {
|
if (!(soundURL === oldSoundURL) || (soundURL === "")) {
|
||||||
|
if (soundURL) {
|
||||||
debugPrint("Loading ambient sound into cache");
|
debugPrint("Loading ambient sound into cache");
|
||||||
|
// Use prefetch to detect URL loading errors
|
||||||
|
var resource = SoundCache.prefetch(soundURL);
|
||||||
|
function onStateChanged() {
|
||||||
|
if (resource.state === Resource.State.FINISHED) {
|
||||||
|
resource.stateChanged.disconnect(onStateChanged);
|
||||||
ambientSound = SoundCache.getSound(soundURL);
|
ambientSound = SoundCache.getSound(soundURL);
|
||||||
|
} else if (resource.state === Resource.State.FAILED) {
|
||||||
|
resource.stateChanged.disconnect(onStateChanged);
|
||||||
|
debugPrint("Failed to download ambient sound: " + soundURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource.stateChanged.connect(onStateChanged);
|
||||||
|
onStateChanged(resource.state);
|
||||||
|
}
|
||||||
if (soundPlaying && soundPlaying.playing) {
|
if (soundPlaying && soundPlaying.playing) {
|
||||||
|
debugPrint("URL changed, stopping current ambient sound");
|
||||||
soundPlaying.stop();
|
soundPlaying.stop();
|
||||||
soundPlaying = false;
|
soundPlaying = false;
|
||||||
if (WANT_COLOR_CHANGE) {
|
|
||||||
Entities.editEntity(entity, { color: COLOR_OFF });
|
|
||||||
}
|
|
||||||
debugPrint("Restarting ambient sound");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.clickDownOnEntity = function(entityID, mouseEvent) {
|
||||||
|
if (mouseEvent.isPrimaryButton) {
|
||||||
|
this._toggle("primary click");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.startFarTrigger = function() {
|
||||||
|
this._toggle("far click");
|
||||||
|
};
|
||||||
|
|
||||||
|
this._toggle = function(hint) {
|
||||||
|
// Toggle between ON/OFF state, but only if not in edit mode
|
||||||
|
if (Settings.getValue("io.highfidelity.isEditting")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||||
|
if (!props.userData) {
|
||||||
|
debugPrint("userData is empty; ignoring " + hint);
|
||||||
|
this.cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = JSON.parse(props.userData);
|
||||||
|
data.disabled = !data.disabled;
|
||||||
|
|
||||||
|
debugPrint(hint + " -- triggering ambient sound " + (data.disabled ? "OFF" : "ON") + " (" + data.soundURL + ")");
|
||||||
|
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
|
// Save the userData and notify nearby listeners of the change
|
||||||
|
Entities.editEntity(entity, {
|
||||||
|
userData: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
Messages.sendMessage(entity, "toggled");
|
||||||
|
};
|
||||||
|
|
||||||
|
this._updateColor = function(disabled) {
|
||||||
|
// Update Shape or Text Entity color based on ON/OFF status
|
||||||
|
var props = Entities.getEntityProperties(entity, [ "color", "textColor" ]);
|
||||||
|
var targetColor = disabled ? COLOR_OFF : COLOR_ON;
|
||||||
|
var currentColor = props.textColor || props.color;
|
||||||
|
var newProps = props.textColor ? { textColor: targetColor } : { color: targetColor };
|
||||||
|
|
||||||
|
if (currentColor.red !== targetColor.red ||
|
||||||
|
currentColor.green !== targetColor.green ||
|
||||||
|
currentColor.blue !== targetColor.blue) {
|
||||||
|
Entities.editEntity(entity, newProps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.preload = function(entityID) {
|
this.preload = function(entityID) {
|
||||||
// Load the sound and range from the entity userData fields, and note the position of the entity.
|
// Load the sound and range from the entity userData fields, and note the position of the entity.
|
||||||
debugPrint("Ambient sound preload");
|
debugPrint("Ambient sound preload " + entityID);
|
||||||
entity = entityID;
|
entity = entityID;
|
||||||
_this = this;
|
_this = this;
|
||||||
checkTimer = Script.setInterval(this.maybeUpdate, UPDATE_INTERVAL_MSECS);
|
|
||||||
|
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||||
|
var data = {};
|
||||||
|
if (props.userData) {
|
||||||
|
data = JSON.parse(props.userData);
|
||||||
|
}
|
||||||
|
var changed = false;
|
||||||
|
for(var p in DEFAULT_USERDATA) {
|
||||||
|
if (!(p in data)) {
|
||||||
|
data[p] = DEFAULT_USERDATA[p];
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.grabbableKey.wantsTrigger) {
|
||||||
|
data.grabbableKey.wantsTrigger = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
debugPrint("applying default values to userData");
|
||||||
|
Entities.editEntity(entity, { userData: JSON.stringify(data) });
|
||||||
|
}
|
||||||
|
this._updateColor(data.disabled);
|
||||||
|
this.updateSettings();
|
||||||
|
|
||||||
|
// Subscribe to toggle notifications using entity ID as a channel name
|
||||||
|
Messages.subscribe(entity);
|
||||||
|
Messages.messageReceived.connect(this, "_onMessageReceived");
|
||||||
|
};
|
||||||
|
|
||||||
|
this._onMessageReceived = function(channel, message, sender, local) {
|
||||||
|
// Handle incoming toggle notifications
|
||||||
|
if (channel === entity && message === "toggled") {
|
||||||
|
debugPrint("received " + message + " from " + sender);
|
||||||
|
this.updateSettings();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.maybeUpdate = function() {
|
this.maybeUpdate = function() {
|
||||||
|
@ -98,40 +223,45 @@
|
||||||
_this.updateSettings();
|
_this.updateSettings();
|
||||||
var HYSTERESIS_FRACTION = 0.1;
|
var HYSTERESIS_FRACTION = 0.1;
|
||||||
var props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
var props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
||||||
|
if (disabled || !props.position) {
|
||||||
|
_this.cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
center = props.position;
|
center = props.position;
|
||||||
rotation = props.rotation;
|
rotation = props.rotation;
|
||||||
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, center));
|
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, center));
|
||||||
if (distance <= range) {
|
if (distance <= range) {
|
||||||
var volume = (1.0 - distance / range) * maxVolume;
|
var volume = (1.0 - distance / range) * maxVolume;
|
||||||
if (!soundPlaying && ambientSound.downloaded) {
|
soundOptions.orientation = rotation;
|
||||||
soundPlaying = Audio.playSound(ambientSound, { loop: true,
|
soundOptions.volume = volume;
|
||||||
localOnly: true,
|
if (!soundPlaying && ambientSound && ambientSound.downloaded) {
|
||||||
orientation: rotation,
|
debugPrint("Starting ambient sound: " + soundURL + " (duration: " + ambientSound.duration + ")");
|
||||||
volume: volume });
|
soundPlaying = Audio.playSound(ambientSound, soundOptions);
|
||||||
debugPrint("Starting ambient sound, volume: " + volume);
|
|
||||||
if (WANT_COLOR_CHANGE) {
|
|
||||||
Entities.editEntity(entity, { color: COLOR_ON });
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (soundPlaying && soundPlaying.playing) {
|
} else if (soundPlaying && soundPlaying.playing) {
|
||||||
soundPlaying.setOptions( { volume: volume, orientation: rotation } );
|
soundPlaying.setOptions(soundOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (soundPlaying && soundPlaying.playing && (distance > range * HYSTERESIS_FRACTION)) {
|
} else if (soundPlaying && soundPlaying.playing && (distance > range * HYSTERESIS_FRACTION)) {
|
||||||
soundPlaying.stop();
|
soundPlaying.stop();
|
||||||
soundPlaying = false;
|
soundPlaying = false;
|
||||||
Entities.editEntity(entity, { color: { red: 128, green: 128, blue: 128 }});
|
debugPrint("Out of range, stopping ambient sound: " + soundURL);
|
||||||
debugPrint("Out of range, stopping ambient sound");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.unload = function(entityID) {
|
this.unload = function(entityID) {
|
||||||
debugPrint("Ambient sound unload");
|
debugPrint("Ambient sound unload");
|
||||||
|
this.cleanup();
|
||||||
|
Messages.unsubscribe(entity);
|
||||||
|
Messages.messageReceived.disconnect(this, "_onMessageReceived");
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cleanup = function() {
|
||||||
if (checkTimer) {
|
if (checkTimer) {
|
||||||
Script.clearInterval(checkTimer);
|
Script.clearInterval(checkTimer);
|
||||||
|
checkTimer = false;
|
||||||
}
|
}
|
||||||
if (soundPlaying && soundPlaying.playing) {
|
if (soundPlaying && soundPlaying.playing) {
|
||||||
soundPlaying.stop();
|
soundPlaying.stop();
|
||||||
|
soundPlaying = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue