From b8d2d71e07cad53bcbf2a8316ab5cc66d14c0af5 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sat, 2 Mar 2024 11:27:04 -0800 Subject: [PATCH] move audio zones to zone entity properties --- assignment-client/src/audio/AudioMixer.cpp | 169 +++++++++++---- assignment-client/src/audio/AudioMixer.h | 44 ++-- .../src/audio/AudioMixerSlave.cpp | 61 ++++-- assignment-client/src/avatars/AvatarMixer.cpp | 8 +- assignment-client/src/avatars/AvatarMixer.h | 1 - assignment-client/src/octree/OctreeServer.cpp | 2 +- .../resources/describe-settings.json | 6 +- .../entities/src/EntityItemProperties.cpp | 23 ++- libraries/entities/src/EntityItemProperties.h | 2 + libraries/entities/src/EntityPropertyFlags.h | 11 + .../entities/src/ZoneAudioPropertyGroup.cpp | 194 ++++++++++++++++++ .../entities/src/ZoneAudioPropertyGroup.h | 97 +++++++++ libraries/entities/src/ZoneEntityItem.cpp | 17 +- libraries/entities/src/ZoneEntityItem.h | 12 +- libraries/networking/src/udt/PacketHeaders.h | 1 + .../create/assets/data/createAppTooltips.json | 15 ++ .../html/js/entityProperties.js | 38 +++- .../entityProperties/html/tabs/zone_audio.png | Bin 0 -> 598 bytes 18 files changed, 610 insertions(+), 91 deletions(-) create mode 100644 libraries/entities/src/ZoneAudioPropertyGroup.cpp create mode 100644 libraries/entities/src/ZoneAudioPropertyGroup.h create mode 100644 scripts/system/create/entityProperties/html/tabs/zone_audio.png diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 788dfeab93..7947582b5c 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "AudioLogging.h" #include "AudioHelpers.h" @@ -39,6 +40,7 @@ #include "AvatarAudioStream.h" #include "InjectedAudioStream.h" #include "crash-handler/CrashHandler.h" +#include "../entities/AssignmentParentFinder.h" using namespace std; @@ -56,9 +58,7 @@ float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD }; float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE }; map> AudioMixer::_availableCodecs{ }; QStringList AudioMixer::_codecPreferenceOrder{}; -vector AudioMixer::_audioZones; -vector AudioMixer::_zoneSettings; -vector AudioMixer::_zoneReverbSettings; +unordered_map AudioMixer::_audioZones; AudioMixer::AudioMixer(ReceivedMessage& message) : ThreadedAssignment(message) @@ -112,6 +112,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::handleNodeMuteRequestPacket)); packetReceiver.registerListener(PacketType::KillAvatar, PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::handleKillAvatarPacket)); + packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, + PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::handleOctreePacket)); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedMicrophoneAudioNoEcho, @@ -405,7 +407,7 @@ void AudioMixer::start() { // prepare the NodeList nodeList->addSetOfNodeTypesToNodeInterestSet({ - NodeType::Agent, NodeType::EntityScriptServer, + NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer, NodeType::UpstreamAudioMixer, NodeType::DownstreamAudioMixer }); nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); }; @@ -417,12 +419,19 @@ void AudioMixer::start() { parseSettingsObject(settingsObject); } + setupEntityQuery(); + // mix state unsigned int frame = 1; while (!_isFinished) { auto ticTimer = _ticTiming.timer(); + // Set our query each frame + { + _entityViewer.queryOctree(); + } + if (_startFrameTimestamp.time_since_epoch().count() == 0) { _startFrameTimestamp = _idealFrameTimestamp = p_high_resolution_clock::now(); } else { @@ -555,8 +564,6 @@ void AudioMixer::clearDomainSettings() { _noiseMutingThreshold = DEFAULT_NOISE_MUTING_THRESHOLD; _codecPreferenceOrder.clear(); _audioZones.clear(); - _zoneSettings.clear(); - _zoneReverbSettings.clear(); } void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { @@ -727,8 +734,13 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { if (allOk) { glm::vec3 corner(xMin, yMin, zMin); glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin); - AABox zoneAABox(corner, dimensions); - _audioZones.push_back({ zoneName, zoneAABox }); + + Transform t; + t.setTranslation(corner + 0.5f * dimensions); + t.setScale(dimensions); + _audioZones[zoneName].inverseTransform = t.getInverseMatrix(); + _audioZones[zoneName].volume = dimensions.x * dimensions.y * dimensions.z; + qCDebug(audio) << "Added zone:" << zoneName << "(corner:" << corner << ", dimensions:" << dimensions << ")"; } } @@ -749,28 +761,17 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { coefficientObject.contains(LISTENER) && coefficientObject.contains(COEFFICIENT)) { - auto itSource = find_if(begin(_audioZones), end(_audioZones), [&](const ZoneDescription& description) { - return description.name == coefficientObject.value(SOURCE).toString(); - }); - auto itListener = find_if(begin(_audioZones), end(_audioZones), [&](const ZoneDescription& description) { - return description.name == coefficientObject.value(LISTENER).toString(); - }); + auto itSource = _audioZones.find(coefficientObject.value(SOURCE).toString()); bool ok; float coefficient = coefficientObject.value(COEFFICIENT).toString().toFloat(&ok); + if (ok && coefficient <= 1.0f && itSource != _audioZones.end()) { + auto listener = coefficientObject.value(LISTENER).toString(); + itSource->second.listeners.emplace_back(listener); + itSource->second.coefficients.emplace_back(coefficient); - if (ok && coefficient <= 1.0f && - itSource != end(_audioZones) && - itListener != end(_audioZones)) { - - ZoneSettings settings; - settings.source = itSource - begin(_audioZones); - settings.listener = itListener - begin(_audioZones); - settings.coefficient = coefficient; - - _zoneSettings.push_back(settings); - qCDebug(audio) << "Added Coefficient:" << itSource->name << itListener->name << settings.coefficient; + qCDebug(audio) << "Added Coefficient:" << itSource->first << listener << coefficient; } } } @@ -791,21 +792,16 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { reverbObject.contains(WET_LEVEL)) { bool okReverbTime, okWetLevel; - auto itZone = find_if(begin(_audioZones), end(_audioZones), [&](const ZoneDescription& description) { - return description.name == reverbObject.value(ZONE).toString(); - }); + auto itZone = _audioZones.find(reverbObject.value(ZONE).toString()); float reverbTime = reverbObject.value(REVERB_TIME).toString().toFloat(&okReverbTime); float wetLevel = reverbObject.value(WET_LEVEL).toString().toFloat(&okWetLevel); - if (okReverbTime && okWetLevel && itZone != end(_audioZones)) { - ReverbSettings settings; - settings.zone = itZone - begin(_audioZones); - settings.reverbTime = reverbTime; - settings.wetLevel = wetLevel; + if (okReverbTime && okWetLevel && itZone != _audioZones.end()) { + itZone->second.reverbEnabled = true; + itZone->second.reverbTime = reverbTime; + itZone->second.wetLevel = wetLevel; - _zoneReverbSettings.push_back(settings); - - qCDebug(audio) << "Added Reverb:" << itZone->name << reverbTime << wetLevel; + qCDebug(audio) << "Added Reverb:" << itZone->first << reverbTime << wetLevel; } } } @@ -813,6 +809,107 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { } } +void AudioMixer::setupEntityQuery() { + _entityViewer.init(); + EntityTreePointer entityTree = _entityViewer.getTree(); + DependencyManager::registerInheritance(); + DependencyManager::set(entityTree); + + connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AudioMixer::entityAdded); + connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AudioMixer::entityRemoved); + + // ES query: {"type": "Zone"} + QJsonObject zoneQuery; + zoneQuery["type"] = "Zone"; + + QJsonObject queryFlags; + queryFlags["includeAncestors"] = true; + queryFlags["includeDescendants"] = true; + zoneQuery["flags"] = queryFlags; + zoneQuery["name"] = true; // Handy for debugging. + + _entityViewer.getOctreeQuery().setJSONParameters(zoneQuery); +} + +void AudioMixer::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { + PacketType packetType = message->getType(); + + switch (packetType) { + case PacketType::OctreeStats: + { // Ignore stats, but may have a different Entity packet appended. + OctreeHeadlessViewer::parseOctreeStats(message, senderNode); + const auto piggyBackedSizeWithHeader = message->getBytesLeftToRead(); + if (piggyBackedSizeWithHeader > 0) { + // pull out the piggybacked packet and create a new QSharedPointer for it + auto buffer = std::unique_ptr(new char[piggyBackedSizeWithHeader]); + memcpy(buffer.get(), message->getRawMessage() + message->getPosition(), piggyBackedSizeWithHeader); + + auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr()); + auto newMessage = QSharedPointer::create(*newPacket); + handleOctreePacket(newMessage, senderNode); + } + break; + } + + case PacketType::EntityData: + _entityViewer.processDatagram(*message, senderNode); + break; + + case PacketType::EntityErase: + _entityViewer.processEraseMessage(*message, senderNode); + break; + + default: + qCDebug(audio) << "Unexpected packet type:" << packetType; + break; + } +} + +void updateAudioZone(EntityItem* entity, std::unordered_map& audioZones) { + auto zoneEntity = (ZoneEntityItem*)entity; + auto& audioZone = audioZones[entity->getID().toString()]; + auto& audioSettings = zoneEntity->getAudioProperties(); + + vec3 dimensions = entity->getScaledDimensions(); + Transform t; + t.setTranslation(entity->getWorldPosition()); + t.setScale(dimensions); + t.setRotation(entity->getWorldOrientation()); + audioZone.inverseTransform = t.getInverseMatrix(); + audioZone.volume = dimensions.x * dimensions.y * dimensions.z; + + audioZone.reverbEnabled = audioSettings.getReverbEnabled(); + audioZone.reverbTime = audioSettings.getReverbTime(); + audioZone.wetLevel = audioSettings.getReverbWetLevel(); + + audioZone.listeners.clear(); + auto listenerZones = audioSettings.getListenerZones(); + audioZone.listeners.reserve(listenerZones.length()); + for (auto& listener : listenerZones) { + audioZone.listeners.push_back(listener.toString()); + } + + audioZone.coefficients = audioSettings.getListenerAttenuationCoefficients().toStdVector(); + + /*qCDebug(audio) << "Updated audio zone:" << entity->getID().toString() << "(position:" << t.getTranslation() + << ", dimensions:" << t.getScale() << ")";*/ +} + +void AudioMixer::entityAdded(EntityItem* entity) { + if (entity->getType() == EntityTypes::Zone) { + updateAudioZone(entity, _audioZones); + entity->registerChangeHandler([entity](const EntityItemID& entityItemID) { + updateAudioZone(entity, _audioZones); + }); + } +} + +void AudioMixer::entityRemoved(EntityItem* entity) { + if (entity->getType() == EntityTypes::Zone) { + _audioZones.erase(entity->getID().toString()); + } +} + AudioMixer::Timer::Timing::Timing(uint64_t& sum) : _sum(sum) { _timing = p_high_resolution_clock::now(); } diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 5b75ed54d2..658b86a4bd 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -14,7 +14,7 @@ #include -#include +#include #include #include #include @@ -25,6 +25,8 @@ #include "AudioMixerStats.h" #include "AudioMixerSlavePool.h" +#include "../entities/EntityTreeHeadlessViewer.h" + class PositionalAudioStream; class AvatarAudioStream; class AudioHRTF; @@ -36,28 +38,22 @@ class AudioMixer : public ThreadedAssignment { public: AudioMixer(ReceivedMessage& message); - - struct ZoneDescription { - QString name; - AABox area; - }; struct ZoneSettings { - int source; - int listener; - float coefficient; - }; - struct ReverbSettings { - int zone; - float reverbTime; - float wetLevel; + glm::mat4 inverseTransform; + float volume { FLT_MAX }; + + bool reverbEnabled { false }; + float reverbTime { 0.0f }; + float wetLevel { 0.0f }; + + std::vector listeners; + std::vector coefficients; }; static int getStaticJitterFrames() { return _numStaticJitterFrames; } static bool shouldMute(float quietestFrame) { return quietestFrame > _noiseMutingThreshold; } static float getAttenuationPerDoublingInDistance() { return _attenuationPerDoublingInDistance; } - static const std::vector& getAudioZones() { return _audioZones; } - static const std::vector& getZoneSettings() { return _zoneSettings; } - static const std::vector& getReverbSettings() { return _zoneReverbSettings; } + static const std::unordered_map& getAudioZones() { return _audioZones; } static const std::pair negotiateCodec(std::vector codecs); static bool shouldReplicateTo(const Node& from, const Node& to) { @@ -72,12 +68,17 @@ public slots: void run() override; void sendStatsPacket() override; + // Audio zone possibly changed + void entityAdded(EntityItem* entity); + void entityRemoved(EntityItem* entity); + private slots: // packet handlers void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleNodeMuteRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleNodeKilled(SharedNodePointer killedNode); void handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); void queueAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode); void queueReplicatedAudioPacket(QSharedPointer packet); @@ -146,14 +147,17 @@ private: static std::map _availableCodecs; static QStringList _codecPreferenceOrder; - static std::vector _audioZones; - static std::vector _zoneSettings; - static std::vector _zoneReverbSettings; + static std::unordered_map _audioZones; float _throttleStartTarget = 0.9f; float _throttleBackoffTarget = 0.44f; AudioMixerSlave::SharedData _workerSharedData; + + void setupEntityQuery(); + + // Attach to entity tree for audio zone info. + EntityTreeHeadlessViewer _entityViewer; }; #endif // hifi_AudioMixer_h diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 204095ab94..9c0ef4a237 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -646,27 +646,36 @@ void sendMutePacket(const SharedNodePointer& node, AudioMixerClientData& data) { data.setShouldMuteClient(false); } +const AABox UNIT_BOX(vec3(-0.5f), vec3(1.0f)); void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData& data) { bool hasReverb = false; float reverbTime, wetLevel; - auto& reverbSettings = AudioMixer::getReverbSettings(); auto& audioZones = AudioMixer::getAudioZones(); AvatarAudioStream* stream = data.getAvatarAudioStream(); - glm::vec3 streamPosition = stream->getPosition(); + vec4 streamPosition = vec4(stream->getPosition(), 1.0f); // find reverb properties - for (const auto& settings : reverbSettings) { - AABox box = audioZones[settings.zone].area; - if (box.contains(streamPosition)) { - hasReverb = true; - reverbTime = settings.reverbTime; - wetLevel = settings.wetLevel; - break; + QString bestZone; + float bestZoneVolume = FLT_MAX; + for (const auto& zone : audioZones) { + if (zone.second.reverbEnabled) { + vec4 localPosition = zone.second.inverseTransform * streamPosition; + if (UNIT_BOX.contains(localPosition) && zone.second.volume < bestZoneVolume) { + bestZone = zone.first; + bestZoneVolume = zone.second.volume; + } } } + if (bestZoneVolume < FLT_MAX) { + const auto& zone = audioZones.at(bestZone); + hasReverb = zone.reverbEnabled; + reverbTime = zone.reverbTime; + wetLevel = zone.wetLevel; + } + // check if data changed bool dataChanged = (stream->hasReverb() != hasReverb) || (stream->hasReverb() && (stream->getRevebTime() != reverbTime || stream->getWetLevel() != wetLevel)); @@ -759,18 +768,40 @@ float computeGain(float masterAvatarGain, } auto& audioZones = AudioMixer::getAudioZones(); - auto& zoneSettings = AudioMixer::getZoneSettings(); // find distance attenuation coefficient float attenuationPerDoublingInDistance = AudioMixer::getAttenuationPerDoublingInDistance(); - for (const auto& settings : zoneSettings) { - if (audioZones[settings.source].area.contains(streamToAdd.getPosition()) && - audioZones[settings.listener].area.contains(listeningNodeStream.getPosition())) { - attenuationPerDoublingInDistance = settings.coefficient; - break; + + float bestZonesVolume = FLT_MAX; + float bestZonesCoefficient; + for (const auto& sourceZone : audioZones) { + if (sourceZone.second.listeners.size() > 0 && sourceZone.second.listeners.size() == sourceZone.second.coefficients.size()) { + vec4 localSourcePosition = sourceZone.second.inverseTransform * vec4(streamToAdd.getPosition(), 1.0f); + if (UNIT_BOX.contains(localSourcePosition)) { + size_t listenerIndex = 0; + for (const auto& listener : sourceZone.second.listeners) { + const auto& listenerZone = audioZones.find(listener); + if (listenerZone != audioZones.end()) { + vec4 localListenerPosition = listenerZone->second.inverseTransform * vec4(listeningNodeStream.getPosition(), 1.0f); + if (UNIT_BOX.contains(localListenerPosition)) { + // This isn't an exact solution, but we target the smallest sum of volumes of the source and listener zones + const float zonesVolume = sourceZone.second.volume + listenerZone->second.volume; + if (zonesVolume < bestZonesVolume) { + bestZonesVolume = zonesVolume; + bestZonesCoefficient = sourceZone.second.coefficients[listenerIndex]; + } + } + } + listenerIndex++; + } + } } } + if (bestZonesVolume < FLT_MAX) { + attenuationPerDoublingInDistance = bestZonesCoefficient; + } + if (attenuationPerDoublingInDistance < 0.0f) { // translate a negative zone setting to distance limit const float MIN_DISTANCE_LIMIT = ATTN_DISTANCE_REF + 1.0f; // silent after 1m diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 7279627daf..8d322c36f2 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -1085,7 +1085,7 @@ void AvatarMixer::setupEntityQuery() { DependencyManager::set(entityTree); connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AvatarMixer::entityAdded); - connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityChange); + connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityRemoved); // ES query: {"avatarPriority": true, "type": "Zone"} QJsonObject priorityZoneQuery; @@ -1140,7 +1140,7 @@ void AvatarMixer::entityAdded(EntityItem* entity) { if (entity->getType() == EntityTypes::Zone) { _dirtyHeroStatus = true; entity->registerChangeHandler([this](const EntityItemID& entityItemID) { - entityChange(); + _dirtyHeroStatus = true; }); } } @@ -1151,10 +1151,6 @@ void AvatarMixer::entityRemoved(EntityItem * entity) { } } -void AvatarMixer::entityChange() { - _dirtyHeroStatus = true; -} - void AvatarMixer::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 92821277de..dfea2b83f7 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -52,7 +52,6 @@ public slots: // Avatar zone possibly changed void entityAdded(EntityItem* entity); void entityRemoved(EntityItem* entity); - void entityChange(); private slots: void queueIncomingPacket(QSharedPointer message, SharedNodePointer node); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index d9a2faa2de..983b88e176 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1239,7 +1239,7 @@ void OctreeServer::beginRunning() { // we need to ask the DS about agents so we can ping/reply with them nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer, - NodeType::AvatarMixer }); + NodeType::AvatarMixer, NodeType::AudioMixer }); beforeRun(); // after payload has been processed diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index ebfb519eac..58d5df5407 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1235,7 +1235,7 @@ { "name": "zones", "type": "table", - "label": "Zones", + "label": "Zones (deprecated, use Zone Entities)", "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, "content_setting": true, @@ -1287,7 +1287,7 @@ { "name": "attenuation_coefficients", "type": "table", - "label": "Attenuation Coefficients", + "label": "Attenuation Coefficients (deprecated, use Zone Entities)", "help": "In this table you can set custom attenuation coefficients between audio zones", "content_setting": true, "numbered": true, @@ -1317,7 +1317,7 @@ { "name": "reverb", "type": "table", - "label": "Reverb Settings", + "label": "Reverb Settings (deprecated, use Zone Entities)", "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "content_setting": true, diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index e39bc5fad3..5db64d12e4 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -44,6 +44,7 @@ AnimationPropertyGroup EntityItemProperties::_staticAnimation; SkyboxPropertyGroup EntityItemProperties::_staticSkybox; HazePropertyGroup EntityItemProperties::_staticHaze; BloomPropertyGroup EntityItemProperties::_staticBloom; +ZoneAudioPropertyGroup EntityItemProperties::_staticAudio; KeyLightPropertyGroup EntityItemProperties::_staticKeyLight; AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight; GrabPropertyGroup EntityItemProperties::_staticGrab; @@ -87,6 +88,7 @@ void EntityItemProperties::debugDump() const { getKeyLight().debugDump(); getAmbientLight().debugDump(); getBloom().debugDump(); + getAudio().debugDump(); getGrab().debugDump(); qCDebug(entities) << " changed properties..."; @@ -613,6 +615,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { changedProperties += _skybox.getChangedProperties(); changedProperties += _haze.getChangedProperties(); changedProperties += _bloom.getChangedProperties(); + changedProperties += _audio.getChangedProperties(); CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); @@ -853,7 +856,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * avatar entities, false if they won't be. * @property {Uuid} cloneOriginID - The ID of the entity that this entity was cloned from. * - * @property {Uuid[]} renderWithZones=[]] - A list of entity IDs representing with which zones this entity should render. + * @property {Uuid[]} renderWithZones=[] - A list of entity IDs representing with which zones this entity should render. * If it is empty, this entity will render normally. Otherwise, this entity will only render if your avatar is within * one of the zones in this list. * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. Use the rotation @@ -1481,6 +1484,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.ComponentMode} bloomMode="inherit" - Configures the bloom in the zone. * @property {Entities.Bloom} bloom - The bloom properties of the zone. * + * @property {Entities.ZoneAudio} audio - The audio properties of the zone. + * * @property {boolean} flyingAllowed=true - true if visitors can fly in the zone; false if they * cannot. Only works for domain entities. * @property {boolean} ghostingAllowed=true - true if visitors with avatar collisions turned off will not @@ -1834,6 +1839,7 @@ ScriptValue EntityItemProperties::copyToScriptValue(ScriptEngine* engine, bool s _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _bloom.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + _audio.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); @@ -2211,6 +2217,7 @@ void EntityItemProperties::copyFromScriptValue(const ScriptValue& object, bool h _skybox.copyFromScriptValue(object, namesSet, _defaultSettings); _haze.copyFromScriptValue(object, namesSet, _defaultSettings); _bloom.copyFromScriptValue(object, namesSet, _defaultSettings); + _audio.copyFromScriptValue(object, namesSet, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL); @@ -2496,6 +2503,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { _skybox.merge(other._skybox); _haze.merge(other._haze); _bloom.merge(other._bloom); + _audio.merge(other._audio); COPY_PROPERTY_IF_CHANGED(flyingAllowed); COPY_PROPERTY_IF_CHANGED(ghostingAllowed); COPY_PROPERTY_IF_CHANGED(filterURL); @@ -2892,6 +2900,13 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_THRESHOLD, Bloom, bloom, BloomThreshold, bloomThreshold); ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_SIZE, Bloom, bloom, BloomSize, bloomSize); } + { // Audio + ADD_GROUP_PROPERTY_TO_MAP(PROP_REVERB_ENABLED, Audio, audio, ReverbEnabled, reverbEnabled); + ADD_GROUP_PROPERTY_TO_MAP(PROP_REVERB_TIME, Audio, audio, ReverbTime, reverbTime); + ADD_GROUP_PROPERTY_TO_MAP(PROP_REVERB_WET_LEVEL, Audio, audio, ReverbWetLevel, reverbWetLevel); + ADD_GROUP_PROPERTY_TO_MAP(PROP_LISTENER_ZONES, Audio, audio, ListenerZones, listenerZones); + ADD_GROUP_PROPERTY_TO_MAP(PROP_LISTENER_ATTENUATION_COEFFICIENTS, Audio, audio, ListenerAttenuationCoefficients, listenerAttenuationCoefficients); + } ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString); @@ -3307,6 +3322,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticBloom.setProperties(properties); _staticBloom.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _staticAudio.setProperties(properties); + _staticAudio.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL()); @@ -3775,6 +3793,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); properties.getHaze().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); properties.getBloom().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getAudio().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); @@ -4147,6 +4166,7 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); _bloom.markAllChanged(); + _audio.markAllChanged(); _flyingAllowedChanged = true; _ghostingAllowedChanged = true; _filterURLChanged = true; @@ -4738,6 +4758,7 @@ QList EntityItemProperties::listChangedProperties() { getSkybox().listChangedProperties(out); getHaze().listChangedProperties(out); getBloom().listChangedProperties(out); + getAudio().listChangedProperties(out); if (flyingAllowedChanged()) { out += "flyingAllowed"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ef54c2495d..6340313f2d 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -61,6 +61,7 @@ #include "BloomPropertyGroup.h" #include "PulsePropertyGroup.h" #include "RingGizmoPropertyGroup.h" +#include "ZoneAudioPropertyGroup.h" #include "MaterialMappingMode.h" #include "BillboardMode.h" @@ -331,6 +332,7 @@ public: DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Bloom, bloom, BloomPropertyGroup); + DEFINE_PROPERTY_GROUP(Audio, audio, ZoneAudioPropertyGroup); DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 881b4e2ba7..ca96c04fd8 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -167,6 +167,11 @@ enum EntityPropertyList { PROP_DERIVED_32, PROP_DERIVED_33, PROP_DERIVED_34, + PROP_DERIVED_35, + PROP_DERIVED_36, + PROP_DERIVED_37, + PROP_DERIVED_38, + PROP_DERIVED_39, PROP_AFTER_LAST_ITEM, @@ -301,6 +306,12 @@ enum EntityPropertyList { PROP_AVATAR_PRIORITY = PROP_DERIVED_33, // Screen-sharing PROP_SCREENSHARE = PROP_DERIVED_34, + // Audio + PROP_REVERB_ENABLED = PROP_DERIVED_35, + PROP_REVERB_TIME = PROP_DERIVED_36, + PROP_REVERB_WET_LEVEL = PROP_DERIVED_37, + PROP_LISTENER_ZONES = PROP_DERIVED_38, + PROP_LISTENER_ATTENUATION_COEFFICIENTS = PROP_DERIVED_39, // Polyvox PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0, diff --git a/libraries/entities/src/ZoneAudioPropertyGroup.cpp b/libraries/entities/src/ZoneAudioPropertyGroup.cpp new file mode 100644 index 0000000000..aeedeea977 --- /dev/null +++ b/libraries/entities/src/ZoneAudioPropertyGroup.cpp @@ -0,0 +1,194 @@ +// +// ZoneAudioPropertyGroup.cpp +// libraries/entities/src +// +// Created by HifiExperiments on 11/28/23 +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ZoneAudioPropertyGroup.h" + +#include + +#include "EntityItemProperties.h" +#include "EntityItemPropertiesMacros.h" + +void ZoneAudioPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_REVERB_ENABLED, Audio, audio, ReverbEnabled, reverbEnabled); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_REVERB_TIME, Audio, audio, ReverbTime, reverbTime); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_REVERB_WET_LEVEL, Audio, audio, ReverbWetLevel, reverbWetLevel); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_LISTENER_ZONES, Audio, audio, ListenerZones, listenerZones); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_LISTENER_ATTENUATION_COEFFICIENTS, Audio, audio, ListenerAttenuationCoefficients, listenerAttenuationCoefficients); +} + +void ZoneAudioPropertyGroup::copyFromScriptValue(const ScriptValue& object, const QSet &namesSet, bool& _defaultSettings) { + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(audio, reverbEnabled, bool, setReverbEnabled); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(audio, reverbTime, float, setReverbTime); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(audio, reverbWetLevel, float, setReverbWetLevel); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(audio, listenerZones, qVectorQUuid, setListenerZones); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(audio, listenerAttenuationCoefficients, qVectorFloat, setListenerAttenuationCoefficients); +} + +void ZoneAudioPropertyGroup::merge(const ZoneAudioPropertyGroup& other) { + COPY_PROPERTY_IF_CHANGED(reverbEnabled); + COPY_PROPERTY_IF_CHANGED(reverbTime); + COPY_PROPERTY_IF_CHANGED(reverbWetLevel); + COPY_PROPERTY_IF_CHANGED(listenerZones); + COPY_PROPERTY_IF_CHANGED(listenerAttenuationCoefficients); +} + +void ZoneAudioPropertyGroup::debugDump() const { + qCDebug(entities) << " ZoneAudioPropertyGroup: ---------------------------------------------"; + qCDebug(entities) << " _reverbEnabled:" << _reverbEnabled; + qCDebug(entities) << " _reverbTime:" << _reverbTime; + qCDebug(entities) << " _reverbWetLevel:" << _reverbWetLevel; + qCDebug(entities) << " _listenerZones:" << _listenerZones; + qCDebug(entities) << " _listenerAttenuationCoefficients:" << _listenerAttenuationCoefficients; +} + +void ZoneAudioPropertyGroup::listChangedProperties(QList& out) { + if (reverbEnabledChanged()) { + out << "reverbEnabled"; + } + if (reverbTimeChanged()) { + out << "reverbTime"; + } + if (reverbWetLevelChanged()) { + out << "reverbWetLevel"; + } + if (listenerZonesChanged()) { + out << "listenerZones"; + } + if (listenerAttenuationCoefficientsChanged()) { + out << "listenerAttenuationCoefficients"; + } +} + +bool ZoneAudioPropertyGroup::appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_REVERB_ENABLED, getReverbEnabled()); + APPEND_ENTITY_PROPERTY(PROP_REVERB_TIME, getReverbTime()); + APPEND_ENTITY_PROPERTY(PROP_REVERB_WET_LEVEL, getReverbWetLevel()); + APPEND_ENTITY_PROPERTY(PROP_LISTENER_ZONES, getListenerZones()); + APPEND_ENTITY_PROPERTY(PROP_LISTENER_ATTENUATION_COEFFICIENTS, getListenerAttenuationCoefficients()); + + return true; +} + +bool ZoneAudioPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { + int bytesRead = 0; + bool overwriteLocalData = true; + bool somethingChanged = false; + + READ_ENTITY_PROPERTY(PROP_REVERB_ENABLED, bool, setReverbEnabled); + READ_ENTITY_PROPERTY(PROP_REVERB_TIME, float, setReverbTime); + READ_ENTITY_PROPERTY(PROP_REVERB_WET_LEVEL, float, setReverbWetLevel); + READ_ENTITY_PROPERTY(PROP_LISTENER_ZONES, QVector, setListenerZones); + READ_ENTITY_PROPERTY(PROP_LISTENER_ATTENUATION_COEFFICIENTS, QVector, setListenerAttenuationCoefficients); + + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_REVERB_ENABLED, ReverbEnabled); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_REVERB_TIME, ReverbTime); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_REVERB_WET_LEVEL, ReverbWetLevel); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_LISTENER_ZONES, ListenerZones); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_LISTENER_ATTENUATION_COEFFICIENTS, ListenerAttenuationCoefficients); + + processedBytes += bytesRead; + + Q_UNUSED(somethingChanged); + + return true; +} + +void ZoneAudioPropertyGroup::markAllChanged() { + _reverbEnabledChanged = true; + _reverbTimeChanged = true; + _reverbWetLevelChanged = true; + _listenerZonesChanged = true; + _listenerAttenuationCoefficientsChanged = true; +} + +EntityPropertyFlags ZoneAudioPropertyGroup::getChangedProperties() const { + EntityPropertyFlags changedProperties; + + CHECK_PROPERTY_CHANGE(PROP_REVERB_ENABLED, reverbEnabled); + CHECK_PROPERTY_CHANGE(PROP_REVERB_TIME, reverbTime); + CHECK_PROPERTY_CHANGE(PROP_REVERB_WET_LEVEL, reverbWetLevel); + CHECK_PROPERTY_CHANGE(PROP_LISTENER_ZONES, listenerZones); + CHECK_PROPERTY_CHANGE(PROP_LISTENER_ATTENUATION_COEFFICIENTS, listenerAttenuationCoefficients); + + return changedProperties; +} + +void ZoneAudioPropertyGroup::getProperties(EntityItemProperties& properties) const { + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Audio, ReverbEnabled, getReverbEnabled); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Audio, ReverbTime, getReverbTime); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Audio, ReverbWetLevel, getReverbWetLevel); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Audio, ListenerZones, getListenerZones); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Audio, ListenerAttenuationCoefficients, getListenerAttenuationCoefficients); +} + +bool ZoneAudioPropertyGroup::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Audio, ReverbEnabled, reverbEnabled, setReverbEnabled); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Audio, ReverbTime, reverbTime, setReverbTime); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Audio, ReverbWetLevel, reverbWetLevel, setReverbWetLevel); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Audio, ListenerZones, listenerZones, setListenerZones); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Audio, ListenerAttenuationCoefficients, listenerAttenuationCoefficients, setListenerAttenuationCoefficients); + + return somethingChanged; +} + +EntityPropertyFlags ZoneAudioPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties; + + requestedProperties += PROP_REVERB_ENABLED; + requestedProperties += PROP_REVERB_TIME; + requestedProperties += PROP_REVERB_WET_LEVEL; + requestedProperties += PROP_LISTENER_ZONES; + requestedProperties += PROP_LISTENER_ATTENUATION_COEFFICIENTS; + + return requestedProperties; +} + +void ZoneAudioPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_REVERB_ENABLED, getReverbEnabled()); + APPEND_ENTITY_PROPERTY(PROP_REVERB_TIME, getReverbTime()); + APPEND_ENTITY_PROPERTY(PROP_REVERB_WET_LEVEL, getReverbWetLevel()); + APPEND_ENTITY_PROPERTY(PROP_LISTENER_ZONES, getListenerZones()); + APPEND_ENTITY_PROPERTY(PROP_LISTENER_ATTENUATION_COEFFICIENTS, getListenerAttenuationCoefficients()); +} + +int ZoneAudioPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_REVERB_ENABLED, bool, setReverbEnabled); + READ_ENTITY_PROPERTY(PROP_REVERB_TIME, float, setReverbTime); + READ_ENTITY_PROPERTY(PROP_REVERB_WET_LEVEL, float, setReverbWetLevel); + READ_ENTITY_PROPERTY(PROP_LISTENER_ZONES, QVector, setListenerZones); + READ_ENTITY_PROPERTY(PROP_LISTENER_ATTENUATION_COEFFICIENTS, QVector, setListenerAttenuationCoefficients); + + return bytesRead; +} diff --git a/libraries/entities/src/ZoneAudioPropertyGroup.h b/libraries/entities/src/ZoneAudioPropertyGroup.h new file mode 100644 index 0000000000..a99a43e2be --- /dev/null +++ b/libraries/entities/src/ZoneAudioPropertyGroup.h @@ -0,0 +1,97 @@ +// +// ZoneAudioPropertyGroup.h +// libraries/entities/src +// +// Created by HifiExperiments on 11/28/23 +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef hifi_ZoneAudioPropertyGroup_h +#define hifi_ZoneAudioPropertyGroup_h + +#include +#include + +#include "PropertyGroup.h" +#include "EntityItemPropertiesMacros.h" + +class EntityItemProperties; +class EncodeBitstreamParams; +class OctreePacketData; +class EntityTreeElementExtraEncodeData; +class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; + +/*@jsdoc + * Zone audio is defined by the following properties: + * @typedef {object} Entities.ZoneAudio + * @property {boolean} reverbEnabled=false - If reverb should be enabled for listeners in this zone. + * @property {number} reverbTime=1.0 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60. + * @property {number} reverbWetLevel=50 - Adjusts the wet/dry percentage, from completely dry (0%) to completely wet (100%). + * @property {Uuid[]} listenerZones=[] - A list of entity IDs representing listener zones with this zone as a source. + * Sounds from this zone being heard by a listener in a listener zone will be attenuated by the corresponding + * listenerAttenuationCoefficient. + * @property {number[]} listenerAttenuationCoefficients=[] - A list of attenuation coefficients. Each coefficient will be + * applied to sounds coming from this zone and being heard by a listener in the corresponding listenerZone. + */ +class ZoneAudioPropertyGroup : public PropertyGroup { +public: + // EntityItemProperty related helpers + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, + EntityItemProperties& defaultEntityProperties) const override; + virtual void copyFromScriptValue(const ScriptValue& object, const QSet &namesSet, bool& _defaultSettings) override; + + void merge(const ZoneAudioPropertyGroup& other); + + virtual void debugDump() const override; + virtual void listChangedProperties(QList& out) override; + + virtual bool appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, + const unsigned char*& dataAt, int& processedBytes) override; + virtual void markAllChanged() override; + virtual EntityPropertyFlags getChangedProperties() const override; + + // EntityItem related helpers + // methods for getting/setting all properties of an entity + virtual void getProperties(EntityItemProperties& propertiesOut) const override; + + /// returns true if something changed + virtual bool setProperties(const EntityItemProperties& properties) override; + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + DEFINE_PROPERTY(PROP_REVERB_ENABLED, ReverbEnabled, reverbEnabled, bool, false); + DEFINE_PROPERTY(PROP_REVERB_TIME, ReverbTime, reverbTime, float, 1.0f); + DEFINE_PROPERTY(PROP_REVERB_WET_LEVEL, ReverbWetLevel, reverbWetLevel, float, 50.0f); + DEFINE_PROPERTY(PROP_LISTENER_ZONES, ListenerZones, listenerZones, QVector, QVector()); + DEFINE_PROPERTY(PROP_LISTENER_ATTENUATION_COEFFICIENTS, ListenerAttenuationCoefficients, listenerAttenuationCoefficients, QVector, QVector()); + +}; + +#endif // hifi_ZoneAudioPropertyGroup_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 8ac5a4329c..cefd2ccb26 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -60,6 +60,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de }); _hazeProperties.getProperties(properties); _bloomProperties.getProperties(properties); + _audioProperties.getProperties(properties); COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); @@ -90,6 +91,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie }); _hazePropertiesChanged |= _hazeProperties.setProperties(properties); _bloomPropertiesChanged |= _bloomProperties.setProperties(properties); + bool audioPropertiesChanged = _audioProperties.setProperties(properties); SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); @@ -103,8 +105,8 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); SET_ENTITY_PROPERTY_FROM_PROPERTIES(screenshare, setScreenshare); - somethingChanged |= _keyLightPropertiesChanged || _ambientLightPropertiesChanged || - _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; + somethingChanged |= _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _skyboxPropertiesChanged || + _hazePropertiesChanged || _bloomPropertiesChanged || audioPropertiesChanged; return somethingChanged; } @@ -168,6 +170,13 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, dataAt += bytesFromBloom; } + { + int bytesFromAudio = _audioProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, somethingChanged); + bytesRead += bytesFromAudio; + dataAt += bytesFromAudio; + } + READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); @@ -194,6 +203,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += _skyboxProperties.getEntityProperties(params); requestedProperties += _hazeProperties.getEntityProperties(params); requestedProperties += _bloomProperties.getEntityProperties(params); + requestedProperties += _audioProperties.getEntityProperties(params); requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED; @@ -235,6 +245,8 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits propertyFlags, propertiesDidntFit, propertyCount, appendState); _bloomProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _audioProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); @@ -267,6 +279,7 @@ void ZoneEntityItem::debugDump() const { _skyboxProperties.debugDump(); _hazeProperties.debugDump(); _bloomProperties.debugDump(); + _audioProperties.debugDump(); } void ZoneEntityItem::setShapeType(ShapeType type) { diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 2b61bbd346..ccf895ca44 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -22,6 +22,7 @@ #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" #include "BloomPropertyGroup.h" +#include "ZoneAudioPropertyGroup.h" class ZoneEntityItem : public EntityItem { public: @@ -46,10 +47,9 @@ public: OctreeElement::AppendState& appendState) const override; virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) override; - + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; static bool getZonesArePickable() { return _zonesArePickable; } @@ -90,6 +90,7 @@ public: const HazePropertyGroup& getHazeProperties() const { return _hazeProperties; } const BloomPropertyGroup& getBloomProperties() const { return _bloomProperties; } + const ZoneAudioPropertyGroup& getAudioProperties() const { return _audioProperties; } bool getFlyingAllowed() const { return _flyingAllowed; } void setFlyingAllowed(bool value) { _flyingAllowed = value; } @@ -152,6 +153,7 @@ protected: SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; BloomPropertyGroup _bloomProperties; + ZoneAudioPropertyGroup _audioProperties; bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; @@ -167,7 +169,7 @@ protected: bool _keyLightPropertiesChanged { false }; bool _ambientLightPropertiesChanged { false }; bool _skyboxPropertiesChanged { false }; - bool _hazePropertiesChanged{ false }; + bool _hazePropertiesChanged { false }; bool _bloomPropertiesChanged { false }; static bool _drawZoneBoundaries; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 33ed430e41..6f84f25e86 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -293,6 +293,7 @@ enum class EntityVersion : PacketVersion { Mirror, EntityTags, WantsKeyboardFocus, + AudioZones, // Add new versions above here NUM_PACKET_TYPE, diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 04c7d61c68..b4597e9141 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -158,6 +158,21 @@ "bloom.bloomSize": { "tooltip": "The radius of bloom. The higher the value, the larger the bloom." }, + "audio.reverbEnabled": { + "tooltip": "If reverb should be enabled for listeners in this zone." + }, + "audio.reverbTime": { + "tooltip": "The time (seconds) for the reverb tail to decay by 60dB." + }, + "audio.reverbWetLevel": { + "tooltip": "Adjusts the wet/dry percentage, from completely dry (0%) to completely wet (100%)." + }, + "audio.listenerZones": { + "tooltip": "A list of entity IDs representing listener zones with this zone as a source." + }, + "audio.listenerAttenuationCoefficients": { + "tooltip": "A list of attenuation coefficients." + }, "avatarPriority": { "tooltip": "Alter Avatars' update priorities." }, diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index 140c893399..8687988c57 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -620,6 +620,42 @@ const GROUPS = [ } ] }, + { + id: "zone_audio", + label: "ZONE AUDIO", + properties: [ + { + label: "Enable Reverb", + type: "bool", + propertyID: "audio.enableReverb" + }, + { + label: "Reverb Time", + type: "number-draggable", + min: 0, + max: 10, + step: 0.1, + decimals: 2, + propertyID: "audio.reverbTime", + showPropertyRule: { "audio.enableReverb": "true" }, + }, + { + label: "Reverb Wet Level", + type: "number-draggable", + min: 0, + max: 100, + step: 1, + decimals: 1, + propertyID: "audio.reverbWetLevel", + showPropertyRule: { "audio.enableReverb": "true" }, + }, + { + label: "Listener Zones", + type: "multipleZonesSelection", + propertyID: "audio.listenerZones", + } + ] + }, { id: "model", label: "MODEL", @@ -1764,7 +1800,7 @@ const GROUPS_PER_TYPE = { Shape: [ 'base', 'shape', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], Text: [ 'base', 'text', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], Zone: [ 'base', 'zone', 'zone_key_light', 'zone_skybox', 'zone_ambient_light', 'zone_haze', - 'zone_bloom', 'zone_avatar_priority', 'spatial', 'behavior', 'scripts', 'physics' ], + 'zone_bloom', 'zone_avatar_priority', 'zone_audio', 'spatial', 'behavior', 'scripts', 'physics' ], Model: [ 'base', 'model', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], Image: [ 'base', 'image', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], Web: [ 'base', 'web', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], diff --git a/scripts/system/create/entityProperties/html/tabs/zone_audio.png b/scripts/system/create/entityProperties/html/tabs/zone_audio.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7adf8fa50949a26577e637b2a2d19cbf88a138 GIT binary patch literal 598 zcmWm8O-Pe*0LSs)^X%EP7oX+BL^Bio!_XvjIM$o?vS+^J7HCsEC`GQhmIon4R+%fG zO-u_(n+P^WtwgW_YoOFY$l|Fs@?afIJcMONvx_SOCD~$!@9lH=-m=wJYt%Vv02-^M zvK~NoDkTeG<;w9b0IGINQzwv?k%~efm=erIbB!4Y-|_sb8v#ICEcG?OwR}MA1-6rb z7z5lTz`7kM8v}M<@;ARd2~hLaN^`^2Wye-BnXDiQwPf-xX$JO$HcuazmG4))F+Pv% zD_@)TZe{R2vU?g{DB?KolQ7E;q$BQTmH4>LO|BTU-EzKJ7%}adcp&RA@O!BaT7b80 zM1~FH+2+@jJKn;s({*G~p(Mj{ZkholF%rp(Xdfp`+JbiqJt#)~v{?vR zoi7vBFNB-{ zr86izX`7UP7ndj^ww^mHWB5YYmAPYMbK6Hbp8w#KT1xh zq-PID`$_{hKij$*3;Y!$uNY}4q<_&Dh$?u2TX20)kc3jwFL0~$nr_u?8I3xNes|bP ei^&anW;eszaqvOxZr+iNAOKjaYAdJ8?Y{rIU&;jl literal 0 HcmV?d00001