From e2161d66917b9c364d905004cb682cde34ef7499 Mon Sep 17 00:00:00 2001 From: Hartmnt Date: Tue, 10 Oct 2023 17:59:26 +0000 Subject: [PATCH] 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 | 65 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/src/mumble/AudioOutput.cpp b/src/mumble/AudioOutput.cpp index 3994491d15b..ffd28b5b091 100644 --- a/src/mumble/AudioOutput.cpp +++ b/src/mumble/AudioOutput.cpp @@ -100,18 +100,27 @@ 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 + att = 1.0f; + } else if (Global::get().s.fAudioMaxDistVolume > 0.99f) { + // 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 +128,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 +670,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 +711,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 +733,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 +742,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; } } }