overte/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
2016-02-08 10:05:11 -08:00

206 lines
9 KiB
JavaScript

//
// ACAudioSearchAndInject.js
// audio
//
// Created by Eric Levin 2/1/2016
// Copyright 2016 High Fidelity, Inc.
// This AC script searches for special sound entities nearby avatars and plays those sounds based off information specified in the entity's
// user data field ( see acAudioSearchAndCompatibilityEntitySpawner.js for an example)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
Script.include("https://rawgit.com/highfidelity/hifi/master/examples/libraries/utils.js");
var SOUND_DATA_KEY = "soundKey";
var QUERY_RADIUS = 50;
EntityViewer.setKeyholeRadius(QUERY_RADIUS);
Entities.setPacketsPerSecond(6000);
Agent.isAvatar = true;
var DEFAULT_SOUND_DATA = {
volume: 0.5,
loop: false,
interval: -1, // An interval of -1 means this sound only plays once (if it's non-looping) (In seconds)
intervalSpread: 0 // amount of randomness to add to the interval
};
var MIN_PLAY_INTERVAL = 200;
var UPDATE_TIME = 100;
var EXPIRATION_TIME = 5000;
var soundEntityMap = {};
var soundUrls = {};
var avatarPositions = [];
function update() {
var avatars = AvatarList.getAvatarIdentifiers();
for (var i = 0; i < avatars.length; i++) {
var avatar = AvatarList.getAvatar(avatars[i]);
var avatarPosition = avatar.position;
if (!avatarPosition) {
continue;
}
EntityViewer.setPosition(avatarPosition);
EntityViewer.queryOctree();
avatarPositions.push(avatarPosition);
}
Script.setTimeout(function() {
avatarPositions.forEach(function(avatarPosition) {
var entities = Entities.findEntities(avatarPosition, QUERY_RADIUS);
handleFoundSoundEntities(entities);
});
//Now wipe list for next query;
avatarPositions = [];
}, UPDATE_TIME);
handleActiveSoundEntities();
}
function handleActiveSoundEntities() {
// Go through all our sound entities, if they have passed expiration time, remove them from map
for (var potentialSoundEntity in soundEntityMap) {
if (!soundEntityMap.hasOwnProperty(potentialSoundEntity)) {
// The current property is not a direct property of soundEntityMap so ignore it
continue;
}
var soundEntity = potentialSoundEntity;
var soundProperties = soundEntityMap[soundEntity];
soundProperties.timeWithoutAvatarInRange += UPDATE_TIME;
if (soundProperties.timeWithoutAvatarInRange > EXPIRATION_TIME && soundProperties.soundInjector) {
// An avatar hasn't been within range of this sound entity recently, so remove it from map
soundProperties.soundInjector.stop();
delete soundEntityMap[soundEntity];
} else if (soundProperties.isDownloaded) {
// If this sound hasn't expired yet, we want to potentially play it!
if (soundProperties.readyToPlay) {
var newPosition = Entities.getEntityProperties(soundEntity, "position").position;
if (!soundProperties.soundInjector) {
soundProperties.soundInjector = Audio.playSound(soundProperties.sound, {
volume: soundProperties.volume,
position: newPosition,
loop: soundProperties.loop
});
} else {
soundProperties.soundInjector.restart();
}
soundProperties.readyToPlay = false;
} else if (soundProperties.sound && soundProperties.loop === false && soundProperties.interval !== -1) {
// We need to check all of our entities that are not looping but have an interval associated with them
// to see if it's time for them to play again
soundProperties.timeSinceLastPlay += UPDATE_TIME;
if (soundProperties.timeSinceLastPlay > soundProperties.currentInterval) {
soundProperties.readyToPlay = true;
soundProperties.timeSinceLastPlay = 0;
// Now let's get our new current interval
soundProperties.currentInterval = soundProperties.interval + randFloat(-soundProperties.intervalSpread, soundProperties.intervalSpread);
soundProperties.currentInterval = Math.max(MIN_PLAY_INTERVAL, soundProperties.currentInterval);
}
}
}
}
}
function handleFoundSoundEntities(entities) {
entities.forEach(function(entity) {
var soundData = getEntityCustomData(SOUND_DATA_KEY, entity);
if (soundData && soundData.url) {
//check sound entities list- if it's not in, add it
if (!soundEntityMap[entity]) {
var soundProperties = {
url: soundData.url,
volume: soundData.volume || DEFAULT_SOUND_DATA.volume,
loop: soundData.loop || DEFAULT_SOUND_DATA.loop,
interval: soundData.interval || DEFAULT_SOUND_DATA.interval,
intervalSpread: soundData.intervalSpread || DEFAULT_SOUND_DATA.intervalSpread,
readyToPlay: false,
position: Entities.getEntityProperties(entity, "position").position,
timeSinceLastPlay: 0,
timeWithoutAvatarInRange: 0,
isDownloaded: false
};
if (soundProperties.interval !== -1) {
soundProperties.currentInterval = soundProperties.interval + randFloat(-soundProperties.intervalSpread, soundProperties.intervalSpread);
soundProperties.currentInterval = Math.max(MIN_PLAY_INTERVAL, soundProperties.currentInterval);
}
soundEntityMap[entity] = soundProperties;
if (!soundUrls[soundData.url]) {
// We need to download sound before we add it to our map
var sound = SoundCache.getSound(soundData.url);
// Only add it to map once it's downloaded
soundUrls[soundData.url] = sound;
sound.ready.connect(function() {
soundProperties.sound = sound;
soundProperties.readyToPlay = true;
soundProperties.isDownloaded = true;
soundEntityMap[entity] = soundProperties;
});
} else {
// We already have sound downloaded, so just add it to map right away
soundProperties.sound = soundUrls[soundData.url];
soundProperties.readyToPlay = true;
soundProperties.isDownloaded = true;
soundEntityMap[entity] = soundProperties;
}
} else {
//If this sound is in our map already, we want to reset timeWithoutAvatarInRange
// Also we want to check to see if the entity has been updated with new sound data- if so we want to update!
soundEntityMap[entity].timeWithoutAvatarInRange = 0;
checkForSoundPropertyChanges(soundEntityMap[entity], soundData);
}
}
});
}
function checkForSoundPropertyChanges(currentProps, newProps) {
var needsNewInjector = false;
if (currentProps.interval !== newProps.interval && !currentProps.loop) {
// interval only applies to non looping sounds
currentProps.interval = newProps.interval;
currentProps.currentInterval = currentProps.interval + randFloat(-currentProps.intervalSpread, currentProps.intervalSpread);
currentProps.currentInterval = Math.max(MIN_PLAY_INTERVAL, currentProps.currentInterval);
currentProps.readyToPlay = true;
}
if (currentProps.intervalSpread !== currentProps.intervalSpread) {
currentProps.currentInterval = currentProps.interval + randFloat(-currentProps.intervalSpread, currentProps.intervalSpread);
currentProps.currentInterval = Math.max(MIN_PLAY_INTERVAL, currentProps.currentInterval);
}
if (currentProps.volume !== newProps.volume) {
currentProps.volume = newProps.volume;
needsNewInjector = true;
}
if (currentProps.url !== newProps.url) {
currentProps.url = newProps.url;
currentProps.sound = null;
if (!soundUrls[currentProps.url]) {
var sound = SoundCache.getSound(currentProps.url);
currentProps.isDownloaded = false;
sound.ready.connect(function() {
currentProps.sound = sound;
currentProps.isDownloaded = true;
});
} else {
currentProps.sound = sound;
}
needsNewInjector = true;
}
if (needsNewInjector) {
// If we were looping we need to stop that so new changes are applied
currentProps.soundInjector.stop();
currentProps.soundInjector = null;
currentProps.readyToPlay = true;
}
}
Script.setInterval(update, UPDATE_TIME);