Optimize bDelayPan and divisions#3706
Conversation
| if ( ( i & 1 ) == 0 ) | ||
| { | ||
| // if even : left channel | ||
| vecfIntermProcBuf[i] += vecsData[i] * fGainL; | ||
| } | ||
| else | ||
| { | ||
| // if odd : right channel | ||
| vecfIntermProcBuf[i] += vecsData[i] * fGainR; | ||
| } |
There was a problem hiding this comment.
Got rid of modulo computation
c6038cc to
ccd6f5e
Compare
ann0see
left a comment
There was a problem hiding this comment.
The function is still way too complex imo.
|
To avoid having the clipping to short twice, we may want someting like Maybe... |
| const float fGain = vecvecfGains[iChanCnt][j]; | ||
| const float fPan = bDelayPan ? 0.5f : vecvecfPannings[iChanCnt][j]; | ||
|
|
||
| // calculate combined gain/pan for each stereo channel where we define |
There was a problem hiding this comment.
This could do with expanding.
I thought the Server up-mixed all incoming channels to stereo (so it could treat them the same for all connected clients), did all the mixes in stereo - to allow a single path (either delay pan or not) - and then down-mixed to mono for clients with mono out.
The changes here seem to do something different but I don't quite follow it.
There was a problem hiding this comment.
I think there's a mono only code path?
There was a problem hiding this comment.
There is in this new flow. What I mean is I don't understand
a) exactly what was happening before this new mono flow was introduced
b) exactly what is happening now
c) whether it's therefore having the right effect
So it needs a bigger comment in the code to explain it.
Well... it needs a good answer 😄 and then a good enough comment to explain the new code.
There was a problem hiding this comment.
New flow:
if delay pan:
// code for delay pan in mono or stereo mode
// not sure why there is even delay panning in mono mode but ok.
else:
// mono or stereo mixing
old flow:
if mono:
if delay pan:
// delay pan mixing
else:
// non delay pan mixing
else:
if delay pan:
// parity based computation for delay pan
else:
// parity based mixing
Please check the code itself, not the diff as this is confusing
There was a problem hiding this comment.
// not sure why there is even delay panning in mono mode but ok.
A mono client can pan any client in their mix and it will affect the output. ("I want more of their left channel in my mix down")
A stereo client can pan any client in their mix and it will affect the output. ("I want them moved left in my mix")
There was a problem hiding this comment.
Please check the code itself, not the diff as this is confusing
I can see it moves the two ifs inside out. But I can't see where the panning is applied on the source channel for a mono target now.
There was a problem hiding this comment.
Not sure I understand correctly. I didn't change the semantics I believe?
I believe that a mono target may not have panning?
There was a problem hiding this comment.
A mono client can pan any client in their mix and it will affect the output.
If the source is stereo, it has two channels. The mono target can, therefore, adjust the mix - either with delaypan or not - that gets then mixed down to mono.
I think - but I'm not sure - the change here inverts the logic and prevents that.
There was a problem hiding this comment.
Ok. Let's disect the logic.
vecNumAudioChannels[iChanCnt] == 1 => mono target channel. I only replaced /2 by *0.5f which is equivalent
vecNumAudioChannels[iChanCnt] != 1 => stereo target channel
Let j be arbitrary between 0 and iNumClients -1
case bDelayPan == false and vecNumAudioChannels[j] == 1 ( isMono == true):
-
Old version skips setting iPanDel, iPanL, iPanR statement
-
new version skips entire if (bDelayPan) section moving to the else block.
-
Since cNumAudioChannels[j] == 1 , old version enters mono: "copy same mono data" ... loop.
-
Since isMono == true, new version enters "copy same mono data" ... loop
-
since bDelayPan == false, old version skips // pan address shift and executes
vecfIntermProcBuf[k] += vecsData[i] * fGainL [...] -
since bDelayPan == false, and the entire outer conditional is already skipped before, new version checks (isMono) and executes
vecfIntermProcBuf[k] += vecsData[i] * fGainL
=> looks valid
case bDelayPan == false and vecNumAudioChannels[j] != 1 ( isMono == false):
-
old version enters
//stereo. -
new version enters else and checks isMono -> ` // left/right channel vecfIntermProcBuf[i] += vecsData[i] * fGainL;```
-
old version checks bDelayPan == false, and thus checks
if ( ( i & 1 ) == 0 ) [...]-> optimised in the new version
This also looks valid?
case bDelayPan == true and vecNumAudioChannels[j] == 1 ( isMono == true):
-
old version enters
// mono: copy same[]... -
new version enters
iPanDel[...] -
old version enters
// pan address shift [...] ilpan = I- iPanDelL... -
new version enters same
==> also looks valid
case bDelayPan == true and vecNumAudioChannels[j] != 1 ( isMono == false)
New:
// stereo
for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i += 2 )
{
// pan address shift
iLpan = i - 2 * iPanDelL;
Old (unoptimised)
for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ )
{
// left/right channel
if ( bDelayPan )
{
// pan address shift
if ( ( i & 1 ) == 0 )
{
iPan = i - 2 * iPanDelL; // if even : left channel
}
Also looks valid.
Could you please specify the variable state which you think is invalid?
There was a problem hiding this comment.
Since cNumAudioChannels[j] == 1 , old version enters mono: "copy same mono data" ... loop.
Uh? How did you get that? vecNumAudioChannels[j] is not equivalent to cNumAudioChannels[j] so I don't see where that came from.
This is what I mean -- I think cNumAudioChannels[j] is where the sound comes from and vecNumAudioChannels[j] is where the sound goes to. You look at vecNumAudioChannels[j] to determine isMono and then assume that means every connected client is mono for that output.
| for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 ) | ||
| { | ||
| vecfIntermProcBuf[i] += ( static_cast<float> ( vecsData[k] ) + vecsData[k + 1] ) / 2; | ||
| vecfIntermProcBuf[i] += ( static_cast<float> ( vecsData[k] ) + vecsData[k + 1] ) * 0.5f; |
There was a problem hiding this comment.
Couldn't we just right shift the short to halve it?
| for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 ) | ||
| { | ||
| vecfIntermProcBuf[i] += fGain * ( static_cast<float> ( vecsData[k] ) + vecsData[k + 1] ) / 2; | ||
| vecfIntermProcBuf[i] += fGain * ( static_cast<float> ( vecsData[k] ) + vecsData[k + 1] ) * 0.5f; |
There was a problem hiding this comment.
I don't know if we do this right. Shouldn't we normalize the floats to -1 .. 1 to get most out of the float precision? And even if we did we wouldn't mitigate clipping because adding many channels might still clip while we don't scale back or check, if clipping occurred. When converting back to short we don't care for the actual range, we just hard clip everything back into the short range. That doesn't seem right at all to me.
Short description of changes
Moves delay pan checking slightly out of loop as this setting does not change often.
Moreover replaces some divisions with *0.5 and remove some modulo computations
CHANGELOG: Server: Optimize mixing
Context: Fixes an issue?
Related to: #3662
Does this change need documentation? What needs to be documented and how?
No
Status of this Pull Request
Ready for review
What is missing until this pull request can be merged?
Tested and it seems to work, I still would like an external test.
Checklist