diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..34ae64aee8 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -159,6 +159,19 @@ Rectangle { onXChanged: rightMostInputLevelPos = x + width } } + + RowLayout { + spacing: muteMic.spacing*2; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Warn when muted"); + checked: AudioScriptingInterface.warnWhenMuted; + onClicked: { + AudioScriptingInterface.warnWhenMuted = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + } + } + } } Separator {} diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index bb40f69b0b..4a4b3c146b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -25,6 +25,7 @@ QString Audio::DESKTOP { "Desktop" }; QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; +Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true }; Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false }; @@ -39,10 +40,12 @@ Audio::Audio() : _devices(_contextIsHMD) { auto client = DependencyManager::get().data(); connect(client, &AudioClient::muteToggled, this, &Audio::setMuted); connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction); + connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted); connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); + enableWarnWhenMuted(enableWarnWhenMutedSetting.get()); onContextChanged(); setMuted(mutedSetting.get()); } @@ -109,6 +112,28 @@ void Audio::enableNoiseReduction(bool enable) { } } +bool Audio::warnWhenMutedEnabled() const { + return resultWithReadLock([&] { + return _enableWarnWhenMuted; + }); +} + +void Audio::enableWarnWhenMuted(bool enable) { + bool changed = false; + withWriteLock([&] { + if (_enableWarnWhenMuted != enable) { + _enableWarnWhenMuted = enable; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false)); + enableWarnWhenMutedSetting.set(enable); + changed = true; + } + }); + if (changed) { + emit warnWhenMutedChanged(enable); + } +} + float Audio::getInputVolume() const { return resultWithReadLock([&] { return _inputVolume; diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..7e216eb0b2 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -58,6 +58,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) + Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) @@ -75,6 +76,7 @@ public: bool isMuted() const; bool noiseReductionEnabled() const; + bool warnWhenMutedEnabled() const; float getInputVolume() const; float getInputLevel() const; bool isClipping() const; @@ -192,7 +194,7 @@ signals: * }); */ void mutedChanged(bool isMuted); - + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -201,6 +203,14 @@ signals: */ void noiseReductionChanged(bool isEnabled); + /**jsdoc + * Triggered when "warn when muted" is enabled or disabled. + * @function Audio.warnWhenMutedChanged + * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false. + * @returns {Signal} + */ + void warnWhenMutedChanged(bool isEnabled); + /**jsdoc * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged @@ -248,6 +258,7 @@ public slots: private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); + void enableWarnWhenMuted(bool enable); void setInputVolume(float volume); void onInputLoudnessChanged(float loudness, bool isClipping); @@ -262,6 +273,7 @@ private: bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. + bool _enableWarnWhenMuted { true }; bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 8c50a195ee..1c10d24f23 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1531,6 +1531,14 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) { } } +void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) { + if (_warnWhenMuted != enable) { + _warnWhenMuted = enable; + if (emitSignal) { + emit warnWhenMutedChanged(_warnWhenMuted); + } + } +} bool AudioClient::setIsStereoInput(bool isStereoInput) { bool stereoInputChanged = false; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 29036b7c71..6d3483b0f8 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -210,6 +210,9 @@ public slots: void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } + void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); + bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } + bool getLocalEcho() { return _shouldEchoLocally; } void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } @@ -246,6 +249,7 @@ signals: void inputVolumeChanged(float volume); void muteToggled(bool muted); void noiseReductionChanged(bool noiseReductionEnabled); + void warnWhenMutedChanged(bool warnWhenMutedEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); void inputLoudnessChanged(float loudness, bool isClipping); @@ -365,6 +369,7 @@ private: bool _shouldEchoLocally; bool _shouldEchoToServer; bool _isNoiseGateEnabled; + bool _warnWhenMuted; bool _reverb; AudioEffectOptions _scriptReverbOptions; diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 14ac96c8c6..d759b7d885 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -17,68 +17,104 @@ (function() { // BEGIN LOCAL_SCOPE - var lastInputLoudness = 0.0; - var sampleRate = 8.0; // Hz - var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack - var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release - var holdReset = 2.0 * sampleRate; // 2 seconds hold - var holdCount = 0; - var warningOverlayID = null; + var lastInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; + var pollInterval = null; + var warningText = "Muted"; + var textDimensions = { x: 100, y: 50 }; - function showWarning() { - if (warningOverlayID) { - return; - } - warningOverlayID = Overlays.addOverlay("text3d", { - name: "Muted-Warning", - localPosition: { x: 0.2, y: -0.35, z: -1.0 }, - localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), - text: "Warning: you are muted", - textAlpha: 1, - color: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - lineHeight: 0.042, - visible: true, - ignoreRayIntersection: true, - drawInFront: true, - grabbable: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") - }); - }; + function showWarning() { + if (warningOverlayID) { + return; + } - function hideWarning() { - if (!warningOverlayID) { - return; - } - Overlays.deleteOverlay(warningOverlayID); - warningOverlayID = null; - } + var windowWidth; + var windowHeight; + if (HMD.active) { + var viewportDimension = Controller.getViewportDimensions(); + windowWidth = viewportDimension.x; + windowHeight = viewportDimension.y; + } else { + windowWidth = Window.innerWidth; + windowHeight = Window.innerHeight; + } - function cleanup() { - Overlays.deleteOverlay(warningOverlayID); - } + warningOverlayID = Overlays.addOverlay("text", { + name: "Muted-Warning", + font: { size: 36 }, + text: warningText, + x: windowWidth / 2 - textDimensions.x / 2, + y: windowHeight / 2 - textDimensions.y / 2, + width: textDimensions.x, + height: textDimensions.y, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + visible: true + }); + } - Script.scriptEnding.connect(cleanup); + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } - Script.setInterval(function() { + function startPoll() { + if (pollInterval) { + return; + } + pollInterval = Script.setInterval(function() { + var inputLoudness = Audio.inputLevel; + var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; + inputLoudness += tc * (lastInputLoudness - inputLoudness); + lastInputLoudness = inputLoudness; - var inputLoudness = Audio.inputLevel; - var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; - inputLoudness += tc * (lastInputLoudness - inputLoudness); - lastInputLoudness = inputLoudness; + if (inputLoudness > 0.1) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } - if (Audio.muted && inputLoudness > 0.3) { - holdCount = holdReset; - } else { - holdCount = Math.max(holdCount - 1, 0); - } + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); + } - if (holdCount > 0) { - showWarning(); - } else { - hideWarning(); - } - }, 1000.0 / sampleRate); + function stopPoll() { + if (!pollInterval) { + return; + } + Script.clearInterval(pollInterval); + pollInterval = null; + hideWarning(); + } + + function startOrStopPoll() { + if (Audio.warnWhenMuted && Audio.muted) { + startPoll(); + } else { + stopPoll(); + } + } + + function cleanup() { + stopPoll(); + } + + Script.scriptEnding.connect(cleanup); + + startOrStopPoll(); + Audio.mutedChanged.connect(startOrStopPoll); + Audio.warnWhenMutedChanged.connect(startOrStopPoll); }()); // END LOCAL_SCOPE