diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index afd4047c68..d6f893c42e 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -89,7 +89,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : PacketType::NodeIgnoreRequest, PacketType::RadiusIgnoreRequest, PacketType::RequestsDomainListData, - PacketType::PerAvatarGainSet }, + PacketType::PerAvatarGainSet, + PacketType::AudioSoloRequest }, this, "queueAudioPacket"); // packets whose consequences are global should be processed on the main thread diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 7e1420ef60..0aee185088 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -98,6 +98,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) { case PacketType::RadiusIgnoreRequest: parseRadiusIgnoreRequest(packet, node); break; + case PacketType::AudioSoloRequest: + parseSoloRequest(packet, node); + break; default: Q_UNREACHABLE(); } @@ -295,6 +298,25 @@ void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer message, const SharedNodePointer& node) { + + bool addToSolo; + message->readPrimitive(&addToSolo); + + while (message->getBytesLeftToRead()) { + // parse out the UUID being solod from the packet + QUuid solodUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + if (addToSolo) { + _solodNodes.push_back(solodUUID); + } else { + auto it = std::find(std::begin(_solodNodes), std::end(_solodNodes), solodUUID); + _solodNodes.erase(it); + } + } +} + AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() { auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){ return stream->getStreamIdentifier().isNull(); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 610b258789..dd5681c537 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -65,6 +65,7 @@ public: void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node); void parseNodeIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); void parseRadiusIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); + void parseSoloRequest(QSharedPointer message, const SharedNodePointer& node); // attempt to pop a frame from each audio stream, and return the number of streams from this client int checkBuffersBeforeFrameSend(); @@ -150,6 +151,9 @@ public: const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; } + + const std::vector& getSolodNodes() const { return _solodNodes; } + bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; } void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; } @@ -209,6 +213,8 @@ private: std::atomic_bool _isIgnoreRadiusEnabled { false }; + std::vector _solodNodes; + bool _hasReceivedFirstMix { false }; }; diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 57bada47f1..7007e684cb 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener, return true; } + if (!listenerData.getSolodNodes().empty()) { + return !contains(listenerData.getSolodNodes(), stream.nodeStreamID.nodeID); + } + bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() || stream.positionalStream->isIgnoreBoxEnabled()); if (shouldCheckIgnoreBox && diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index bdd6d0edc1..ace8f72875 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -270,7 +270,8 @@ AudioClient::AudioClient() : configureReverb(); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket"); packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket"); packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket"); @@ -278,6 +279,11 @@ AudioClient::AudioClient() : packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); + + auto& domainHandler = nodeList->getDomainHandler(); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] { + _solo.reset(); + }); } AudioClient::~AudioClient() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 5e7f1fb8a0..751bddd35d 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -46,7 +46,6 @@ #include #include - #include #include @@ -171,6 +170,7 @@ public: void stopRecording(); void setAudioPaused(bool pause); + AudioSolo& getAudioSolo() override { return _solo; } #ifdef Q_OS_WIN static QString getWinDeviceName(wchar_t* guid); @@ -446,6 +446,8 @@ private: #if defined(Q_OS_ANDROID) bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop? #endif + + AudioSolo _solo; Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index bbfd79d0aa..0f075ab224 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -19,6 +19,7 @@ #include "AudioInjectorOptions.h" #include "AudioInjector.h" +#include "AudioSolo.h" class AudioInjector; class AudioInjectorLocalBuffer; @@ -38,6 +39,8 @@ public: // take care to delete it when ~AudioInjector, as parenting Qt semantics will not work virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0; + virtual AudioSolo& getAudioSolo() = 0; + public slots: virtual bool shouldLoopbackInjectors() { return false; } diff --git a/libraries/audio/src/AudioSolo.cpp b/libraries/audio/src/AudioSolo.cpp new file mode 100644 index 0000000000..306c728dcb --- /dev/null +++ b/libraries/audio/src/AudioSolo.cpp @@ -0,0 +1,77 @@ +// +// AudioSolo.cpp +// +// +// Created by Clement Brisset on 11/5/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AudioSolo.h" + +#include + +bool AudioSolo::isSoloing() const { + Lock lock(_mutex); + return !_nodesSoloed.empty(); +} + +QVector AudioSolo::getUUIDs() const { + Lock lock(_mutex); + return _nodesSoloed.values().toVector(); +} + +void AudioSolo::addUUIDs(QVector uuidList) { + // create a reliable NLPacket with space for the solo UUIDs + auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest, + uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(bool), true); + soloPacket->writePrimitive(true); + + { + Lock lock(_mutex); + for (auto uuid : uuidList) { + if (_nodesSoloed.contains(uuid)) { + qWarning() << "Uuid already in solo list:" << uuid; + } else { + // write the node ID to the packet + soloPacket->write(uuid.toRfc4122()); + _nodesSoloed.insert(uuid); + } + } + } + + // send off this ignore packet reliably to the matching node + auto nodeList = DependencyManager::get(); + nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer }); +} + +void AudioSolo::removeUUIDs(QVector uuidList) { + // create a reliable NLPacket with space for the solo UUIDs + auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest, + uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(bool), true); + soloPacket->writePrimitive(false); + + { + Lock lock(_mutex); + for (auto uuid : uuidList) { + if (!_nodesSoloed.contains(uuid)) { + qWarning() << "Uuid not in solo list:" << uuid; + } else { + // write the node ID to the packet + soloPacket->write(uuid.toRfc4122()); + _nodesSoloed.remove(uuid); + } + } + } + + // send off this ignore packet reliably to the matching node + auto nodeList = DependencyManager::get(); + nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer }); +} + +void AudioSolo::reset() { + removeUUIDs(getUUIDs()); +} + diff --git a/libraries/audio/src/AudioSolo.h b/libraries/audio/src/AudioSolo.h new file mode 100644 index 0000000000..ef2d2d0bec --- /dev/null +++ b/libraries/audio/src/AudioSolo.h @@ -0,0 +1,38 @@ +// +// AudioSolo.h +// libraries/audio/src +// +// Created by Clement Brisset on 11/5/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_AudioSolo_h +#define hifi_AudioSolo_h + +#include + +#include +#include + +class AudioSolo { + using Mutex = std::mutex; + using Lock = std::unique_lock; + +public: + bool isSoloing() const; + QVector getUUIDs() const; + void addUUIDs(QVector uuidList); + void removeUUIDs(QVector uuidList); + void reset(); + +private: + mutable Mutex _mutex; + QSet _nodesSoloed; +}; + +#endif // hifi_AudioSolo_h diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 37a4b32940..e5efe05ad0 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -126,14 +126,13 @@ public: EntityScriptCallMethod, ChallengeOwnershipRequest, ChallengeOwnershipReply, - OctreeDataFileRequest, OctreeDataFileReply, OctreeDataPersist, - EntityClone, EntityQueryInitialResultsComplete, BulkAvatarTraits, + AudioSoloRequest, NUM_PACKET_TYPE }; diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 1220a9b769..7808a86566 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -25,13 +25,31 @@ class AudioScriptingInterface : public QObject, public Dependency { // JSDoc for property is in Audio.h. Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) + Q_PROPERTY(bool isSoloing READ isSoloing) + Q_PROPERTY(QVector soloList READ getSoloList) public: - virtual ~AudioScriptingInterface() {} + virtual ~AudioScriptingInterface() = default; void setLocalAudioInterface(AbstractAudioInterface* audioInterface); protected: - AudioScriptingInterface() {} + AudioScriptingInterface() = default; + + bool isSoloing() const { + return _localAudioInterface->getAudioSolo().isSoloing(); + } + QVector getSoloList() const { + return _localAudioInterface->getAudioSolo().getUUIDs(); + } + Q_INVOKABLE void addToSoloList(QVector uuidList) { + _localAudioInterface->getAudioSolo().addUUIDs(uuidList); + } + Q_INVOKABLE void removeFromSoloList(QVector uuidList) { + _localAudioInterface->getAudioSolo().removeUUIDs(uuidList); + } + Q_INVOKABLE void resetSoloList() { + _localAudioInterface->getAudioSolo().reset(); + } // these methods are protected to stop C++ callers from calling, but invokable from script