Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 46 additions & 41 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients
// stereo: apply stereo-to-mono attenuation
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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just right shift the short to halve it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not with a float. Doing so with an int before conversion would reduce the resolution by 1 bit.

Doing * 0.5f or / 2.0f with a float preserves the full resolution in the mantissa and just changes the exponent. I wouldn't be surprised if the compiler produces exactly the same code in both cases.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ 2.0f feels more intuitive, somehow. If there's no execution speed difference, I'd rather keep it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unlikely to be different - however this may depend on the compiler. 0.5f is easier for the compiler to optimise than / 2, I'd argue.

}
}
}
Expand All @@ -1001,7 +1001,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients
// stereo: apply stereo-to-mono attenuation
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;
Copy link
Copy Markdown
Member

@dingodoppelt dingodoppelt May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the way the signal summation work was progressive:

  • one client at 1.0f -> 1.0f / 1.0f -> 1.0f
  • two clients at 1.0f -> ( 1.0f + 1.0f ) / 2.0f -> 1.0f

etc. Clipping should never happen. However, that's ignoring panning, of course. If you multiple L 1.0f by 1.1f and divide R 1.0f by 1.1f, you will get clipping. I guess - as panning is on the client GUI as is level - this is seen as something the client user has under their control: drop the channel level to allow for panning.

As with all the audio code -- I could easily be wrong. I can't follow it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Floats are internally normalised anyway, having the max available precision in the mantissa, and then scaling with the exponent. This all happens internally within the floating-point engine.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with all the audio code -- I could easily be wrong. I can't follow it.

Which proves that it must be refactored...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Floats are internally normalised anyway, having the max available precision in the mantissa, and then scaling with the exponent. This all happens internally within the floating-point engine.

I think normalization in the float representation only means to have only one non-zero digit in front of the mantissa and therefore scaling the exponent. I think we still miss out on precision because we convert big numbers to float while float has the most precision up to values of 2 so normalizing in an audio DSP sense means scaling to -1 .. 1.

I thought the way the signal summation work was progressive:

one client at 1.0f -> 1.0f / 1.0f -> 1.0f
two clients at 1.0f -> ( 1.0f + 1.0f ) / 2.0f -> 1.0f

etc. Clipping should never happen.

I couldn't find anything which takes the number of clients into account, i.e. there is no scaling happening. We just keep adding and adding...
I think we actually do clip all the time. I added a qDebug() to the Float2Short function and, oh boy is this thing clipping with only two clients (one playback, one drums)

}
}
}
Expand All @@ -1020,7 +1020,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients
const int maxPanDelay = MAX_DELAY_PANNING_SAMPLES;

int iPanDelL = 0, iPanDelR = 0, iPanDel;
int iLpan, iRpan, iPan;
int iLpan, iRpan;

for ( j = 0; j < iNumClients; j++ )
{
Expand All @@ -1036,21 +1036,20 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients
const float fGainL = MathUtils::GetLeftPan ( fPan, false ) * fGain;
const float fGainR = MathUtils::GetRightPan ( fPan, false ) * fGain;

const bool isMono = vecNumAudioChannels[j] == 1;

if ( bDelayPan )
{
iPanDel = lround ( (float) ( 2 * maxPanDelay - 2 ) * ( vecvecfPannings[iChanCnt][j] - 0.5f ) );
iPanDelL = ( iPanDel > 0 ) ? iPanDel : 0;
iPanDelR = ( iPanDel < 0 ) ? -iPanDel : 0;
}

if ( vecNumAudioChannels[j] == 1 )
{
// mono: copy same mono data in both out stereo audio channels
for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 )
if ( isMono )
{
// left/right channel
if ( bDelayPan )
// mono: copy same mono data in both out stereo audio channels
for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 )
{
// left/right channel
// pan address shift

// left channel
Expand Down Expand Up @@ -1079,54 +1078,60 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients
vecfIntermProcBuf[k + 1] += vecsData[iRpan] * fGainR;
}
}
else
{
vecfIntermProcBuf[k] += vecsData[i] * fGainL;
vecfIntermProcBuf[k + 1] += vecsData[i] * fGainR;
}
}
}
else
{
// stereo
for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ )
else
{
// left/right channel
if ( bDelayPan )
// stereo
for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i += 2 )
{
// pan address shift
if ( ( i & 1 ) == 0 )

iLpan = i - 2 * iPanDelL; // left channel
iRpan = ( i + 1 ) - 2 * iPanDelR; // right channel

// interleaved channels
if ( iLpan < 0 )
{
iPan = i - 2 * iPanDelL; // if even : left channel
// get from second
iLpan = iLpan + 2 * iServerFrameSizeSamples;
vecfIntermProcBuf[i] += vecsData2[iLpan] * fGain;
}
else
{
iPan = i - 2 * iPanDelR; // if odd : right channel
vecfIntermProcBuf[i] += vecsData[iLpan] * fGain;
}
// interleaved channels
if ( iPan < 0 )

if ( iRpan < 0 )
{
// get from second
iPan = iPan + 2 * iServerFrameSizeSamples;
vecfIntermProcBuf[i] += vecsData2[iPan] * fGain;
iRpan = iRpan + 2 * iServerFrameSizeSamples;
vecfIntermProcBuf[i + 1] += vecsData2[iRpan] * fGain;
}
else
{
vecfIntermProcBuf[i] += vecsData[iPan] * fGain;
vecfIntermProcBuf[i + 1] += vecsData[iRpan] * fGain;
}
}
else
}
}
else
{
if ( isMono )
{
// mono: copy same mono data in both out stereo audio channels
for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 )
{
if ( ( i & 1 ) == 0 )
{
// if even : left channel
vecfIntermProcBuf[i] += vecsData[i] * fGainL;
}
else
{
// if odd : right channel
vecfIntermProcBuf[i] += vecsData[i] * fGainR;
}
Comment on lines -1120 to -1129
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got rid of modulo computation

vecfIntermProcBuf[k] += vecsData[i] * fGainL;
vecfIntermProcBuf[k + 1] += vecsData[i] * fGainR;
}
}
else
{
for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i += 2 )
{
// left/right channel
vecfIntermProcBuf[i] += vecsData[i] * fGainL;
vecfIntermProcBuf[i + 1] += vecsData[i + 1] * fGainR;
}
}
}
Expand Down
Loading