diff --git a/src/client.cpp b/src/client.cpp index 273bb2a98b..b492d07875 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -24,6 +24,7 @@ #include "client.h" +#include "compressor.h" /* Implementation *************************************************************/ CClient::CClient ( const quint16 iPortNumber, @@ -54,6 +55,8 @@ CClient::CClient ( const quint16 iPortNumber, iSndCrdFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ), bSndCrdConversionBufferRequired ( false ), iSndCardMonoBlockSizeSamConvBuff ( 0 ), + fAudioInputPeak ( 0 ), + fAudioOutputPeak ( 0 ), bFraSiFactPrefSupported ( false ), bFraSiFactDefSupported ( false ), bFraSiFactSafeSupported ( false ), @@ -1024,13 +1027,30 @@ void CClient::ProcessAudioDataIntern ( CVector& vecfStereoSndCrd ) for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) { - // clip samples for stereo pan mode - vecfStereoSndCrd[i] = ClipFloat ( - fGainL * vecfStereoSndCrd[j] + fGainR * vecfStereoSndCrd[j + 1] ); + vecfStereoSndCrd[i] = + fGainL * vecfStereoSndCrd[j] + fGainR * vecfStereoSndCrd[j + 1]; } } } + // apply local audio input compressor + if ( eAudioChannelConf == CC_STEREO ) + { + for ( i = 0; i < iMonoBlockSizeSam; i++ ) + { + stereoCompressor(SYSTEM_SAMPLE_RATE_HZ, fAudioInputPeak, + vecfStereoSndCrd[2 * i], vecfStereoSndCrd[2 * i + 1]); + } + } + else + { + for ( i = 0; i < iMonoBlockSizeSam; i++ ) + { + monoCompressor(SYSTEM_SAMPLE_RATE_HZ, fAudioInputPeak, + vecfStereoSndCrd[i]); + } + } + // Support for mono-in/stereo-out mode: Per definition this mode works in // full stereo mode at the transmission level. The only thing which is done // is to mix both sound card inputs together and then put this signal on @@ -1122,8 +1142,8 @@ void CClient::ProcessAudioDataIntern ( CVector& vecfStereoSndCrd ) { for ( i = 0; i < iStereoBlockSizeSam; i++ ) { - vecfStereoSndCrd[i] = ClipFloat ( - vecfStereoSndCrd[i] + vecfStereoSndCrdMuteStream[i] * fMuteOutStreamGain ); + vecfStereoSndCrd[i] = + vecfStereoSndCrd[i] + vecfStereoSndCrdMuteStream[i] * fMuteOutStreamGain; } } @@ -1147,6 +1167,13 @@ void CClient::ProcessAudioDataIntern ( CVector& vecfStereoSndCrd ) vecfStereoSndCrd.Reset ( 0 ); } + // apply local audio output compressor + for ( i = 0; i < iMonoBlockSizeSam; i++ ) + { + stereoCompressor(SYSTEM_SAMPLE_RATE_HZ, fAudioOutputPeak, + vecfStereoSndCrd[2 * i], vecfStereoSndCrd[2 * i + 1]); + } + // update socket buffer size Channel.UpdateSocketBufferSize(); diff --git a/src/client.h b/src/client.h index ede8e9b90f..937835d074 100755 --- a/src/client.h +++ b/src/client.h @@ -344,6 +344,8 @@ class CClient : public QObject CVector vecDataConvBuf; CVector vecfStereoSndCrdMuteStream; CVector vecZeros; + float fAudioInputPeak; + float fAudioOutputPeak; bool bFraSiFactPrefSupported; bool bFraSiFactDefSupported; diff --git a/src/compressor.h b/src/compressor.h new file mode 100644 index 0000000000..c2a0e052a2 --- /dev/null +++ b/src/compressor.h @@ -0,0 +1,91 @@ +/* + ZynAddSubFX - a software synthesizer + + Compressor.h - simple audio compressor macros + Copyright (C) 2016 Hans Petter Selasky + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. +*/ + +#pragma once + +static inline bool +floatIsValid(const float x) +{ + const float __r = x * 0.0f; + return (__r == 0.0f || __r == -0.0f); +} + +#define stereoCompressor(div,pv,l,r) do { \ + /* \ + * Don't max the output range to avoid \ + * overflowing sample rate conversion and \ + * equalizer filters in the DSP's output \ + * path. Keep one 10th, 1dB, reserved. \ + */ \ + static constexpr float __limit = \ + 1.0f - (1.0f / 10.0f); \ + float __peak; \ + \ + /* sanity checks */ \ + __peak = (pv); \ + if (!floatIsValid(__peak)) \ + __peak = 0.0; \ + if (!floatIsValid(l)) \ + (l) = 0.0; \ + if (!floatIsValid(r)) \ + (r) = 0.0; \ + /* compute maximum */ \ + if ((l) < -__peak) \ + __peak = -(l); \ + else if ((l) > __peak) \ + __peak = (l); \ + if ((r) < -__peak) \ + __peak = -(r); \ + else if ((r) > __peak) \ + __peak = (r); \ + /* compressor */ \ + if (__peak > __limit) { \ + (l) /= __peak; \ + (r) /= __peak; \ + (l) *= __limit; \ + (r) *= __limit; \ + __peak -= __peak / (div); \ + } \ + (pv) = __peak; \ +} while (0) + +#define monoCompressor(div,pv,l) do { \ + /* \ + * Don't max the output range to avoid \ + * overflowing sample rate conversion and \ + * equalizer filters in the DSP's output \ + * path. Keep one 10th, 1dB, reserved. \ + */ \ + static constexpr float __limit = \ + 1.0f - (1.0f / 10.0f); \ + float __peak; \ + \ + /* sanity checks */ \ + __peak = (pv); \ + if (!floatIsValid(__peak)) \ + __peak = 0.0; \ + if (!floatIsValid(l)) \ + (l) = 0.0; \ + /* compute maximum */ \ + if ((l) < -__peak) \ + __peak = -(l); \ + else if ((l) > __peak) \ + __peak = (l); \ + /* compressor */ \ + if (__peak > __limit) { \ + (l) /= __peak; \ + (l) *= __limit; \ + __peak -= __peak / (div); \ + } \ + (pv) = __peak; \ +} while (0) + diff --git a/src/server.cpp b/src/server.cpp index efd514b08d..da5c310a1f 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -24,6 +24,7 @@ #include "server.h" +#include "compressor.h" // CHighPrecisionTimer implementation ****************************************** #ifdef _WIN32 @@ -339,8 +340,8 @@ CServer::CServer ( const int iNewMaxNumChan, vecvecfGains.Init ( iMaxNumChannels ); vecvecfPannings.Init ( iMaxNumChannels ); vecvecfData.Init ( iMaxNumChannels ); - vecvecfSendData.Init ( iMaxNumChannels ); vecvecfIntermediateProcBuf.Init ( iMaxNumChannels ); + vecfOutputAudioPeak.Init ( iMaxNumChannels, 0 ); vecvecbyCodedData.Init ( iMaxNumChannels ); vecNumAudioChannels.Init ( iMaxNumChannels ); vecNumFrameSizeConvBlocks.Init ( iMaxNumChannels ); @@ -356,10 +357,6 @@ CServer::CServer ( const int iNewMaxNumChan, // we always use stereo audio buffers (which is the worst case) vecvecfData[i].Init ( 2 /* stereo */ * DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES /* worst case buffer size */ ); - // (note that we only allocate iMaxNumChannels buffers for the send - // and coded data because of the OMP implementation) - vecvecfSendData[i].Init ( 2 /* stereo */ * DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES /* worst case buffer size */ ); - // allocate worst case memory for intermediate processing buffers in float precision vecvecfIntermediateProcBuf[i].Init ( 2 /* stereo */ * DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES /* worst case buffer size */ ); @@ -1120,7 +1117,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, { int i, j, k, iUnused; CVector& vecfIntermProcBuf = vecvecfIntermediateProcBuf[iChanCnt]; // use reference for faster access - CVector& vecfSendData = vecvecfSendData[iChanCnt]; // use reference for faster access + float &fAudioPeak = vecfOutputAudioPeak[iChanCnt]; // get actual ID of current channel const int iCurChanID = vecChanIDsCurConChan[iChanCnt]; @@ -1183,11 +1180,9 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, // When adding multiple sound sources together // the resulting signal level may exceed the maximum // audio range which is from -1.0f to 1.0f inclusivly. - // Clip the intermediate sound buffer to be within - // the expected range for ( i = 0; i < iServerFrameSizeSamples; i++ ) { - vecfSendData[i] = ClipFloat ( vecfIntermProcBuf[i] ); + monoCompressor(SYSTEM_SAMPLE_RATE_HZ, fAudioPeak, vecfIntermProcBuf[i]); } } else @@ -1255,11 +1250,10 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, // When adding multiple sound sources together // the resulting signal level may exceed the maximum // audio range which is from -1.0f to 1.0f inclusivly. - // Clip the intermediate sound buffer to be within - // the expected range - for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + for ( i = 0; i < iServerFrameSizeSamples; i++ ) { - vecfSendData[i] = ClipFloat ( vecfIntermProcBuf[i] ); + stereoCompressor(SYSTEM_SAMPLE_RATE_HZ, fAudioPeak, + vecfIntermProcBuf[2 * i], vecfIntermProcBuf[2 * i + 1]); } } @@ -1303,12 +1297,12 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, // is false and the Get() function is not called at all. Therefore if the buffer is not needed // we do not spend any time in the function but go directly inside the if condition. if ( ( vecUseDoubleSysFraSizeConvBuf[iChanCnt] == 0 ) || - DoubleFrameSizeConvBufOut[iCurChanID].Put ( vecfSendData, SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt] ) ) + DoubleFrameSizeConvBufOut[iCurChanID].Put ( vecfIntermProcBuf, SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt] ) ) { if ( vecUseDoubleSysFraSizeConvBuf[iChanCnt] != 0 ) { // get the large frame from the conversion buffer - DoubleFrameSizeConvBufOut[iCurChanID].GetAll ( vecfSendData, DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt] ); + DoubleFrameSizeConvBufOut[iCurChanID].GetAll ( vecfIntermProcBuf, DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt] ); } for ( int iB = 0; iB < vecNumFrameSizeConvBlocks[iChanCnt]; iB++ ) @@ -1321,7 +1315,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, opus_custom_encoder_ctl ( pCurOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iClientFrameSizeSamples ) ) ); iUnused = opus_custom_encode_float ( pCurOpusEncoder, - &vecfSendData[iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]], + &vecfIntermProcBuf[iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]], iClientFrameSizeSamples, &vecvecbyCodedData[iChanCnt][0], iCeltNumCodedBytes ); diff --git a/src/server.h b/src/server.h index d4d6c7c1c2..e7ea521d2c 100644 --- a/src/server.h +++ b/src/server.h @@ -361,8 +361,8 @@ class CServer : CVector vecNumFrameSizeConvBlocks; CVector vecUseDoubleSysFraSizeConvBuf; CVector vecAudioComprType; - CVector > vecvecfSendData; CVector > vecvecfIntermediateProcBuf; + CVector vecfOutputAudioPeak; CVector > vecvecbyCodedData; // Channel levels