From cfaa841746ac97e3ef9f49ca0ccf087b98d4ae85 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 5 Feb 2019 12:34:42 -0800 Subject: [PATCH 01/10] Add output silence-detection to the noise gate processing --- libraries/audio/src/AudioGate.cpp | 108 +++++++++++++++++++++--------- libraries/audio/src/AudioGate.h | 9 ++- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index e9cdf832d2..0df46ac532 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -138,8 +138,8 @@ public: int32_t hysteresis(int32_t peak); int32_t envelope(int32_t attn); - virtual void process(int16_t* input, int16_t* output, int numFrames) = 0; - virtual void removeDC(int16_t* input, int16_t* output, int numFrames) = 0; + virtual bool process(int16_t* input, int16_t* output, int numFrames) = 0; + virtual bool removeDC(int16_t* input, int16_t* output, int numFrames) = 0; }; GateImpl::GateImpl(int sampleRate) { @@ -403,14 +403,15 @@ public: GateMono(int sampleRate) : GateImpl(sampleRate) {} // mono input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateMono::process(int16_t* input, int16_t* output, int numFrames) { +bool GateMono::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -453,15 +454,21 @@ void GateMono::process(int16_t* input, int16_t* output, int numFrames) { x = MULQ31(x, attn); // store 16-bit output - output[n] = (int16_t)saturateQ30(x); + x = saturateQ30(x); + output[n] = (int16_t)x; + + mask |= x; } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -471,8 +478,13 @@ void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x); // store 16-bit output - output[n] = (int16_t)saturateQ30(x); + x = saturateQ30(x); + output[n] = (int16_t)x; + + mask |= x; } + + return mask != 0; } // @@ -489,14 +501,15 @@ public: GateStereo(int sampleRate) : GateImpl(sampleRate) {} // interleaved stereo input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { +bool GateStereo::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -541,16 +554,23 @@ void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { x1 = MULQ31(x1, attn); // store 16-bit output - output[2*n+0] = (int16_t)saturateQ30(x0); - output[2*n+1] = (int16_t)saturateQ30(x1); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + output[2*n+0] = (int16_t)x0; + output[2*n+1] = (int16_t)x1; + + mask |= (x0 | x1); } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -561,9 +581,15 @@ void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x0, x1); // store 16-bit output - output[2*n+0] = (int16_t)saturateQ30(x0); - output[2*n+1] = (int16_t)saturateQ30(x1); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + output[2*n+0] = (int16_t)x0; + output[2*n+1] = (int16_t)x1; + + mask |= (x0 | x1); } + + return mask != 0; } // @@ -580,14 +606,15 @@ public: GateQuad(int sampleRate) : GateImpl(sampleRate) {} // interleaved quad input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { +bool GateQuad::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -636,18 +663,27 @@ void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { x3 = MULQ31(x3, attn); // store 16-bit output - output[4*n+0] = (int16_t)saturateQ30(x0); - output[4*n+1] = (int16_t)saturateQ30(x1); - output[4*n+2] = (int16_t)saturateQ30(x2); - output[4*n+3] = (int16_t)saturateQ30(x3); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + x2 = saturateQ30(x2); + x3 = saturateQ30(x3); + output[4*n+0] = (int16_t)x0; + output[4*n+1] = (int16_t)x1; + output[4*n+2] = (int16_t)x2; + output[4*n+3] = (int16_t)x3; + + mask |= (x0 | x1 | x2 | x3); } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -660,11 +696,19 @@ void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x0, x1, x2, x3); // store 16-bit output - output[4*n+0] = (int16_t)saturateQ30(x0); - output[4*n+1] = (int16_t)saturateQ30(x1); - output[4*n+2] = (int16_t)saturateQ30(x2); - output[4*n+3] = (int16_t)saturateQ30(x3); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + x2 = saturateQ30(x2); + x3 = saturateQ30(x3); + output[4*n+0] = (int16_t)x0; + output[4*n+1] = (int16_t)x1; + output[4*n+2] = (int16_t)x2; + output[4*n+3] = (int16_t)x3; + + mask |= (x0 | x1 | x2 | x3); } + + return mask != 0; } // @@ -721,12 +765,12 @@ AudioGate::~AudioGate() { delete _impl; } -void AudioGate::render(int16_t* input, int16_t* output, int numFrames) { - _impl->process(input, output, numFrames); +bool AudioGate::render(int16_t* input, int16_t* output, int numFrames) { + return _impl->process(input, output, numFrames); } -void AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { - _impl->removeDC(input, output, numFrames); +bool AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { + return _impl->removeDC(input, output, numFrames); } void AudioGate::setThreshold(float threshold) { diff --git a/libraries/audio/src/AudioGate.h b/libraries/audio/src/AudioGate.h index d4ae3c5fe8..6fc7ca83df 100644 --- a/libraries/audio/src/AudioGate.h +++ b/libraries/audio/src/AudioGate.h @@ -18,9 +18,12 @@ public: AudioGate(int sampleRate, int numChannels); ~AudioGate(); - // interleaved int16_t input/output (in-place is allowed) - void render(int16_t* input, int16_t* output, int numFrames); - void removeDC(int16_t* input, int16_t* output, int numFrames); + // + // Process interleaved int16_t input/output (in-place is allowed). + // Returns true when output is non-zero. + // + bool render(int16_t* input, int16_t* output, int numFrames); + bool removeDC(int16_t* input, int16_t* output, int numFrames); void setThreshold(float threshold); void setRelease(float release); From f9afe6fe43e4885baefa3a0dd466a7525156ff5b Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 5 Feb 2019 16:39:03 -0800 Subject: [PATCH 02/10] Detect loudness and clipping on the raw audio input --- libraries/audio-client/src/AudioClient.cpp | 61 +++++++++++----------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index b2cba2351f..e738b326e6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1075,45 +1075,25 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { void AudioClient::handleAudioInput(QByteArray& audioBuffer) { if (!_audioPaused) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { + + bool audioGateOpen = false; + + if (!_muted) { int16_t* samples = reinterpret_cast(audioBuffer.data()); int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); if (_isNoiseGateEnabled) { // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); + audioGateOpen = _audioGate->render(samples, samples, numFrames); } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { - _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + audioGateOpen = _audioGate->removeDC(samples, samples, numFrames); } emit inputReceived(audioBuffer); } - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); + // detect gate opening and closing bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed _audioGateOpen = audioGateOpen; @@ -1186,10 +1166,29 @@ void AudioClient::handleMicAudioInput() { static int16_t networkAudioSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) { - if (_muted) { - _inputRingBuffer.shiftReadPosition(inputSamplesRequired); - } else { - _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); + + _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); + + // detect loudness and clipping on the raw input + int32_t loudness = 0; + bool didClip = false; + for (int i = 0; i < inputSamplesRequired; ++i) { + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)inputAudioSamples.get()[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); + } + _lastInputLoudness = (float)loudness / inputSamplesRequired; + + if (didClip) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; + } + + emit inputLoudnessChanged(_lastInputLoudness); + + if (!_muted) { possibleResampling(_inputToNetworkResampler, inputAudioSamples.get(), networkAudioSamples, inputSamplesRequired, numNetworkSamples, From 918e2fc7ab7a3f665ef8d34903602b8a6f7a00f4 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 6 Feb 2019 16:08:30 -0800 Subject: [PATCH 03/10] Proper detection of digital clipping on the unprocessed audio input. Triggered when 3+ consecutive samples > -0.1 dBFS. --- libraries/audio-client/src/AudioClient.cpp | 64 ++++++++++++++++++---- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e738b326e6..cd1363a84d 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -170,6 +170,57 @@ static void channelDownmix(int16_t* source, int16_t* dest, int numSamples) { } } +static float computeLoudness(int16_t* samples, int numSamples, int numChannels, bool& isClipping) { + + const int32_t CLIPPING_THRESHOLD = 32392; // -0.1 dBFS + const int32_t CLIPPING_DETECTION = 3; // consecutive samples over threshold + + float scale = numSamples ? 1.0f / numSamples : 0.0f; + + int32_t loudness = 0; + isClipping = false; + + if (numChannels == 2) { + int32_t oversLeft = 0; + int32_t oversRight = 0; + + for (int i = 0; i < numSamples/2; i++) { + int32_t left = std::abs((int32_t)samples[2*i+0]); + int32_t right = std::abs((int32_t)samples[2*i+1]); + + loudness += left; + loudness += right; + + if (left > CLIPPING_THRESHOLD) { + isClipping |= (++oversLeft >= CLIPPING_DETECTION); + } else { + oversLeft = 0; + } + if (right > CLIPPING_THRESHOLD) { + isClipping |= (++oversRight >= CLIPPING_DETECTION); + } else { + oversRight = 0; + } + } + } else { + int32_t overs = 0; + + for (int i = 0; i < numSamples; i++) { + int32_t sample = std::abs((int32_t)samples[i]); + + loudness += sample; + + if (sample > CLIPPING_THRESHOLD) { + isClipping |= (++overs >= CLIPPING_DETECTION); + } else { + overs = 0; + } + } + } + + return (float)loudness * scale; +} + static inline float convertToFloat(int16_t sample) { return (float)sample * (1 / 32768.0f); } @@ -1170,17 +1221,10 @@ void AudioClient::handleMicAudioInput() { _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); // detect loudness and clipping on the raw input - int32_t loudness = 0; - bool didClip = false; - for (int i = 0; i < inputSamplesRequired; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)inputAudioSamples.get()[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / inputSamplesRequired; + bool isClipping = false; + _lastInputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); - if (didClip) { + if (isClipping) { _timeSinceLastClip = 0.0f; } else if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; From ba00f95f72f05d6eb38c9d4ab51b8bc3a1e78d6c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 6 Feb 2019 18:23:50 -0800 Subject: [PATCH 04/10] Expose clipping status to audio scripting interface --- interface/src/scripting/Audio.cpp | 23 ++++++++++++++++++---- interface/src/scripting/Audio.h | 14 ++++++++++++- libraries/audio-client/src/AudioClient.cpp | 3 ++- libraries/audio-client/src/AudioClient.h | 2 +- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 524170c7a5..9ccb698da0 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -150,18 +150,33 @@ float Audio::getInputLevel() const { }); } -void Audio::onInputLoudnessChanged(float loudness) { +bool Audio::isClipping() const { + return resultWithReadLock([&] { + return _isClipping; + }); +} + +void Audio::onInputLoudnessChanged(float loudness, bool isClipping) { float level = loudnessToLevel(loudness); - bool changed = false; + bool levelChanged = false; + bool isClippingChanged = false; + withWriteLock([&] { if (_inputLevel != level) { _inputLevel = level; - changed = true; + levelChanged = true; + } + if (_isClipping != isClipping) { + _isClipping = isClipping; + isClippingChanged = true; } }); - if (changed) { + if (levelChanged) { emit inputLevelChanged(level); } + if (isClippingChanged) { + emit clippingChanged(isClipping); + } } QString Audio::getContext() const { diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 4c4bf6dd60..63758d0633 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -41,6 +41,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * above the noise floor. * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – * 1.0 (the onset of clipping). Read-only. + * @property {boolean} clipping - true if the audio input is clipping, otherwise false. * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some * devices, and others might only support values of 0.0 and 1.0. @@ -58,6 +59,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) 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) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) @@ -74,6 +76,7 @@ public: bool noiseReductionEnabled() const; float getInputVolume() const; float getInputLevel() const; + bool isClipping() const; QString getContext() const; void showMicMeter(bool show); @@ -217,6 +220,14 @@ signals: */ void inputLevelChanged(float level); + /**jsdoc + * Triggered when the clipping state of the input audio changes. + * @function Audio.clippingChanged + * @param {boolean} isClipping - true if the audio input is clipping, otherwise false. + * @returns {Signal} + */ + void clippingChanged(bool isClipping); + /**jsdoc * Triggered when the current context of the audio changes. * @function Audio.contextChanged @@ -237,7 +248,7 @@ private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); void setInputVolume(float volume); - void onInputLoudnessChanged(float loudness); + void onInputLoudnessChanged(float loudness, bool isClipping); protected: // Audio must live on a separate thread from AudioClient to avoid deadlocks @@ -247,6 +258,7 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; + bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index cd1363a84d..c9cf1646ec 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1229,8 +1229,9 @@ void AudioClient::handleMicAudioInput() { } else if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; } + isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time - emit inputLoudnessChanged(_lastInputLoudness); + emit inputLoudnessChanged(_lastInputLoudness, isClipping); if (!_muted) { possibleResampling(_inputToNetworkResampler, diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index f3e1ad9a52..94ed2ce132 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -248,7 +248,7 @@ signals: void noiseReductionChanged(bool noiseReductionEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); - void inputLoudnessChanged(float loudness); + void inputLoudnessChanged(float loudness, bool isClipping); void outputBytesToNetwork(int numBytes); void inputBytesFromNetwork(int numBytes); void noiseGateOpened(); From 2617febbcd8a31de616beb35347568d00d77bedd Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:31:13 -0800 Subject: [PATCH 05/10] Replace ad-hoc audio meter with meter calibrated in dBFS --- interface/src/scripting/Audio.cpp | 24 ++++------------------ libraries/audio-client/src/AudioClient.cpp | 2 +- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 9ccb698da0..d9185a9a70 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "AudioClient.h" +#include "AudioHelpers.h" #include "ui/AvatarInputs.h" using namespace scripting; @@ -26,26 +27,9 @@ QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; float Audio::loudnessToLevel(float loudness) { - const float LOG2 = log(2.0f); - const float METER_LOUDNESS_SCALE = 2.8f / 5.0f; - const float LOG2_LOUDNESS_FLOOR = 11.0f; - - float level = 0.0f; - - loudness += 1.0f; - float log2loudness = logf(loudness) / LOG2; - - if (log2loudness <= LOG2_LOUDNESS_FLOOR) { - level = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE; - } else { - level = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE; - } - - if (level > 1.0f) { - level = 1.0; - } - - return level; + float level = 6.02059991f * fastLog2f(loudness); // level in dBFS + level = (level + 60.0f) * (1/48.0f); // map [-60, -12] dBFS to [0, 1] + return glm::clamp(level, 0.0f, 1.0f); } Audio::Audio() : _devices(_contextIsHMD) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c9cf1646ec..4489d19806 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -175,7 +175,7 @@ static float computeLoudness(int16_t* samples, int numSamples, int numChannels, const int32_t CLIPPING_THRESHOLD = 32392; // -0.1 dBFS const int32_t CLIPPING_DETECTION = 3; // consecutive samples over threshold - float scale = numSamples ? 1.0f / numSamples : 0.0f; + float scale = numSamples ? 1.0f / (numSamples * 32768.0f) : 0.0f; int32_t loudness = 0; isClipping = false; From d014dc2d1447d08c0af322f9adb5e1b5450b8cac Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:35:20 -0800 Subject: [PATCH 06/10] Add audio meter ballistics for less display jitter (10ms attack, 300ms release) --- libraries/audio-client/src/AudioClient.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4489d19806..60a95ff58a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1222,7 +1222,11 @@ void AudioClient::handleMicAudioInput() { // detect loudness and clipping on the raw input bool isClipping = false; - _lastInputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + float inputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + + float tc = (inputLoudness > _lastInputLoudness) ? 0.378f : 0.967f; // 10ms attack, 300ms release @ 100Hz + inputLoudness += tc * (_lastInputLoudness - inputLoudness); + _lastInputLoudness = inputLoudness; if (isClipping) { _timeSinceLastClip = 0.0f; From 52aa20b4d58074a18033deaae28c8a317d2ea557 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:47:56 -0800 Subject: [PATCH 07/10] Add "gated" indicator to audio level meter --- interface/resources/qml/hifi/audio/MicBar.qml | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index fee37ca1c1..3dbf55818e 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -16,7 +16,13 @@ import TabletScriptingInterface 1.0 Rectangle { readonly property var level: AudioScriptingInterface.inputLevel; - + + property bool gated: false; + Component.onCompleted: { + AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); + AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + } + property bool standalone: false; property var dragTarget: null; @@ -189,7 +195,7 @@ Rectangle { Rectangle { // mask id: mask; - width: parent.width * level; + width: gated ? 0 : parent.width * level; radius: 5; anchors { bottom: parent.bottom; @@ -225,5 +231,19 @@ Rectangle { } } } + + Rectangle { + id: gatedIndicator; + visible: gated + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: "#0080FF"; + anchors { + right: parent.left; + verticalCenter: parent.verticalCenter; + } + } } } From d2abf1a56d5e9e67cfa9a064d415f8564b8381d4 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 15:52:29 -0800 Subject: [PATCH 08/10] Add "clipping" indicator to audio level meter --- interface/resources/qml/hifi/audio/MicBar.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 3dbf55818e..615235470f 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -234,7 +234,7 @@ Rectangle { Rectangle { id: gatedIndicator; - visible: gated + visible: gated && !AudioScriptingInterface.clipping radius: 4; width: 2 * radius; @@ -245,5 +245,19 @@ Rectangle { verticalCenter: parent.verticalCenter; } } + + Rectangle { + id: clippingIndicator; + visible: AudioScriptingInterface.clipping + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: colors.red; + anchors { + left: parent.right; + verticalCenter: parent.verticalCenter; + } + } } } From b04a59af1ba7cd4be7e7842d3149dd1f782fc39c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 16:31:25 -0800 Subject: [PATCH 09/10] Calibrate meter to encourage hotter mic levels --- interface/src/scripting/Audio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index d9185a9a70..fb64dbe098 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -28,7 +28,7 @@ Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, float Audio::loudnessToLevel(float loudness) { float level = 6.02059991f * fastLog2f(loudness); // level in dBFS - level = (level + 60.0f) * (1/48.0f); // map [-60, -12] dBFS to [0, 1] + level = (level + 48.0f) * (1/39.0f); // map [-48, -9] dBFS to [0, 1] return glm::clamp(level, 0.0f, 1.0f); } From 5681a996a1b9f74810c4c6261d15fe5a2162aafb Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 7 Feb 2019 16:33:41 -0800 Subject: [PATCH 10/10] Fix meter color gradient, to encourage proper mic levels. High levels are now yellow, actual digital clipping sets red indicator light. --- interface/resources/qml/hifi/audio/MicBar.qml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 615235470f..39f75a9182 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -83,6 +83,7 @@ Rectangle { readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; @@ -218,16 +219,12 @@ Rectangle { color: colors.greenStart; } GradientStop { - position: 0.8; + position: 0.5; color: colors.greenEnd; } - GradientStop { - position: 0.81; - color: colors.red; - } GradientStop { position: 1; - color: colors.red; + color: colors.yellow; } } }