Merge pull request #10756 from zzmp/audio/input-level

Update chrome for the audio input level meter
This commit is contained in:
Zach Pomerantz 2017-06-20 15:33:17 -07:00 committed by GitHub
commit 8e1cf763e3
12 changed files with 296 additions and 271 deletions

View file

@ -12,84 +12,21 @@ import QtQuick.Controls 1.3
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
import "./hifi/audio" as HifiAudio
Hifi.AvatarInputs {
id: root
id: root;
objectName: "AvatarInputs"
width: rootWidth
height: controls.height
x: 10; y: 5
width: audio.width;
height: audio.height;
x: 10; y: 5;
readonly property int rootWidth: 265
readonly property int iconSize: 24
readonly property int iconPadding: 5
readonly property bool shouldReposition: true;
readonly property bool shouldReposition: true
Settings {
category: "Overlay.AvatarInputs"
property alias x: root.x
property alias y: root.y
}
MouseArea {
id: hover
hoverEnabled: true
drag.target: parent
anchors.fill: parent
}
Item {
id: controls
width: root.rootWidth
height: 44
visible: root.showAudioTools
Rectangle {
anchors.fill: parent
color: "#00000000"
Item {
id: audioMeter
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.iconPadding
anchors.right: parent.right
anchors.rightMargin: root.iconPadding
height: 8
Rectangle {
id: blueRect
color: "blue"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width / 4
}
Rectangle {
id: greenRect
color: "green"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: blueRect.right
anchors.right: redRect.left
}
Rectangle {
id: redRect
color: "red"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width / 5
}
Rectangle {
z: 100
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: (1.0 - root.audioLevel) * parent.width
color: "black"
}
}
}
HifiAudio.MicBar {
id: audio;
visible: root.showAudioTools;
standalone: true;
dragTarget: parent;
}
}

View file

@ -0,0 +1,223 @@
//
// MicBar.qml
// qml/hifi/audio
//
// Created by Zach Pomerantz on 6/14/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
Rectangle {
readonly property var level: Audio.inputLevel;
property bool standalone: false;
property var dragTarget: null;
width: 240;
height: 50;
radius: 5;
color: "#00000000";
border {
width: (standalone || Audio.muted || mouseArea.containsMouse) ? 2 : 0;
color: colors.border;
}
// borders are painted over fill, so reduce the fill to fit inside the border
Rectangle {
color: standalone ? colors.fill : "#00000000";
width: 236;
height: 46;
radius: 5;
anchors {
verticalCenter: parent.verticalCenter;
horizontalCenter: parent.horizontalCenter;
}
}
MouseArea {
id: mouseArea;
anchors {
left: icon.left;
right: bar.right;
top: icon.top;
bottom: icon.bottom;
}
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: { Audio.muted = !Audio.muted; }
drag.target: dragTarget;
}
Item {
id: colors;
readonly property string unmuted: "#FFF";
readonly property string muted: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string red: colors.muted;
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: (Audio.muted && !mouseArea.containsMouse) ? muted : unmuted;
}
Item {
id: icon;
anchors {
left: parent.left;
leftMargin: 5;
verticalCenter: parent.verticalCenter;
}
width: 40;
height: 40;
Item {
Image {
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
function exclusiveOr(a, b) { return (a || b) && !(a && b); }
id: image;
source: exclusiveOr(Audio.muted, mouseArea.containsMouse) ? mutedIcon : unmutedIcon;
width: 30;
height: 30;
anchors {
left: parent.left;
leftMargin: 5;
top: parent.top;
topMargin: 5;
}
}
ColorOverlay {
anchors { fill: image }
source: image;
color: colors.icon;
}
}
}
Item {
id: status;
readonly property string color: (Audio.muted && !mouseArea.containsMouse) ? colors.muted : colors.unmuted;
visible: Audio.muted || mouseArea.containsMouse;
anchors {
left: parent.left;
leftMargin: 50;
verticalCenter: parent.verticalCenter;
}
width: 170;
height: 8
Text {
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: parent.color;
text: Audio.muted ? (mouseArea.containsMouse ? "UNMUTE" : "MUTED") : "MUTE";
font.pointSize: 12;
}
Rectangle {
anchors {
left: parent.left;
verticalCenter: parent.verticalCenter;
}
width: 50;
height: 4;
color: parent.color;
}
Rectangle {
anchors {
right: parent.right;
verticalCenter: parent.verticalCenter;
}
width: 50;
height: 4;
color: parent.color;
}
}
Item {
id: bar;
visible: !status.visible;
anchors.fill: status;
width: status.width;
Rectangle { // base
radius: 4;
anchors { fill: parent }
color: colors.gutter;
}
Rectangle { // mask
id: mask;
width: parent.width * level;
radius: 5;
anchors {
bottom: parent.bottom;
bottomMargin: 0;
top: parent.top;
topMargin: 0;
left: parent.left;
leftMargin: 0;
}
}
LinearGradient {
anchors { fill: mask }
source: mask
start: Qt.point(0, 0);
end: Qt.point(170, 0);
gradient: Gradient {
GradientStop {
position: 0;
color: colors.greenStart;
}
GradientStop {
position: 0.8;
color: colors.greenEnd;
}
GradientStop {
position: 0.81;
color: colors.red;
}
GradientStop {
position: 1;
color: colors.red;
}
}
}
}
}

View file

@ -1,20 +1,16 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "../../styles-uit"
import "../audio" as HifiAudio
Item {
id: tablet
objectName: "tablet"
property double micLevel: 0.8
property int rowIndex: 0
property int columnIndex: 0
property int count: (flowMain.children.length - 1)
// called by C++ code to keep audio bar updated
function setMicLevel(newMicLevel) {
tablet.micLevel = newMicLevel;
}
// used to look up a button by its uuid
function findButtonIndex(uuid) {
if (!uuid) {
@ -83,6 +79,16 @@ Item {
Rectangle {
id: bgTopBar
height: 90
anchors {
top: parent.top
topMargin: 0
left: parent.left
leftMargin: 0
right: parent.right
rightMargin: 0
}
gradient: Gradient {
GradientStop {
position: 0
@ -94,108 +100,13 @@ Item {
color: "#1e1e1e"
}
}
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.topMargin: 0
anchors.top: parent.top
Item {
id: audioIcon
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 40
anchors.left: parent.left
anchors.leftMargin: 5
Image {
id: micIcon
source: "../../../icons/tablet-icons/mic.svg"
}
Item {
visible: (Audio.muted && !toggleMuteMouseArea.containsMouse)
|| (!Audio.muted && toggleMuteMouseArea.containsMouse)
Image {
id: muteIcon
source: "../../../icons/tablet-icons/mic-mute.svg"
}
ColorOverlay {
anchors.fill: muteIcon
source: muteIcon
color: toggleMuteMouseArea.containsMouse ? "#a0a0a0" : "#ff0000"
}
}
}
Item {
id: audioBar
width: 170
height: 10
anchors.left: parent.left
anchors.leftMargin: 50
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: audioBarBase
color: "#333333"
radius: 5
anchors.fill: parent
}
Rectangle {
id: audioBarMask
width: parent.width * tablet.micLevel
color: "#333333"
radius: 5
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
}
LinearGradient {
anchors.fill: audioBarMask
source: audioBarMask
start: Qt.point(0, 0)
end: Qt.point(170, 0)
gradient: Gradient {
GradientStop {
position: 0
color: "#2c8e72"
}
GradientStop {
position: 0.8
color: "#1fc6a6"
}
GradientStop {
position: 0.81
color: "#ea4c5f"
}
GradientStop {
position: 1
color: "#ea4c5f"
}
}
}
}
MouseArea {
id: toggleMuteMouseArea
HifiAudio.MicBar {
anchors {
left: audioIcon.left
right: audioBar.right
top: audioIcon.top
bottom: audioIcon.bottom
left: parent.left
leftMargin: 30
verticalCenter: parent.verticalCenter
}
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
scrollGestureEnabled: false
onClicked: { Audio.muted = !Audio.muted }
}
RalewaySemiBold {
@ -254,27 +165,6 @@ Item {
}
}
states: [
State {
name: "muted state"
PropertyChanges {
target: muteText
text: "UNMUTE"
}
PropertyChanges {
target: muteIcon
visible: !Audio.muted
}
PropertyChanges {
target: tablet
micLevel: 0
}
}
]
function setCurrentItemState(state) {
var index = rowIndex + columnIndex;

View file

@ -23,9 +23,33 @@ QString Audio::HMD { "VR" };
Setting::Handle<bool> 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;
}
Audio::Audio() : _devices(_contextIsHMD) {
auto client = DependencyManager::get<AudioClient>();
connect(client.data(), &AudioClient::muteToggled, this, &Audio::onMutedChanged);
auto client = DependencyManager::get<AudioClient>().data();
connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged);
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
connect(&_devices._inputs, &AudioDeviceList::deviceChanged, this, &Audio::onInputChanged);
enableNoiseReduction(enableNoiseReductionSetting.get());
@ -88,6 +112,15 @@ void Audio::onInputChanged() {
}
}
void Audio::onInputLoudnessChanged(float loudness) {
float level = loudnessToLevel(loudness);
if (_inputLevel != level) {
_inputLevel = level;
emit inputLevelChanged(_inputLevel);
}
}
QString Audio::getContext() const {
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
}

View file

@ -26,6 +26,7 @@ class Audio : public AudioScriptingInterface {
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
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(QString context READ getContext NOTIFY contextChanged)
Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop)
@ -34,11 +35,14 @@ public:
static QString HMD;
static QString DESKTOP;
static float loudnessToLevel(float loudness);
virtual ~Audio() {}
bool isMuted() const { return _isMuted; }
bool noiseReductionEnabled() const { return _enableNoiseReduction; }
float getInputVolume() const { return _inputVolume; }
float getInputLevel() const { return _inputLevel; }
QString getContext() const;
void setMuted(bool muted);
@ -54,12 +58,14 @@ signals:
void mutedChanged(bool isMuted);
void noiseReductionChanged(bool isEnabled);
void inputVolumeChanged(float volume);
void inputLevelChanged(float level);
void contextChanged(const QString& context);
public slots:
void onMutedChanged();
void onContextChanged();
void onInputChanged();
void onInputLoudnessChanged(float loudness);
protected:
// Audio must live on a separate thread from AudioClient to avoid deadlocks
@ -68,6 +74,7 @@ protected:
private:
float _inputVolume { 1.0f };
float _inputLevel { 0.0f };
bool _isMuted { false };
bool _enableNoiseReduction;
bool _contextIsHMD { false };

View file

@ -45,15 +45,6 @@ AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) {
} \
}
#define AI_UPDATE_FLOAT(name, src, epsilon) \
{ \
float val = src; \
if (fabsf(_##name - val) >= epsilon) { \
_##name = val; \
emit name##Changed(); \
} \
}
float AvatarInputs::loudnessToAudioLevel(float loudness) {
const float AUDIO_METER_AVERAGING = 0.5;
const float LOG2 = log(2.0f);
@ -85,27 +76,6 @@ void AvatarInputs::update() {
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
AI_UPDATE(isHMD, qApp->isHMDMode());
auto audioIO = DependencyManager::get<AudioClient>();
const float audioLevel = loudnessToAudioLevel(DependencyManager::get<AudioClient>()->getLastInputLoudness());
AI_UPDATE_FLOAT(audioLevel, audioLevel, 0.01f);
AI_UPDATE(audioClipping, ((audioIO->getTimeSinceLastClip() > 0.0f) && (audioIO->getTimeSinceLastClip() < 1.0f)));
AI_UPDATE(audioMuted, audioIO->isMuted());
//// Make muted icon pulsate
//static const float PULSE_MIN = 0.4f;
//static const float PULSE_MAX = 1.0f;
//static const float PULSE_FREQUENCY = 1.0f; // in Hz
//qint64 now = usecTimestampNow();
//if (now - _iconPulseTimeReference > (qint64)USECS_PER_SECOND) {
// // Prevents t from getting too big, which would diminish glm::cos precision
// _iconPulseTimeReference = now - ((now - _iconPulseTimeReference) % USECS_PER_SECOND);
//}
//float t = (float)(now - _iconPulseTimeReference) / (float)USECS_PER_SECOND;
//float pulseFactor = (glm::cos(t * PULSE_FREQUENCY * 2.0f * PI) + 1.0f) / 2.0f;
//iconColor = PULSE_MIN + (PULSE_MAX - PULSE_MIN) * pulseFactor;
}
void AvatarInputs::setShowAudioTools(bool showAudioTools) {
@ -124,10 +94,6 @@ void AvatarInputs::toggleCameraMute() {
}
}
void AvatarInputs::toggleAudioMute() {
DependencyManager::get<AudioClient>()->toggleMute();
}
void AvatarInputs::resetSensors() {
qApp->resetSensors();
}

View file

@ -25,9 +25,6 @@ class AvatarInputs : public QQuickItem {
AI_PROPERTY(bool, cameraEnabled, false)
AI_PROPERTY(bool, cameraMuted, false)
AI_PROPERTY(bool, audioMuted, false)
AI_PROPERTY(bool, audioClipping, false)
AI_PROPERTY(float, audioLevel, 0)
AI_PROPERTY(bool, isHMD, false)
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
@ -45,16 +42,12 @@ public slots:
signals:
void cameraEnabledChanged();
void cameraMutedChanged();
void audioMutedChanged();
void audioClippingChanged();
void audioLevelChanged();
void isHMDChanged();
void showAudioToolsChanged(bool show);
protected:
Q_INVOKABLE void resetSensors();
Q_INVOKABLE void toggleCameraMute();
Q_INVOKABLE void toggleAudioMute();
private:
float _trailingAudioLoudness{ 0 };

View file

@ -1023,6 +1023,8 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
emit inputReceived(audioBuffer);
}
emit inputLoudnessChanged(_lastInputLoudness);
// state machine to detect gate opening and closing
bool audioGateOpen = (_lastInputLoudness != 0.0f);
bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened

View file

@ -210,6 +210,7 @@ signals:
bool muteToggled();
void mutedByMixer();
void inputReceived(const QByteArray& inputSamples);
void inputLoudnessChanged(float loudness);
void outputBytesToNetwork(int numBytes);
void inputBytesFromNetwork(int numBytes);
void noiseGateOpened();

View file

@ -614,15 +614,6 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
}
}
void TabletProxy::updateAudioBar(const double micLevel) {
auto tablet = getQmlTablet();
if (!tablet) {
//qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
} else {
QMetaObject::invokeMethod(tablet, "setMicLevel", Qt::AutoConnection, Q_ARG(QVariant, QVariant(micLevel)));
}
}
void TabletProxy::emitScriptEvent(QVariant msg) {
if (!_toolbarMode && _qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));

View file

@ -152,13 +152,6 @@ public:
*/
Q_INVOKABLE void removeButton(QObject* tabletButtonProxy);
/**jsdoc
* Updates the audio bar in tablet to reflect latest mic level
* @function TabletProxy#updateAudioBar
* @param micLevel {double} mic level value between 0 and 1
*/
Q_INVOKABLE void updateAudioBar(const double micLevel);
/**jsdoc
* Used to send an event to the html/js embedded in the tablet
* @function TabletProxy#emitScriptEvent

View file

@ -183,11 +183,6 @@
return;
}
//TODO: move to tablet qml?
if (tabletShown) {
gTablet.updateAudioBar(getMicLevel());
}
if (now - validCheckTime > MSECS_PER_SEC) {
validCheckTime = now;
updateTabletWidthFromSettings();
@ -268,12 +263,6 @@
Script.setInterval(updateShowTablet, 100);
// Calculate microphone level with the same scaling equation (log scale, exponentially averaged) in AvatarInputs and pal.js
function getMicLevel() {
//reuse already existing C++ code
return AvatarInputs.loudnessToAudioLevel(MyAvatar.audioLoudness)
}
Script.scriptEnding.connect(function () {
// if we reload scripts in tablet mode make sure we close the currently open window, by calling gotoHomeScreen