From 2794a134c17d48f85044d2ebd8d7dee8c9127544 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 15 Mar 2019 10:44:59 -0700
Subject: [PATCH 01/26] Add master injector gain to audio-mixer

---
 .../src/audio/AudioMixerClientData.h          |  3 +
 .../src/audio/AudioMixerSlave.cpp             | 56 ++++++++++++-------
 assignment-client/src/audio/AudioMixerSlave.h |  7 ++-
 3 files changed, 43 insertions(+), 23 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index 653749f619..f9d113c53d 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -84,6 +84,8 @@ public:
 
     float getMasterAvatarGain() const { return _masterAvatarGain; }
     void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
+    float getMasterInjectorGain() const { return _masterInjectorGain; }
+    void setMasterInjectorGain(float gain) { _masterInjectorGain = gain; }
 
     AudioLimiter audioLimiter;
 
@@ -189,6 +191,7 @@ private:
     int _frameToSendStats { 0 };
 
     float _masterAvatarGain { 1.0f };   // per-listener mixing gain, applied only to avatars
+    float _masterInjectorGain { 1.0f }; // per-listener mixing gain, applied only to injectors
 
     CodecPluginPointer _codec;
     QString _selectedCodecName;
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index a920b45161..f7f8e8a9c1 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -50,7 +50,7 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
 
 // mix helpers
 inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd);
-inline float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream,
+inline float computeGain(float masterAvatarGain, float masterInjectorGain, const AvatarAudioStream& listeningNodeStream,
         const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho);
 inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
         const glm::vec3& relativePosition);
@@ -338,8 +338,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
         }
 
         if (!isThrottling) {
-            updateHRTFParameters(stream, *listenerAudioStream,
-                                 listenerData->getMasterAvatarGain());
+            updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+                                 listenerData->getMasterInjectorGain());
         }
         return false;
     });
@@ -363,8 +363,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
         }
 
         if (!isThrottling) {
-            updateHRTFParameters(stream, *listenerAudioStream,
-                                 listenerData->getMasterAvatarGain());
+            updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+                                 listenerData->getMasterInjectorGain());
         }
         return false;
     });
@@ -381,13 +381,13 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
             stream.approximateVolume = approximateVolume(stream, listenerAudioStream);
         } else {
             if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) {
-                addStream(stream, *listenerAudioStream, 0.0f, isSoloing);
+                addStream(stream, *listenerAudioStream, 0.0f, 0.0f, isSoloing);
                 streams.skipped.push_back(move(stream));
                 ++stats.activeToSkipped;
                 return true;
             }
 
-            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), listenerData->getMasterInjectorGain(),
                       isSoloing);
 
             if (shouldBeInactive(stream)) {
@@ -423,7 +423,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
                 return true;
             }
 
-            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), listenerData->getMasterInjectorGain(),
                       isSoloing);
 
             if (shouldBeInactive(stream)) {
@@ -491,7 +491,9 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
 
 void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream,
                                 AvatarAudioStream& listeningNodeStream,
-                                float masterListenerGain, bool isSoloing) {
+                                float masterAvatarGain,
+                                float masterInjectorGain,
+                                bool isSoloing) {
     ++stats.totalMixes;
 
     auto streamToAdd = mixableStream.positionalStream;
@@ -504,9 +506,10 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
     float distance = glm::max(glm::length(relativePosition), EPSILON);
     float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
 
-    float gain = masterListenerGain;
+    float gain = masterAvatarGain;
     if (!isSoloing) {
-        gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
+        gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
+                           distance, isEcho);
     }
 
     const int HRTF_DATASET_INDEX = 1;
@@ -585,8 +588,9 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
 }
 
 void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
-                                      AvatarAudioStream& listeningNodeStream,
-                                      float masterListenerGain) {
+                                           AvatarAudioStream& listeningNodeStream,
+                                           float masterAvatarGain,
+                                           float masterInjectorGain) {
     auto streamToAdd = mixableStream.positionalStream;
 
     // check if this is a server echo of a source back to itself
@@ -595,7 +599,8 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream&
     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 gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
+                             distance, isEcho);
     float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
 
     mixableStream.hrtf->setParameterHistory(azimuth, distance, gain);
@@ -720,6 +725,7 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
     // injector: apply attenuation
     if (streamToAdd.getType() == PositionalAudioStream::Injector) {
         gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
+        // injector: skip master gain
     }
 
     // avatar: skip attenuation - it is too costly to approximate
@@ -729,16 +735,23 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
     float distance = glm::length(relativePosition);
     return gain / distance;
 
-    // avatar: skip master gain - it is constant for all streams
+    // avatar: skip master gain
 }
 
-float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream,
-        const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho) {
+float computeGain(float masterAvatarGain,
+                  float masterInjectorGain,
+                  const AvatarAudioStream& listeningNodeStream,
+                  const PositionalAudioStream& streamToAdd,
+                  const glm::vec3& relativePosition,
+                  float distance,
+                  bool isEcho) {
     float gain = 1.0f;
 
     // injector: apply attenuation
     if (streamToAdd.getType() == PositionalAudioStream::Injector) {
         gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
+        // apply master gain
+        gain *= masterInjectorGain;
 
     // avatar: apply fixed off-axis attenuation to make them quieter as they turn away
     } else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) {
@@ -754,8 +767,8 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
 
         gain *= offAxisCoefficient;
 
-        // apply master gain, only to avatars
-        gain *= masterListenerGain;
+        // apply master gain
+        gain *= masterAvatarGain;
     }
 
     auto& audioZones = AudioMixer::getAudioZones();
@@ -797,8 +810,9 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
     return gain;
 }
 
-float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
-        const glm::vec3& relativePosition) {
+float computeAzimuth(const AvatarAudioStream& listeningNodeStream,
+                     const PositionalAudioStream& streamToAdd,
+                     const glm::vec3& relativePosition) {
     glm::quat inverseOrientation = glm::inverse(listeningNodeStream.getOrientation());
 
     glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
diff --git a/assignment-client/src/audio/AudioMixerSlave.h b/assignment-client/src/audio/AudioMixerSlave.h
index 3d979da1fc..9765ea8639 100644
--- a/assignment-client/src/audio/AudioMixerSlave.h
+++ b/assignment-client/src/audio/AudioMixerSlave.h
@@ -57,10 +57,13 @@ private:
     bool prepareMix(const SharedNodePointer& listener);
     void addStream(AudioMixerClientData::MixableStream& mixableStream,
                    AvatarAudioStream& listeningNodeStream,
-                   float masterListenerGain, bool isSoloing);
+                   float masterAvatarGain,
+                   float masterInjectorGain,
+                   bool isSoloing);
     void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
                               AvatarAudioStream& listeningNodeStream,
-                              float masterListenerGain);
+                              float masterAvatarGain,
+                              float masterInjectorGain);
     void resetHRTFState(AudioMixerClientData::MixableStream& mixableStream);
 
     void addStreams(Node& listener, AudioMixerClientData& listenerData);

From a5a305f1816cb56c9a0434d41c446d74ccca38d2 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 15 Mar 2019 12:05:51 -0700
Subject: [PATCH 02/26] Handle InjectorGainSet packet at the audio-mixer

---
 assignment-client/src/audio/AudioMixer.cpp         |  1 +
 .../src/audio/AudioMixerClientData.cpp             | 14 ++++++++++++++
 assignment-client/src/audio/AudioMixerClientData.h |  1 +
 libraries/networking/src/udt/PacketHeaders.h       |  2 +-
 4 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index f67c54239e..201e24d4b9 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -97,6 +97,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
             PacketType::RadiusIgnoreRequest,
             PacketType::RequestsDomainListData,
             PacketType::PerAvatarGainSet,
+            PacketType::InjectorGainSet,
             PacketType::AudioSoloRequest },
             this, "queueAudioPacket");
 
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 90698bfac8..b8d3ec62a6 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -92,6 +92,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
             case PacketType::PerAvatarGainSet:
                 parsePerAvatarGainSet(*packet, node);
                 break;
+            case PacketType::InjectorGainSet:
+                parseInjectorGainSet(*packet, node);
+                break;
             case PacketType::NodeIgnoreRequest:
                 parseNodeIgnoreRequest(packet, node);
                 break;
@@ -205,6 +208,17 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
     }
 }
 
+void AudioMixerClientData::parseInjectorGainSet(ReceivedMessage& message, const SharedNodePointer& node) {
+    QUuid uuid = node->getUUID();
+
+    uint8_t packedGain;
+    message.readPrimitive(&packedGain);
+    float gain = unpackFloatGainFromByte(packedGain);
+
+    setMasterInjectorGain(gain);
+    qCDebug(audio) << "Setting MASTER injector gain for " << uuid << " to " << gain;
+}
+
 void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) {
     auto it = std::find_if(_streams.active.cbegin(), _streams.active.cend(), [nodeID](const MixableStream& mixableStream){
         return mixableStream.nodeStreamID.nodeID == nodeID && mixableStream.nodeStreamID.streamID.isNull();
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index f9d113c53d..4a1ca7f9b5 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -63,6 +63,7 @@ public:
     void negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node);
     void parseRequestsDomainListData(ReceivedMessage& message);
     void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
+    void parseInjectorGainSet(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);
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 0ec7c40ca4..413ff14b17 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -57,7 +57,7 @@ public:
         ICEServerQuery,
         OctreeStats,
         SetAvatarTraits,
-        UNUSED_PACKET_TYPE,
+        InjectorGainSet,
         AssignmentClientStatus,
         NoisyMute,
         AvatarIdentity,

From ca0379f6de206502722aa27f31137870fc762677 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 15 Mar 2019 17:24:50 -0700
Subject: [PATCH 03/26] Send InjectorGainSet packet to the audio-mixer

---
 libraries/networking/src/NodeList.cpp | 25 +++++++++++++++++++++++++
 libraries/networking/src/NodeList.h   |  4 ++++
 2 files changed, 29 insertions(+)

diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index e6eb6087b0..eec710322e 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -265,6 +265,8 @@ void NodeList::reset(bool skipDomainHandlerReset) {
     _avatarGainMap.clear();
     _avatarGainMapLock.unlock();
 
+    _injectorGain = 0.0f;
+
     if (!skipDomainHandlerReset) {
         // clear the domain connection information, unless they're the ones that asked us to reset
         _domainHandler.softReset();
@@ -1087,6 +1089,29 @@ float NodeList::getAvatarGain(const QUuid& nodeID) {
     return 0.0f;
 }
 
+void NodeList::setInjectorGain(float gain) {
+    auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
+    if (audioMixer) {
+        // setup the packet
+        auto setInjectorGainPacket = NLPacket::create(PacketType::InjectorGainSet, sizeof(float), true);
+
+        // We need to convert the gain in dB (from the script) to an amplitude before packing it.
+        setInjectorGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.02059991f)));
+
+        qCDebug(networking) << "Sending Set Injector Gain packet with Gain:" << gain;
+
+        sendPacket(std::move(setInjectorGainPacket), *audioMixer);
+        _injectorGain = gain;
+
+    } else {
+        qWarning() << "Couldn't find audio mixer to send set gain request";
+    }
+}
+
+float NodeList::getInjectorGain() {
+    return _injectorGain;
+}
+
 void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
     // send a request to domain-server to kick the node with the given session ID
     // the domain-server will handle the persistence of the kick (via username or IP)
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index e135bc937d..d2a1212d64 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -83,6 +83,8 @@ public:
     bool isPersonalMutingNode(const QUuid& nodeID) const;
     void setAvatarGain(const QUuid& nodeID, float gain);
     float getAvatarGain(const QUuid& nodeID);
+    void setInjectorGain(float gain);
+    float getInjectorGain();
 
     void kickNodeBySessionID(const QUuid& nodeID);
     void muteNodeBySessionID(const QUuid& nodeID);
@@ -181,6 +183,8 @@ private:
     mutable QReadWriteLock _avatarGainMapLock;
     tbb::concurrent_unordered_map<QUuid, float, UUIDHasher> _avatarGainMap;
 
+    std::atomic<float> _injectorGain { 0.0f };
+
     void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
 #if defined(Q_OS_ANDROID)
     Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false };

