From 01181defe623687cece8f8a52826c89ae302be49 Mon Sep 17 00:00:00 2001 From: Hartmnt Date: Tue, 10 Oct 2023 17:49:05 +0000 Subject: [PATCH 1/3] FIX(client,positional-audio): Fix ManualPlacement plugin orientation indicator Previously, the ManualPlacement plugin for the users position/orientation was pointing in the wrong (opposite) direction. This fix swaps the orientation of the sprite. It also set the default azimuth to 0 instead of 180 to make the orientation of the listener intuitive for the user by default. The new default azimuth will not change the logic of the plugin whatsoever. --- src/mumble/ManualPlugin.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mumble/ManualPlugin.cpp b/src/mumble/ManualPlugin.cpp index 5c811e97af5..bf7260aa635 100644 --- a/src/mumble/ManualPlugin.cpp +++ b/src/mumble/ManualPlugin.cpp @@ -23,7 +23,7 @@ static QPointer< Manual > mDlg = nullptr; static bool bLinkable = false; static bool bActive = true; -static int iAzimuth = 180; +static int iAzimuth = 0; static int iElevation = 0; static const QString defaultContext = QString::fromLatin1("Mumble"); @@ -52,8 +52,8 @@ Manual::Manual(QWidget *p) : QDialog(p) { // The center of the indicator's circle will represent the current position indicator.addEllipse(QRectF(-indicatorDiameter / 2, -indicatorDiameter / 2, indicatorDiameter, indicatorDiameter)); // A line will indicate the indicator's orientation (azimuth) - indicator.moveTo(0, indicatorDiameter / 2); - indicator.lineTo(0, indicatorDiameter); + indicator.moveTo(0, -indicatorDiameter / 2); + indicator.lineTo(0, -indicatorDiameter); m_qgiPosition = m_qgsScene->addPath(indicator); From 42d56f73d159e11162d899d230eba80e714a1958 Mon Sep 17 00:00:00 2001 From: Hartmnt Date: Tue, 10 Oct 2023 17:59:26 +0000 Subject: [PATCH 2/3] FIX(client,positional-audio): Allow positional volume of 0 in certain circumstances This commit does 4 things to improve a minimum positional volume of 0: 1) It checks, if the user selected a minimum volume of 0 and, if the audio source is exceeding the maximum distance. When both are true, it hard-codes the sample volume to 0 irrespective of other settings. 2) It changes the minimum gain calculation to be 25% of the maximum positional volume (irrespective of listener direction). This will make sure that low volume situations will not be boosted by the arbitrary 1/20 minimum that was used before, while still making sure that both ears receive some sound in most cases. 3) It reduces the min volume clamping from 0.01 to 0.005. This is done because with a minimum volume of 0 a hard cut could be heard when using the new logic from 1). By reducing the minimal clamp, we reduce the volume difference between the last possible step and 0. The value 0.005 was found experimentally with the manual placement plugin, a maximum distance of 20, and a minimum volume of 0. 4) It sets the volume gain factor to 1, if the listener is (almost) inside an audio source. --- src/mumble/AudioOutput.cpp | 68 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/mumble/AudioOutput.cpp b/src/mumble/AudioOutput.cpp index 3994491d15b..d8e80e9073a 100644 --- a/src/mumble/AudioOutput.cpp +++ b/src/mumble/AudioOutput.cpp @@ -100,18 +100,30 @@ AudioOutput::~AudioOutput() { float AudioOutput::calcGain(float dotproduct, float distance) { // dotproduct is in the range [-1, 1], thus we renormalize it to the range [0, 1] float dotfactor = (dotproduct + 1.0f) / 2.0f; - float att; + // Volume on the ear opposite to the sound should never reach 0 in the real world. + // Therefore, we define the minimum volume as 1/4th of the theoretical maximum (ignoring + // the sound direction but taking distance into account) for _any_ ear. + const float offset = (1.0f - dotfactor) * 0.25f; + dotfactor += offset; + + float att; - // No distance attenuation - if (Global::get().s.fAudioMaxDistVolume > 0.99f) { - att = qMin(1.0f, dotfactor + Global::get().s.fAudioBloom); + if (distance < 0.01f) { + // Listener is "inside" source -> no attenuation + // Without this extra check, we would have a dotfactor of 0.5 + // despite being numerically inside the source leading to a loss + // of volume. + att = 1.0f; + } else if (Global::get().s.fAudioMaxDistVolume > 0.99f) { + // User selected no distance attenuation + att = std::min(1.0f, dotfactor + Global::get().s.fAudioBloom); } else if (distance < Global::get().s.fAudioMinDistance) { // Fade in blooming as soon as the sound source enters fAudioMinDistance and increase it to its full // capability when the audio source is at the same position as the local player float bloomfac = Global::get().s.fAudioBloom * (1.0f - distance / Global::get().s.fAudioMinDistance); - att = qMin(1.0f, bloomfac + dotfactor); + att = std::min(1.0f, bloomfac + dotfactor); } else { float datt; @@ -119,8 +131,9 @@ float AudioOutput::calcGain(float dotproduct, float distance) { datt = Global::get().s.fAudioMaxDistVolume; } else { float mvol = Global::get().s.fAudioMaxDistVolume; - if (mvol < 0.01f) - mvol = 0.01f; + if (mvol < 0.005f) { + mvol = 0.005f; + } float drel = (distance - Global::get().s.fAudioMinDistance) / (Global::get().s.fAudioMaxDistance - Global::get().s.fAudioMinDistance); @@ -660,21 +673,30 @@ bool AudioOutput::mix(void *outbuff, unsigned int frameCount) { } } + const bool isAudible = + (Global::get().s.fAudioMaxDistVolume > 0) || (len < Global::get().s.fAudioMaxDistance); + for (unsigned int s = 0; s < nchan; ++s) { const float dot = bSpeakerPositional[s] ? connectionVec.x * speaker[s * 3 + 0] + connectionVec.y * speaker[s * 3 + 1] + connectionVec.z * speaker[s * 3 + 2] : 1.0f; - // Volume on the ear opposite to the sound should never reach 0 in the real world. - // The gain is multiplied by 19/20 and 1/20 is added. This will have the effect - // of bringing the lowest value up to 1/20, while keeping the highest value at 1. - // E.g. calcGain() = 1; 1 * 19/20 + 1/20 = 0.95 + 0.05 = 1 - // calcGain() = 0; 0 * 19/20 + 1/20 = 0 + 0.05 = 0.05 - const float str = svol[s] * (1 / 20.0 + (19 / 20.0) * calcGain(dot, len)) * volumeAdjustment; + float channelVol; + if (isAudible) { + // In the current contex, we know that sound reaches at least one ear. + channelVol = svol[s] * calcGain(dot, len) * volumeAdjustment; + } else { + // The user has set the minimum positional volume to 0 and this sound source + // is exceeding the positional volume range. This means that the sound is completely + // inaudible at the current position. We therefore set the volume the to 0, + // making sure the user really can not hear any audio from that source. + channelVol = 0; + } + float *RESTRICT o = output + s; - const float old = (buffer->pfVolume[s] >= 0.0f) ? buffer->pfVolume[s] : str; - const float inc = (str - old) / static_cast< float >(frameCount); - buffer->pfVolume[s] = str; + const float old = (buffer->pfVolume[s] >= 0.0f) ? buffer->pfVolume[s] : channelVol; + const float inc = (channelVol - old) / static_cast< float >(frameCount); + buffer->pfVolume[s] = channelVol; // Calculates the ITD offset of the audio data this frame. // Interaural Time Delay (ITD) is a small time delay between your ears @@ -692,10 +714,10 @@ bool AudioOutput::mix(void *outbuff, unsigned int frameCount) { const float incOffset = (offset - oldOffset) / static_cast< float >(frameCount); buffer->piOffset[s] = offset; /* - qWarning("%d: Pos %f %f %f : Dot %f Len %f Str %f", s, speaker[s*3+0], - speaker[s*3+1], speaker[s*3+2], dot, len, str); + qWarning("%d: Pos %f %f %f : Dot %f Len %f ChannelVol %f", s, speaker[s*3+0], + speaker[s*3+1], speaker[s*3+2], dot, len, channelVol); */ - if ((old >= 0.00000001f) || (str >= 0.00000001f)) { + if ((old >= 0.00000001f) || (channelVol >= 0.00000001f)) { for (unsigned int i = 0; i < frameCount; ++i) { unsigned int currentOffset = oldOffset + incOffset * i; if (speech && speech->bStereo) { @@ -714,8 +736,8 @@ bool AudioOutput::mix(void *outbuff, unsigned int frameCount) { // Mix the current audio source into the output by adding it to the elements of the output buffer after // having applied a volume adjustment for (unsigned int s = 0; s < nchan; ++s) { - const float str = svol[s] * volumeAdjustment; - float *RESTRICT o = output + s; + const float channelVol = svol[s] * volumeAdjustment; + float *RESTRICT o = output + s; if (buffer->bStereo) { // Linear-panning stereo stream according to the projection of fSpeaker vector on left-right // direction. @@ -723,10 +745,10 @@ bool AudioOutput::mix(void *outbuff, unsigned int frameCount) { for (unsigned int i = 0; i < frameCount; ++i) o[i * nchan] += (pfBuffer[2 * i] * fStereoPanningFactor[2 * s + 0] + pfBuffer[2 * i + 1] * fStereoPanningFactor[2 * s + 1]) - * str; + * channelVol; } else { for (unsigned int i = 0; i < frameCount; ++i) - o[i * nchan] += pfBuffer[i] * str; + o[i * nchan] += pfBuffer[i] * channelVol; } } } From 7f510bc175ad1d71686865e6690da90ead71ee86 Mon Sep 17 00:00:00 2001 From: Hartmnt Date: Tue, 10 Oct 2023 22:03:31 +0000 Subject: [PATCH 3/3] FIX(client,positional-audio): Fix positional minimum and maximum distance constraints 8d7e1b52c29 added constraints to the positional distance sliders and spinner boxes. It was decided that the maximum distance must always be at least 1m larger than the minimum and vice versa. However, the methods which update the slider value were - incorrectly - using +1 and -1, which only equates to 0.1m in slide space. The subsequent update happening in the methods, which update the spinner boxes, were now using the incorrect 0.1m change in the checks and in turn adding or subtracting 1 in spinner box space (which correctly equates to 1m in spinner box space). Because of the order of loading operations, only the minimum slider is affected by the problem: It would reduce by 0.9m everytime the options dialog was loaded (and saved). This commit fixes the problem by adding and subtracting 10 in two of the methods which is 1m in slider space. It also slightly changes the wording of the comments --- src/mumble/AudioConfigDialog.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mumble/AudioConfigDialog.cpp b/src/mumble/AudioConfigDialog.cpp index 0ef432dc978..07871cfceab 100644 --- a/src/mumble/AudioConfigDialog.cpp +++ b/src/mumble/AudioConfigDialog.cpp @@ -866,22 +866,22 @@ void AudioOutputDialog::on_qsbMinimumDistance_valueChanged(double value) { qsMinDistance->setValue(value * 10); // Ensure that max distance is always a least 1m larger than min distance - qsMaxDistance->setValue(std::max(qsMaxDistance->value(), static_cast< int >(value * 10) + 1)); + qsMaxDistance->setValue(std::max(qsMaxDistance->value(), static_cast< int >(value * 10) + 10)); } void AudioOutputDialog::on_qsMaxDistance_valueChanged(int value) { QSignalBlocker blocker(qsbMaximumDistance); qsbMaximumDistance->setValue(value / 10.0f); - // Ensure that max distance is always a least 1m larger than min distance + // Ensure that min distance is always a least 1m less than max distance qsbMinimumDistance->setValue(std::min(qsbMinimumDistance->value(), (value / 10.0) - 1)); } void AudioOutputDialog::on_qsbMaximumDistance_valueChanged(double value) { QSignalBlocker blocker(qsMaxDistance); qsMaxDistance->setValue(value * 10); - // Ensure that max distance is always a least 1m larger than min distance - qsMinDistance->setValue(std::min(qsMinDistance->value(), static_cast< int >(value * 10) - 1)); + // Ensure that min distance is always a least 1m less than max distance + qsMinDistance->setValue(std::min(qsMinDistance->value(), static_cast< int >(value * 10) - 10)); } void AudioOutputDialog::on_qsMinimumVolume_valueChanged(int value) {