Merge pull request from davidkelly/dk/palVolumeSorting

PAL average volume sorting
This commit is contained in:
Howard Stearns 2017-03-01 15:34:05 -08:00 committed by GitHub
commit 48ad7fcaae
5 changed files with 118 additions and 34 deletions
interface/resources
scripts/system

BIN
interface/resources/fonts/hifi-glyphs.ttf Normal file → Executable file

Binary file not shown.

View file

@ -31,6 +31,7 @@ Item {
property real displayNameTextPixelSize: 18
property int usernameTextHeight: 12
property real audioLevel: 0.0
property real avgAudioLevel: 0.0
property bool isMyCard: false
property bool selected: false
property bool isAdmin: false
@ -55,7 +56,7 @@ Item {
id: textContainer
// Size
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
height: childrenRect.height
height: selected || isMyCard ? childrenRect.height : childrenRect.height - 15
anchors.verticalCenter: parent.verticalCenter
// DisplayName field for my card
@ -273,6 +274,7 @@ Item {
// Style
radius: 4
color: "#c5c5c5"
visible: isMyCard || selected
// Rectangle for the zero-gain point on the VU meter
Rectangle {
id: vuMeterZeroGain
@ -303,6 +305,7 @@ Item {
id: vuMeterBase
// Anchors
anchors.fill: parent
visible: isMyCard || selected
// Style
color: parent.color
radius: parent.radius
@ -310,6 +313,7 @@ Item {
// Rectangle for the VU meter audio level
Rectangle {
id: vuMeterLevel
visible: isMyCard || selected
// Size
width: (thisNameCard.audioLevel) * parent.width
// Style

View file

@ -13,6 +13,7 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
import "../styles-uit"
import "../controls-uit" as HifiControls
@ -33,7 +34,7 @@ Rectangle {
property int actionButtonAllowance: actionButtonWidth * 2
property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance)
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true}) // valid dummy until set
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
property bool iAmAdmin: false
@ -57,6 +58,8 @@ Rectangle {
category: "pal"
property bool filtered: false
property int nearDistance: 30
property int sortIndicatorColumn: 1
property int sortIndicatorOrder: Qt.AscendingOrder
}
function refreshWithFilter() {
// We should just be able to set settings.filtered to filter.checked, but see #3249, so send to .js for saving.
@ -96,6 +99,7 @@ Rectangle {
displayName: myData.displayName
userName: myData.userName
audioLevel: myData.audioLevel
avgAudioLevel: myData.avgAudioLevel
isMyCard: true
// Size
width: minNameCardWidth
@ -190,8 +194,24 @@ Rectangle {
centerHeaderText: true
sortIndicatorVisible: true
headerVisible: true
onSortIndicatorColumnChanged: sortModel()
onSortIndicatorOrderChanged: sortModel()
sortIndicatorColumn: settings.sortIndicatorColumn
sortIndicatorOrder: settings.sortIndicatorOrder
onSortIndicatorColumnChanged: {
settings.sortIndicatorColumn = sortIndicatorColumn
sortModel()
}
onSortIndicatorOrderChanged: {
settings.sortIndicatorOrder = sortIndicatorOrder
sortModel()
}
TableViewColumn {
role: "avgAudioLevel"
title: "LOUD"
width: actionButtonWidth
movable: false
resizable: false
}
TableViewColumn {
id: displayNameHeader
@ -201,13 +221,6 @@ Rectangle {
movable: false
resizable: false
}
TableViewColumn {
role: "personalMute"
title: "MUTE"
width: actionButtonWidth
movable: false
resizable: false
}
TableViewColumn {
role: "ignore"
title: "IGNORE"
@ -238,7 +251,7 @@ Rectangle {
// This Rectangle refers to each Row in the table.
rowDelegate: Rectangle { // The only way I know to specify a row height.
// Size
height: rowHeight
height: styleData.selected ? rowHeight : rowHeight - 15
color: styleData.selected
? hifi.colors.orangeHighlight
: styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd
@ -249,6 +262,8 @@ Rectangle {
id: itemCell
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore"
property bool isButton: styleData.role === "mute" || styleData.role === "kick"
property bool isAvgAudio: styleData.role === "avgAudioLevel"
// This NameCard refers to the cell that contains an avatar's
// DisplayName and UserName
NameCard {
@ -257,7 +272,8 @@ Rectangle {
displayName: styleData.value
userName: model ? model.userName : ""
audioLevel: model ? model.audioLevel : 0.0
visible: !isCheckBox && !isButton
avgAudioLevel: model ? model.avgAudioLevel : 0.0
visible: !isCheckBox && !isButton && !isAvgAudio
uuid: model ? model.sessionId : ""
selected: styleData.selected
isAdmin: model && model.admin
@ -267,6 +283,30 @@ Rectangle {
// Anchors
anchors.left: parent.left
}
HifiControls.GlyphButton {
function getGlyph() {
var fileName = "vol_";
if (model["personalMute"]) {
fileName += "x_";
}
fileName += (4.0*(model ? model.avgAudioLevel : 0.0)).toFixed(0);
return hifi.glyphs[fileName];
}
id: avgAudioVolume
visible: isAvgAudio
glyph: getGlyph()
width: 32
size: height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
var newValue = !model["personalMute"];
userModel.setProperty(model.userIndex, "personalMute", newValue)
userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming
Users["personalMute"](model.sessionId, newValue)
UserActivityLogger["palAction"](newValue ? "personalMute" : "un-personalMute", model.sessionId)
}
}
// This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now)
// KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox
@ -311,7 +351,7 @@ Rectangle {
visible: isButton
anchors.centerIn: parent
width: 32
height: 24
height: 32
onClicked: {
Users[styleData.role](model.sessionId)
UserActivityLogger["palAction"](styleData.role, model.sessionId)
@ -363,7 +403,7 @@ Rectangle {
anchors.left: table.left
anchors.top: table.top
anchors.topMargin: 1
anchors.leftMargin: nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6
anchors.leftMargin: actionButtonWidth + nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6
RalewayRegular {
id: helpText
text: "[?]"
@ -537,16 +577,21 @@ Rectangle {
break;
case 'updateAudioLevel':
for (var userId in message.params) {
var audioLevel = message.params[userId];
var audioLevel = message.params[userId][0];
var avgAudioLevel = message.params[userId][1];
// If the userId is 0, we're updating "myData".
if (userId == 0) {
myData.audioLevel = audioLevel;
myCard.audioLevel = audioLevel; // Defensive programming
myData.avgAudioLevel = avgAudioLevel;
myCard.avgAudioLevel = avgAudioLevel;
} else {
var userIndex = findSessionIndex(userId);
if (userIndex != -1) {
userModel.setProperty(userIndex, "audioLevel", audioLevel);
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
userModel.setProperty(userIndex, "avgAudioLevel", avgAudioLevel);
userModelData[userIndex].avgAudioLevel = avgAudioLevel;
}
}
}

View file

@ -318,5 +318,15 @@ Item {
readonly property string deg: "\\"
readonly property string px: "|"
readonly property string editPencil: "\ue00d"
readonly property string vol_0: "\ue00e"
readonly property string vol_1: "\ue00f"
readonly property string vol_2: "\ue010"
readonly property string vol_3: "\ue011"
readonly property string vol_4: "\ue012"
readonly property string vol_x_0: "\ue013"
readonly property string vol_x_1: "\ue014"
readonly property string vol_x_2: "\ue015"
readonly property string vol_x_3: "\ue016"
readonly property string vol_x_4: "\ue017"
}
}

View file

@ -206,6 +206,17 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() {
});
};
/* this contains current gain for a given node (by session id). More efficient than
* querying it, plus there isn't a getGain function so why write one */
var sessionGains = {};
function convertDbToLinear(decibels) {
// +20db = 10x, 0dB = 1x, -10dB = 0.1x, etc...
// but, your perception is that something 2x as loud is +10db
// so we go from -60 to +20 or 1/64x to 4x. For now, we can
// maybe scale the signal this way??
return Math.pow(2, decibels/10.0);
}
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
var data;
switch (message.method) {
@ -311,6 +322,7 @@ function populateUserList(selectData) {
userName: '',
sessionId: id || '',
audioLevel: 0.0,
avgAudioLevel: 0.0,
admin: false,
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
ignore: !!id && Users.getIgnoreStatus(id) // ditto
@ -604,41 +616,54 @@ function receiveMessage(channel, messageString, senderID) {
}
}
var AVERAGING_RATIO = 0.05;
var LOUDNESS_FLOOR = 11.0;
var LOUDNESS_SCALE = 2.8 / 5.0;
var LOG2 = Math.log(2.0);
var AUDIO_PEAK_DECAY = 0.02;
var myData = {}; // we're not includied in ExtendedOverlay.get.
function scaleAudio(val) {
var audioLevel = 0.0;
if (val <= LOUDNESS_FLOOR) {
audioLevel = val / LOUDNESS_FLOOR * LOUDNESS_SCALE;
} else {
audioLevel = (val -(LOUDNESS_FLOOR -1 )) * LOUDNESS_SCALE;
}
if (audioLevel > 1.0) {
audioLevel = 1;
}
return audioLevel;
}
function getAudioLevel(id) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
// of updating (the latter for efficiency too).
var avatar = AvatarList.getAvatar(id);
var audioLevel = 0.0;
var avgAudioLevel = 0.0;
var data = id ? ExtendedOverlay.get(id) : myData;
if (!data) {
return audioLevel;
}
if (data) {
// we will do exponential moving average by taking some the last loudness and averaging
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
// we will do exponential moving average by taking some the last loudness and averaging
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
// add 1 to insure we don't go log() and hit -infinity. Math.log is
// natural log, so to get log base 2, just divide by ln(2).
var logLevel = Math.log(data.accumulatedLevel + 1) / LOG2;
// add 1 to insure we don't go log() and hit -infinity. Math.log is
// natural log, so to get log base 2, just divide by ln(2).
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
if (logLevel <= LOUDNESS_FLOOR) {
audioLevel = logLevel / LOUDNESS_FLOOR * LOUDNESS_SCALE;
} else {
audioLevel = (logLevel - (LOUDNESS_FLOOR - 1.0)) * LOUDNESS_SCALE;
// decay avgAudioLevel
avgAudioLevel = Math.max((1-AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
data.avgAudioLevel = avgAudioLevel;
data.audioLevel = audioLevel;
// now scale for the gain. Also, asked to boost the low end, so one simple way is
// to take sqrt of the value. Lets try that, see how it feels.
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel *(sessionGains[id] || 0.75)));
}
if (audioLevel > 1.0) {
audioLevel = 1;
}
data.audioLevel = audioLevel;
return audioLevel;
return [audioLevel, avgAudioLevel];
}
function createAudioInterval(interval) {