mirror of
https://github.com/lubosz/overte.git
synced 2025-04-26 09:35:31 +02:00
Merge pull request #9698 from davidkelly/dk/palVolumeSorting
PAL average volume sorting
This commit is contained in:
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
BIN
interface/resources/fonts/hifi-glyphs.ttf
Normal file → Executable file
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue