diff --git a/ChangeLog b/ChangeLog index 557d4e4ef0..27a52fcc7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,8 @@ ### 3.7.0dev <- NOTE: the release version number will be 3.7.1 ### +- Added Pan-Delay code by Detlef Hennings, www.eclim.de + - Automatic channel fader adjustment simplifies mixer setup by using the channel level meters (#1071). (contributed by @JohannesBrx) diff --git a/src/global.h b/src/global.h index 28ff6444b6..044fb1c9a6 100755 --- a/src/global.h +++ b/src/global.h @@ -93,6 +93,9 @@ LED bar: lbr #define SYSTEM_FRAME_SIZE_SAMPLES 64 #define DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ( 2 * SYSTEM_FRAME_SIZE_SAMPLES ) +// additional buffer for delay panning +#define MAX_DELAY_PANNING_SAMPLES 64 + // default server address and port numbers #define DEFAULT_SERVER_ADDRESS "anygenre1.jamulus.io" #define DEFAULT_PORT_NUMBER 22124 diff --git a/src/main.cpp b/src/main.cpp index 4847b701c6..5484a63b85 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,6 +69,7 @@ int main ( int argc, char** argv ) bool bMuteStream = false; bool bMuteMeInPersonalMix = false; bool bDisableRecording = false; + bool bDelayPan = false; bool bNoAutoJackConnect = false; bool bUseTranslation = true; bool bCustomPortNumberGiven = false; @@ -381,6 +382,17 @@ int main ( int argc, char** argv ) continue; } + // Enable delay panning on startup ---------------------------------------- + if ( GetFlagArgument ( argv, + i, + "-P", + "--delaypan" ) ) + { + bDelayPan = true; + qInfo() << "- starting with delay panning"; + CommandLineOptions << "--delaypan"; + continue; + } // Central server ------------------------------------------------------ if ( GetStringArgument ( argc, @@ -742,6 +754,7 @@ int main ( int argc, char** argv ) bUseDoubleSystemFrameSize, bUseMultithreading, bDisableRecording, + bDelayPan, eLicenceType ); #ifndef HEADLESS @@ -845,6 +858,7 @@ QString UsageArguments ( char **argv ) " -m, --htmlstatus enable HTML status file, set file name\n" " -o, --serverinfo infos of this server in the format:\n" " [name];[city];[country as QLocale ID]\n" + " -P, --delaypan start with delay panning enabled\n" " -R, --recording sets directory to contain recorded jams\n" " --norecord disables recording (when enabled by default by -R)\n" " -s, --server start server\n" diff --git a/src/server.cpp b/src/server.cpp old mode 100755 new mode 100644 index bd9635882f..04bf53dbc9 --- a/src/server.cpp +++ b/src/server.cpp @@ -233,6 +233,7 @@ CServer::CServer ( const int iNewMaxNumChan, const bool bNUseDoubleSystemFrameSize, const bool bNUseMultithreading, const bool bDisableRecording, + const bool bNDelayPan, const ELicenceType eNLicenceType ) : bUseDoubleSystemFrameSize ( bNUseDoubleSystemFrameSize ), bUseMultithreading ( bNUseMultithreading ), @@ -252,6 +253,7 @@ CServer::CServer ( const int iNewMaxNumChan, &ConnLessProtocol ), bDisableRecording ( bDisableRecording ), bAutoRunMinimized ( false ), + bDelayPan ( bNDelayPan ), eLicenceType ( eNLicenceType ), bDisconnectAllClientsOnQuit ( bNDisconnectAllClientsOnQuit ), pSignalHandler ( CSignalHandler::getSingletonP() ) @@ -330,7 +332,6 @@ CServer::CServer ( const int iNewMaxNumChan, iServerFrameSizeSamples = SYSTEM_FRAME_SIZE_SAMPLES; } - // To avoid audio clitches, in the entire realtime timer audio processing // routine including the ProcessData no memory must be allocated. Since we // do not know the required sizes for the vectors, we allocate memory for @@ -341,6 +342,7 @@ CServer::CServer ( const int iNewMaxNumChan, vecvecfGains.Init ( iMaxNumChannels ); vecvecfPannings.Init ( iMaxNumChannels ); vecvecsData.Init ( iMaxNumChannels ); + vecvecsData2.Init ( iMaxNumChannels ); vecvecsSendData.Init ( iMaxNumChannels ); vecvecfIntermediateProcBuf.Init ( iMaxNumChannels ); vecvecbyCodedData.Init ( iMaxNumChannels ); @@ -357,6 +359,7 @@ CServer::CServer ( const int iNewMaxNumChan, // we always use stereo audio buffers (which is the worst case) vecvecsData[i].Init ( 2 /* stereo */ * DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES /* worst case buffer size */ ); + vecvecsData2[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) @@ -934,6 +937,16 @@ static CTimingMeas JitterMeas ( 1000, "test2.dat" ); JitterMeas.Measure(); // TE FutureSynchronizer.waitForFinished(); FutureSynchronizer.clearFutures(); } + if ( bDelayPan ) + { + for ( int i = 0; i < iNumClients; i++ ) + { + for ( int j = 0; j < 2 * (iServerFrameSizeSamples); j++ ) + { + vecvecsData2[i][j] = vecvecsData[i][j]; + } + } + } } else { @@ -1196,60 +1209,112 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, else { // Stereo target channel ----------------------------------------------- + + const int maxPanDelay = MAX_DELAY_PANNING_SAMPLES; + + int iLpan, iRpan, iPan; + for ( j = 0; j < iNumClients; j++ ) { // get a reference to the audio data and gain/pan of the current client const CVector& vecsData = vecvecsData[j]; + const CVector& vecsData2 = vecvecsData2[j]; + const float fGain = vecvecfGains[iChanCnt][j]; - const float fPan = vecvecfPannings[iChanCnt][j]; + const float fPan = bDelayPan ? 0.5f : vecvecfPannings[iChanCnt][j]; + const int iPanDel = lround( (float)( 2 * maxPanDelay - 2 ) * ( vecvecfPannings[iChanCnt][j] - 0.5f ) ); + const int iPanDelL = ( iPanDel > 0 ) ? iPanDel : 0; + const int iPanDelR = ( iPanDel < 0 ) ? -iPanDel : 0; // calculate combined gain/pan for each stereo channel where we define // the panning that center equals full gain for both channels const float fGainL = MathUtils::GetLeftPan ( fPan, false ) * fGain; const float fGainR = MathUtils::GetRightPan ( fPan, false ) * fGain; - // if channel gain is 1, avoid multiplication for speed optimization - if ( ( fGainL == 1.0f ) && ( fGainR == 1.0f ) ) + if ( vecNumAudioChannels[j] == 1 ) { - 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 ) { - // mono: copy same mono data in both out stereo audio channels - for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 ) + // left/right channel + if ( bDelayPan ) { - // left/right channel - vecfIntermProcBuf[k] += vecsData[i]; - vecfIntermProcBuf[k + 1] += vecsData[i]; + // pan address shift + + // left channel + iLpan = i - iPanDelL; + if ( iLpan < 0 ) + { + // get from second + iLpan = iLpan + iServerFrameSizeSamples; + vecfIntermProcBuf[k] += vecsData2[iLpan] * fGainL; + } + else + { + vecfIntermProcBuf[k] += vecsData[iLpan] * fGainL; + } + + // right channel + iRpan = i - iPanDelR; + if ( iRpan < 0 ) + { + // get from second + iRpan = iRpan + iServerFrameSizeSamples; + vecfIntermProcBuf[k + 1] += vecsData2[iRpan] * fGainR; + } + else + { + vecfIntermProcBuf[k + 1] += vecsData[iRpan] * fGainR; + } } - } - else - { - // stereo - for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) + else { - vecfIntermProcBuf[i] += vecsData[i]; + vecfIntermProcBuf[k] += vecsData[i] * fGainL; + vecfIntermProcBuf[k + 1] += vecsData[i] * fGainR; } } } else { - if ( vecNumAudioChannels[j] == 1 ) + // stereo + for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i++ ) { - // mono: copy same mono data in both out stereo audio channels - for ( i = 0, k = 0; i < iServerFrameSizeSamples; i++, k += 2 ) + // left/right channel + if ( bDelayPan ) { - // left/right channel - vecfIntermProcBuf[k] += vecsData[i] * fGainL; - vecfIntermProcBuf[k + 1] += vecsData[i] * fGainR; + // pan address shift + if ( (i & 1) == 0 ) + { + iPan = i - 2 * iPanDelL; // if even : left channel + } + else + { + iPan = i - 2 * iPanDelR; // if odd : right channel + } + // interleaved channels + if ( iPan < 0 ) + { + // get from second + iPan = iPan + 2 * iServerFrameSizeSamples; + vecfIntermProcBuf[i] += vecsData2[iPan] * fGain; + } + else + { + vecfIntermProcBuf[i] += vecsData[iPan] * fGain; + } } - } - else - { - // stereo - for ( i = 0; i < ( 2 * iServerFrameSizeSamples ); i += 2 ) + else { - // left/right channel - vecfIntermProcBuf[i] += vecsData[i] * fGainL; - vecfIntermProcBuf[i + 1] += vecsData[i + 1] * fGainR; + if ( (i & 1) == 0 ) + { + // if even : left channel + vecfIntermProcBuf[i] += vecsData[i] * fGainL; + } + else + { + // if odd : right channel + vecfIntermProcBuf[i] += vecsData[i] * fGainR; + } } } } diff --git a/src/server.h b/src/server.h old mode 100755 new mode 100644 index b4e78b4462..d9c7a331b3 --- a/src/server.h +++ b/src/server.h @@ -1,5 +1,5 @@ /******************************************************************************\ - * Copyright (c) 2004-2020 + * Copyright (c) 2004-2021 * * Author(s): * Volker Fischer @@ -183,6 +183,7 @@ class CServer : const bool bNUseDoubleSystemFrameSize, const bool bNUseMultithreading, const bool bDisableRecording, + const bool bNDelayPan, const ELicenceType eNLicenceType ); virtual ~CServer(); @@ -221,6 +222,9 @@ class CServer : void CreateAndSendRecorderStateForAllConChannels(); + // delay panning + void SetEnableDelayPanning ( bool bDelayPanningOn ) { bDelayPan = bDelayPanningOn; } + bool IsDelayPanningEnabled() { return bDelayPan; } // Server list management -------------------------------------------------- void UpdateServerList() { ServerListManager.Update(); } @@ -270,6 +274,9 @@ class CServer : void SetAutoRunMinimized ( const bool NAuRuMin ) { bAutoRunMinimized = NAuRuMin; } bool GetAutoRunMinimized() { return bAutoRunMinimized; } + int GetClientNumAudioChannels ( const int iChanNum ) + { return vecChannels[iChanNum].GetNumAudioChannels(); } + protected: // access functions for actual channels bool IsConnected ( const int iChanNum ) { return vecChannels[iChanNum].IsConnected(); } @@ -358,6 +365,7 @@ class CServer : CVector > vecvecfGains; CVector > vecvecfPannings; CVector > vecvecsData; + CVector > vecvecsData2; CVector vecNumAudioChannels; CVector vecNumFrameSizeConvBlocks; CVector vecUseDoubleSysFraSizeConvBuf; @@ -394,6 +402,9 @@ class CServer : // GUI settings bool bAutoRunMinimized; + // for delay panning + bool bDelayPan; + // messaging QString strWelcomeMessage; ELicenceType eLicenceType; diff --git a/src/serverdlg.cpp b/src/serverdlg.cpp old mode 100755 new mode 100644 index 1ddc15263d..fdf6c9a824 --- a/src/serverdlg.cpp +++ b/src/serverdlg.cpp @@ -219,11 +219,12 @@ CServerDlg::CServerDlg ( CServer* pNServP, } // set up list view for connected clients - lvwClients->setColumnWidth ( 0, 170 ); - lvwClients->setColumnWidth ( 1, 200 ); + lvwClients->setColumnWidth ( 0, 170 ); // 170 // IP:port + lvwClients->setColumnWidth ( 1, 200 ); // 200 // Name + lvwClients->setColumnWidth ( 2, 120 ); // 60 // Buf-Frames + lvwClients->setColumnWidth ( 3, 50 ); // Channels lvwClients->clear(); - // TEST workaround for resize problem of window after iconize in task bar lvwClients->setMinimumWidth ( 170 + 130 + 60 + 205 ); lvwClients->setMinimumHeight ( 140 ); @@ -315,6 +316,16 @@ lvwClients->setMinimumHeight ( 140 ); ModifyAutoStartEntry ( bCurAutoStartMinState ); #endif + // update delay panning check box + if ( pServer->IsDelayPanningEnabled() ) + { + chbEnableDelayPanning->setCheckState(Qt::Checked); + } + else + { + chbEnableDelayPanning->setCheckState(Qt::Unchecked); + } + // Recorder controls chbEnableRecorder->setChecked ( pServer->GetRecordingEnabled() ); edtCurrentSessionDir->setText ( "" ); @@ -382,6 +393,10 @@ lvwClients->setMinimumHeight ( 140 ); QObject::connect ( chbEnableRecorder, &QCheckBox::stateChanged, this, &CServerDlg::OnEnableRecorderStateChanged ); + // delay panning + QObject::connect ( chbEnableDelayPanning, &QCheckBox::stateChanged, + this, &CServerDlg::OnEnableDelayPanningStateChanged ); + // line edits QObject::connect ( edtCentralServerAddress, &QLineEdit::editingFinished, this, &CServerDlg::OnCentralServerAddressEditingFinished ); @@ -674,6 +689,10 @@ void CServerDlg::OnTimer() vecpListViewItems[i]->setText ( 2, QString().setNum ( veciJitBufNumFrames[i] ) ); + // show num of audio channels + int iNumAudioChs = pServer->GetClientNumAudioChannels ( i ); + vecpListViewItems[i]->setText ( 3, QString().setNum ( iNumAudioChs ) ); + vecpListViewItems[i]->setHidden ( false ); } else diff --git a/src/serverdlg.h b/src/serverdlg.h index 9906bbb1a3..d11c98e9cf 100755 --- a/src/serverdlg.h +++ b/src/serverdlg.h @@ -115,6 +115,7 @@ public slots: void OnSysTrayActivated ( QSystemTrayIcon::ActivationReason ActReason ); void OnWelcomeMessageChanged() { pServer->SetWelcomeMessage ( tedWelcomeMessage->toPlainText() ); } void OnLanguageChanged ( QString strLanguage ) { pSettings->strLanguage = strLanguage; } + void OnEnableDelayPanningStateChanged ( int value ) { pServer->SetEnableDelayPanning ( Qt::CheckState::Checked == value ); } void OnNewRecordingClicked() { pServer->RequestNewRecording(); } void OnRecordingDirClicked(); void OnClearRecordingDirClicked(); diff --git a/src/serverdlgbase.ui b/src/serverdlgbase.ui old mode 100755 new mode 100644 index 3600f07d5d..76a05af7f1 --- a/src/serverdlgbase.ui +++ b/src/serverdlgbase.ui @@ -27,7 +27,7 @@ false - 3 + 4 @@ -44,6 +44,11 @@ Jitter Buffer Size + + + Channels + + @@ -265,6 +270,13 @@ + + + + Enable delay panning + + +