mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-16 22:30:42 +02:00
Merge pull request #9352 from zfox23/PerAvatarVolume
PAL: Per-Avatar Volume Slider; visual tweaks
This commit is contained in:
commit
d2c7342a62
11 changed files with 195 additions and 29 deletions
|
@ -28,6 +28,7 @@
|
|||
#include <StDev.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioRingBuffer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
#include "AvatarAudioStream.h"
|
||||
|
@ -68,7 +69,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
||||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
||||
packetReceiver.registerListener(PacketType::PerAvatarGainSet, this, "handlePerAvatarGainSetDataPacket");
|
||||
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||
}
|
||||
|
@ -186,7 +188,8 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
|||
nodeList->eachNode([&killedNode](const SharedNodePointer& node) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
clientData->removeHRTFsForNode(killedNode->getUUID());
|
||||
QUuid killedUUID = killedNode->getUUID();
|
||||
clientData->removeHRTFsForNode(killedUUID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -240,6 +243,20 @@ void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> p
|
|||
sendingNode->parseIgnoreRequestMessage(packet);
|
||||
}
|
||||
|
||||
void AudioMixer::handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
if (clientData) {
|
||||
QUuid listeningNodeUUID = sendingNode->getUUID();
|
||||
// parse the UUID from the packet
|
||||
QUuid audioSourceUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
uint8_t packedGain;
|
||||
packet->readPrimitive(&packedGain);
|
||||
float gain = unpackFloatGainFromByte(packedGain);
|
||||
clientData->hrtfForStream(audioSourceUUID, QUuid()).setGainAdjustment(gain);
|
||||
qDebug() << "Setting gain adjustment for hrtf[" << listeningNodeUUID << "][" << audioSourceUUID << "] to " << gain;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
sendingNode->parseIgnoreRadiusRequestMessage(packet);
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ private slots:
|
|||
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
|
||||
void start();
|
||||
void removeHRTFsForFinishedInjector(const QUuid& streamID);
|
||||
|
|
|
@ -10,35 +10,35 @@
|
|||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../styles-uit"
|
||||
|
||||
Row {
|
||||
Item {
|
||||
id: thisNameCard
|
||||
// Spacing
|
||||
spacing: 10
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
anchors {
|
||||
topMargin: (parent.height - contentHeight)/2
|
||||
bottomMargin: (parent.height - contentHeight)/2
|
||||
verticalCenter: parent.verticalCenter
|
||||
leftMargin: 10
|
||||
rightMargin: 10
|
||||
}
|
||||
|
||||
// Properties
|
||||
property int contentHeight: 50
|
||||
property string uuid: ""
|
||||
property string displayName: ""
|
||||
property string userName: ""
|
||||
property int displayTextHeight: 18
|
||||
property int usernameTextHeight: 12
|
||||
property real audioLevel: 0.0
|
||||
property bool isMyCard: false
|
||||
property bool selected: false
|
||||
|
||||
/* User image commented out for now - will probably be re-introduced later.
|
||||
Column {
|
||||
id: avatarImage
|
||||
// Size
|
||||
height: contentHeight
|
||||
height: parent.height
|
||||
width: height
|
||||
Image {
|
||||
id: userImage
|
||||
|
@ -49,12 +49,12 @@ Row {
|
|||
}
|
||||
}
|
||||
*/
|
||||
Column {
|
||||
Item {
|
||||
id: textContainer
|
||||
// Size
|
||||
width: parent.width - /*avatarImage.width - */parent.anchors.leftMargin - parent.anchors.rightMargin - parent.spacing
|
||||
height: contentHeight
|
||||
|
||||
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
|
||||
height: childrenRect.height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// DisplayName Text
|
||||
FiraSansSemiBold {
|
||||
id: displayNameText
|
||||
|
@ -63,6 +63,8 @@ Row {
|
|||
elide: Text.ElideRight
|
||||
// Size
|
||||
width: parent.width
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
// Text Size
|
||||
size: thisNameCard.displayTextHeight
|
||||
// Text Positioning
|
||||
|
@ -80,6 +82,8 @@ Row {
|
|||
visible: thisNameCard.displayName
|
||||
// Size
|
||||
width: parent.width
|
||||
// Anchors
|
||||
anchors.top: displayNameText.bottom
|
||||
// Text Size
|
||||
size: thisNameCard.usernameTextHeight
|
||||
// Text Positioning
|
||||
|
@ -90,25 +94,56 @@ Row {
|
|||
|
||||
// Spacer
|
||||
Item {
|
||||
id: spacer
|
||||
height: 4
|
||||
width: parent.width
|
||||
// Anchors
|
||||
anchors.top: userNameText.bottom
|
||||
}
|
||||
|
||||
// VU Meter
|
||||
Rectangle { // CHANGEME to the appropriate type!
|
||||
Rectangle {
|
||||
id: nameCardVUMeter
|
||||
// Size
|
||||
width: parent.width
|
||||
width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
|
||||
height: 8
|
||||
// Anchors
|
||||
anchors.top: spacer.bottom
|
||||
// Style
|
||||
radius: 4
|
||||
color: "#c5c5c5"
|
||||
// Rectangle for the zero-gain point on the VU meter
|
||||
Rectangle {
|
||||
id: vuMeterZeroGain
|
||||
visible: gainSlider.visible
|
||||
// Size
|
||||
width: 4
|
||||
height: 18
|
||||
// Style
|
||||
color: hifi.colors.darkGray
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (-gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue) * gainSlider.width - 4
|
||||
}
|
||||
// Rectangle for the VU meter line
|
||||
Rectangle {
|
||||
id: vuMeterLine
|
||||
width: gainSlider.width
|
||||
visible: gainSlider.visible
|
||||
// Style
|
||||
color: vuMeterBase.color
|
||||
radius: nameCardVUMeter.radius
|
||||
height: nameCardVUMeter.height / 2
|
||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||
}
|
||||
// Rectangle for the VU meter base
|
||||
Rectangle {
|
||||
id: vuMeterBase
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
// Style
|
||||
color: "#c5c5c5"
|
||||
color: parent.color
|
||||
radius: parent.radius
|
||||
}
|
||||
// Rectangle for the VU meter audio level
|
||||
|
@ -117,7 +152,7 @@ Row {
|
|||
// Size
|
||||
width: (thisNameCard.audioLevel) * parent.width
|
||||
// Style
|
||||
color: "#c5c5c5"
|
||||
color: parent.color
|
||||
radius: parent.radius
|
||||
// Anchors
|
||||
anchors.bottom: parent.bottom
|
||||
|
@ -138,5 +173,66 @@ Row {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Per-Avatar Gain Slider
|
||||
Slider {
|
||||
id: gainSlider
|
||||
// Size
|
||||
width: parent.width
|
||||
height: 14
|
||||
// Anchors
|
||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||
// Properties
|
||||
visible: !isMyCard && selected
|
||||
value: pal.gainSliderValueDB[uuid] ? pal.gainSliderValueDB[uuid] : 0.0
|
||||
minimumValue: -60.0
|
||||
maximumValue: 20.0
|
||||
stepSize: 5
|
||||
updateValueWhileDragging: true
|
||||
onValueChanged: updateGainFromQML(uuid, value)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onWheel: {
|
||||
// Do nothing.
|
||||
}
|
||||
onDoubleClicked: {
|
||||
gainSlider.value = 0.0
|
||||
}
|
||||
onPressed: {
|
||||
// Pass through to Slider
|
||||
mouse.accepted = false
|
||||
}
|
||||
onReleased: {
|
||||
// Pass through to Slider
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
style: SliderStyle {
|
||||
groove: Rectangle {
|
||||
color: "#c5c5c5"
|
||||
implicitWidth: gainSlider.width
|
||||
implicitHeight: 4
|
||||
radius: 2
|
||||
opacity: 0
|
||||
}
|
||||
handle: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
color: (control.pressed || control.hovered) ? "#00b4ef" : "#8F8F8F"
|
||||
implicitWidth: 10
|
||||
implicitHeight: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateGainFromQML(avatarUuid, sliderValue) {
|
||||
if (pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
|
||||
pal.gainSliderValueDB[avatarUuid] = sliderValue;
|
||||
var data = {
|
||||
sessionId: avatarUuid,
|
||||
gain: sliderValue
|
||||
};
|
||||
pal.sendToScript({method: 'updateGain', params: data});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ Rectangle {
|
|||
// Style
|
||||
color: "#E3E3E3"
|
||||
// Properties
|
||||
property int myCardHeight: 70
|
||||
property int myCardHeight: 90
|
||||
property int rowHeight: 70
|
||||
property int actionButtonWidth: 75
|
||||
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
|
||||
|
@ -32,6 +32,9 @@ Rectangle {
|
|||
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
|
||||
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
|
||||
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
|
||||
property var gainSliderValueDB: ({});
|
||||
|
||||
// This is the container for the PAL
|
||||
Rectangle {
|
||||
|
@ -51,7 +54,7 @@ Rectangle {
|
|||
id: myInfo
|
||||
// Size
|
||||
width: palContainer.width
|
||||
height: myCardHeight + 20
|
||||
height: myCardHeight
|
||||
// Style
|
||||
color: pal.color
|
||||
// Anchors
|
||||
|
@ -65,6 +68,7 @@ Rectangle {
|
|||
displayName: myData.displayName
|
||||
userName: myData.userName
|
||||
audioLevel: myData.audioLevel
|
||||
isMyCard: true
|
||||
// Size
|
||||
width: nameCardWidth
|
||||
height: parent.height
|
||||
|
@ -206,6 +210,8 @@ Rectangle {
|
|||
userName: model && model.userName
|
||||
audioLevel: model && model.audioLevel
|
||||
visible: !isCheckBox && !isButton
|
||||
uuid: model && model.sessionId
|
||||
selected: styleData.selected
|
||||
// Size
|
||||
width: nameCardWidth
|
||||
height: parent.height
|
||||
|
@ -492,8 +498,9 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'clearIgnored':
|
||||
case 'clearLocalQMLData':
|
||||
ignored = {};
|
||||
gainSliderValueDB = {};
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message:', JSON.stringify(message));
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
void renderSilent(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames);
|
||||
|
||||
//
|
||||
// HRTF local gain adjustment
|
||||
// HRTF local gain adjustment in amplitude (1.0 == unity)
|
||||
//
|
||||
void setGainAdjustment(float gain) { _gainAdjust = HRTF_GAIN * gain; };
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "AccountManager.h"
|
||||
#include "AddressManager.h"
|
||||
#include "Assignment.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "HifiSockAddr.h"
|
||||
#include "FingerprintUtils.h"
|
||||
|
||||
|
@ -951,6 +952,30 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
|
|||
}
|
||||
}
|
||||
|
||||
void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||
// cannot set gain of yourself or nobody
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||
auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
|
||||
if (audioMixer) {
|
||||
// setup the packet
|
||||
auto setAvatarGainPacket = NLPacket::create(PacketType::PerAvatarGainSet, NUM_BYTES_RFC4122_UUID + sizeof(float), true);
|
||||
|
||||
// write the node ID to the packet
|
||||
setAvatarGainPacket->write(nodeID.toRfc4122());
|
||||
// We need to convert the gain in dB (from the script) to an amplitude before packing it.
|
||||
setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.0206f)));
|
||||
|
||||
qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
|
||||
|
||||
sendPacket(std::move(setAvatarGainPacket), *audioMixer);
|
||||
} else {
|
||||
qWarning() << "Couldn't find audio mixer to send set gain request";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "NodeList::setAvatarGain called with an invalid ID or an ID which matches the current session ID:" << nodeID;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
|
||||
// send a request to domain-server to kick the node with the given session ID
|
||||
// the domain-server will handle the persistence of the kick (via username or IP)
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
bool isIgnoringNode(const QUuid& nodeID) const;
|
||||
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
|
||||
bool isPersonalMutingNode(const QUuid& nodeID) const;
|
||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||
|
||||
void kickNodeBySessionID(const QUuid& nodeID);
|
||||
void muteNodeBySessionID(const QUuid& nodeID);
|
||||
|
|
|
@ -106,7 +106,8 @@ public:
|
|||
ViewFrustum,
|
||||
RequestsDomainListData,
|
||||
ExitingSpaceBubble,
|
||||
LAST_PACKET_TYPE = ExitingSpaceBubble
|
||||
PerAvatarGainSet,
|
||||
LAST_PACKET_TYPE = PerAvatarGainSet
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -42,6 +42,11 @@ bool UsersScriptingInterface::getPersonalMuteStatus(const QUuid& nodeID) {
|
|||
return DependencyManager::get<NodeList>()->isPersonalMutingNode(nodeID);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||
// ask the NodeList to set the gain of the specified avatar
|
||||
DependencyManager::get<NodeList>()->setAvatarGain(nodeID, gain);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
||||
// ask the NodeList to kick the user with the given session ID
|
||||
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
||||
|
|
|
@ -61,6 +61,15 @@ public slots:
|
|||
*/
|
||||
bool getPersonalMuteStatus(const QUuid& nodeID);
|
||||
|
||||
/**jsdoc
|
||||
* Sets an avatar's gain for you and you only.
|
||||
* Units are Decibels (dB)
|
||||
* @function Users.setAvatarGain
|
||||
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify.
|
||||
* @param {float} gain The gain of the avatar you'd like to set. Units are dB.
|
||||
*/
|
||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||
|
||||
/**jsdoc
|
||||
* Kick another user.
|
||||
* @function Users.kick
|
||||
|
|
|
@ -233,6 +233,10 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
|
|||
removeOverlays();
|
||||
populateUserList();
|
||||
break;
|
||||
case 'updateGain':
|
||||
data = message.params;
|
||||
Users.setAvatarGain(data['sessionId'], data['gain']);
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
||||
}
|
||||
|
@ -582,14 +586,14 @@ button.clicked.connect(onClicked);
|
|||
pal.visibleChanged.connect(onVisibleChanged);
|
||||
pal.closed.connect(off);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
function clearIgnoredInQMLAndClosePAL() {
|
||||
pal.sendToQml({ method: 'clearIgnored' });
|
||||
function clearLocalQMLDataAndClosePAL() {
|
||||
pal.sendToQml({ method: 'clearLocalQMLData' });
|
||||
if (pal.visible) {
|
||||
onClicked(); // Close the PAL
|
||||
}
|
||||
}
|
||||
Window.domainChanged.connect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
|
||||
|
||||
//
|
||||
// Cleanup.
|
||||
|
@ -600,8 +604,8 @@ Script.scriptEnding.connect(function () {
|
|||
pal.visibleChanged.disconnect(onVisibleChanged);
|
||||
pal.closed.disconnect(off);
|
||||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.unsubscribe(CHANNEL);
|
||||
Messages.messageReceived.disconnect(receiveMessage);
|
||||
off();
|
||||
|
|
Loading…
Reference in a new issue