From 3186a9468231daff21687c76999469c012910824 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Wed, 20 Mar 2019 15:48:05 -0700
Subject: [PATCH 04/26] Add Users.setInjectorGain() and Users.getInjectorGain()
 to the scripting interface

---
 .../script-engine/src/UsersScriptingInterface.cpp |  9 +++++++++
 .../script-engine/src/UsersScriptingInterface.h   | 15 +++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp
index fef11c12e9..a0593d3ff8 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.cpp
+++ b/libraries/script-engine/src/UsersScriptingInterface.cpp
@@ -51,6 +51,15 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
     return DependencyManager::get<NodeList>()->getAvatarGain(nodeID);
 }
 
+void UsersScriptingInterface::setInjectorGain(float gain) {
+    // ask the NodeList to set the audio injector gain
+    DependencyManager::get<NodeList>()->setInjectorGain(gain);
+}
+
+float UsersScriptingInterface::getInjectorGain() {
+    return DependencyManager::get<NodeList>()->getInjectorGain();
+}
+
 void UsersScriptingInterface::kick(const QUuid& nodeID) {
     // ask the NodeList to kick the user with the given session ID
     DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h
index 57de205066..17a84248a1 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.h
+++ b/libraries/script-engine/src/UsersScriptingInterface.h
@@ -90,6 +90,21 @@ public slots:
     */
     float getAvatarGain(const QUuid& nodeID);
 
+    /**jsdoc
+     * Sets the audio injector gain at the server.
+     * Units are Decibels (dB)
+     * @function Users.setInjectorGain
+     * @param {number} gain (in dB)
+    */
+    void setInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the audio injector gain at the server.
+     * @function Users.getInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    float getInjectorGain();
+
     /**jsdoc
      * Kick/ban another user. Removes them from the server and prevents them from returning. Bans by either user name (if 
      * available) or machine fingerprint otherwise. This will only do anything if you're an admin of the domain you're in. 

From 7311c3ac06aa3e1425c169afc22f0d0c2842900e Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Thu, 21 Mar 2019 11:51:49 -0700
Subject: [PATCH 05/26] Move the new audio volume API from Users scripting
 interface to Audio scripting interface

---
 interface/src/scripting/Audio.cpp             | 36 ++++++++++++++++---
 interface/src/scripting/Audio.h               | 30 ++++++++++++++++
 .../src/UsersScriptingInterface.cpp           |  9 -----
 .../src/UsersScriptingInterface.h             | 15 --------
 4 files changed, 61 insertions(+), 29 deletions(-)

diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index bf43db3044..e0474b7bba 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -374,6 +374,18 @@ void Audio::handlePushedToTalk(bool enabled) {
     }
 }
 
+void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
+    withWriteLock([&] {
+        _devices.chooseInputDevice(device, isHMD);
+    });
+}
+
+void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
+    withWriteLock([&] {
+        _devices.chooseOutputDevice(device, isHMD);
+    });
+}
+
 void Audio::setReverb(bool enable) {
     withWriteLock([&] {
         DependencyManager::get<AudioClient>()->setReverb(enable);
@@ -386,14 +398,28 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) {
     });
 }
 
-void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
+void Audio::setAvatarGain(float gain) {
     withWriteLock([&] {
-        _devices.chooseInputDevice(device, isHMD);
+        // ask the NodeList to set the master avatar gain
+        DependencyManager::get<NodeList>()->setAvatarGain("", gain);
     });
 }
 
-void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
-    withWriteLock([&] {
-        _devices.chooseOutputDevice(device, isHMD);
+float Audio::getAvatarGain() {
+    return resultWithReadLock<float>([&] {
+        return DependencyManager::get<NodeList>()->getAvatarGain("");
+    });
+}
+
+void Audio::setInjectorGain(float gain) {
+    withWriteLock([&] {
+        // ask the NodeList to set the audio injector gain
+        DependencyManager::get<NodeList>()->setInjectorGain(gain);
+    });
+}
+
+float Audio::getInjectorGain() {
+    return resultWithReadLock<float>([&] {
+        return DependencyManager::get<NodeList>()->getInjectorGain();
     });
 }
diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index 9ee230fc29..14a75d5ffe 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -170,6 +170,36 @@ public:
      */
     Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
 
+    /**jsdoc
+     * Sets the master avatar gain at the server.
+     * Units are Decibels (dB)
+     * @function Audio.setAvatarGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setAvatarGain(float gain);
+
+    /**jsdoc
+     * Gets the master avatar gain at the server.
+     * @function Audio.getAvatarGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getAvatarGain();
+
+    /**jsdoc
+     * Sets the audio injector gain at the server.
+     * Units are Decibels (dB)
+     * @function Audio.setInjectorGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the audio injector gain at the server.
+     * @function Audio.getInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getInjectorGain();
+
     /**jsdoc
      * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
      * @function Audio.startRecording
diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp
index a0593d3ff8..fef11c12e9 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.cpp
+++ b/libraries/script-engine/src/UsersScriptingInterface.cpp
@@ -51,15 +51,6 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
     return DependencyManager::get<NodeList>()->getAvatarGain(nodeID);
 }
 
-void UsersScriptingInterface::setInjectorGain(float gain) {
-    // ask the NodeList to set the audio injector gain
-    DependencyManager::get<NodeList>()->setInjectorGain(gain);
-}
-
-float UsersScriptingInterface::getInjectorGain() {
-    return DependencyManager::get<NodeList>()->getInjectorGain();
-}
-
 void UsersScriptingInterface::kick(const QUuid& nodeID) {
     // ask the NodeList to kick the user with the given session ID
     DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h
index 17a84248a1..57de205066 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.h
+++ b/libraries/script-engine/src/UsersScriptingInterface.h
@@ -90,21 +90,6 @@ public slots:
     */
     float getAvatarGain(const QUuid& nodeID);
 
-    /**jsdoc
-     * Sets the audio injector gain at the server.
-     * Units are Decibels (dB)
-     * @function Users.setInjectorGain
-     * @param {number} gain (in dB)
-    */
-    void setInjectorGain(float gain);
-
-    /**jsdoc
-     * Gets the audio injector gain at the server.
-     * @function Users.getInjectorGain
-     * @returns {number} gain (in dB)
-    */
-    float getInjectorGain();
-
     /**jsdoc
      * Kick/ban another user. Removes them from the server and prevents them from returning. Bans by either user name (if 
      * available) or machine fingerprint otherwise. This will only do anything if you're an admin of the domain you're in. 

From e6c720f793f79217fe11f99381b6caf878efdf3b Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 10:12:31 -0700
Subject: [PATCH 06/26] Add AudioClient mixing gains for local injectors and
 system sounds

---
 libraries/audio-client/src/AudioClient.cpp | 4 +++-
 libraries/audio-client/src/AudioClient.h   | 4 ++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 1c10d24f23..79811ac98f 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -1366,7 +1366,9 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
             memset(_localScratchBuffer, 0, bytesToRead);
             if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
 
-                float gain = injector->getVolume();
+                bool isSystemSound = !injector->isPositionSet() && !injector->isAmbisonic();
+
+                float gain = injector->getVolume() * (isSystemSound ? _systemInjectorGain : _localInjectorGain);
 
                 if (injector->isAmbisonic()) {
 
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index b9648219a5..6e1f48d5f4 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -239,6 +239,8 @@ public slots:
     void setInputVolume(float volume, bool emitSignal = true);
     void setReverb(bool reverb);
     void setReverbOptions(const AudioEffectOptions* options);
+    void setLocalInjectorGain(float gain) { _localInjectorGain = gain; };
+    void setSystemInjectorGain(float gain) { _systemInjectorGain = gain; };
 
     void outputNotify();
 
@@ -393,6 +395,8 @@ private:
     int16_t* _outputScratchBuffer { NULL };
 
     // for local audio (used by audio injectors thread)
+    std::atomic<float> _localInjectorGain { 1.0f };
+    std::atomic<float> _systemInjectorGain { 1.0f };
     float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
     int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
     float* _localOutputMixBuffer { NULL };

From 23a6a66528ac68792594cd0cf7547737c7ddba94 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 10:21:54 -0700
Subject: [PATCH 07/26] Add local injector gains to the Audio scripting
 interface

---
 interface/src/scripting/Audio.cpp | 34 ++++++++++++++++++++++++++
 interface/src/scripting/Audio.h   | 40 +++++++++++++++++++++++++++----
 2 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index e0474b7bba..7e1a35762a 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -423,3 +423,37 @@ float Audio::getInjectorGain() {
         return DependencyManager::get<NodeList>()->getInjectorGain();
     });
 }
+
+void Audio::setLocalInjectorGain(float gain) {
+    withWriteLock([&] {
+        if (_localInjectorGain != gain) {
+            _localInjectorGain = gain;
+            // convert dB to amplitude
+            gain = fastExp2f(gain / 6.02059991f);
+            DependencyManager::get<AudioClient>()->setLocalInjectorGain(gain);
+        }
+    });
+}
+
+float Audio::getLocalInjectorGain() {
+    return resultWithReadLock<float>([&] {
+        return _localInjectorGain;
+    });
+}
+
+void Audio::setSystemInjectorGain(float gain) {
+    withWriteLock([&] {
+        if (_systemInjectorGain != gain) {
+            _systemInjectorGain = gain;
+            // convert dB to amplitude
+            gain = fastExp2f(gain / 6.02059991f);
+            DependencyManager::get<AudioClient>()->setSystemInjectorGain(gain);
+        }
+    });
+}
+
+float Audio::getSystemInjectorGain() {
+    return resultWithReadLock<float>([&] {
+        return _systemInjectorGain;
+    });
+}
diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index 14a75d5ffe..d6823ea452 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -171,7 +171,7 @@ public:
     Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
 
     /**jsdoc
-     * Sets the master avatar gain at the server.
+     * Sets the avatar gain at the server.
      * Units are Decibels (dB)
      * @function Audio.setAvatarGain
      * @param {number} gain (in dB)
@@ -179,14 +179,14 @@ public:
     Q_INVOKABLE void setAvatarGain(float gain);
 
     /**jsdoc
-     * Gets the master avatar gain at the server.
+     * Gets the avatar gain at the server.
      * @function Audio.getAvatarGain
      * @returns {number} gain (in dB)
     */
     Q_INVOKABLE float getAvatarGain();
 
     /**jsdoc
-     * Sets the audio injector gain at the server.
+     * Sets the injector gain at the server.
      * Units are Decibels (dB)
      * @function Audio.setInjectorGain
      * @param {number} gain (in dB)
@@ -194,12 +194,42 @@ public:
     Q_INVOKABLE void setInjectorGain(float gain);
 
     /**jsdoc
-     * Gets the audio injector gain at the server.
+     * Gets the injector gain at the server.
      * @function Audio.getInjectorGain
      * @returns {number} gain (in dB)
     */
     Q_INVOKABLE float getInjectorGain();
 
+    /**jsdoc
+     * Sets the local injector gain in the client.
+     * Units are Decibels (dB)
+     * @function Audio.setLocalInjectorGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setLocalInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the local injector gain in the client.
+     * @function Audio.getLocalInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getLocalInjectorGain();
+
+    /**jsdoc
+     * Sets the injector gain for system sounds.
+     * Units are Decibels (dB)
+     * @function Audio.setSystemInjectorGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setSystemInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the injector gain for system sounds.
+     * @function Audio.getSystemInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getSystemInjectorGain();
+
     /**jsdoc
      * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
      * @function Audio.startRecording
@@ -380,6 +410,8 @@ private:
 
     float _inputVolume { 1.0f };
     float _inputLevel { 0.0f };
+    float _localInjectorGain { 0.0f };  // in dB
+    float _systemInjectorGain { 0.0f }; // in dB
     bool _isClipping { false };
     bool _enableNoiseReduction { true };  // Match default value of AudioClient::_isNoiseGateEnabled.
     bool _enableWarnWhenMuted { true };

From e8ddee280d6e8c11f897791c7d7041a1edd1a7c7 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 10:24:30 -0700
Subject: [PATCH 08/26] Quantize and limit the local injector gains to match
 the network protocol

---
 interface/src/scripting/Audio.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index 7e1a35762a..6dd1c40ef5 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -430,6 +430,8 @@ void Audio::setLocalInjectorGain(float gain) {
             _localInjectorGain = gain;
             // convert dB to amplitude
             gain = fastExp2f(gain / 6.02059991f);
+            // quantize and limit to match NodeList::setInjectorGain()
+            gain = unpackFloatGainFromByte(packFloatGainToByte(gain));
             DependencyManager::get<AudioClient>()->setLocalInjectorGain(gain);
         }
     });
@@ -447,6 +449,8 @@ void Audio::setSystemInjectorGain(float gain) {
             _systemInjectorGain = gain;
             // convert dB to amplitude
             gain = fastExp2f(gain / 6.02059991f);
+            // quantize and limit to match NodeList::setInjectorGain()
+            gain = unpackFloatGainFromByte(packFloatGainToByte(gain));
             DependencyManager::get<AudioClient>()->setSystemInjectorGain(gain);
         }
     });

From e671a124c3c5d6a14510111417f6346c81d8efe4 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 17:58:17 -0700
Subject: [PATCH 09/26] Cleanup

---
 assignment-client/src/audio/AudioMixerClientData.cpp | 6 +++---
 interface/src/scripting/Audio.cpp                    | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index b8d3ec62a6..41b72c04d2 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -200,11 +200,11 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
     if (avatarUUID.isNull()) {
         // set the MASTER avatar gain
         setMasterAvatarGain(gain);
-        qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain;
+        qCDebug(audio) << "Setting MASTER avatar gain for" << uuid << "to" << gain;
     } else {
         // set the per-source avatar gain
         setGainForAvatar(avatarUUID, gain);
-        qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUUID << "] to " << gain;
+        qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUUID << "] to" << gain;
     }
 }
 
@@ -216,7 +216,7 @@ void AudioMixerClientData::parseInjectorGainSet(ReceivedMessage& message, const
     float gain = unpackFloatGainFromByte(packedGain);
 
     setMasterInjectorGain(gain);
-    qCDebug(audio) << "Setting MASTER injector gain for " << uuid << " to " << gain;
+    qCDebug(audio) << "Setting MASTER injector gain for" << uuid << "to" << gain;
 }
 
 void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) {
diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index 6dd1c40ef5..ac5ddd6a6a 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -401,13 +401,13 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) {
 void Audio::setAvatarGain(float gain) {
     withWriteLock([&] {
         // ask the NodeList to set the master avatar gain
-        DependencyManager::get<NodeList>()->setAvatarGain("", gain);
+        DependencyManager::get<NodeList>()->setAvatarGain(QUuid(), gain);
     });
 }
 
 float Audio::getAvatarGain() {
     return resultWithReadLock<float>([&] {
-        return DependencyManager::get<NodeList>()->getAvatarGain("");
+        return DependencyManager::get<NodeList>()->getAvatarGain(QUuid());
     });
 }
 

From cbeb4b0b208fc40bc00d32e31bcce3b6903cc7e0 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 23 Mar 2019 06:48:37 -0700
Subject: [PATCH 10/26] Persist the audio-mixer settings across domain changes
 and server resets

---
 libraries/networking/src/NodeList.cpp | 38 ++++++++++++++++++---------
 libraries/networking/src/NodeList.h   |  3 ++-
 2 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index eec710322e..0021a594bc 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -265,8 +265,6 @@ void NodeList::reset(bool skipDomainHandlerReset) {
     _avatarGainMap.clear();
     _avatarGainMapLock.unlock();
 
-    _injectorGain = 0.0f;
-
     if (!skipDomainHandlerReset) {
         // clear the domain connection information, unless they're the ones that asked us to reset
         _domainHandler.softReset();
@@ -1018,6 +1016,14 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
 
         // also send them the current ignore radius state.
         sendIgnoreRadiusStateToNode(newNode);
+
+        // also send the current avatar and injector gains
+        if (_avatarGain != 0.0f) {
+            setAvatarGain(QUuid(), _avatarGain);
+        }
+        if (_injectorGain != 0.0f) {
+            setInjectorGain(_injectorGain);
+        }
     }
     if (newNode->getType() == NodeType::AvatarMixer) {
         // this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session,
@@ -1064,13 +1070,17 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
 
             if (nodeID.isNull()) {
                 qCDebug(networking) << "Sending Set MASTER Avatar Gain packet with Gain:" << gain;
-            } else {
-                qCDebug(networking) << "Sending Set Avatar Gain packet with UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
-            }
 
-            sendPacket(std::move(setAvatarGainPacket), *audioMixer);
-            QWriteLocker lock{ &_avatarGainMapLock };
-            _avatarGainMap[nodeID] = gain;
+                sendPacket(std::move(setAvatarGainPacket), *audioMixer);
+                _avatarGain = gain;
+
+            } else {
+                qCDebug(networking) << "Sending Set Avatar Gain packet with UUID:" << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
+
+                sendPacket(std::move(setAvatarGainPacket), *audioMixer);
+                QWriteLocker lock{ &_avatarGainMapLock };
+                _avatarGainMap[nodeID] = gain;
+            }
 
         } else {
             qWarning() << "Couldn't find audio mixer to send set gain request";
@@ -1081,10 +1091,14 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
 }
 
 float NodeList::getAvatarGain(const QUuid& nodeID) {
-    QReadLocker lock{ &_avatarGainMapLock };
-    auto it = _avatarGainMap.find(nodeID);
-    if (it != _avatarGainMap.cend()) {
-        return it->second;
+    if (nodeID.isNull()) {
+        return _avatarGain;
+    } else {
+        QReadLocker lock{ &_avatarGainMapLock };
+        auto it = _avatarGainMap.find(nodeID);
+        if (it != _avatarGainMap.cend()) {
+            return it->second;
+        }
     }
     return 0.0f;
 }
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index d2a1212d64..f871560fba 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -183,7 +183,8 @@ private:
     mutable QReadWriteLock _avatarGainMapLock;
     tbb::concurrent_unordered_map<QUuid, float, UUIDHasher> _avatarGainMap;
 
-    std::atomic<float> _injectorGain { 0.0f };
+    std::atomic<float> _avatarGain { 0.0f };    // in dB
+    std::atomic<float> _injectorGain { 0.0f };  // in dB
 
     void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
 #if defined(Q_OS_ANDROID)

From 649bb92e6c8d628ce4825e2dc946cdf624b981e0 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 23 Mar 2019 16:00:02 -0700
Subject: [PATCH 11/26] Prototype an updated Audio tab with 3 independent
 volume controls

---
 interface/resources/qml/hifi/audio/Audio.qml  | 176 +++++++++++++++---
 .../qml/hifi/audio/LoopbackAudio.qml          |  16 +-
 .../qml/hifi/audio/PlaySampleSound.qml        |  16 +-
 3 files changed, 169 insertions(+), 39 deletions(-)

diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index da306f911b..92dfcb4117 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -85,8 +85,19 @@ Rectangle {
     }
 
     function updateMyAvatarGainFromQML(sliderValue, isReleased) {
-        if (Users.getAvatarGain(myAvatarUuid) != sliderValue) {
-            Users.setAvatarGain(myAvatarUuid, sliderValue);
+        if (AudioScriptingInterface.getAvatarGain() != sliderValue) {
+            AudioScriptingInterface.setAvatarGain(sliderValue);
+        }
+    }
+    function updateInjectorGainFromQML(sliderValue, isReleased) {
+        if (AudioScriptingInterface.getInjectorGain() != sliderValue) {
+            AudioScriptingInterface.setInjectorGain(sliderValue);       // server side
+            AudioScriptingInterface.setLocalInjectorGain(sliderValue);  // client side
+        }
+    }
+    function updateSystemInjectorGainFromQML(sliderValue, isReleased) {
+        if (AudioScriptingInterface.getSystemInjectorGain() != sliderValue) {
+            AudioScriptingInterface.setSystemInjectorGain(sliderValue);
         }
     }
 
@@ -254,6 +265,14 @@ Rectangle {
                 color: hifi.colors.white;
                 text: qsTr("Choose input device");
             }
+
+            AudioControls.LoopbackAudio {
+                x: margins.paddings
+
+                visible: (bar.currentIndex === 1 && isVR) ||
+                    (bar.currentIndex === 0 && !isVR);
+                anchors { right: parent.right }
+            }
         }
 
         ListView {
@@ -301,13 +320,6 @@ Rectangle {
                 }
             }
         }
-        AudioControls.LoopbackAudio {
-            x: margins.paddings
-
-            visible: (bar.currentIndex === 1 && isVR) ||
-                (bar.currentIndex === 0 && !isVR);
-            anchors { left: parent.left; leftMargin: margins.paddings }
-        }
 
         Separator {}
 
@@ -335,6 +347,14 @@ Rectangle {
                 color: hifi.colors.white;
                 text: qsTr("Choose output device");
             }
+
+            AudioControls.PlaySampleSound {
+                x: margins.paddings
+
+                visible: (bar.currentIndex === 1 && isVR) ||
+                         (bar.currentIndex === 0 && !isVR);
+                anchors { right: parent.right }
+            }
         }
 
         ListView {
@@ -370,20 +390,20 @@ Rectangle {
         }
 
         Item {
-            id: gainContainer
+            id: avatarGainContainer
             x: margins.paddings;
             width: parent.width - margins.paddings*2
-            height: gainSliderTextMetrics.height
+            height: avatarGainSliderTextMetrics.height
 
             HifiControlsUit.Slider {
-                id: gainSlider
+                id: avatarGainSlider
                 anchors.right: parent.right
                 height: parent.height
                 width: 200
                 minimumValue: -60.0
                 maximumValue: 20.0
                 stepSize: 5
-                value: Users.getAvatarGain(myAvatarUuid)
+                value: AudioScriptingInterface.getAvatarGain()
                 onValueChanged: {
                     updateMyAvatarGainFromQML(value, false);
                 }
@@ -399,7 +419,7 @@ Rectangle {
                         // Do nothing.
                     }
                     onDoubleClicked: {
-                        gainSlider.value = 0.0
+                        avatarGainSlider.value = 0.0
                     }
                     onPressed: {
                         // Pass through to Slider
@@ -413,13 +433,13 @@ Rectangle {
                 }
             }
             TextMetrics {
-                id: gainSliderTextMetrics
-                text: gainSliderText.text
-                font: gainSliderText.font
+                id: avatarGainSliderTextMetrics
+                text: avatarGainSliderText.text
+                font: avatarGainSliderText.font
             }
             RalewayRegular {
                 // The slider for my card is special, it controls the master gain
-                id: gainSliderText;
+                id: avatarGainSliderText;
                 text: "Avatar volume";
                 size: 16;
                 anchors.left: parent.left;
@@ -429,12 +449,122 @@ Rectangle {
             }
         }
 
-        AudioControls.PlaySampleSound {
-            x: margins.paddings
+        Item {
+            id: injectorGainContainer
+            x: margins.paddings;
+            width: parent.width - margins.paddings*2
+            height: injectorGainSliderTextMetrics.height
 
-            visible: (bar.currentIndex === 1 && isVR) ||
-                     (bar.currentIndex === 0 && !isVR);
-            anchors { left: parent.left; leftMargin: margins.paddings }
+            HifiControlsUit.Slider {
+                id: injectorGainSlider
+                anchors.right: parent.right
+                height: parent.height
+                width: 200
+                minimumValue: -60.0
+                maximumValue: 20.0
+                stepSize: 5
+                value: AudioScriptingInterface.getInjectorGain()
+                onValueChanged: {
+                    updateInjectorGainFromQML(value, false);
+                }
+                onPressedChanged: {
+                    if (!pressed) {
+                        updateInjectorGainFromQML(value, false);
+                    }
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    onWheel: {
+                        // Do nothing.
+                    }
+                    onDoubleClicked: {
+                        injectorGainSlider.value = 0.0
+                    }
+                    onPressed: {
+                        // Pass through to Slider
+                        mouse.accepted = false
+                    }
+                    onReleased: {
+                        // the above mouse.accepted seems to make this
+                        // never get called, nonetheless...
+                        mouse.accepted = false
+                    }
+                }
+            }
+            TextMetrics {
+                id: injectorGainSliderTextMetrics
+                text: injectorGainSliderText.text
+                font: injectorGainSliderText.font
+            }
+            RalewayRegular {
+                id: injectorGainSliderText;
+                text: "Environment volume";
+                size: 16;
+                anchors.left: parent.left;
+                color: hifi.colors.white;
+                horizontalAlignment: Text.AlignLeft;
+                verticalAlignment: Text.AlignTop;
+            }
+        }
+
+        Item {
+            id: systemInjectorGainContainer
+            x: margins.paddings;
+            width: parent.width - margins.paddings*2
+            height: systemInjectorGainSliderTextMetrics.height
+
+            HifiControlsUit.Slider {
+                id: systemInjectorGainSlider
+                anchors.right: parent.right
+                height: parent.height
+                width: 200
+                minimumValue: -60.0
+                maximumValue: 20.0
+                stepSize: 5
+                value: AudioScriptingInterface.getSystemInjectorGain()
+                onValueChanged: {
+                    updateSystemInjectorGainFromQML(value, false);
+                }
+                onPressedChanged: {
+                    if (!pressed) {
+                        updateSystemInjectorGainFromQML(value, false);
+                    }
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    onWheel: {
+                        // Do nothing.
+                    }
+                    onDoubleClicked: {
+                        systemInjectorGainSlider.value = 0.0
+                    }
+                    onPressed: {
+                        // Pass through to Slider
+                        mouse.accepted = false
+                    }
+                    onReleased: {
+                        // the above mouse.accepted seems to make this
+                        // never get called, nonetheless...
+                        mouse.accepted = false
+                    }
+                }
+            }
+            TextMetrics {
+                id: systemInjectorGainSliderTextMetrics
+                text: systemInjectorGainSliderText.text
+                font: systemInjectorGainSliderText.font
+            }
+            RalewayRegular {
+                id: systemInjectorGainSliderText;
+                text: "System Sound volume";
+                size: 16;
+                anchors.left: parent.left;
+                color: hifi.colors.white;
+                horizontalAlignment: Text.AlignLeft;
+                verticalAlignment: Text.AlignTop;
+            }
         }
     }
 }
diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
index 8ec0ffc496..74bc0f67dc 100644
--- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
+++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
@@ -44,7 +44,7 @@ RowLayout {
     }
 
     HifiControlsUit.Button {
-        text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE");
+        text: audioLoopedBack ? qsTr("STOP TESTING") : qsTr("TEST YOUR VOICE");
         color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue;
         onClicked: {
             if (audioLoopedBack) {
@@ -57,11 +57,11 @@ RowLayout {
         }
     }
 
-    RalewayRegular {
-        Layout.leftMargin: 2;
-        size: 14;
-        color: "white";
-        font.italic: true
-        text: audioLoopedBack ? qsTr("Speak in your input") : "";
-    }
+//    RalewayRegular {
+//        Layout.leftMargin: 2;
+//        size: 14;
+//        color: "white";
+//        font.italic: true
+//        text: audioLoopedBack ? qsTr("Speak in your input") : "";
+//    }
 }
diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
index b9d9727dab..0eb78f3efe 100644
--- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml
+++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
@@ -56,16 +56,16 @@ RowLayout {
     HifiConstants { id: hifi; }
 
     HifiControlsUit.Button {
-        text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND");
+        text: isPlaying ? qsTr("STOP TESTING") : qsTr("TEST YOUR SOUND");
         color: isPlaying ? hifi.buttons.red : hifi.buttons.blue;
         onClicked: isPlaying ? stopSound() : playSound();
     }
 
-    RalewayRegular {
-        Layout.leftMargin: 2;
-        size: 14;
-        color: "white";
-        font.italic: true
-        text: isPlaying ? qsTr("Listen to your output") : "";
-    }
+//    RalewayRegular {
+//        Layout.leftMargin: 2;
+//        size: 14;
+//        color: "white";
+//        font.italic: true
+//        text: isPlaying ? qsTr("Listen to your output") : "";
+//    }
 }

From 4658e34b4be6bd1340e56d320f775fb1d2ee3d7b Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Mon, 25 Mar 2019 11:29:27 -0700
Subject: [PATCH 12/26] update indent spacing

---
 interface/resources/qml/hifi/audio/Audio.qml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index ded28506b8..46ea64a323 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -669,10 +669,10 @@ Rectangle {
             }
         }   
         AudioControls.PlaySampleSound {
-          id: playSampleSound
-          x: margins.paddings
-          anchors.top: systemInjectorGainContainer.bottom;
-          anchors.topMargin: 10;
+              id: playSampleSound
+              x: margins.paddings
+              anchors.top: systemInjectorGainContainer.bottom;
+              anchors.topMargin: 10;
         }
     }
 }

From afe46cd78bfcaf759bf3099c8a37d7d7bb4406fb Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Mon, 25 Mar 2019 11:29:34 -0700
Subject: [PATCH 13/26] update indent spacing


From 1057166418e4c0526c2caa6e7a02c6b06b4fd63c Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 15 Mar 2019 10:44:59 -0700
Subject: [PATCH 14/26] Add master injector gain to audio-mixer

---
 .../src/audio/AudioMixerClientData.h          |  3 +
 .../src/audio/AudioMixerSlave.cpp             | 56 ++++++++++++-------
 assignment-client/src/audio/AudioMixerSlave.h |  7 ++-
 3 files changed, 43 insertions(+), 23 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index 653749f619..f9d113c53d 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -84,6 +84,8 @@ public:
 
     float getMasterAvatarGain() const { return _masterAvatarGain; }
     void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
+    float getMasterInjectorGain() const { return _masterInjectorGain; }
+    void setMasterInjectorGain(float gain) { _masterInjectorGain = gain; }
 
     AudioLimiter audioLimiter;
 
@@ -189,6 +191,7 @@ private:
     int _frameToSendStats { 0 };
 
     float _masterAvatarGain { 1.0f };   // per-listener mixing gain, applied only to avatars
+    float _masterInjectorGain { 1.0f }; // per-listener mixing gain, applied only to injectors
 
     CodecPluginPointer _codec;
     QString _selectedCodecName;
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index a920b45161..f7f8e8a9c1 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -50,7 +50,7 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
 
 // mix helpers
 inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd);
-inline float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream,
+inline float computeGain(float masterAvatarGain, float masterInjectorGain, const AvatarAudioStream& listeningNodeStream,
         const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho);
 inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
         const glm::vec3& relativePosition);
@@ -338,8 +338,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
         }
 
         if (!isThrottling) {
-            updateHRTFParameters(stream, *listenerAudioStream,
-                                 listenerData->getMasterAvatarGain());
+            updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+                                 listenerData->getMasterInjectorGain());
         }
         return false;
     });
@@ -363,8 +363,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
         }
 
         if (!isThrottling) {
-            updateHRTFParameters(stream, *listenerAudioStream,
-                                 listenerData->getMasterAvatarGain());
+            updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+                                 listenerData->getMasterInjectorGain());
         }
         return false;
     });
@@ -381,13 +381,13 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
             stream.approximateVolume = approximateVolume(stream, listenerAudioStream);
         } else {
             if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) {
-                addStream(stream, *listenerAudioStream, 0.0f, isSoloing);
+                addStream(stream, *listenerAudioStream, 0.0f, 0.0f, isSoloing);
                 streams.skipped.push_back(move(stream));
                 ++stats.activeToSkipped;
                 return true;
             }
 
-            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), listenerData->getMasterInjectorGain(),
                       isSoloing);
 
             if (shouldBeInactive(stream)) {
@@ -423,7 +423,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
                 return true;
             }
 
-            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
+            addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), listenerData->getMasterInjectorGain(),
                       isSoloing);
 
             if (shouldBeInactive(stream)) {
@@ -491,7 +491,9 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
 
 void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream,
                                 AvatarAudioStream& listeningNodeStream,
-                                float masterListenerGain, bool isSoloing) {
+                                float masterAvatarGain,
+                                float masterInjectorGain,
+                                bool isSoloing) {
     ++stats.totalMixes;
 
     auto streamToAdd = mixableStream.positionalStream;
@@ -504,9 +506,10 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
     float distance = glm::max(glm::length(relativePosition), EPSILON);
     float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
 
-    float gain = masterListenerGain;
+    float gain = masterAvatarGain;
     if (!isSoloing) {
-        gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
+        gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
+                           distance, isEcho);
     }
 
     const int HRTF_DATASET_INDEX = 1;
@@ -585,8 +588,9 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
 }
 
 void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
-                                      AvatarAudioStream& listeningNodeStream,
-                                      float masterListenerGain) {
+                                           AvatarAudioStream& listeningNodeStream,
+                                           float masterAvatarGain,
+                                           float masterInjectorGain) {
     auto streamToAdd = mixableStream.positionalStream;
 
     // check if this is a server echo of a source back to itself
@@ -595,7 +599,8 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream&
     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 gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
+                             distance, isEcho);
     float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
 
     mixableStream.hrtf->setParameterHistory(azimuth, distance, gain);
@@ -720,6 +725,7 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
     // injector: apply attenuation
     if (streamToAdd.getType() == PositionalAudioStream::Injector) {
         gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
+        // injector: skip master gain
     }
 
     // avatar: skip attenuation - it is too costly to approximate
@@ -729,16 +735,23 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
     float distance = glm::length(relativePosition);
     return gain / distance;
 
-    // avatar: skip master gain - it is constant for all streams
+    // avatar: skip master gain
 }
 
-float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream,
-        const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho) {
+float computeGain(float masterAvatarGain,
+                  float masterInjectorGain,
+                  const AvatarAudioStream& listeningNodeStream,
+                  const PositionalAudioStream& streamToAdd,
+                  const glm::vec3& relativePosition,
+                  float distance,
+                  bool isEcho) {
     float gain = 1.0f;
 
     // injector: apply attenuation
     if (streamToAdd.getType() == PositionalAudioStream::Injector) {
         gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
+        // apply master gain
+        gain *= masterInjectorGain;
 
     // avatar: apply fixed off-axis attenuation to make them quieter as they turn away
     } else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) {
@@ -754,8 +767,8 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
 
         gain *= offAxisCoefficient;
 
-        // apply master gain, only to avatars
-        gain *= masterListenerGain;
+        // apply master gain
+        gain *= masterAvatarGain;
     }
 
     auto& audioZones = AudioMixer::getAudioZones();
@@ -797,8 +810,9 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
     return gain;
 }
 
-float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
-        const glm::vec3& relativePosition) {
+float computeAzimuth(const AvatarAudioStream& listeningNodeStream,
+                     const PositionalAudioStream& streamToAdd,
+                     const glm::vec3& relativePosition) {
     glm::quat inverseOrientation = glm::inverse(listeningNodeStream.getOrientation());
 
     glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
diff --git a/assignment-client/src/audio/AudioMixerSlave.h b/assignment-client/src/audio/AudioMixerSlave.h
index 3d979da1fc..9765ea8639 100644
--- a/assignment-client/src/audio/AudioMixerSlave.h
+++ b/assignment-client/src/audio/AudioMixerSlave.h
@@ -57,10 +57,13 @@ private:
     bool prepareMix(const SharedNodePointer& listener);
     void addStream(AudioMixerClientData::MixableStream& mixableStream,
                    AvatarAudioStream& listeningNodeStream,
-                   float masterListenerGain, bool isSoloing);
+                   float masterAvatarGain,
+                   float masterInjectorGain,
+                   bool isSoloing);
     void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
                               AvatarAudioStream& listeningNodeStream,
-                              float masterListenerGain);
+                              float masterAvatarGain,
+                              float masterInjectorGain);
     void resetHRTFState(AudioMixerClientData::MixableStream& mixableStream);
 
     void addStreams(Node& listener, AudioMixerClientData& listenerData);

From b15651f1ebcf161fb50253bc9333e906cd27454b Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 15 Mar 2019 12:05:51 -0700
Subject: [PATCH 15/26] Handle InjectorGainSet packet at the audio-mixer

---
 assignment-client/src/audio/AudioMixer.cpp         |  1 +
 .../src/audio/AudioMixerClientData.cpp             | 14 ++++++++++++++
 assignment-client/src/audio/AudioMixerClientData.h |  1 +
 libraries/networking/src/udt/PacketHeaders.h       |  2 +-
 4 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index f67c54239e..201e24d4b9 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -97,6 +97,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
             PacketType::RadiusIgnoreRequest,
             PacketType::RequestsDomainListData,
             PacketType::PerAvatarGainSet,
+            PacketType::InjectorGainSet,
             PacketType::AudioSoloRequest },
             this, "queueAudioPacket");
 
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 90698bfac8..b8d3ec62a6 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -92,6 +92,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
             case PacketType::PerAvatarGainSet:
                 parsePerAvatarGainSet(*packet, node);
                 break;
+            case PacketType::InjectorGainSet:
+                parseInjectorGainSet(*packet, node);
+                break;
             case PacketType::NodeIgnoreRequest:
                 parseNodeIgnoreRequest(packet, node);
                 break;
@@ -205,6 +208,17 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
     }
 }
 
+void AudioMixerClientData::parseInjectorGainSet(ReceivedMessage& message, const SharedNodePointer& node) {
+    QUuid uuid = node->getUUID();
+
+    uint8_t packedGain;
+    message.readPrimitive(&packedGain);
+    float gain = unpackFloatGainFromByte(packedGain);
+
+    setMasterInjectorGain(gain);
+    qCDebug(audio) << "Setting MASTER injector gain for " << uuid << " to " << gain;
+}
+
 void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) {
     auto it = std::find_if(_streams.active.cbegin(), _streams.active.cend(), [nodeID](const MixableStream& mixableStream){
         return mixableStream.nodeStreamID.nodeID == nodeID && mixableStream.nodeStreamID.streamID.isNull();
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index f9d113c53d..4a1ca7f9b5 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -63,6 +63,7 @@ public:
     void negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node);
     void parseRequestsDomainListData(ReceivedMessage& message);
     void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
+    void parseInjectorGainSet(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);
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 0ec7c40ca4..413ff14b17 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -57,7 +57,7 @@ public:
         ICEServerQuery,
         OctreeStats,
         SetAvatarTraits,
-        UNUSED_PACKET_TYPE,
+        InjectorGainSet,
         AssignmentClientStatus,
         NoisyMute,
         AvatarIdentity,

From 755762e8ecf7a7a1d70f21ba6a6c15557f5f2914 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 15 Mar 2019 17:24:50 -0700
Subject: [PATCH 16/26] Send InjectorGainSet packet to the audio-mixer

---
 libraries/networking/src/NodeList.cpp | 25 +++++++++++++++++++++++++
 libraries/networking/src/NodeList.h   |  4 ++++
 2 files changed, 29 insertions(+)

diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index e6eb6087b0..eec710322e 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -265,6 +265,8 @@ void NodeList::reset(bool skipDomainHandlerReset) {
     _avatarGainMap.clear();
     _avatarGainMapLock.unlock();
 
+    _injectorGain = 0.0f;
+
     if (!skipDomainHandlerReset) {
         // clear the domain connection information, unless they're the ones that asked us to reset
         _domainHandler.softReset();
@@ -1087,6 +1089,29 @@ float NodeList::getAvatarGain(const QUuid& nodeID) {
     return 0.0f;
 }
 
+void NodeList::setInjectorGain(float gain) {
+    auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
+    if (audioMixer) {
+        // setup the packet
+        auto setInjectorGainPacket = NLPacket::create(PacketType::InjectorGainSet, sizeof(float), true);
+
+        // We need to convert the gain in dB (from the script) to an amplitude before packing it.
+        setInjectorGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.02059991f)));
+
+        qCDebug(networking) << "Sending Set Injector Gain packet with Gain:" << gain;
+
+        sendPacket(std::move(setInjectorGainPacket), *audioMixer);
+        _injectorGain = gain;
+
+    } else {
+        qWarning() << "Couldn't find audio mixer to send set gain request";
+    }
+}
+
+float NodeList::getInjectorGain() {
+    return _injectorGain;
+}
+
 void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
     // send a request to domain-server to kick the node with the given session ID
     // the domain-server will handle the persistence of the kick (via username or IP)
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index e135bc937d..d2a1212d64 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -83,6 +83,8 @@ public:
     bool isPersonalMutingNode(const QUuid& nodeID) const;
     void setAvatarGain(const QUuid& nodeID, float gain);
     float getAvatarGain(const QUuid& nodeID);
+    void setInjectorGain(float gain);
+    float getInjectorGain();
 
     void kickNodeBySessionID(const QUuid& nodeID);
     void muteNodeBySessionID(const QUuid& nodeID);
@@ -181,6 +183,8 @@ private:
     mutable QReadWriteLock _avatarGainMapLock;
     tbb::concurrent_unordered_map<QUuid, float, UUIDHasher> _avatarGainMap;
 
+    std::atomic<float> _injectorGain { 0.0f };
+
     void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
 #if defined(Q_OS_ANDROID)
     Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false };

From 4a6e495f5fa96eaa4772aaeb615035a050ec3a0f Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Wed, 20 Mar 2019 15:48:05 -0700
Subject: [PATCH 17/26] Add Users.setInjectorGain() and Users.getInjectorGain()
 to the scripting interface

---
 .../script-engine/src/UsersScriptingInterface.cpp |  9 +++++++++
 .../script-engine/src/UsersScriptingInterface.h   | 15 +++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp
index 9beb52f20a..7b30e087e5 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.cpp
+++ b/libraries/script-engine/src/UsersScriptingInterface.cpp
@@ -51,6 +51,15 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
     return DependencyManager::get<NodeList>()->getAvatarGain(nodeID);
 }
 
+void UsersScriptingInterface::setInjectorGain(float gain) {
+    // ask the NodeList to set the audio injector gain
+    DependencyManager::get<NodeList>()->setInjectorGain(gain);
+}
+
+float UsersScriptingInterface::getInjectorGain() {
+    return DependencyManager::get<NodeList>()->getInjectorGain();
+}
+
 void UsersScriptingInterface::kick(const QUuid& nodeID) {
 
     if (_kickConfirmationOperator) {
diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h
index f8ca974b8b..d6750b263d 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.h
+++ b/libraries/script-engine/src/UsersScriptingInterface.h
@@ -97,6 +97,21 @@ public slots:
     */
     float getAvatarGain(const QUuid& nodeID);
 
+    /**jsdoc
+     * Sets the audio injector gain at the server.
+     * Units are Decibels (dB)
+     * @function Users.setInjectorGain
+     * @param {number} gain (in dB)
+    */
+    void setInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the audio injector gain at the server.
+     * @function Users.getInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    float getInjectorGain();
+
     /**jsdoc
      * Kick/ban another user. Removes them from the server and prevents them from returning. Bans by either user name (if 
      * available) or machine fingerprint otherwise. This will only do anything if you're an admin of the domain you're in. 

From a2d261d20ca1273b99ab90952945569baa43a153 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Thu, 21 Mar 2019 11:51:49 -0700
Subject: [PATCH 18/26] Move the new audio volume API from Users scripting
 interface to Audio scripting interface

---
 interface/src/scripting/Audio.cpp             | 36 ++++++++++++++++---
 interface/src/scripting/Audio.h               | 30 ++++++++++++++++
 .../src/UsersScriptingInterface.cpp           |  9 -----
 .../src/UsersScriptingInterface.h             | 15 --------
 4 files changed, 61 insertions(+), 29 deletions(-)

diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index b1b5077e60..f9560c84f7 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -377,6 +377,18 @@ void Audio::handlePushedToTalk(bool enabled) {
     }
 }
 
+void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
+    withWriteLock([&] {
+        _devices.chooseInputDevice(device, isHMD);
+    });
+}
+
+void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
+    withWriteLock([&] {
+        _devices.chooseOutputDevice(device, isHMD);
+    });
+}
+
 void Audio::setReverb(bool enable) {
     withWriteLock([&] {
         DependencyManager::get<AudioClient>()->setReverb(enable);
@@ -389,14 +401,28 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) {
     });
 }
 
-void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
+void Audio::setAvatarGain(float gain) {
     withWriteLock([&] {
-        _devices.chooseInputDevice(device, isHMD);
+        // ask the NodeList to set the master avatar gain
+        DependencyManager::get<NodeList>()->setAvatarGain("", gain);
     });
 }
 
-void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
-    withWriteLock([&] {
-        _devices.chooseOutputDevice(device, isHMD);
+float Audio::getAvatarGain() {
+    return resultWithReadLock<float>([&] {
+        return DependencyManager::get<NodeList>()->getAvatarGain("");
+    });
+}
+
+void Audio::setInjectorGain(float gain) {
+    withWriteLock([&] {
+        // ask the NodeList to set the audio injector gain
+        DependencyManager::get<NodeList>()->setInjectorGain(gain);
+    });
+}
+
+float Audio::getInjectorGain() {
+    return resultWithReadLock<float>([&] {
+        return DependencyManager::get<NodeList>()->getInjectorGain();
     });
 }
diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index 9ee230fc29..14a75d5ffe 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -170,6 +170,36 @@ public:
      */
     Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
 
+    /**jsdoc
+     * Sets the master avatar gain at the server.
+     * Units are Decibels (dB)
+     * @function Audio.setAvatarGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setAvatarGain(float gain);
+
+    /**jsdoc
+     * Gets the master avatar gain at the server.
+     * @function Audio.getAvatarGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getAvatarGain();
+
+    /**jsdoc
+     * Sets the audio injector gain at the server.
+     * Units are Decibels (dB)
+     * @function Audio.setInjectorGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the audio injector gain at the server.
+     * @function Audio.getInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getInjectorGain();
+
     /**jsdoc
      * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
      * @function Audio.startRecording
diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp
index 7b30e087e5..9beb52f20a 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.cpp
+++ b/libraries/script-engine/src/UsersScriptingInterface.cpp
@@ -51,15 +51,6 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
     return DependencyManager::get<NodeList>()->getAvatarGain(nodeID);
 }
 
-void UsersScriptingInterface::setInjectorGain(float gain) {
-    // ask the NodeList to set the audio injector gain
-    DependencyManager::get<NodeList>()->setInjectorGain(gain);
-}
-
-float UsersScriptingInterface::getInjectorGain() {
-    return DependencyManager::get<NodeList>()->getInjectorGain();
-}
-
 void UsersScriptingInterface::kick(const QUuid& nodeID) {
 
     if (_kickConfirmationOperator) {
diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h
index d6750b263d..f8ca974b8b 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.h
+++ b/libraries/script-engine/src/UsersScriptingInterface.h
@@ -97,21 +97,6 @@ public slots:
     */
     float getAvatarGain(const QUuid& nodeID);
 
-    /**jsdoc
-     * Sets the audio injector gain at the server.
-     * Units are Decibels (dB)
-     * @function Users.setInjectorGain
-     * @param {number} gain (in dB)
-    */
-    void setInjectorGain(float gain);
-
-    /**jsdoc
-     * Gets the audio injector gain at the server.
-     * @function Users.getInjectorGain
-     * @returns {number} gain (in dB)
-    */
-    float getInjectorGain();
-
     /**jsdoc
      * Kick/ban another user. Removes them from the server and prevents them from returning. Bans by either user name (if 
      * available) or machine fingerprint otherwise. This will only do anything if you're an admin of the domain you're in. 

From 95b4f954a6a1ed2912a6c47ac2c5c6d86a29d8ea Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 10:12:31 -0700
Subject: [PATCH 19/26] Add AudioClient mixing gains for local injectors and
 system sounds

---
 libraries/audio-client/src/AudioClient.cpp | 4 +++-
 libraries/audio-client/src/AudioClient.h   | 4 ++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 9d645a1dbf..9fa9a0bc18 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -1368,7 +1368,9 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
             memset(_localScratchBuffer, 0, bytesToRead);
             if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
 
-                float gain = options.volume;
+                bool isSystemSound = !injector->isPositionSet() && !injector->isAmbisonic();
+
+                float gain = injector->getVolume() * (isSystemSound ? _systemInjectorGain : _localInjectorGain);
 
                 if (options.ambisonic) {
 
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index a153f22bf3..7608bf5cdb 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -241,6 +241,8 @@ public slots:
     void setInputVolume(float volume, bool emitSignal = true);
     void setReverb(bool reverb);
     void setReverbOptions(const AudioEffectOptions* options);
+    void setLocalInjectorGain(float gain) { _localInjectorGain = gain; };
+    void setSystemInjectorGain(float gain) { _systemInjectorGain = gain; };
 
     void outputNotify();
 
@@ -395,6 +397,8 @@ private:
     int16_t* _outputScratchBuffer { NULL };
 
     // for local audio (used by audio injectors thread)
+    std::atomic<float> _localInjectorGain { 1.0f };
+    std::atomic<float> _systemInjectorGain { 1.0f };
     float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
     int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
     float* _localOutputMixBuffer { NULL };

From 37429a07b8a7a2262582f4b77779d46db3debdc3 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 10:21:54 -0700
Subject: [PATCH 20/26] Add local injector gains to the Audio scripting
 interface

---
 interface/src/scripting/Audio.cpp | 34 ++++++++++++++++++++++++++
 interface/src/scripting/Audio.h   | 40 +++++++++++++++++++++++++++----
 2 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index f9560c84f7..b3c7b25745 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -426,3 +426,37 @@ float Audio::getInjectorGain() {
         return DependencyManager::get<NodeList>()->getInjectorGain();
     });
 }
+
+void Audio::setLocalInjectorGain(float gain) {
+    withWriteLock([&] {
+        if (_localInjectorGain != gain) {
+            _localInjectorGain = gain;
+            // convert dB to amplitude
+            gain = fastExp2f(gain / 6.02059991f);
+            DependencyManager::get<AudioClient>()->setLocalInjectorGain(gain);
+        }
+    });
+}
+
+float Audio::getLocalInjectorGain() {
+    return resultWithReadLock<float>([&] {
+        return _localInjectorGain;
+    });
+}
+
+void Audio::setSystemInjectorGain(float gain) {
+    withWriteLock([&] {
+        if (_systemInjectorGain != gain) {
+            _systemInjectorGain = gain;
+            // convert dB to amplitude
+            gain = fastExp2f(gain / 6.02059991f);
+            DependencyManager::get<AudioClient>()->setSystemInjectorGain(gain);
+        }
+    });
+}
+
+float Audio::getSystemInjectorGain() {
+    return resultWithReadLock<float>([&] {
+        return _systemInjectorGain;
+    });
+}
diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index 14a75d5ffe..d6823ea452 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -171,7 +171,7 @@ public:
     Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
 
     /**jsdoc
-     * Sets the master avatar gain at the server.
+     * Sets the avatar gain at the server.
      * Units are Decibels (dB)
      * @function Audio.setAvatarGain
      * @param {number} gain (in dB)
@@ -179,14 +179,14 @@ public:
     Q_INVOKABLE void setAvatarGain(float gain);
 
     /**jsdoc
-     * Gets the master avatar gain at the server.
+     * Gets the avatar gain at the server.
      * @function Audio.getAvatarGain
      * @returns {number} gain (in dB)
     */
     Q_INVOKABLE float getAvatarGain();
 
     /**jsdoc
-     * Sets the audio injector gain at the server.
+     * Sets the injector gain at the server.
      * Units are Decibels (dB)
      * @function Audio.setInjectorGain
      * @param {number} gain (in dB)
@@ -194,12 +194,42 @@ public:
     Q_INVOKABLE void setInjectorGain(float gain);
 
     /**jsdoc
-     * Gets the audio injector gain at the server.
+     * Gets the injector gain at the server.
      * @function Audio.getInjectorGain
      * @returns {number} gain (in dB)
     */
     Q_INVOKABLE float getInjectorGain();
 
+    /**jsdoc
+     * Sets the local injector gain in the client.
+     * Units are Decibels (dB)
+     * @function Audio.setLocalInjectorGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setLocalInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the local injector gain in the client.
+     * @function Audio.getLocalInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getLocalInjectorGain();
+
+    /**jsdoc
+     * Sets the injector gain for system sounds.
+     * Units are Decibels (dB)
+     * @function Audio.setSystemInjectorGain
+     * @param {number} gain (in dB)
+    */
+    Q_INVOKABLE void setSystemInjectorGain(float gain);
+
+    /**jsdoc
+     * Gets the injector gain for system sounds.
+     * @function Audio.getSystemInjectorGain
+     * @returns {number} gain (in dB)
+    */
+    Q_INVOKABLE float getSystemInjectorGain();
+
     /**jsdoc
      * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
      * @function Audio.startRecording
@@ -380,6 +410,8 @@ private:
 
     float _inputVolume { 1.0f };
     float _inputLevel { 0.0f };
+    float _localInjectorGain { 0.0f };  // in dB
+    float _systemInjectorGain { 0.0f }; // in dB
     bool _isClipping { false };
     bool _enableNoiseReduction { true };  // Match default value of AudioClient::_isNoiseGateEnabled.
     bool _enableWarnWhenMuted { true };

From 155bd39da6339410682a3c2bfd8b1c2bcb16947f Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 10:24:30 -0700
Subject: [PATCH 21/26] Quantize and limit the local injector gains to match
 the network protocol

---
 interface/src/scripting/Audio.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index b3c7b25745..330ed7abfe 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -433,6 +433,8 @@ void Audio::setLocalInjectorGain(float gain) {
             _localInjectorGain = gain;
             // convert dB to amplitude
             gain = fastExp2f(gain / 6.02059991f);
+            // quantize and limit to match NodeList::setInjectorGain()
+            gain = unpackFloatGainFromByte(packFloatGainToByte(gain));
             DependencyManager::get<AudioClient>()->setLocalInjectorGain(gain);
         }
     });
@@ -450,6 +452,8 @@ void Audio::setSystemInjectorGain(float gain) {
             _systemInjectorGain = gain;
             // convert dB to amplitude
             gain = fastExp2f(gain / 6.02059991f);
+            // quantize and limit to match NodeList::setInjectorGain()
+            gain = unpackFloatGainFromByte(packFloatGainToByte(gain));
             DependencyManager::get<AudioClient>()->setSystemInjectorGain(gain);
         }
     });

From c15813b44225235a96fd507d031c6e6650d0eedb Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Fri, 22 Mar 2019 17:58:17 -0700
Subject: [PATCH 22/26] Cleanup

---
 assignment-client/src/audio/AudioMixerClientData.cpp | 6 +++---
 interface/src/scripting/Audio.cpp                    | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index b8d3ec62a6..41b72c04d2 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -200,11 +200,11 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
     if (avatarUUID.isNull()) {
         // set the MASTER avatar gain
         setMasterAvatarGain(gain);
-        qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain;
+        qCDebug(audio) << "Setting MASTER avatar gain for" << uuid << "to" << gain;
     } else {
         // set the per-source avatar gain
         setGainForAvatar(avatarUUID, gain);
-        qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUUID << "] to " << gain;
+        qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUUID << "] to" << gain;
     }
 }
 
@@ -216,7 +216,7 @@ void AudioMixerClientData::parseInjectorGainSet(ReceivedMessage& message, const
     float gain = unpackFloatGainFromByte(packedGain);
 
     setMasterInjectorGain(gain);
-    qCDebug(audio) << "Setting MASTER injector gain for " << uuid << " to " << gain;
+    qCDebug(audio) << "Setting MASTER injector gain for" << uuid << "to" << gain;
 }
 
 void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) {
diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index 330ed7abfe..4f2171d451 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -404,13 +404,13 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) {
 void Audio::setAvatarGain(float gain) {
     withWriteLock([&] {
         // ask the NodeList to set the master avatar gain
-        DependencyManager::get<NodeList>()->setAvatarGain("", gain);
+        DependencyManager::get<NodeList>()->setAvatarGain(QUuid(), gain);
     });
 }
 
 float Audio::getAvatarGain() {
     return resultWithReadLock<float>([&] {
-        return DependencyManager::get<NodeList>()->getAvatarGain("");
+        return DependencyManager::get<NodeList>()->getAvatarGain(QUuid());
     });
 }
 

From 3d7c3e7b6f6fe3d737cc86e038966617397258f5 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 23 Mar 2019 06:48:37 -0700
Subject: [PATCH 23/26] Persist the audio-mixer settings across domain changes
 and server resets

---
 libraries/networking/src/NodeList.cpp | 38 ++++++++++++++++++---------
 libraries/networking/src/NodeList.h   |  3 ++-
 2 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index eec710322e..0021a594bc 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -265,8 +265,6 @@ void NodeList::reset(bool skipDomainHandlerReset) {
     _avatarGainMap.clear();
     _avatarGainMapLock.unlock();
 
-    _injectorGain = 0.0f;
-
     if (!skipDomainHandlerReset) {
         // clear the domain connection information, unless they're the ones that asked us to reset
         _domainHandler.softReset();
@@ -1018,6 +1016,14 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
 
         // also send them the current ignore radius state.
         sendIgnoreRadiusStateToNode(newNode);
+
+        // also send the current avatar and injector gains
+        if (_avatarGain != 0.0f) {
+            setAvatarGain(QUuid(), _avatarGain);
+        }
+        if (_injectorGain != 0.0f) {
+            setInjectorGain(_injectorGain);
+        }
     }
     if (newNode->getType() == NodeType::AvatarMixer) {
         // this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session,
@@ -1064,13 +1070,17 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
 
             if (nodeID.isNull()) {
                 qCDebug(networking) << "Sending Set MASTER Avatar Gain packet with Gain:" << gain;
-            } else {
-                qCDebug(networking) << "Sending Set Avatar Gain packet with UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
-            }
 
-            sendPacket(std::move(setAvatarGainPacket), *audioMixer);
-            QWriteLocker lock{ &_avatarGainMapLock };
-            _avatarGainMap[nodeID] = gain;
+                sendPacket(std::move(setAvatarGainPacket), *audioMixer);
+                _avatarGain = gain;
+
+            } else {
+                qCDebug(networking) << "Sending Set Avatar Gain packet with UUID:" << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
+
+                sendPacket(std::move(setAvatarGainPacket), *audioMixer);
+                QWriteLocker lock{ &_avatarGainMapLock };
+                _avatarGainMap[nodeID] = gain;
+            }
 
         } else {
             qWarning() << "Couldn't find audio mixer to send set gain request";
@@ -1081,10 +1091,14 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
 }
 
 float NodeList::getAvatarGain(const QUuid& nodeID) {
-    QReadLocker lock{ &_avatarGainMapLock };
-    auto it = _avatarGainMap.find(nodeID);
-    if (it != _avatarGainMap.cend()) {
-        return it->second;
+    if (nodeID.isNull()) {
+        return _avatarGain;
+    } else {
+        QReadLocker lock{ &_avatarGainMapLock };
+        auto it = _avatarGainMap.find(nodeID);
+        if (it != _avatarGainMap.cend()) {
+            return it->second;
+        }
     }
     return 0.0f;
 }
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index d2a1212d64..f871560fba 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -183,7 +183,8 @@ private:
     mutable QReadWriteLock _avatarGainMapLock;
     tbb::concurrent_unordered_map<QUuid, float, UUIDHasher> _avatarGainMap;
 
-    std::atomic<float> _injectorGain { 0.0f };
+    std::atomic<float> _avatarGain { 0.0f };    // in dB
+    std::atomic<float> _injectorGain { 0.0f };  // in dB
 
     void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
 #if defined(Q_OS_ANDROID)

From 7b56bef83815b230cda13711165f60e15c7bfd58 Mon Sep 17 00:00:00 2001
From: Ken Cooke <ken@highfidelity.io>
Date: Sat, 23 Mar 2019 16:00:02 -0700
Subject: [PATCH 24/26] Prototype an updated Audio tab with 3 independent
 volume controls

---
 interface/resources/qml/hifi/audio/Audio.qml  | 158 ++++++++++++++++--
 .../qml/hifi/audio/LoopbackAudio.qml          |  16 +-
 .../qml/hifi/audio/PlaySampleSound.qml        |  16 +-
 3 files changed, 158 insertions(+), 32 deletions(-)

diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index cd0f290da4..8fdd0368e2 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -87,8 +87,19 @@ Rectangle {
     }
 
     function updateMyAvatarGainFromQML(sliderValue, isReleased) {
-        if (Users.getAvatarGain(myAvatarUuid) != sliderValue) {
-            Users.setAvatarGain(myAvatarUuid, sliderValue);
+        if (AudioScriptingInterface.getAvatarGain() != sliderValue) {
+            AudioScriptingInterface.setAvatarGain(sliderValue);
+        }
+    }
+    function updateInjectorGainFromQML(sliderValue, isReleased) {
+        if (AudioScriptingInterface.getInjectorGain() != sliderValue) {
+            AudioScriptingInterface.setInjectorGain(sliderValue);       // server side
+            AudioScriptingInterface.setLocalInjectorGain(sliderValue);  // client side
+        }
+    }
+    function updateSystemInjectorGainFromQML(sliderValue, isReleased) {
+        if (AudioScriptingInterface.getSystemInjectorGain() != sliderValue) {
+            AudioScriptingInterface.setSystemInjectorGain(sliderValue);
         }
     }
 
@@ -334,6 +345,7 @@ Rectangle {
                 color: hifi.colors.white;
                 text: qsTr("Choose input device");
             }
+
         }
 
         ListView {
@@ -462,22 +474,22 @@ Rectangle {
         }
 
         Item {
-            id: gainContainer
+            id: avatarGainContainer
             x: margins.paddings;
             anchors.top: outputView.bottom;
             anchors.topMargin: 10;
             width: parent.width - margins.paddings*2
-            height: gainSliderTextMetrics.height
+            height: avatarGainSliderTextMetrics.height
 
             HifiControlsUit.Slider {
-                id: gainSlider
+                id: avatarGainSlider
                 anchors.right: parent.right
                 height: parent.height
                 width: 200
                 minimumValue: -60.0
                 maximumValue: 20.0
                 stepSize: 5
-                value: Users.getAvatarGain(myAvatarUuid)
+                value: AudioScriptingInterface.getAvatarGain()
                 onValueChanged: {
                     updateMyAvatarGainFromQML(value, false);
                 }
@@ -493,7 +505,7 @@ Rectangle {
                         // Do nothing.
                     }
                     onDoubleClicked: {
-                        gainSlider.value = 0.0
+                        avatarGainSlider.value = 0.0
                     }
                     onPressed: {
                         // Pass through to Slider
@@ -507,13 +519,13 @@ Rectangle {
                 }
             }
             TextMetrics {
-                id: gainSliderTextMetrics
-                text: gainSliderText.text
-                font: gainSliderText.font
+                id: avatarGainSliderTextMetrics
+                text: avatarGainSliderText.text
+                font: avatarGainSliderText.font
             }
             RalewayRegular {
                 // The slider for my card is special, it controls the master gain
-                id: gainSliderText;
+                id: avatarGainSliderText;
                 text: "Avatar volume";
                 size: 16;
                 anchors.left: parent.left;
@@ -523,15 +535,129 @@ Rectangle {
             }
         }
 
+        Item {
+            id: injectorGainContainer
+            x: margins.paddings;
+            width: parent.width - margins.paddings*2
+            height: injectorGainSliderTextMetrics.height
+
+            HifiControlsUit.Slider {
+                id: injectorGainSlider
+                anchors.right: parent.right
+                height: parent.height
+                width: 200
+                minimumValue: -60.0
+                maximumValue: 20.0
+                stepSize: 5
+                value: AudioScriptingInterface.getInjectorGain()
+                onValueChanged: {
+                    updateInjectorGainFromQML(value, false);
+                }
+                onPressedChanged: {
+                    if (!pressed) {
+                        updateInjectorGainFromQML(value, false);
+                    }
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    onWheel: {
+                        // Do nothing.
+                    }
+                    onDoubleClicked: {
+                        injectorGainSlider.value = 0.0
+                    }
+                    onPressed: {
+                        // Pass through to Slider
+                        mouse.accepted = false
+                    }
+                    onReleased: {
+                        // the above mouse.accepted seems to make this
+                        // never get called, nonetheless...
+                        mouse.accepted = false
+                    }
+                }
+            }
+            TextMetrics {
+                id: injectorGainSliderTextMetrics
+                text: injectorGainSliderText.text
+                font: injectorGainSliderText.font
+            }
+            RalewayRegular {
+                id: injectorGainSliderText;
+                text: "Environment volume";
+                size: 16;
+                anchors.left: parent.left;
+                color: hifi.colors.white;
+                horizontalAlignment: Text.AlignLeft;
+                verticalAlignment: Text.AlignTop;
+            }
+        }
+
+        Item {
+            id: systemInjectorGainContainer
+            x: margins.paddings;
+            width: parent.width - margins.paddings*2
+            height: systemInjectorGainSliderTextMetrics.height
+
+            HifiControlsUit.Slider {
+                id: systemInjectorGainSlider
+                anchors.right: parent.right
+                height: parent.height
+                width: 200
+                minimumValue: -60.0
+                maximumValue: 20.0
+                stepSize: 5
+                value: AudioScriptingInterface.getSystemInjectorGain()
+                onValueChanged: {
+                    updateSystemInjectorGainFromQML(value, false);
+                }
+                onPressedChanged: {
+                    if (!pressed) {
+                        updateSystemInjectorGainFromQML(value, false);
+                    }
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    onWheel: {
+                        // Do nothing.
+                    }
+                    onDoubleClicked: {
+                        systemInjectorGainSlider.value = 0.0
+                    }
+                    onPressed: {
+                        // Pass through to Slider
+                        mouse.accepted = false
+                    }
+                    onReleased: {
+                        // the above mouse.accepted seems to make this
+                        // never get called, nonetheless...
+                        mouse.accepted = false
+                    }
+                }
+            }
+            TextMetrics {
+                id: systemInjectorGainSliderTextMetrics
+                text: systemInjectorGainSliderText.text
+                font: systemInjectorGainSliderText.font
+            }
+            RalewayRegular {
+                id: systemInjectorGainSliderText;
+                text: "System Sound volume";
+                size: 16;
+                anchors.left: parent.left;
+                color: hifi.colors.white;
+                horizontalAlignment: Text.AlignLeft;
+                verticalAlignment: Text.AlignTop;
+            }
+        }
+
         AudioControls.PlaySampleSound {
             id: playSampleSound
             x: margins.paddings
-            anchors.top: gainContainer.bottom;
+            anchors.top: systemInjectorGainContainer.bottom;
             anchors.topMargin: 10;
-
-            visible: (bar.currentIndex === 1 && isVR) ||
-                     (bar.currentIndex === 0 && !isVR);
-            anchors { left: parent.left; leftMargin: margins.paddings }
         }
     }
 }
diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
index 8ec0ffc496..74bc0f67dc 100644
--- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
+++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
@@ -44,7 +44,7 @@ RowLayout {
     }
 
     HifiControlsUit.Button {
-        text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE");
+        text: audioLoopedBack ? qsTr("STOP TESTING") : qsTr("TEST YOUR VOICE");
         color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue;
         onClicked: {
             if (audioLoopedBack) {
@@ -57,11 +57,11 @@ RowLayout {
         }
     }
 
-    RalewayRegular {
-        Layout.leftMargin: 2;
-        size: 14;
-        color: "white";
-        font.italic: true
-        text: audioLoopedBack ? qsTr("Speak in your input") : "";
-    }
+//    RalewayRegular {
+//        Layout.leftMargin: 2;
+//        size: 14;
+//        color: "white";
+//        font.italic: true
+//        text: audioLoopedBack ? qsTr("Speak in your input") : "";
+//    }
 }
diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
index b9d9727dab..0eb78f3efe 100644
--- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml
+++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
@@ -56,16 +56,16 @@ RowLayout {
     HifiConstants { id: hifi; }
 
     HifiControlsUit.Button {
-        text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND");
+        text: isPlaying ? qsTr("STOP TESTING") : qsTr("TEST YOUR SOUND");
         color: isPlaying ? hifi.buttons.red : hifi.buttons.blue;
         onClicked: isPlaying ? stopSound() : playSound();
     }
 
-    RalewayRegular {
-        Layout.leftMargin: 2;
-        size: 14;
-        color: "white";
-        font.italic: true
-        text: isPlaying ? qsTr("Listen to your output") : "";
-    }
+//    RalewayRegular {
+//        Layout.leftMargin: 2;
+//        size: 14;
+//        color: "white";
+//        font.italic: true
+//        text: isPlaying ? qsTr("Listen to your output") : "";
+//    }
 }

From a2d754cebd786741ec7d714576d4462ee3618177 Mon Sep 17 00:00:00 2001
From: Wayne Chen <chen.wayne@outlook.com>
Date: Tue, 26 Mar 2019 10:26:29 -0700
Subject: [PATCH 25/26] fixing audio screen with master

---
 interface/resources/qml/hifi/audio/Audio.qml  |  4 ++++
 .../qml/hifi/audio/LoopbackAudio.qml          | 19 +++++++++++--------
 .../qml/hifi/audio/PlaySampleSound.qml        | 17 ++++++++++-------
 3 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index 8fdd0368e2..fe86d7d930 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -540,6 +540,8 @@ Rectangle {
             x: margins.paddings;
             width: parent.width - margins.paddings*2
             height: injectorGainSliderTextMetrics.height
+            anchors.top: avatarGainContainer.bottom;
+            anchors.topMargin: 10;
 
             HifiControlsUit.Slider {
                 id: injectorGainSlider
@@ -599,6 +601,8 @@ Rectangle {
             x: margins.paddings;
             width: parent.width - margins.paddings*2
             height: systemInjectorGainSliderTextMetrics.height
+            anchors.top: injectorGainContainer.bottom;
+            anchors.topMargin: 10;
 
             HifiControlsUit.Slider {
                 id: systemInjectorGainSlider
diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
index 74bc0f67dc..8d1099d38c 100644
--- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml
+++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml
@@ -44,8 +44,11 @@ RowLayout {
     }
 
     HifiControlsUit.Button {
-        text: audioLoopedBack ? qsTr("STOP TESTING") : qsTr("TEST YOUR VOICE");
+        text: audioLoopedBack ? qsTr("STOP TESTING VOICE") : qsTr("TEST YOUR VOICE");
         color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue;
+        fontSize: 15;
+        width: 200;
+        height: 32;
         onClicked: {
             if (audioLoopedBack) {
                 loopbackTimer.stop();
@@ -57,11 +60,11 @@ RowLayout {
         }
     }
 
-//    RalewayRegular {
-//        Layout.leftMargin: 2;
-//        size: 14;
-//        color: "white";
-//        font.italic: true
-//        text: audioLoopedBack ? qsTr("Speak in your input") : "";
-//    }
+    RalewayRegular {
+        Layout.leftMargin: 2;
+        size: 14;
+        color: "white";
+        font.italic: true
+        text: audioLoopedBack ? qsTr("Speak in your input") : "";
+    }
 }
diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
index 0eb78f3efe..4675f6087a 100644
--- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml
+++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml
@@ -59,13 +59,16 @@ RowLayout {
         text: isPlaying ? qsTr("STOP TESTING") : qsTr("TEST YOUR SOUND");
         color: isPlaying ? hifi.buttons.red : hifi.buttons.blue;
         onClicked: isPlaying ? stopSound() : playSound();
+        fontSize: 15;
+        width: 200;
+        height: 32;
     }
 
-//    RalewayRegular {
-//        Layout.leftMargin: 2;
-//        size: 14;
-//        color: "white";
-//        font.italic: true
-//        text: isPlaying ? qsTr("Listen to your output") : "";
-//    }
+    RalewayRegular {
+        Layout.leftMargin: 2;
+        size: 14;
+        color: "white";
+        font.italic: true
+        text: isPlaying ? qsTr("Listen to your output") : "";
+    }
 }

From c0b71150eafa60947b5a1390ac8c18795cb25dd8 Mon Sep 17 00:00:00 2001
From: Wayne Chen <chen.wayne@outlook.com>
Date: Tue, 26 Mar 2019 10:58:04 -0700
Subject: [PATCH 26/26] removing an extra button

---
 interface/resources/qml/hifi/audio/Audio.qml | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index 69cbad458b..195dd78a0e 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -438,14 +438,6 @@ Rectangle {
                 color: hifi.colors.white;
                 text: qsTr("Choose output device");
             }
-
-            AudioControls.PlaySampleSound {
-                x: margins.paddings
-
-                visible: (bar.currentIndex === 1 && isVR) ||
-                         (bar.currentIndex === 0 && !isVR);
-                anchors { right: parent.right }
-            }
         }
 
         ListView {