// // Created by HifiExperiments on 12/30/2023 // 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 // #include "SoundEntityItem.h" #include #include "EntitiesLogging.h" #include "EntityItemProperties.h" #include "EntityTree.h" #include "EntityTreeElement.h" EntityItemPointer SoundEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { std::shared_ptr entity(new SoundEntityItem(entityID), [](SoundEntityItem* ptr) { ptr->deleteLater(); }); entity->setProperties(properties); return entity; } // our non-pure virtual subclass for now... SoundEntityItem::SoundEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Sound; } SoundEntityItem::~SoundEntityItem() { auto manager = DependencyManager::get(); if (manager && _injector) { manager->stop(_injector); } } EntityItemProperties SoundEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(soundURL, getURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(volume, getVolume); COPY_ENTITY_PROPERTY_TO_PROPERTIES(timeOffset, getTimeOffset); COPY_ENTITY_PROPERTY_TO_PROPERTIES(pitch, getPitch); COPY_ENTITY_PROPERTY_TO_PROPERTIES(playing, getPlaying); COPY_ENTITY_PROPERTY_TO_PROPERTIES(loop, getLoop); COPY_ENTITY_PROPERTY_TO_PROPERTIES(positional, getPositional); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localOnly, getLocalOnly); return properties; } bool SoundEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(soundURL, setURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(volume, setVolume); SET_ENTITY_PROPERTY_FROM_PROPERTIES(timeOffset, setTimeOffset); SET_ENTITY_PROPERTY_FROM_PROPERTIES(pitch, setPitch); SET_ENTITY_PROPERTY_FROM_PROPERTIES(playing, setPlaying); SET_ENTITY_PROPERTY_FROM_PROPERTIES(loop, setLoop); SET_ENTITY_PROPERTY_FROM_PROPERTIES(positional, setPositional); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localOnly, setLocalOnly); return somethingChanged; } int SoundEntityItem::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_SOUND_URL, QString, setURL); READ_ENTITY_PROPERTY(PROP_SOUND_VOLUME, float, setVolume); READ_ENTITY_PROPERTY(PROP_SOUND_TIME_OFFSET, float, setTimeOffset); READ_ENTITY_PROPERTY(PROP_SOUND_PITCH, float, setPitch); READ_ENTITY_PROPERTY(PROP_SOUND_PLAYING, bool, setPlaying); READ_ENTITY_PROPERTY(PROP_SOUND_LOOP, bool, setLoop); READ_ENTITY_PROPERTY(PROP_SOUND_POSITIONAL, bool, setPositional); READ_ENTITY_PROPERTY(PROP_SOUND_LOCAL_ONLY, bool, setLocalOnly); return bytesRead; } EntityPropertyFlags SoundEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SOUND_URL; requestedProperties += PROP_SOUND_VOLUME; requestedProperties += PROP_SOUND_TIME_OFFSET; requestedProperties += PROP_SOUND_PITCH; requestedProperties += PROP_SOUND_PLAYING; requestedProperties += PROP_SOUND_LOOP; requestedProperties += PROP_SOUND_POSITIONAL; requestedProperties += PROP_SOUND_LOCAL_ONLY; return requestedProperties; } void SoundEntityItem::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_SOUND_URL, getURL()); APPEND_ENTITY_PROPERTY(PROP_SOUND_VOLUME, getVolume()); APPEND_ENTITY_PROPERTY(PROP_SOUND_TIME_OFFSET, getTimeOffset()); APPEND_ENTITY_PROPERTY(PROP_SOUND_PITCH, getPitch()); APPEND_ENTITY_PROPERTY(PROP_SOUND_PLAYING, getPlaying()); APPEND_ENTITY_PROPERTY(PROP_SOUND_LOOP, getLoop()); APPEND_ENTITY_PROPERTY(PROP_SOUND_POSITIONAL, getPositional()); APPEND_ENTITY_PROPERTY(PROP_SOUND_LOCAL_ONLY, getLocalOnly()); } void SoundEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "SOUND EntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " name:" << _name; qCDebug(entities) << " url:" << _url; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << "SOUND EntityItem Ptr:" << this; } bool SoundEntityItem::shouldCreateSound(const EntityTreePointer& tree) const { bool clientShouldMakeSound = _localOnly || isMyAvatarEntity() || tree->isServerlessMode(); bool serverShouldMakeSound = !_localOnly; return (clientShouldMakeSound && tree->getIsClient()) || (serverShouldMakeSound && tree->isEntityServer()); } void SoundEntityItem::update(const quint64& now) { const auto tree = getTree(); if (tree) { std::lock_guard lock(_soundLock); _updateNeeded = false; withReadLock([&] { if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } }); if (_sound) { if (_sound->isLoaded()) { updateSound(true); } else { connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); } } } } void SoundEntityItem::locationChanged(bool tellPhysics, bool tellChildren) { EntityItem::locationChanged(tellPhysics, tellChildren); updateSound(); } void SoundEntityItem::dimensionsChanged() { EntityItem::dimensionsChanged(); updateSound(); } void SoundEntityItem::setURL(const QString& value) { bool changed = false; withWriteLock([&] { if (value != _url) { _url = value; changed = true; } }); if (changed) { const auto tree = getTree(); if (!tree) { _updateNeeded = true; return; } std::lock_guard lock(_soundLock); withReadLock([&] { if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } }); if (_sound) { if (_sound->isLoaded()) { updateSound(true); } else { connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); } } } } QString SoundEntityItem::getURL() const { return resultWithReadLock([&] { return _url; }); } void SoundEntityItem::setVolume(float value) { bool changed = false; withWriteLock([&] { if (value != _volume) { _volume = value; changed = true; } }); if (changed) { updateSound(); } } float SoundEntityItem::getVolume() const { return resultWithReadLock([&] { return _volume; }); } void SoundEntityItem::setTimeOffset(float value) { bool changed = false; withWriteLock([&] { if (value != _timeOffset) { _timeOffset = value; changed = true; } }); if (changed) { updateSound(true); } } float SoundEntityItem::getTimeOffset() const { return resultWithReadLock([&] { return _timeOffset; }); } void SoundEntityItem::setPitch(float value) { bool changed = false; withWriteLock([&] { if (value != _pitch) { _pitch = value; changed = true; } }); if (changed) { updateSound(true); } } float SoundEntityItem::getPitch() const { return resultWithReadLock([&] { return _pitch; }); } void SoundEntityItem::setPlaying(bool value) { bool changed = false; withWriteLock([&] { if (value != _playing) { _playing = value; changed = true; } }); if (changed) { updateSound(); } } bool SoundEntityItem::getPlaying() const { return resultWithReadLock([&] { return _playing; }); } void SoundEntityItem::setLoop(bool value) { bool changed = false; withWriteLock([&] { if (value != _loop) { _loop = value; changed = true; } }); if (changed) { updateSound(true); } } bool SoundEntityItem::getLoop() const { return resultWithReadLock([&] { return _loop; }); } void SoundEntityItem::setPositional(bool value) { bool changed = false; withWriteLock([&] { if (value != _positional) { _positional = value; changed = true; } }); if (changed) { updateSound(); } } bool SoundEntityItem::getPositional() const { return resultWithReadLock([&] { return _positional; }); } void SoundEntityItem::setLocalOnly(bool value) { bool changed = false; withWriteLock([&] { if (value != _localOnly) { _localOnly = value; changed = true; } }); if (changed) { const auto tree = getTree(); if (!tree) { _updateNeeded = true; return; } std::lock_guard lock(_soundLock); bool createdSound = false; withReadLock([&] { if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); createdSound = true; } }); if (!createdSound) { _sound = nullptr; if (_injector) { DependencyManager::get()->stop(_injector); _injector = nullptr; } } if (_sound) { if (_sound->isLoaded()) { updateSound(true); } else { connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); } } } } bool SoundEntityItem::getLocalOnly() const { return resultWithReadLock([&] { return _localOnly; }); } bool SoundEntityItem::restartSound(bool lock) { if (lock) { _soundLock.lock(); } if (!_sound) { if (lock) { _soundLock.unlock(); } return false; } AudioInjectorOptions options; withReadLock([&] { const glm::quat orientation = getWorldOrientation(); options.position = getWorldPosition() + orientation * (getScaledDimensions() * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); options.positionSet = _positional; options.volume = _volume; options.loop = _loop; options.orientation = orientation; options.localOnly = _localOnly || _sound->isAmbisonic(); // force localOnly when ambisonic options.secondOffset = _timeOffset; options.pitch = _pitch; }); // stereo option isn't set from script, this comes from sound metadata or filename options.stereo = _sound->isStereo(); options.ambisonic = _sound->isAmbisonic(); if (_injector) { DependencyManager::get()->setOptions(_injector, options); } else { _injector = DependencyManager::get()->playSound(_sound, options); } if (lock) { _soundLock.unlock(); } return true; } void SoundEntityItem::updateSound(bool restart) { std::lock_guard lock(_soundLock); if (!_sound) { return; } if (restart) { if (_injector) { DependencyManager::get()->stop(_injector); _injector = nullptr; } } if (_playing) { restartSound(); } else { if (_injector) { DependencyManager::get()->stop(_injector); _injector = nullptr; } } }