mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Merge pull request #14352 from Atlante45/feat/solo-source
[master] Add audio soloing feature
This commit is contained in:
commit
ec4f2f781d
13 changed files with 234 additions and 13 deletions
|
@ -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
|
||||
|
|
|
@ -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<ReceivedMessa
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioMixerClientData::parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
|
||||
|
||||
uint8_t addToSolo;
|
||||
message->readPrimitive(&addToSolo);
|
||||
|
||||
while (message->getBytesLeftToRead()) {
|
||||
// parse out the UUID being soloed from the packet
|
||||
QUuid soloedUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
if (addToSolo) {
|
||||
_soloedNodes.push_back(soloedUUID);
|
||||
} else {
|
||||
auto it = std::remove(std::begin(_soloedNodes), std::end(_soloedNodes), soloedUUID);
|
||||
_soloedNodes.erase(it, std::end(_soloedNodes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
||||
auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){
|
||||
return stream->getStreamIdentifier().isNull();
|
||||
|
|
|
@ -65,6 +65,7 @@ public:
|
|||
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
|
||||
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||
void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||
void parseSoloRequest(QSharedPointer<ReceivedMessage> 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<QUuid>& getSoloedNodes() const { return _soloedNodes; }
|
||||
|
||||
bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
|
||||
void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
|
||||
|
||||
|
@ -209,6 +213,8 @@ private:
|
|||
|
||||
std::atomic_bool _isIgnoreRadiusEnabled { false };
|
||||
|
||||
std::vector<QUuid> _soloedNodes;
|
||||
|
||||
bool _hasReceivedFirstMix { false };
|
||||
};
|
||||
|
||||
|
|
|
@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!listenerData.getSoloedNodes().empty()) {
|
||||
return !contains(listenerData.getSoloedNodes(), stream.nodeStreamID.nodeID);
|
||||
}
|
||||
|
||||
bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
|
||||
stream.positionalStream->isIgnoreBoxEnabled());
|
||||
if (shouldCheckIgnoreBox &&
|
||||
|
@ -310,6 +314,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
memset(_mixSamples, 0, sizeof(_mixSamples));
|
||||
|
||||
bool isThrottling = _numToRetain != -1;
|
||||
bool isSoloing = !listenerData->getSoloedNodes().empty();
|
||||
|
||||
auto& streams = listenerData->getStreams();
|
||||
|
||||
|
@ -376,13 +381,14 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
stream.approximateVolume = approximateVolume(stream, listenerAudioStream);
|
||||
} else {
|
||||
if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) {
|
||||
addStream(stream, *listenerAudioStream, 0.0f);
|
||||
addStream(stream, *listenerAudioStream, 0.0f, isSoloing);
|
||||
streams.skipped.push_back(move(stream));
|
||||
++stats.activeToSkipped;
|
||||
return true;
|
||||
}
|
||||
|
||||
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain());
|
||||
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
|
||||
isSoloing);
|
||||
|
||||
if (shouldBeInactive(stream)) {
|
||||
// To reduce artifacts we still call render to flush the HRTF for every silent
|
||||
|
@ -417,7 +423,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
return true;
|
||||
}
|
||||
|
||||
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain());
|
||||
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
|
||||
isSoloing);
|
||||
|
||||
if (shouldBeInactive(stream)) {
|
||||
// To reduce artifacts we still call render to flush the HRTF for every silent
|
||||
|
@ -484,7 +491,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
|
||||
void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream,
|
||||
AvatarAudioStream& listeningNodeStream,
|
||||
float masterListenerGain) {
|
||||
float masterListenerGain, bool isSoloing) {
|
||||
++stats.totalMixes;
|
||||
|
||||
auto streamToAdd = mixableStream.positionalStream;
|
||||
|
@ -495,9 +502,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
|
|||
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
|
||||
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
float gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
|
||||
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
|
||||
|
||||
float gain = 1.0f;
|
||||
if (!isSoloing) {
|
||||
gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
|
||||
}
|
||||
|
||||
const int HRTF_DATASET_INDEX = 1;
|
||||
|
||||
if (!streamToAdd->lastPopSucceeded()) {
|
||||
|
|
|
@ -57,7 +57,7 @@ private:
|
|||
bool prepareMix(const SharedNodePointer& listener);
|
||||
void addStream(AudioMixerClientData::MixableStream& mixableStream,
|
||||
AvatarAudioStream& listeningNodeStream,
|
||||
float masterListenerGain);
|
||||
float masterListenerGain, bool isSoloing);
|
||||
void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
|
||||
AvatarAudioStream& listeningNodeStream,
|
||||
float masterListenerGain);
|
||||
|
|
|
@ -50,6 +50,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
|
|||
* <em>Read-only.</em>
|
||||
* @property {object} devices <em>Read-only.</em> <strong>Deprecated:</strong> This property is deprecated and will be
|
||||
* removed.
|
||||
* @property {boolean} isSoloing <em>Read-only.</em> <code>true</code> if any nodes are soloed.
|
||||
* @property {Uuid[]} soloList <em>Read-only.</em> Get the list of currently soloed node UUIDs.
|
||||
*/
|
||||
|
||||
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
|
||||
|
|
|
@ -270,7 +270,8 @@ AudioClient::AudioClient() :
|
|||
|
||||
configureReverb();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
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,16 @@ 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();
|
||||
});
|
||||
connect(nodeList.data(), &NodeList::nodeActivated, this, [this](SharedNodePointer node) {
|
||||
if (node->getType() == NodeType::AudioMixer) {
|
||||
_solo.resend();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AudioClient::~AudioClient() {
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
#include <AudioConstants.h>
|
||||
#include <AudioGate.h>
|
||||
|
||||
|
||||
#include <shared/RateCounter.h>
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
@ -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 };
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
88
libraries/audio/src/AudioSolo.cpp
Normal file
88
libraries/audio/src/AudioSolo.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// AudioSolo.cpp
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "AudioSolo.h"
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
bool AudioSolo::isSoloing() const {
|
||||
Lock lock(_mutex);
|
||||
return !_nodesSoloed.empty();
|
||||
}
|
||||
|
||||
QVector<QUuid> AudioSolo::getUUIDs() const {
|
||||
Lock lock(_mutex);
|
||||
return _nodesSoloed.values().toVector();
|
||||
}
|
||||
|
||||
void AudioSolo::addUUIDs(QVector<QUuid> uuidList) {
|
||||
// create a reliable NLPacket with space for the solo UUIDs
|
||||
auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
|
||||
uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true);
|
||||
uint8_t addToSoloList = (uint8_t)true;
|
||||
soloPacket->writePrimitive(addToSoloList);
|
||||
|
||||
{
|
||||
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 solo packet reliably to the matching node
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
|
||||
}
|
||||
|
||||
void AudioSolo::removeUUIDs(QVector<QUuid> uuidList) {
|
||||
// create a reliable NLPacket with space for the solo UUIDs
|
||||
auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
|
||||
uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true);
|
||||
uint8_t addToSoloList = (uint8_t)false;
|
||||
soloPacket->writePrimitive(addToSoloList);
|
||||
|
||||
{
|
||||
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 solo packet reliably to the matching node
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
|
||||
}
|
||||
|
||||
void AudioSolo::reset() {
|
||||
Lock lock(_mutex);
|
||||
removeUUIDs(getUUIDs());
|
||||
}
|
||||
|
||||
|
||||
void AudioSolo::resend() {
|
||||
Lock lock(_mutex);
|
||||
auto uuids = getUUIDs();
|
||||
_nodesSoloed.clear();
|
||||
addUUIDs(uuids);
|
||||
}
|
||||
|
40
libraries/audio/src/AudioSolo.h
Normal file
40
libraries/audio/src/AudioSolo.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// 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 <mutex>
|
||||
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
class AudioSolo {
|
||||
using Mutex = std::recursive_mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
|
||||
public:
|
||||
bool isSoloing() const;
|
||||
QVector<QUuid> getUUIDs() const;
|
||||
void addUUIDs(QVector<QUuid> uuidList);
|
||||
void removeUUIDs(QVector<QUuid> uuidList);
|
||||
void reset();
|
||||
|
||||
void resend();
|
||||
|
||||
private:
|
||||
mutable Mutex _mutex;
|
||||
QSet<QUuid> _nodesSoloed;
|
||||
};
|
||||
|
||||
#endif // hifi_AudioSolo_h
|
|
@ -126,14 +126,13 @@ public:
|
|||
EntityScriptCallMethod,
|
||||
ChallengeOwnershipRequest,
|
||||
ChallengeOwnershipReply,
|
||||
|
||||
OctreeDataFileRequest,
|
||||
OctreeDataFileReply,
|
||||
OctreeDataPersist,
|
||||
|
||||
EntityClone,
|
||||
EntityQueryInitialResultsComplete,
|
||||
BulkAvatarTraits,
|
||||
AudioSoloRequest,
|
||||
|
||||
NUM_PACKET_TYPE
|
||||
};
|
||||
|
|
|
@ -25,13 +25,49 @@ 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<QUuid> soloList READ getSoloList)
|
||||
|
||||
public:
|
||||
virtual ~AudioScriptingInterface() {}
|
||||
virtual ~AudioScriptingInterface() = default;
|
||||
void setLocalAudioInterface(AbstractAudioInterface* audioInterface);
|
||||
|
||||
bool isSoloing() const {
|
||||
return _localAudioInterface->getAudioSolo().isSoloing();
|
||||
}
|
||||
|
||||
QVector<QUuid> getSoloList() const {
|
||||
return _localAudioInterface->getAudioSolo().getUUIDs();
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Add nodes to the audio solo list
|
||||
* @function Audio.addToSoloList
|
||||
* @param {Uuid[]} uuidList - List of node UUIDs to add to the solo list.
|
||||
*/
|
||||
Q_INVOKABLE void addToSoloList(QVector<QUuid> uuidList) {
|
||||
_localAudioInterface->getAudioSolo().addUUIDs(uuidList);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Remove nodes from the audio solo list
|
||||
* @function Audio.removeFromSoloList
|
||||
* @param {Uuid[]} uuidList - List of node UUIDs to remove from the solo list.
|
||||
*/
|
||||
Q_INVOKABLE void removeFromSoloList(QVector<QUuid> uuidList) {
|
||||
_localAudioInterface->getAudioSolo().removeUUIDs(uuidList);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Reset the list of soloed nodes.
|
||||
* @function Audio.resetSoloList
|
||||
*/
|
||||
Q_INVOKABLE void resetSoloList() {
|
||||
_localAudioInterface->getAudioSolo().reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
AudioScriptingInterface() {}
|
||||
AudioScriptingInterface() = default;
|
||||
|
||||
// these methods are protected to stop C++ callers from calling, but invokable from script
|
||||
|
||||
|
|
Loading…
Reference in a new issue