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..41b72c04d2 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; @@ -197,14 +200,25 @@ 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; } } +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 653749f619..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 message, const SharedNodePointer& node); void parseRadiusIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); void parseSoloRequest(QSharedPointer message, const SharedNodePointer& node); @@ -84,6 +85,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 +192,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(&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(&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); diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index cd0f290da4..195dd78a0e 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); } } @@ -382,6 +393,7 @@ Rectangle { } } } + AudioControls.LoopbackAudio { id: loopbackAudio x: margins.paddings @@ -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,133 @@ Rectangle { } } + Item { + id: injectorGainContainer + x: margins.paddings; + width: parent.width - margins.paddings*2 + height: injectorGainSliderTextMetrics.height + anchors.top: avatarGainContainer.bottom; + anchors.topMargin: 10; + + 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 + anchors.top: injectorGainContainer.bottom; + anchors.topMargin: 10; + + 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 578d222eb2..db0614dfdc 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 YOUR VOICE") : 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 b9d9727dab..033af99d04 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -56,16 +56,19 @@ 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(); + 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") : ""; +// } } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b1b5077e60..4f2171d451 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()->setReverb(enable); @@ -389,14 +401,66 @@ 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()->setAvatarGain(QUuid(), gain); }); } -void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) { - withWriteLock([&] { - _devices.chooseOutputDevice(device, isHMD); +float Audio::getAvatarGain() { + return resultWithReadLock([&] { + return DependencyManager::get()->getAvatarGain(QUuid()); + }); +} + +void Audio::setInjectorGain(float gain) { + withWriteLock([&] { + // ask the NodeList to set the audio injector gain + DependencyManager::get()->setInjectorGain(gain); + }); +} + +float Audio::getInjectorGain() { + return resultWithReadLock([&] { + return DependencyManager::get()->getInjectorGain(); + }); +} + +void Audio::setLocalInjectorGain(float gain) { + withWriteLock([&] { + if (_localInjectorGain != 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()->setLocalInjectorGain(gain); + } + }); +} + +float Audio::getLocalInjectorGain() { + return resultWithReadLock([&] { + return _localInjectorGain; + }); +} + +void Audio::setSystemInjectorGain(float gain) { + withWriteLock([&] { + if (_systemInjectorGain != 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()->setSystemInjectorGain(gain); + } + }); +} + +float Audio::getSystemInjectorGain() { + return resultWithReadLock([&] { + return _systemInjectorGain; }); } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 9ee230fc29..d6823ea452 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -170,6 +170,66 @@ public: */ Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); + /**jsdoc + * Sets the 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 avatar gain at the server. + * @function Audio.getAvatarGain + * @returns {number} gain (in dB) + */ + Q_INVOKABLE float getAvatarGain(); + + /**jsdoc + * Sets the 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 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 @@ -350,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 }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 0bb7256bf6..c537fea646 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 = !options.positionSet && !options.ambisonic; + + float gain = options.volume * (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 _localInjectorGain { 1.0f }; + std::atomic _systemInjectorGain { 1.0f }; float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; float* _localOutputMixBuffer { NULL }; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index e6eb6087b0..0021a594bc 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -1016,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, @@ -1062,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"; @@ -1079,14 +1091,41 @@ 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; } +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..f871560fba 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,9 @@ private: mutable QReadWriteLock _avatarGainMapLock; tbb::concurrent_unordered_map _avatarGainMap; + std::atomic _avatarGain { 0.0f }; // in dB + std::atomic _injectorGain { 0.0f }; // in dB + void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode); #if defined(Q_OS_ANDROID) Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; 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,