From 878774b5d3b5d9f4ea70ef184a4d6119d6bd3c32 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 24 Mar 2024 13:31:16 -0700 Subject: [PATCH 1/8] sound entities --- assignment-client/src/audio/AudioMixer.cpp | 1 + assignment-client/src/avatars/AvatarMixer.cpp | 1 + .../src/entities/EntityServer.cpp | 8 + .../src/scripts/EntityScriptServer.cpp | 1 + libraries/audio/src/AudioInjector.cpp | 3 + libraries/entities/CMakeLists.txt | 2 +- .../entities/src/EntityItemProperties.cpp | 139 +++++++ libraries/entities/src/EntityItemProperties.h | 13 + libraries/entities/src/EntityPropertyFlags.h | 10 + .../entities/src/EntityScriptingInterface.cpp | 26 ++ .../entities/src/EntityScriptingInterface.h | 21 + libraries/entities/src/EntityTree.h | 4 + libraries/entities/src/EntityTreeElement.cpp | 8 +- libraries/entities/src/EntityTypes.cpp | 2 + libraries/entities/src/EntityTypes.h | 3 + libraries/entities/src/SoundEntityItem.cpp | 371 ++++++++++++++++++ libraries/entities/src/SoundEntityItem.h | 100 +++++ libraries/networking/src/udt/PacketHeaders.h | 1 + libraries/physics/CMakeLists.txt | 1 + scripts/system/assets/images/tools/sound.svg | 258 ++++++++++++ .../create/assets/data/createAppTooltips.json | 24 ++ .../create/assets/images/icon-sound.svg | 84 ++++ scripts/system/create/edit.js | 19 +- .../system/create/entityList/entityList.js | 4 +- .../create/entityList/html/js/entityList.js | 1 + .../html/js/entityProperties.js | 57 +++ .../entityProperties/html/tabs/sound.png | Bin 0 -> 684 bytes .../entitySelectionTool.js | 2 +- .../system/create/modules/brokenURLReport.js | 11 + scripts/system/create/qml/EditTabView.qml | 12 + .../system/create/qml/EditToolsTabView.qml | 12 + scripts/system/create/qml/icons/sound.svg | 59 +++ scripts/system/html/js/includes.js | 1 + 33 files changed, 1249 insertions(+), 10 deletions(-) create mode 100644 libraries/entities/src/SoundEntityItem.cpp create mode 100644 libraries/entities/src/SoundEntityItem.h create mode 100644 scripts/system/assets/images/tools/sound.svg create mode 100644 scripts/system/create/assets/images/icon-sound.svg create mode 100644 scripts/system/create/entityProperties/html/tabs/sound.png create mode 100644 scripts/system/create/qml/icons/sound.svg diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 92719abef6..83fe2cc08f 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -816,6 +816,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { void AudioMixer::setupEntityQuery() { _entityViewer.init(); EntityTreePointer entityTree = _entityViewer.getTree(); + entityTree->setIsServer(true); DependencyManager::registerInheritance(); DependencyManager::set(entityTree); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 8d322c36f2..81a9b89b0a 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -1081,6 +1081,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { void AvatarMixer::setupEntityQuery() { _entityViewer.init(); EntityTreePointer entityTree = _entityViewer.getTree(); + entityTree->setIsServer(true); DependencyManager::registerInheritance(); DependencyManager::set(entityTree); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index d27a69ff7c..9cbaaa0ea1 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -16,9 +16,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -49,6 +51,9 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache ctor DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityClone, @@ -71,6 +76,8 @@ EntityServer::~EntityServer() { void EntityServer::aboutToFinish() { DependencyManager::get()->cleanup(); + DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); OctreeServer::aboutToFinish(); @@ -90,6 +97,7 @@ OctreePointer EntityServer::createTree() { EntityTreePointer tree = std::make_shared(true); tree->createRootElement(); tree->addNewlyCreatedHook(this); + tree->setIsEntityServer(true); if (!_entitySimulation) { SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(tree); diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index b16e4561d6..3f49ecd889 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -313,6 +313,7 @@ void EntityScriptServer::run() { entityScriptingInterface->setEntityTree(_entityViewer.getTree()); auto treePtr = _entityViewer.getTree(); + treePtr->setIsServer(true); DependencyManager::set(treePtr); if (!_entitySimulation) { diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 2df766377f..8c3a6b118e 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -132,9 +132,11 @@ void AudioInjector::restart() { bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) { AudioInjectorOptions options; + uint32_t numBytes; withWriteLock([&] { _state = AudioInjectorState::NotFinished; options = _options; + numBytes = _audioData->getNumBytes(); }); int byteOffset = 0; @@ -142,6 +144,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj int numChannels = options.ambisonic ? 4 : (options.stereo ? 2 : 1); byteOffset = (int)(AudioConstants::SAMPLE_RATE * options.secondOffset * numChannels); byteOffset *= AudioConstants::SAMPLE_SIZE; + byteOffset = byteOffset % numBytes; } _currentSendOffset = byteOffset; diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index e04d9f9fa8..830cecd1ed 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -12,7 +12,7 @@ include_hifi_library_headers(image) include_hifi_library_headers(ktx) include_hifi_library_headers(material-networking) include_hifi_library_headers(procedural) -link_hifi_libraries(shared shaders networking octree avatars graphics model-networking script-engine) +link_hifi_libraries(audio shared shaders networking octree avatars graphics model-networking script-engine) if (WIN32) add_compile_definitions(_USE_MATH_DEFINES) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 5db64d12e4..a32dcf78c0 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -690,6 +690,16 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_GIZMO_TYPE, gizmoType); changedProperties += _ring.getChangedProperties(); + // Sound + CHECK_PROPERTY_CHANGE(PROP_SOUND_URL, soundURL); + CHECK_PROPERTY_CHANGE(PROP_SOUND_VOLUME, volume); + CHECK_PROPERTY_CHANGE(PROP_SOUND_TIME_OFFSET, timeOffset); + CHECK_PROPERTY_CHANGE(PROP_SOUND_PITCH, pitch); + CHECK_PROPERTY_CHANGE(PROP_SOUND_PLAYING, playing); + CHECK_PROPERTY_CHANGE(PROP_SOUND_LOOP, loop); + CHECK_PROPERTY_CHANGE(PROP_SOUND_POSITIONAL, positional); + CHECK_PROPERTY_CHANGE(PROP_SOUND_LOCAL_ONLY, localOnly); + return changedProperties; } @@ -883,6 +893,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} * @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} + * @see {@link Entities.EntityProperties-Sound|EntityProperties-Sound} * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} @@ -1340,6 +1351,34 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * }); */ +/*@jsdoc + * The "Sound" {@link Entities.EntityType|EntityType} plays a sound from a URL. It has properties in addition to + * the common {@link Entities.EntityProperties|EntityProperties}. + * + * @typedef {object} Entities.EntityProperties-Sound + * @property {string} soundURL="" - The URL of the sound to play, as a wav, mp3, or raw file. Supports stereo and ambisonic. + * @property {boolean} playing=true - Whether or not the sound should play. + * @property {number} volume=1.0 - The volume of the sound, from 0 to 1. + * @property {number} pitch=1.0 - The relative sample rate at which to resample the sound, within +/- 2 octaves. + * @property {number} timeOffset=0.0 - The time (in seconds) at which to start playback within the sound file. If looping, + * this only affects the first loop. + * @property {boolean} loop=true - Whether or not to loop the sound. + * @property {boolean} positional=true - Whether or not the volume of the sound should decay with distance. + * @property {boolean} localOnly=false - Whether or not the sound should play locally for everyone (unsynced), or synchronously + * for everyone via the Entity Mixer. + * @example Create a Sound entity. + * var entity = Entities.addEntity({ + * type: "Sound", + * soundURL: "https://themushroomkingdom.net/sounds/wav/lm/lm_gold_mouse.wav", + * positional: true, + * volume: 0.75, + * localOnly: true, + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -4 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + /*@jsdoc * The "Sphere" {@link Entities.EntityType|EntityType} is the same as the "Shape" * {@link Entities.EntityType|EntityType} except that its shape value is always set to "Sphere" @@ -1961,6 +2000,18 @@ ScriptValue EntityItemProperties::copyToScriptValue(ScriptEngine* engine, bool s _ring.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } + // Sound only + if (_type == EntityTypes::Sound) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_URL, soundURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_VOLUME, volume); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_TIME_OFFSET, timeOffset); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_PITCH, pitch); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_PLAYING, playing); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_LOOP, loop); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_POSITIONAL, positional); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOUND_LOCAL_ONLY, localOnly); + } + /*@jsdoc * The axis-aligned bounding box of an entity. * @typedef {object} Entities.BoundingBox @@ -2292,6 +2343,16 @@ void EntityItemProperties::copyFromScriptValue(const ScriptValue& object, bool h COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(gizmoType, GizmoType); _ring.copyFromScriptValue(object, namesSet, _defaultSettings); + // Sound + COPY_PROPERTY_FROM_QSCRIPTVALUE(soundURL, QString, setSoundURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(volume, float, setVolume); + COPY_PROPERTY_FROM_QSCRIPTVALUE(timeOffset, float, setTimeOffset); + COPY_PROPERTY_FROM_QSCRIPTVALUE(pitch, float, setPitch); + COPY_PROPERTY_FROM_QSCRIPTVALUE(playing, bool, setPlaying); + COPY_PROPERTY_FROM_QSCRIPTVALUE(loop, bool, setLoop); + COPY_PROPERTY_FROM_QSCRIPTVALUE(positional, bool, setPositional); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localOnly, bool, setLocalOnly); + // Handle conversions from old 'textures' property to "imageURL" if (namesSet.contains("textures")) { ScriptValue V = object.property("textures"); @@ -2578,6 +2639,16 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(gizmoType); _ring.merge(other._ring); + // Sound + COPY_PROPERTY_IF_CHANGED(soundURL); + COPY_PROPERTY_IF_CHANGED(volume); + COPY_PROPERTY_IF_CHANGED(timeOffset); + COPY_PROPERTY_IF_CHANGED(pitch); + COPY_PROPERTY_IF_CHANGED(playing); + COPY_PROPERTY_IF_CHANGED(loop); + COPY_PROPERTY_IF_CHANGED(positional); + COPY_PROPERTY_IF_CHANGED(localOnly); + _lastEdited = usecTimestampNow(); } @@ -3002,6 +3073,16 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_GROUP_PROPERTY_TO_MAP(PROP_MAJOR_TICK_MARKS_COLOR, Ring, ring, MajorTickMarksColor, majorTickMarksColor); ADD_GROUP_PROPERTY_TO_MAP(PROP_MINOR_TICK_MARKS_COLOR, Ring, ring, MinorTickMarksColor, minorTickMarksColor); } + + // Sound + ADD_PROPERTY_TO_MAP(PROP_SOUND_URL, SoundURL, soundURL, QString); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_SOUND_VOLUME, Volume, volume, float, 0.0f, 1.0f); + ADD_PROPERTY_TO_MAP(PROP_SOUND_TIME_OFFSET, TimeOffset, timeOffset, float); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_SOUND_PITCH, Pitch, pitch, float, 1.0f / 16.0f, 16.0f); + ADD_PROPERTY_TO_MAP(PROP_SOUND_PLAYING, Playing, playing, bool); + ADD_PROPERTY_TO_MAP(PROP_SOUND_LOOP, Loop, loop, bool); + ADD_PROPERTY_TO_MAP(PROP_SOUND_POSITIONAL, Positional, positional, bool); + ADD_PROPERTY_TO_MAP(PROP_SOUND_LOCAL_ONLY, LocalOnly, localOnly, bool); }); auto iter = _propertyInfos.find(propertyName); @@ -3449,6 +3530,17 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticRing.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); } + + if (properties.getType() == EntityTypes::Sound) { + APPEND_ENTITY_PROPERTY(PROP_SOUND_URL, properties.getSoundURL()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_VOLUME, properties.getVolume()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_TIME_OFFSET, properties.getTimeOffset()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_PITCH, properties.getPitch()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_PLAYING, properties.getPlaying()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_LOOP, properties.getLoop()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_POSITIONAL, properties.getPositional()); + APPEND_ENTITY_PROPERTY(PROP_SOUND_LOCAL_ONLY, properties.getLocalOnly()); + } } if (propertyCount > 0) { @@ -3911,6 +4003,17 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getRing().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); } + if (properties.getType() == EntityTypes::Sound) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_URL, QString, setSoundURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_VOLUME, float, setVolume); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_TIME_OFFSET, float, setTimeOffset); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_PITCH, float, setPitch); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_PLAYING, bool, setPlaying); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_LOOP, bool, setLoop); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_POSITIONAL, bool, setPositional); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOUND_LOCAL_ONLY, bool, setLocalOnly); + } + return valid; } @@ -4240,6 +4343,16 @@ void EntityItemProperties::markAllChanged() { // Gizmo _gizmoTypeChanged = true; _ring.markAllChanged(); + + // Sound + _soundURLChanged = true; + _volumeChanged = true; + _timeOffsetChanged = true; + _pitchChanged = true; + _playingChanged = true; + _loopChanged = true; + _positionalChanged = true; + _localOnlyChanged = true; } // The minimum bounding box for the entity. @@ -4945,6 +5058,32 @@ QList EntityItemProperties::listChangedProperties() { } getRing().listChangedProperties(out); + // Sound + if (soundURLChanged()) { + out += "soundURL"; + } + if (volumeChanged()) { + out += "volume"; + } + if (timeOffsetChanged()) { + out += "timeOffset"; + } + if (pitchChanged()) { + out += "pitch"; + } + if (playingChanged()) { + out += "playing"; + } + if (loopChanged()) { + out += "loop"; + } + if (positionalChanged()) { + out += "positional"; + } + if (localOnlyChanged()) { + out += "localOnly"; + } + return out; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6340313f2d..961049b1c7 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -52,6 +52,7 @@ #include "PolyVoxEntityItem.h" #include "GridEntityItem.h" #include "GizmoEntityItem.h" +#include "SoundEntityItem.h" #include "LightEntityItem.h" #include "ZoneEntityItem.h" @@ -124,6 +125,8 @@ class EntityItemProperties { friend class LightEntityItem; friend class ZoneEntityItem; friend class MaterialEntityItem; + friend class SoundEntityItem; + public: static bool blobToProperties(ScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties); static void propertiesToBlob(ScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, @@ -407,6 +410,16 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_GIZMO_TYPE, GizmoType, gizmoType, GizmoType, GizmoType::RING); DEFINE_PROPERTY_GROUP(Ring, ring, RingGizmoPropertyGroup); + // Sound + DEFINE_PROPERTY_REF(PROP_SOUND_URL, SoundURL, soundURL, QString, ""); + DEFINE_PROPERTY(PROP_SOUND_VOLUME, Volume, volume, float, 1.0f); + DEFINE_PROPERTY(PROP_SOUND_TIME_OFFSET, TimeOffset, timeOffset, float, 0.0f); + DEFINE_PROPERTY(PROP_SOUND_PITCH, Pitch, pitch, float, 1.0f); + DEFINE_PROPERTY(PROP_SOUND_PLAYING, Playing, playing, bool, true); + DEFINE_PROPERTY(PROP_SOUND_LOOP, Loop, loop, bool, true); + DEFINE_PROPERTY(PROP_SOUND_POSITIONAL, Positional, positional, bool, true); + DEFINE_PROPERTY(PROP_SOUND_LOCAL_ONLY, LocalOnly, localOnly, bool, false); + static QString getComponentModeAsString(uint32_t mode); public: diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index ca96c04fd8..591725c2ea 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -394,6 +394,16 @@ enum EntityPropertyList { PROP_MAJOR_TICK_MARKS_COLOR = PROP_DERIVED_17, PROP_MINOR_TICK_MARKS_COLOR = PROP_DERIVED_18, + // Sound + PROP_SOUND_URL = PROP_DERIVED_0, + PROP_SOUND_VOLUME = PROP_DERIVED_1, + PROP_SOUND_TIME_OFFSET = PROP_DERIVED_2, + PROP_SOUND_PITCH = PROP_DERIVED_3, + PROP_SOUND_PLAYING = PROP_DERIVED_4, + PROP_SOUND_POSITIONAL = PROP_DERIVED_5, + PROP_SOUND_LOOP = PROP_DERIVED_6, + PROP_SOUND_LOCAL_ONLY = PROP_DERIVED_7, + // WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above }; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4acf9b8d1b..f0da5f3cf3 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1871,6 +1871,7 @@ bool EntityScriptingInterface::setAllPoints(const QUuid& entityID, const QVector EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; + return false; } EntityTypes::EntityType entityType = entity->getType(); @@ -1907,6 +1908,31 @@ bool EntityScriptingInterface::appendPoint(const QUuid& entityID, const glm::vec return false; } +bool EntityScriptingInterface::restartSound(const QUuid& entityID) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::restartSound no entity with ID" << entityID; + // There is no entity + return false; + } + + EntityTypes::EntityType entityType = entity->getType(); + + if (entityType == EntityTypes::Sound) { + auto soundEntity = std::dynamic_pointer_cast(entity); + bool isPlaying = soundEntity->getPlaying(); + if (isPlaying) { + soundEntity->withWriteLock([&] { + soundEntity->restartSound(); + }); + } + return isPlaying; + } + + return false; +} bool EntityScriptingInterface::actionWorker(const QUuid& entityID, std::function actor) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index c677bdf0a1..59204bdd96 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1237,6 +1237,27 @@ public slots: */ Q_INVOKABLE bool appendPoint(const QUuid& entityID, const glm::vec3& point); + /*@jsdoc + * Restart a {@link Entities.EntityProperties-Sound|Sound} entity, locally only. It must also be localOnly. + * @function Entities.restartSound + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-Sound|Sound} entity. + * @example Play a sound once and repeat it every 3 seconds. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var sound = Entities.addEntity({ + * type: "Sound", + * position: position, + * soundURL: "https://themushroomkingdom.net/sounds/wav/lm/lm_gold_mouse.wav", + * positional: false, + * localOnly: true, + * loop: false, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Script.setInterval(() => { + * Entities.restartSound(sound); + * }, 3000); + */ + Q_INVOKABLE bool restartSound(const QUuid& entityID); + /*@jsdoc * Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about to the program * log. diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 1161bec6e9..a1f97ff621 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -257,6 +257,9 @@ public: void setIsServerlessMode(bool value) { _serverlessDomain = value; } bool isServerlessMode() const { return _serverlessDomain; } + void setIsEntityServer(bool value) { _entityServer = value; } + bool isEntityServer() const { return _entityServer; } + static void setGetEntityObjectOperator(std::function getEntityObjectOperator) { _getEntityObjectOperator = getEntityObjectOperator; } static QObject* getEntityObject(const QUuid& id); @@ -380,6 +383,7 @@ private: std::vector _staleProxies; bool _serverlessDomain { false }; + bool _entityServer { false }; std::map _namedPaths; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 32c791da33..b6f65cff65 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -257,8 +257,8 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori } } else { // if the entity type doesn't support a detailed intersection, then just return the non-AABox results - // Never intersect with particle entities - if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) { + // Never intersect with particle or sound entities + if (localDistance < distance && (entity->getType() != EntityTypes::ParticleEffect && entity->getType() != EntityTypes::Sound)) { distance = localDistance; face = localFace; surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); @@ -409,8 +409,8 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3 } } else { // if the entity type doesn't support a detailed intersection, then just return the non-AABox results - // Never intersect with particle entities - if (localDistance < parabolicDistance && entity->getType() != EntityTypes::ParticleEffect) { + // Never intersect with particle or sound entities + if (localDistance < parabolicDistance && (entity->getType() != EntityTypes::ParticleEffect && entity->getType() != EntityTypes::Sound)) { parabolicDistance = localDistance; face = localFace; surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 95057bedbc..49109fd5dd 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -34,6 +34,7 @@ #include "LightEntityItem.h" #include "ZoneEntityItem.h" #include "MaterialEntityItem.h" +#include "SoundEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -59,6 +60,7 @@ REGISTER_ENTITY_TYPE(Gizmo) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Material) +REGISTER_ENTITY_TYPE(Sound) bool EntityTypes::typeIsValid(EntityType type) { return type > EntityType::Unknown && type <= EntityType::NUM_TYPES; diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 441e77fccd..30d59727d4 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -86,6 +86,8 @@ public: * {@link Entities.EntityProperties-Zone|EntityProperties-Zone} * "Material"Modifies the existing materials on entities and avatars. * {@link Entities.EntityProperties-Material|EntityProperties-Material} + * "Sound"Plays a sound. + * {@link Entities.EntityProperties-Material|EntityProperties-Sound} * * * @typedef {string} Entities.EntityType @@ -108,6 +110,7 @@ public: Light, Zone, Material, + Sound, NUM_TYPES } EntityType; diff --git a/libraries/entities/src/SoundEntityItem.cpp b/libraries/entities/src/SoundEntityItem.cpp new file mode 100644 index 0000000000..575efabb2e --- /dev/null +++ b/libraries/entities/src/SoundEntityItem.cpp @@ -0,0 +1,371 @@ +// +// 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; +} + +void SoundEntityItem::update(const quint64& now) { + withWriteLock([&] { + const auto tree = getTree(); + if (tree) { + _updateNeeded = false; + + if ((_localOnly && tree->getIsClient()) || (!_localOnly && (tree->isEntityServer() || tree->isServerlessMode()))) { + _sound = DependencyManager::get()->getSound(_url); + } + + if (_sound) { + if (_sound->isLoaded()) { + updateSound(true); + } else { + connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); + } + } + } + }); +} + +void SoundEntityItem::setLocalPosition(const glm::vec3& value, bool tellPhysics) { + EntityItem::setLocalPosition(value, tellPhysics); + withWriteLock([&] { + updateSound(); + }); +} + +void SoundEntityItem::setLocalOrientation(const glm::quat& value) { + EntityItem::setLocalOrientation(value); + withWriteLock([&] { + updateSound(); + }); +} + +void SoundEntityItem::setURL(const QString& value) { + withWriteLock([&] { + if (value != _url) { + _url = value; + + const auto tree = getTree(); + if (!tree) { + _updateNeeded = true; + return; + } + + if ((_localOnly && tree->getIsClient()) || (!_localOnly && (tree->isEntityServer() || tree->isServerlessMode()))) { + _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) { + withWriteLock([&] { + if (value != _volume) { + _volume = value; + updateSound(); + } + }); +} + +float SoundEntityItem::getVolume() const { + return resultWithReadLock([&] { + return _volume; + }); +} + +void SoundEntityItem::setTimeOffset(float value) { + withWriteLock([&] { + if (value != _timeOffset) { + _timeOffset = value; + updateSound(true); + } + }); +} + +float SoundEntityItem::getTimeOffset() const { + return resultWithReadLock([&] { + return _timeOffset; + }); +} + +void SoundEntityItem::setPitch(float value) { + withWriteLock([&] { + if (value != _pitch) { + _pitch = value; + updateSound(true); + } + }); +} + +float SoundEntityItem::getPitch() const { + return resultWithReadLock([&] { + return _pitch; + }); +} + +void SoundEntityItem::setPlaying(bool value) { + withWriteLock([&] { + if (value != _playing) { + _playing = value; + updateSound(); + } + }); +} + +bool SoundEntityItem::getPlaying() const { + return resultWithReadLock([&] { + return _playing; + }); +} + +void SoundEntityItem::setLoop(bool value) { + withWriteLock([&] { + if (value != _loop) { + _loop = value; + updateSound(true); + } + }); +} + +bool SoundEntityItem::getLoop() const { + return resultWithReadLock([&] { + return _loop; + }); +} + +void SoundEntityItem::setPositional(bool value) { + withWriteLock([&] { + if (value != _positional) { + _positional = value; + updateSound(); + } + }); +} + +bool SoundEntityItem::getPositional() const { + return resultWithReadLock([&] { + return _positional; + }); +} + +void SoundEntityItem::setLocalOnly(bool value) { + withWriteLock([&] { + if (value != _localOnly) { + _localOnly = value; + + const auto tree = getTree(); + if (!tree) { + _updateNeeded = true; + return; + } + + if ((_localOnly && tree->getIsClient()) || (!_localOnly && (tree->isEntityServer() || tree->isServerlessMode()))) { + _sound = DependencyManager::get()->getSound(_url); + } else { + _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() { + if (!_sound) { + return false; + } + + AudioInjectorOptions options; + options.position = getWorldPosition(); + options.positionSet = _positional; + options.volume = _volume; + options.loop = _loop; + options.orientation = getWorldOrientation(); + options.localOnly = _localOnly; + options.secondOffset = _timeOffset; + options.pitch = _pitch; + + if (_injector) { + DependencyManager::get()->setOptionsAndRestart(_injector, options); + } else { + _injector = DependencyManager::get()->playSound(_sound, options); + } + + return true; +} + +void SoundEntityItem::updateSound(bool restart) { + if (!_sound) { + return; + } + + if (restart) { + if (_injector) { + DependencyManager::get()->stop(_injector); + } + _injector = nullptr; + } + + if (_playing) { + restartSound(); + } else { + if (_injector) { + DependencyManager::get()->stop(_injector); + } + } +} \ No newline at end of file diff --git a/libraries/entities/src/SoundEntityItem.h b/libraries/entities/src/SoundEntityItem.h new file mode 100644 index 0000000000..781c270d45 --- /dev/null +++ b/libraries/entities/src/SoundEntityItem.h @@ -0,0 +1,100 @@ +// +// 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 +// + +#ifndef hifi_SoundEntityItem_h +#define hifi_SoundEntityItem_h + +#include "EntityItem.h" + +#include +#include + +class SoundEntityItem : public EntityItem { +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + SoundEntityItem(const EntityItemID& entityItemID); + ~SoundEntityItem(); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; + bool setSubClassProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + bool shouldBePhysical() const override { return false; } + + virtual void debugDump() const override; + + virtual bool supportsDetailedIntersection() const override { return false; } + + virtual void update(const quint64& now) override; + bool needsToCallUpdate() const override { return _updateNeeded; } + + void setLocalPosition(const glm::vec3& value, bool tellPhysics = true) override; + void setLocalOrientation(const glm::quat& value) override; + + void setURL(const QString& value); + QString getURL() const; + + void setVolume(float value); + float getVolume() const; + + void setTimeOffset(float value); + float getTimeOffset() const; + + void setPitch(float value); + float getPitch() const; + + void setPlaying(bool value); + bool getPlaying() const; + + void setLoop(bool value); + bool getLoop() const; + + void setPositional(bool value); + bool getPositional() const; + + void setLocalOnly(bool value); + bool getLocalOnly() const; + + bool restartSound(); + +protected: + void updateSound(bool restart = false); + + QString _url { "" }; + float _volume { 1.0f }; + float _timeOffset { 0.0f }; + float _pitch { 1.0f }; + bool _playing { true }; + bool _loop { true }; + bool _positional { true }; + bool _localOnly { false }; + + SharedSoundPointer _sound; + AudioInjectorPointer _injector; + bool _updateNeeded { false }; +}; + +#endif // hifi_SoundEntityItem_h diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 6f84f25e86..46e2fe61c7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -294,6 +294,7 @@ enum class EntityVersion : PacketVersion { EntityTags, WantsKeyboardFocus, AudioZones, + SoundEntities, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index 3237e712f9..ad6d5292b9 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -5,6 +5,7 @@ set(TARGET_NAME physics) setup_hifi_library() link_hifi_libraries(shared workload entities shaders) +include_hifi_library_headers(audio) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) include_hifi_library_headers(avatars) diff --git a/scripts/system/assets/images/tools/sound.svg b/scripts/system/assets/images/tools/sound.svg new file mode 100644 index 0000000000..a71b449045 --- /dev/null +++ b/scripts/system/assets/images/tools/sound.svg @@ -0,0 +1,258 @@ + + + +SOUNDSOUNDSOUND + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index b4597e9141..efb33fd422 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -706,5 +706,29 @@ }, "zTextureURL": { "tooltip": "The URL of the texture to map to surfaces perpendicular to the entity's local z-axis. JPG or PNG format." + }, + "soundURL": { + "tooltip": "The URL of the sound, as a wav, mp3, or raw file." + }, + "playing": { + "tooltip": "Whether or not the sound should play." + }, + "volume": { + "tooltip": "The volume of the sound." + }, + "pitch": { + "tooltip": "Alter the pitch of the sound, within +/- 2 octaves. The value is the relative sample rate at which to resample the sound." + }, + "timeOffset": { + "tooltip": "Starts playback from a specified time (seconds) within the sound file." + }, + "loop": { + "tooltip": "Whether or not the sound is played repeatedly." + }, + "positional": { + "tooltip": "Whether or not the sound volume drops off with distance." + }, + "localOnly": { + "tooltip": "Whether the sound should play locally for everyone separately, or globally via the audio mixer." } } diff --git a/scripts/system/create/assets/images/icon-sound.svg b/scripts/system/create/assets/images/icon-sound.svg new file mode 100644 index 0000000000..cf89956c41 --- /dev/null +++ b/scripts/system/create/assets/images/icon-sound.svg @@ -0,0 +1,84 @@ + +image/svg+xml diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index dabdfe34f9..ade2133803 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -51,6 +51,7 @@ var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg"); var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png"); + var DEFAULT_SOUND = "TODO"; var createToolsWindow = new CreateWindow( Script.resolvePath("qml/EditTools.qml"), @@ -94,8 +95,9 @@ var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg"); + var SOUND_URL = Script.resolvePath("assets/images/icon-sound.svg"); - var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material"], function(entityID) { + var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material", "Sound"], function(entityID) { var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]); if (properties.type === "Light") { return { @@ -109,6 +111,8 @@ } else { return { imageURL: "" }; } + } else if (properties.type === "Sound") { + return { imageURL: SOUND_URL, rotation: Quat.fromPitchYawRollDegrees(0, 0, 0) }; } else { return { imageURL: PARTICLE_SYSTEM_URL }; } @@ -492,6 +496,9 @@ exponent: 1.0, cutoff: 75.0, }, + Sound: { + soundURL: DEFAULT_SOUND + }, }; var toolBar = (function () { @@ -570,7 +577,7 @@ if (!properties.grab) { properties.grab = {}; if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && - !(properties.type === "Zone" || properties.type === "Light" + !(properties.type === "Zone" || properties.type === "Light" || properties.type === "Sound" || properties.type === "ParticleEffect" || properties.type === "Web")) { properties.grab.grabbable = true; } else { @@ -1056,6 +1063,12 @@ addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox")); + addButton("newSoundButton", function () { + createNewEntity({ + type: "Sound", + }); + }); + var deactivateCreateIfDesktopWindowsHidden = function() { if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { that.setActive(false); @@ -2041,7 +2054,7 @@ var entityParentIDs = []; var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; - var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "Sound", "ParticleEffect"]; if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { var targetDirection; if (Camera.mode === "entity" || Camera.mode === "independent") { diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index 257f967852..17dd942e13 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -211,7 +211,7 @@ var EntityListTool = function(shouldUseEditTabletApp, selectionManager) { PROFILE("getMultipleProperties", function () { var multipleProperties = Entities.getMultipleEntityProperties(ids, ['position', 'name', 'type', 'locked', 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'serverScripts', - 'skybox.url', 'ambientLight.url', 'created', 'lastEdited']); + 'skybox.url', 'ambientLight.url', 'soundURL', 'created', 'lastEdited']); for (var i = 0; i < multipleProperties.length; i++) { var properties = multipleProperties[i]; @@ -223,6 +223,8 @@ var EntityListTool = function(shouldUseEditTabletApp, selectionManager) { url = properties.materialURL; } else if (properties.type === "Image") { url = properties.imageURL; + } else if (properties.type === "Sound") { + url = properties.soundURL; } //print("Global object before getParentState call: " + JSON.stringify(globalThis)); var parentStatus = that.createApp.getParentState(ids[i]); diff --git a/scripts/system/create/entityList/html/js/entityList.js b/scripts/system/create/entityList/html/js/entityList.js index a64d95a64c..213e4ad09c 100644 --- a/scripts/system/create/entityList/html/js/entityList.js +++ b/scripts/system/create/entityList/html/js/entityList.js @@ -178,6 +178,7 @@ const FILTER_TYPES = [ "PolyVox", "Text", "Grid", + "Sound", ]; const DOUBLE_CLICK_TIMEOUT = 300; // ms diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index 8a1540d411..797156081e 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -1432,6 +1432,62 @@ const GROUPS = [ }, ] }, + { + id: "sound", + label: "SOUND", + properties: [ + { + label: "Sound", + type: "string", + propertyID: "soundURL", + placeholder: "URL", + }, + { + label: "Playing", + type: "bool", + propertyID: "playing", + }, + { + label: "Loop", + type: "bool", + propertyID: "loop", + }, + { + label: "Volume", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "volume", + }, + { + label: "Positional", + type: "bool", + propertyID: "positional", + }, + { + label: "Pitch", + type: "number-draggable", + min: 0.0625, + max: 16, + step: 0.1, + decimals: 2, + propertyID: "pitch", + }, + { + label: "Time Offset", + type: "number-draggable", + step: 0.1, + propertyID: "timeOffset", + }, + { + label: "Local Only", + type: "bool", + propertyID: "localOnly", + } + ] + }, { id: "spatial", label: "SPATIAL", @@ -1811,6 +1867,7 @@ const GROUPS_PER_TYPE = { PolyLine: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], PolyVox: [ 'base', 'polyvox', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], Grid: [ 'base', 'grid', 'spatial', 'behavior', 'scripts', 'physics' ], + Sound: [ 'base', 'sound', 'spatial', 'behavior', 'scripts', 'physics' ], Multiple: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], }; diff --git a/scripts/system/create/entityProperties/html/tabs/sound.png b/scripts/system/create/entityProperties/html/tabs/sound.png new file mode 100644 index 0000000000000000000000000000000000000000..218bfe7c4ffa41221ac8182017661d4136236684 GIT binary patch literal 684 zcmWlWTS!v@7=ZtCnLC@?JTmi!>G776wwkp<4Yy;?r9qx?0Lf1Qf~tG3&*=crs7!5Ye*L=f@aRZ`DG(YG5|)AiCqF8HplroJ(pC9)(^OjR#`}u65*vjjkLr`n?MirCR1TM4efkg8DkK^FV z!X_ZC;aZfgB)AIt8*on;S1naa+&HqV9MB~gH^O*Kvg1PWRV5HqdC)HEQcxaCU`(Im z;2h(T%QSwA&-mN@m`g)>d^w%64nc`EehRaCh-{vw5B;riSdH3H9&L%HMT>(u^amjq z#JUBlje^+#1Sj9{hM0yB@+3MIO8$)G36dvpZoIib5?Z2h7^yNW4=3q`z{9o`>QOc8 zJ|?k*I8+NSR_%?2!(Y;5RgydrNoNIAOm#6fzP^dp%gcbIvkFRd`n`GnV3WOLx-9`v zVhug-T-aiDVQMgpVrEj_AxzzqD$@aJt(?8@yercC&o@sE zpUvu9YM87&OMYrQusC`iMvR1u0JSoODnDDX23+y)209-HrESMWbz(Rf>6^&b`xY%H z__r4^xyv}$>s_PGs!`w^-!WD1!wqS7n@ubjo#sJ5l2(nLK~U>XAmG7p;d|iMo$19S P0D#Q&9PR6iWi9^!#`NqK literal 0 HcmV?d00001 diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index 7a188f3dc0..5238b2d374 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -2241,7 +2241,7 @@ SelectionDisplay = (function() { Entities.editEntity(selectionBox, selectionBoxGeometry); // UPDATE ICON TRANSLATE HANDLE - if (SelectionManager.entityType === "ParticleEffect" || SelectionManager.entityType === "Light") { + if (SelectionManager.entityType === "ParticleEffect" || SelectionManager.entityType === "Light" || SelectionManager.entityType === "Sound") { var iconSelectionBoxGeometry = { position: position, rotation: rotation diff --git a/scripts/system/create/modules/brokenURLReport.js b/scripts/system/create/modules/brokenURLReport.js index c04612bb27..4224826023 100644 --- a/scripts/system/create/modules/brokenURLReport.js +++ b/scripts/system/create/modules/brokenURLReport.js @@ -352,6 +352,17 @@ function brokenURLReport(entityIDs) { }; brokenURLReportUrlList.push(brokenURLReportUrlEntry); } + if (properties.type === "Sound" && properties.soundURL.toLowerCase().startsWith("http")) { + brokenURLReportUrlEntry = { + id: entityIDs[i], + name: properties.name, + type: properties.type, + urlType: "soundURL", + url: soundURL, + validity: "NOT_TESTED" + }; + brokenURLReportUrlList.push(brokenURLReportUrlEntry); + } } if (brokenURLReportUrlList.length === 0) { audioFeedback.confirmation(); diff --git a/scripts/system/create/qml/EditTabView.qml b/scripts/system/create/qml/EditTabView.qml index 96e66c109e..26cc90a357 100644 --- a/scripts/system/create/qml/EditTabView.qml +++ b/scripts/system/create/qml/EditTabView.qml @@ -190,6 +190,18 @@ TabBar { editTabView.currentIndex = 2 } } + + NewEntityButton { + icon: "icons/sound.svg" + text: "SOUND" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newSoundButton" } + }); + editTabView.currentIndex = 2 + } + } } HifiControls.Button { diff --git a/scripts/system/create/qml/EditToolsTabView.qml b/scripts/system/create/qml/EditToolsTabView.qml index 998c3a3aac..445f084392 100644 --- a/scripts/system/create/qml/EditToolsTabView.qml +++ b/scripts/system/create/qml/EditToolsTabView.qml @@ -196,6 +196,18 @@ TabBar { editTabView.currentIndex = tabIndex.properties } } + + NewEntityButton { + icon: "icons/sound.svg" + text: "SOUND" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newSoundButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } } HifiControls.Button { diff --git a/scripts/system/create/qml/icons/sound.svg b/scripts/system/create/qml/icons/sound.svg new file mode 100644 index 0000000000..448156343d --- /dev/null +++ b/scripts/system/create/qml/icons/sound.svg @@ -0,0 +1,59 @@ + + + + + + + diff --git a/scripts/system/html/js/includes.js b/scripts/system/html/js/includes.js index c604115f91..69ae9bed2d 100644 --- a/scripts/system/html/js/includes.js +++ b/scripts/system/html/js/includes.js @@ -20,6 +20,7 @@ const ENTITY_TYPE_ICON = { PolyLine: "", Shape: "n", Sphere: "n", + Sound: "G", Text: "l", Web: "q", Zone: "o", From 9914d4d133807c832baaab9130f83d0bcdb10df4 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sat, 30 Mar 2024 00:05:11 -0700 Subject: [PATCH 2/8] fix stereo sound speed --- libraries/entities/src/EntityItemProperties.cpp | 1 + libraries/entities/src/SoundEntityItem.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a32dcf78c0..7d5c01fd25 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1357,6 +1357,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * * @typedef {object} Entities.EntityProperties-Sound * @property {string} soundURL="" - The URL of the sound to play, as a wav, mp3, or raw file. Supports stereo and ambisonic. + * Note: ambisonic sounds can only play as localOnly. * @property {boolean} playing=true - Whether or not the sound should play. * @property {number} volume=1.0 - The volume of the sound, from 0 to 1. * @property {number} pitch=1.0 - The relative sample rate at which to resample the sound, within +/- 2 octaves. diff --git a/libraries/entities/src/SoundEntityItem.cpp b/libraries/entities/src/SoundEntityItem.cpp index 575efabb2e..997225515c 100644 --- a/libraries/entities/src/SoundEntityItem.cpp +++ b/libraries/entities/src/SoundEntityItem.cpp @@ -336,10 +336,14 @@ bool SoundEntityItem::restartSound() { options.volume = _volume; options.loop = _loop; options.orientation = getWorldOrientation(); - options.localOnly = _localOnly; + 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()->setOptionsAndRestart(_injector, options); } else { From b7a3fb107295872fdd7b605446a705ecfbc4a3cd Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 7 Apr 2024 12:54:37 -0700 Subject: [PATCH 3/8] support non-localOnly sound avatar entities --- libraries/entities/src/SoundEntityItem.cpp | 12 +++++++++--- libraries/entities/src/SoundEntityItem.h | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/SoundEntityItem.cpp b/libraries/entities/src/SoundEntityItem.cpp index 997225515c..57ce158ee0 100644 --- a/libraries/entities/src/SoundEntityItem.cpp +++ b/libraries/entities/src/SoundEntityItem.cpp @@ -129,13 +129,19 @@ void SoundEntityItem::debugDump() const { 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) { withWriteLock([&] { const auto tree = getTree(); if (tree) { _updateNeeded = false; - if ((_localOnly && tree->getIsClient()) || (!_localOnly && (tree->isEntityServer() || tree->isServerlessMode()))) { + if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } @@ -175,7 +181,7 @@ void SoundEntityItem::setURL(const QString& value) { return; } - if ((_localOnly && tree->getIsClient()) || (!_localOnly && (tree->isEntityServer() || tree->isServerlessMode()))) { + if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } @@ -297,7 +303,7 @@ void SoundEntityItem::setLocalOnly(bool value) { return; } - if ((_localOnly && tree->getIsClient()) || (!_localOnly && (tree->isEntityServer() || tree->isServerlessMode()))) { + if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } else { _sound = nullptr; diff --git a/libraries/entities/src/SoundEntityItem.h b/libraries/entities/src/SoundEntityItem.h index 781c270d45..5cbd4bb814 100644 --- a/libraries/entities/src/SoundEntityItem.h +++ b/libraries/entities/src/SoundEntityItem.h @@ -81,6 +81,7 @@ public: bool restartSound(); protected: + bool shouldCreateSound(const EntityTreePointer& tree) const; void updateSound(bool restart = false); QString _url { "" }; From eb7d97064fc1937586cb9355d6c00b5864b12413 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 7 Apr 2024 13:40:42 -0700 Subject: [PATCH 4/8] add sound url prompt --- scripts/system/create/edit.js | 32 +++- scripts/system/create/qml/NewSoundDialog.qml | 159 +++++++++++++++++++ scripts/system/create/qml/NewSoundWindow.qml | 31 ++++ 3 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 scripts/system/create/qml/NewSoundDialog.qml create mode 100644 scripts/system/create/qml/NewSoundWindow.qml diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index ade2133803..27502af711 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -51,7 +51,6 @@ var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg"); var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png"); - var DEFAULT_SOUND = "TODO"; var createToolsWindow = new CreateWindow( Script.resolvePath("qml/EditTools.qml"), @@ -497,7 +496,11 @@ cutoff: 75.0, }, Sound: { - soundURL: DEFAULT_SOUND + volume: 1.0, + playing: true, + loop: true, + positional: true, + localOnly: false }, }; @@ -866,6 +869,18 @@ } } + function handleNewSoundDialogResult(result) { + if (result) { + var soundURL = result.textInput; + if (soundURL) { + createNewEntity({ + type: "Sound", + soundURL: soundURL + }); + } + } + } + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.popFromStack(); @@ -894,6 +909,13 @@ case "newPolyVoxDialogCancel": closeExistingDialogWindow(); break; + case "newSoundDialogAdd": + handleNewSoundDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newSoundDialogCancel": + closeExistingDialogWindow(); + break; } } @@ -1063,11 +1085,7 @@ addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox")); - addButton("newSoundButton", function () { - createNewEntity({ - type: "Sound", - }); - }); + addButton("newSoundButton", createNewEntityDialogButtonCallback("Sound")); var deactivateCreateIfDesktopWindowsHidden = function() { if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { diff --git a/scripts/system/create/qml/NewSoundDialog.qml b/scripts/system/create/qml/NewSoundDialog.qml new file mode 100644 index 0000000000..d0b470109b --- /dev/null +++ b/scripts/system/create/qml/NewSoundDialog.qml @@ -0,0 +1,159 @@ +// +// NewSoundDialog.qml +// qml/hifi +// +// Created by HifiExperiments on 4/7/24 +// Copyright 2024 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 +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Dialogs 1.2 as OriginalDialogs + +import stylesUit 1.0 +import controlsUit 1.0 +import hifi.dialogs 1.0 + +Rectangle { + id: newSoundDialog + // width: parent.width + // height: parent.height + HifiConstants { id: hifi } + color: hifi.colors.baseGray; + signal sendToScript(var message); + property bool keyboardEnabled: false + property bool punctuationMode: false + property bool keyboardRasied: false + + function errorMessageBox(message) { + try { + return desktop.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } catch(e) { + Window.alert(message); + } + } + + Item { + id: column1 + anchors.rightMargin: 10 + anchors.leftMargin: 10 + anchors.bottomMargin: 10 + anchors.topMargin: 10 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: keyboard.top + + Text { + id: text1 + text: qsTr("Sound URL") + color: "#ffffff" + font.pixelSize: 12 + } + + TextInput { + id: soundURL + height: 20 + text: qsTr("") + color: "white" + anchors.top: text1.bottom + anchors.topMargin: 5 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + font.pixelSize: 12 + + onAccepted: { + newSoundDialog.keyboardEnabled = false; + } + + MouseArea { + anchors.fill: parent + onClicked: { + newSoundDialog.keyboardEnabled = HMD.active + parent.focus = true; + parent.forceActiveFocus(); + soundURL.cursorPosition = soundURL.positionAt(mouseX, mouseY, TextInput.CursorBetweenCharaters); + } + } + } + + Rectangle { + id: textInputBox + color: "white" + anchors.fill: soundURL + opacity: 0.1 + } + + Row { + id: row1 + height: 400 + spacing: 30 + anchors.top: soundURL.bottom + anchors.topMargin: 5 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + Column { + id: column3 + height: 400 + spacing: 10 + + Row { + id: row3 + width: 200 + height: 400 + spacing: 5 + + anchors.horizontalCenter: column3.horizontalCenter + anchors.horizontalCenterOffset: 0 + + Button { + id: button1 + text: qsTr("Create") + z: -1 + onClicked: { + newSoundDialog.sendToScript({ + method: "newSoundDialogAdd", + params: { + textInput: soundURL.text + } + }); + } + } + + Button { + id: button2 + z: -1 + text: qsTr("Cancel") + onClicked: { + newSoundDialog.sendToScript({method: "newSoundDialogCancel"}) + } + } + } + } + } + } + + Keyboard { + id: keyboard + raised: parent.keyboardEnabled + numeric: parent.punctuationMode + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } +} diff --git a/scripts/system/create/qml/NewSoundWindow.qml b/scripts/system/create/qml/NewSoundWindow.qml new file mode 100644 index 0000000000..9f78ade0b1 --- /dev/null +++ b/scripts/system/create/qml/NewSoundWindow.qml @@ -0,0 +1,31 @@ +// +// NewSoundWindow.qml +// qml/hifi +// +// Created by HifiExperiments on 4/7/24 +// Copyright 2024 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 +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +StackView { + id: stackView + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.topMargin: 40 + + signal sendToScript(var message); + + NewSoundDialog { + id: dialog + anchors.fill: parent + Component.onCompleted:{ + dialog.sendToScript.connect(stackView.sendToScript); + } + } +} From 376be7b17eefdc29920ba7f4a85c3e069e68a691 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 12 Apr 2024 13:04:30 -0700 Subject: [PATCH 5/8] support registration point, improve locking --- libraries/entities/src/SoundEntityItem.cpp | 37 +++++++++++++--------- libraries/entities/src/SoundEntityItem.h | 4 +-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/libraries/entities/src/SoundEntityItem.cpp b/libraries/entities/src/SoundEntityItem.cpp index 57ce158ee0..201592dc1f 100644 --- a/libraries/entities/src/SoundEntityItem.cpp +++ b/libraries/entities/src/SoundEntityItem.cpp @@ -136,36 +136,42 @@ bool SoundEntityItem::shouldCreateSound(const EntityTreePointer& tree) const { } void SoundEntityItem::update(const quint64& now) { - withWriteLock([&] { - const auto tree = getTree(); - if (tree) { - _updateNeeded = false; + const auto tree = getTree(); + if (tree) { + _updateNeeded = false; + withWriteLock([&] { if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } + }); + withReadLock([&] { if (_sound) { if (_sound->isLoaded()) { updateSound(true); } else { - connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); + connect(_sound.data(), &Resource::finished, this, [&] { + withReadLock([&] { + updateSound(true); + }); + }); } } - } - }); + }); + } } -void SoundEntityItem::setLocalPosition(const glm::vec3& value, bool tellPhysics) { - EntityItem::setLocalPosition(value, tellPhysics); - withWriteLock([&] { +void SoundEntityItem::locationChanged(bool tellPhysics, bool tellChildren) { + EntityItem::locationChanged(tellPhysics, tellChildren); + withReadLock([&] { updateSound(); }); } -void SoundEntityItem::setLocalOrientation(const glm::quat& value) { - EntityItem::setLocalOrientation(value); - withWriteLock([&] { +void SoundEntityItem::dimensionsChanged() { + EntityItem::dimensionsChanged(); + withReadLock([&] { updateSound(); }); } @@ -337,11 +343,12 @@ bool SoundEntityItem::restartSound() { } AudioInjectorOptions options; - options.position = getWorldPosition(); + 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 = getWorldOrientation(); + options.orientation = orientation; options.localOnly = _localOnly || _sound->isAmbisonic(); // force localOnly when ambisonic options.secondOffset = _timeOffset; options.pitch = _pitch; diff --git a/libraries/entities/src/SoundEntityItem.h b/libraries/entities/src/SoundEntityItem.h index 5cbd4bb814..3d1815f557 100644 --- a/libraries/entities/src/SoundEntityItem.h +++ b/libraries/entities/src/SoundEntityItem.h @@ -51,8 +51,8 @@ public: virtual void update(const quint64& now) override; bool needsToCallUpdate() const override { return _updateNeeded; } - void setLocalPosition(const glm::vec3& value, bool tellPhysics = true) override; - void setLocalOrientation(const glm::quat& value) override; + void locationChanged(bool tellPhysics = true, bool tellChildren = true) override; + void dimensionsChanged() override; void setURL(const QString& value); QString getURL() const; From 6a180b14a1afd0acf31896d1a9de77c991e21f6a Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 12 Apr 2024 13:06:54 -0700 Subject: [PATCH 6/8] remove keyboardRasied --- scripts/system/create/qml/NewSoundDialog.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/create/qml/NewSoundDialog.qml b/scripts/system/create/qml/NewSoundDialog.qml index d0b470109b..870e8bb41d 100644 --- a/scripts/system/create/qml/NewSoundDialog.qml +++ b/scripts/system/create/qml/NewSoundDialog.qml @@ -26,7 +26,6 @@ Rectangle { signal sendToScript(var message); property bool keyboardEnabled: false property bool punctuationMode: false - property bool keyboardRasied: false function errorMessageBox(message) { try { From 98c11cef8283f6393b26ec620937fbc16c3724ae Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 14 Apr 2024 22:04:09 -0700 Subject: [PATCH 7/8] locking attempt #2 --- .../entities/src/EntityScriptingInterface.cpp | 4 +- libraries/entities/src/SoundEntityItem.cpp | 203 +++++++++++------- libraries/entities/src/SoundEntityItem.h | 3 +- 3 files changed, 133 insertions(+), 77 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index f0da5f3cf3..10c25f545b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1924,9 +1924,7 @@ bool EntityScriptingInterface::restartSound(const QUuid& entityID) { auto soundEntity = std::dynamic_pointer_cast(entity); bool isPlaying = soundEntity->getPlaying(); if (isPlaying) { - soundEntity->withWriteLock([&] { - soundEntity->restartSound(); - }); + soundEntity->restartSound(true); } return isPlaying; } diff --git a/libraries/entities/src/SoundEntityItem.cpp b/libraries/entities/src/SoundEntityItem.cpp index 201592dc1f..71845920f9 100644 --- a/libraries/entities/src/SoundEntityItem.cpp +++ b/libraries/entities/src/SoundEntityItem.cpp @@ -138,68 +138,68 @@ bool SoundEntityItem::shouldCreateSound(const EntityTreePointer& tree) const { void SoundEntityItem::update(const quint64& now) { const auto tree = getTree(); if (tree) { + std::lock_guard lock(_soundLock); + _updateNeeded = false; - withWriteLock([&] { + withReadLock([&] { if (shouldCreateSound(tree)) { _sound = DependencyManager::get()->getSound(_url); } }); - withReadLock([&] { - if (_sound) { - if (_sound->isLoaded()) { - updateSound(true); - } else { - connect(_sound.data(), &Resource::finished, this, [&] { - withReadLock([&] { - updateSound(true); - }); - }); - } + 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); - withReadLock([&] { - updateSound(); - }); + updateSound(); } void SoundEntityItem::dimensionsChanged() { EntityItem::dimensionsChanged(); - withReadLock([&] { - updateSound(); - }); + updateSound(); } void SoundEntityItem::setURL(const QString& value) { + bool changed = false; withWriteLock([&] { if (value != _url) { _url = value; + changed = true; + } + }); - const auto tree = getTree(); - if (!tree) { - _updateNeeded = true; - return; - } + 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); }); - } + if (_sound) { + if (_sound->isLoaded()) { + updateSound(true); + } else { + connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); } } - }); + } } QString SoundEntityItem::getURL() const { @@ -209,12 +209,17 @@ QString SoundEntityItem::getURL() const { } void SoundEntityItem::setVolume(float value) { + bool changed = false; withWriteLock([&] { if (value != _volume) { _volume = value; - updateSound(); + changed = true; } }); + + if (changed) { + updateSound(); + } } float SoundEntityItem::getVolume() const { @@ -224,12 +229,17 @@ float SoundEntityItem::getVolume() const { } void SoundEntityItem::setTimeOffset(float value) { + bool changed = false; withWriteLock([&] { if (value != _timeOffset) { _timeOffset = value; - updateSound(true); + changed = true; } }); + + if (changed) { + updateSound(true); + } } float SoundEntityItem::getTimeOffset() const { @@ -239,12 +249,17 @@ float SoundEntityItem::getTimeOffset() const { } void SoundEntityItem::setPitch(float value) { + bool changed = false; withWriteLock([&] { if (value != _pitch) { _pitch = value; - updateSound(true); + changed = true; } }); + + if (changed) { + updateSound(true); + } } float SoundEntityItem::getPitch() const { @@ -254,12 +269,17 @@ float SoundEntityItem::getPitch() const { } void SoundEntityItem::setPlaying(bool value) { + bool changed = false; withWriteLock([&] { if (value != _playing) { _playing = value; - updateSound(); + changed = true; } }); + + if (changed) { + updateSound(); + } } bool SoundEntityItem::getPlaying() const { @@ -269,12 +289,17 @@ bool SoundEntityItem::getPlaying() const { } void SoundEntityItem::setLoop(bool value) { + bool changed = false; withWriteLock([&] { if (value != _loop) { _loop = value; - updateSound(true); + changed = true; } }); + + if (changed) { + updateSound(true); + } } bool SoundEntityItem::getLoop() const { @@ -284,12 +309,17 @@ bool SoundEntityItem::getLoop() const { } void SoundEntityItem::setPositional(bool value) { + bool changed = false; withWriteLock([&] { if (value != _positional) { _positional = value; - updateSound(); + changed = true; } }); + + if (changed) { + updateSound(); + } } bool SoundEntityItem::getPositional() const { @@ -299,36 +329,48 @@ bool SoundEntityItem::getPositional() const { } void SoundEntityItem::setLocalOnly(bool value) { + bool changed = false; withWriteLock([&] { if (value != _localOnly) { _localOnly = value; - - const auto tree = getTree(); - if (!tree) { - _updateNeeded = true; - return; - } - - if (shouldCreateSound(tree)) { - _sound = DependencyManager::get()->getSound(_url); - } else { - _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); }); - } - } + 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 { @@ -337,21 +379,30 @@ bool SoundEntityItem::getLocalOnly() const { }); } -bool SoundEntityItem::restartSound() { +bool SoundEntityItem::restartSound(bool lock) { + if (lock) { + _soundLock.lock(); + } + if (!_sound) { + if (lock) { + _soundLock.unlock(); + } return false; } AudioInjectorOptions options; - 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; + 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(); @@ -363,10 +414,16 @@ bool SoundEntityItem::restartSound() { _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; } @@ -385,4 +442,4 @@ void SoundEntityItem::updateSound(bool restart) { DependencyManager::get()->stop(_injector); } } -} \ No newline at end of file +} diff --git a/libraries/entities/src/SoundEntityItem.h b/libraries/entities/src/SoundEntityItem.h index 3d1815f557..bd590d29d2 100644 --- a/libraries/entities/src/SoundEntityItem.h +++ b/libraries/entities/src/SoundEntityItem.h @@ -78,7 +78,7 @@ public: void setLocalOnly(bool value); bool getLocalOnly() const; - bool restartSound(); + bool restartSound(bool lock = false); protected: bool shouldCreateSound(const EntityTreePointer& tree) const; @@ -93,6 +93,7 @@ protected: bool _positional { true }; bool _localOnly { false }; + std::recursive_mutex _soundLock; SharedSoundPointer _sound; AudioInjectorPointer _injector; bool _updateNeeded { false }; From 9d2e03a5a4898f815fc67b700aa9b157462bf8b1 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 19 Jul 2024 17:31:51 -0700 Subject: [PATCH 8/8] fix non-localOnly positional sounds not updating --- libraries/entities/src/SimpleEntitySimulation.cpp | 4 ++-- libraries/entities/src/SoundEntityItem.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index d64efdf87f..ecdc37ebfb 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -62,7 +62,7 @@ void SimpleEntitySimulation::addEntityToInternalLists(EntityItemPointer entity) // we don't allow dynamic objects to move without an owner so nothing to do here } else if (entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); - if (itr != _simpleKinematicEntities.end()) { + if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); entity->setLastSimulated(usecTimestampNow()); } @@ -73,7 +73,7 @@ void SimpleEntitySimulation::addEntityToInternalLists(EntityItemPointer entity) if (entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); - if (itr != _simpleKinematicEntities.end()) { + if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); entity->setLastSimulated(usecTimestampNow()); } diff --git a/libraries/entities/src/SoundEntityItem.cpp b/libraries/entities/src/SoundEntityItem.cpp index 71845920f9..d8c648a61a 100644 --- a/libraries/entities/src/SoundEntityItem.cpp +++ b/libraries/entities/src/SoundEntityItem.cpp @@ -359,8 +359,8 @@ void SoundEntityItem::setLocalOnly(bool value) { if (_injector) { DependencyManager::get()->stop(_injector); + _injector = nullptr; } - _injector = nullptr; } if (_sound) { @@ -409,7 +409,7 @@ bool SoundEntityItem::restartSound(bool lock) { options.ambisonic = _sound->isAmbisonic(); if (_injector) { - DependencyManager::get()->setOptionsAndRestart(_injector, options); + DependencyManager::get()->setOptions(_injector, options); } else { _injector = DependencyManager::get()->playSound(_sound, options); } @@ -431,8 +431,8 @@ void SoundEntityItem::updateSound(bool restart) { if (restart) { if (_injector) { DependencyManager::get()->stop(_injector); + _injector = nullptr; } - _injector = nullptr; } if (_playing) { @@ -440,6 +440,7 @@ void SoundEntityItem::updateSound(bool restart) { } else { if (_injector) { DependencyManager::get()->stop(_injector); + _injector = nullptr; } } }