first cut, pretty ugly

This commit is contained in:
David Kelly 2017-02-17 10:24:01 -07:00
parent 5bef6ea029
commit 51246a2e45
3 changed files with 73 additions and 40 deletions

View file

@ -31,6 +31,7 @@ Item {
property real displayNameTextPixelSize: 18 property real displayNameTextPixelSize: 18
property int usernameTextHeight: 12 property int usernameTextHeight: 12
property real audioLevel: 0.0 property real audioLevel: 0.0
property real avgAudioLevel: 0.0
property bool isMyCard: false property bool isMyCard: false
property bool selected: false property bool selected: false
property bool isAdmin: false property bool isAdmin: false
@ -335,7 +336,7 @@ Item {
} }
} }
// Per-Avatar Gain Slider // Per-Avatar Gain Slider
Slider { Slider {
id: gainSlider id: gainSlider
// Size // Size
@ -369,7 +370,7 @@ Item {
mouse.accepted = false mouse.accepted = false
} }
onReleased: { onReleased: {
// the above mouse.accepted seems to make this // the above mouse.accepted seems to make this
// never get called, nonetheless... // never get called, nonetheless...
mouse.accepted = false mouse.accepted = false
} }

View file

@ -2,7 +2,7 @@
// Pal.qml // Pal.qml
// qml/hifi // qml/hifi
// //
// People Action List // People Action List
// //
// Created by Howard Stearns on 12/12/2016 // Created by Howard Stearns on 12/12/2016
// Copyright 2016 High Fidelity, Inc. // Copyright 2016 High Fidelity, Inc.
@ -29,8 +29,8 @@ Rectangle {
property int myCardHeight: 90 property int myCardHeight: 90
property int rowHeight: 70 property int rowHeight: 70
property int actionButtonWidth: 55 property int actionButtonWidth: 55
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 5 : 3) - 4 - hifi.dimensions.scrollbarBackgroundWidth
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 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 var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
property bool iAmAdmin: false property bool iAmAdmin: false
@ -86,6 +86,7 @@ Rectangle {
displayName: myData.displayName displayName: myData.displayName
userName: myData.userName userName: myData.userName
audioLevel: myData.audioLevel audioLevel: myData.audioLevel
avgAudioLevel: myData.avgAudioLevel
isMyCard: true isMyCard: true
// Size // Size
width: nameCardWidth width: nameCardWidth
@ -184,6 +185,13 @@ Rectangle {
movable: false movable: false
resizable: false resizable: false
} }
TableViewColumn {
role: "avgAudioLevel"
title: "VOL"
width: actionButtonWidth
movable: false
resizable: false
}
TableViewColumn { TableViewColumn {
visible: iAmAdmin visible: iAmAdmin
role: "mute" role: "mute"
@ -218,6 +226,7 @@ Rectangle {
id: itemCell id: itemCell
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore" property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore"
property bool isButton: styleData.role === "mute" || styleData.role === "kick" property bool isButton: styleData.role === "mute" || styleData.role === "kick"
property bool isText: styleData.role == "avgAudioLevel"
// This NameCard refers to the cell that contains an avatar's // This NameCard refers to the cell that contains an avatar's
// DisplayName and UserName // DisplayName and UserName
NameCard { NameCard {
@ -226,7 +235,8 @@ Rectangle {
displayName: styleData.value displayName: styleData.value
userName: model ? model.userName : "" userName: model ? model.userName : ""
audioLevel: model ? model.audioLevel : 0.0 audioLevel: model ? model.audioLevel : 0.0
visible: !isCheckBox && !isButton avgAudioLevel: model ? model.avgAudioLevel : 0.0
visible: !isCheckBox && !isButton && !isText
uuid: model ? model.sessionId : "" uuid: model ? model.sessionId : ""
selected: styleData.selected selected: styleData.selected
isAdmin: model && model.admin isAdmin: model && model.admin
@ -236,7 +246,16 @@ Rectangle {
// Anchors // Anchors
anchors.left: parent.left anchors.left: parent.left
} }
Text {
id: avgAudioVolume
text: model ? model.avgAudioLevel : 0.0
visible: isText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
height: parent.height
}
// This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now) // 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 // KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox
// will appear in the "hovered" state. Hovering over the checkbox will fix it. // will appear in the "hovered" state. Hovering over the checkbox will fix it.
@ -272,7 +291,7 @@ Rectangle {
checked = Qt.binding(function() { return (model[styleData.role])}) checked = Qt.binding(function() { return (model[styleData.role])})
} }
} }
// This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now) // This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now)
HifiControls.Button { HifiControls.Button {
id: actionButton id: actionButton
@ -542,23 +561,28 @@ Rectangle {
} }
} }
break; break;
case 'updateAudioLevel': case 'updateAudioLevel':
for (var userId in message.params) { 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 the userId is 0, we're updating "myData".
if (userId == 0) { if (userId == 0) {
myData.audioLevel = audioLevel; myData.audioLevel = audioLevel;
myCard.audioLevel = audioLevel; // Defensive programming myCard.audioLevel = audioLevel; // Defensive programming
myData.avgAudioLevel = avgAudioLevel;
myCard.avgAudioLevel = avgAudioLevel;
} else { } else {
var userIndex = findSessionIndex(userId); var userIndex = findSessionIndex(userId);
if (userIndex != -1) { if (userIndex != -1) {
userModel.setProperty(userIndex, "audioLevel", audioLevel); userModel.setProperty(userIndex, "audioLevel", audioLevel);
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
userModel.setProperty(userIndex, "avgAudioLevel", avgAudioLevel);
userModelData[userIndex].avgAudioLevel = avgAudioLevel;
} }
} }
} }
break; break;
case 'clearLocalQMLData': case 'clearLocalQMLData':
ignored = {}; ignored = {};
gainSliderValueDB = {}; gainSliderValueDB = {};
break; break;

View file

@ -13,7 +13,7 @@
(function() { // BEGIN LOCAL_SCOPE (function() { // BEGIN LOCAL_SCOPE
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
// something, will revisit as this is sorta horrible. // something, will revisit as this is sorta horrible.
const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
@ -87,7 +87,7 @@ ExtendedOverlay.prototype.hover = function (hovering) {
} else { } else {
lastHoveringId = 0; lastHoveringId = 0;
} }
} }
this.editOverlay({color: color(this.selected, hovering, this.audioLevel)}); this.editOverlay({color: color(this.selected, hovering, this.audioLevel)});
if (this.model) { if (this.model) {
this.model.editOverlay({textures: textures(this.selected, hovering)}); this.model.editOverlay({textures: textures(this.selected, hovering)});
@ -104,7 +104,7 @@ ExtendedOverlay.prototype.select = function (selected) {
if (this.selected === selected) { if (this.selected === selected) {
return; return;
} }
UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key); UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key);
this.editOverlay({color: color(selected, this.hovering, this.audioLevel)}); this.editOverlay({color: color(selected, this.hovering, this.audioLevel)});
@ -273,7 +273,7 @@ function sendToQml(message) {
// //
function addAvatarNode(id) { function addAvatarNode(id) {
var selected = ExtendedOverlay.isSelected(id); var selected = ExtendedOverlay.isSelected(id);
return new ExtendedOverlay(id, "sphere", { return new ExtendedOverlay(id, "sphere", {
drawInFront: true, drawInFront: true,
solid: true, solid: true,
alpha: 0.8, alpha: 0.8,
@ -290,6 +290,7 @@ function populateUserList(selectData) {
userName: '', userName: '',
sessionId: id || '', sessionId: id || '',
audioLevel: 0.0, audioLevel: 0.0,
avgAudioLevel: 0.0,
admin: false, admin: false,
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
ignore: !!id && Users.getIgnoreStatus(id) // ditto ignore: !!id && Users.getIgnoreStatus(id) // ditto
@ -341,7 +342,7 @@ function updateOverlays() {
var target = avatar.position; var target = avatar.position;
var distance = Vec3.distance(target, eye); var distance = Vec3.distance(target, eye);
var offset = 0.2; var offset = 0.2;
// base offset on 1/2 distance from hips to head if we can // base offset on 1/2 distance from hips to head if we can
var headIndex = avatar.getJointIndex("Head"); var headIndex = avatar.getJointIndex("Head");
if (headIndex > 0) { if (headIndex > 0) {
@ -350,7 +351,7 @@ function updateOverlays() {
// get diff between target and eye (a vector pointing to the eye from avatar position) // get diff between target and eye (a vector pointing to the eye from avatar position)
var diff = Vec3.subtract(target, eye); var diff = Vec3.subtract(target, eye);
// move a bit in front, towards the camera // move a bit in front, towards the camera
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
@ -361,12 +362,12 @@ function updateOverlays() {
overlay.editOverlay({ overlay.editOverlay({
color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel), color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel),
position: target, position: target,
dimensions: 0.032 * distance dimensions: 0.032 * distance
}); });
if (overlay.model) { if (overlay.model) {
overlay.model.ping = pingPong; overlay.model.ping = pingPong;
overlay.model.editOverlay({ overlay.model.editOverlay({
position: target, position: target,
scale: 0.2 * distance, // constant apparent size scale: 0.2 * distance, // constant apparent size
rotation: Camera.orientation rotation: Camera.orientation
}); });
@ -433,7 +434,7 @@ function handleMouseMoveEvent(event) { // find out which overlay (if any) is ove
handleMouseMove(pickRay); handleMouseMove(pickRay);
} }
function handleTriggerPressed(hand, value) { function handleTriggerPressed(hand, value) {
// The idea is if you press one trigger, it is the one // The idea is if you press one trigger, it is the one
// we will consider the mouse. Even if the other is pressed, // we will consider the mouse. Even if the other is pressed,
// we ignore it until this one is no longer pressed. // we ignore it until this one is no longer pressed.
isPressed = value > TRIGGER_PRESS_THRESHOLD; isPressed = value > TRIGGER_PRESS_THRESHOLD;
@ -441,10 +442,10 @@ function handleTriggerPressed(hand, value) {
currentHandPressed = isPressed ? hand : 0; currentHandPressed = isPressed ? hand : 0;
return; return;
} }
if (currentHandPressed == hand) { if (currentHandPressed == hand) {
currentHandPressed = isPressed ? hand : 0; currentHandPressed = isPressed ? hand : 0;
return; return;
} }
// otherwise, the other hand is still triggered // otherwise, the other hand is still triggered
// so do nothing. // so do nothing.
} }
@ -574,41 +575,48 @@ function receiveMessage(channel, messageString, senderID) {
Messages.subscribe(CHANNEL); Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(receiveMessage); Messages.messageReceived.connect(receiveMessage);
var AVERAGING_RATIO = 0.05; var AVERAGING_RATIO = 0.05;
var LONG_AVERAGING_RATIO = 0.75;
var LOUDNESS_FLOOR = 11.0; var LOUDNESS_FLOOR = 11.0;
var LOUDNESS_SCALE = 2.8 / 5.0; var LOUDNESS_SCALE = 2.8 / 5.0;
var LOG2 = Math.log(2.0); var LOG2 = Math.log(2.0);
var myData = {}; // we're not includied in ExtendedOverlay.get. 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) { function getAudioLevel(id) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged // 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 // 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). // of updating (the latter for efficiency too).
var avatar = AvatarList.getAvatar(id); var avatar = AvatarList.getAvatar(id);
var audioLevel = 0.0; var audioLevel = 0.0;
var avgAudioLevel = 0.0;
var data = id ? ExtendedOverlay.get(id) : myData; var data = id ? ExtendedOverlay.get(id) : myData;
if (!data) { if (data) {
return audioLevel;
}
// we will do exponential moving average by taking some the last loudness and averaging // 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); data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
data.longAccumulatedLevel = LONG_AVERAGING_RATIO * (data.longAccumulatedLevel || 0) + (1 - LONG_AVERAGING_RATIO) * (avatar.audioLoudness);
// add 1 to insure we don't go log() and hit -infinity. Math.log is // 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). // natural log, so to get log base 2, just divide by ln(2).
var logLevel = Math.log(data.accumulatedLevel + 1) / LOG2; audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
avgAudioLevel = scaleAudio(Math.log(data.longAccumulatedLevel + 1) / LOG2);
if (logLevel <= LOUDNESS_FLOOR) { data.audioLevel = audioLevel;
audioLevel = logLevel / LOUDNESS_FLOOR * LOUDNESS_SCALE; data.averageAudioLevel = avgAudioLevel;
} else {
audioLevel = (logLevel - (LOUDNESS_FLOOR - 1.0)) * LOUDNESS_SCALE;
} }
if (audioLevel > 1.0) { return [audioLevel, avgAudioLevel];
audioLevel = 1;
}
data.audioLevel = audioLevel;
return audioLevel;
} }
function createAudioInterval(interval) { function createAudioInterval(interval) {