diff --git a/src/audiomixerboard.cpp b/src/audiomixerboard.cpp index 5f2534e6..98c708ef 100755 --- a/src/audiomixerboard.cpp +++ b/src/audiomixerboard.cpp @@ -34,17 +34,29 @@ CChannelFader::CChannelFader ( QWidget* pNW, // create new GUI control objects and store pointers to them (note that // QWidget takes the ownership of the pMainGrid so that this only has // to be created locally in this constructor) - pFrame = new QFrame ( pNW ); - QVBoxLayout* pMainGrid = new QVBoxLayout ( pFrame ); - pFader = new QSlider ( Qt::Vertical, pFrame ); - pcbMute = new QCheckBox ( "Mute", pFrame ); - pcbSolo = new QCheckBox ( "Solo", pFrame ); - pLabelInstBox = new QGroupBox ( pFrame ); - plblLabel = new QLabel ( "", pFrame ); - plblInstrument = new QLabel ( pFrame ); - plblCountryFlag = new QLabel ( pFrame ); - QHBoxLayout* pLabelGrid = new QHBoxLayout ( pLabelInstBox ); - QVBoxLayout* pLabelPictGrid = new QVBoxLayout(); + pFrame = new QFrame ( pNW ); + + pLevelsBox = new QWidget ( pFrame ); + plbrChannelLevel = new CMultiColorLEDBar ( pLevelsBox ); + pFader = new QSlider ( Qt::Vertical, pLevelsBox ); + + pMuteSoloBox = new QWidget ( pFrame ); + pcbMute = new QCheckBox ( "Mute", pMuteSoloBox ); + pcbSolo = new QCheckBox ( "Solo", pMuteSoloBox ); + + pLabelInstBox = new QGroupBox ( pFrame ); + plblLabel = new QLabel ( "", pFrame ); + plblInstrument = new QLabel ( pFrame ); + plblCountryFlag = new QLabel ( pFrame ); + + QVBoxLayout* pMainGrid = new QVBoxLayout ( pFrame ); + QHBoxLayout* pLevelsGrid = new QHBoxLayout ( pLevelsBox ); + QVBoxLayout* pMuteSoloGrid = new QVBoxLayout ( pMuteSoloBox ); + QHBoxLayout* pLabelGrid = new QHBoxLayout ( pLabelInstBox ); + QVBoxLayout* pLabelPictGrid = new QVBoxLayout ( ); + + // setup channel level + plbrChannelLevel->setContentsMargins( 0, 3, 2, 3 ); // setup slider pFader->setPageStep ( 1 ); @@ -62,18 +74,30 @@ CChannelFader::CChannelFader ( QWidget* pNW, // set margins of the layouts to zero to get maximum space for the controls pMainGrid->setContentsMargins ( 0, 0, 0, 0 ); + + pLevelsGrid->setContentsMargins ( 0, 0, 0, 0 ); + pLevelsGrid->setSpacing ( 0 ); // only minimal space + + pMuteSoloGrid->setContentsMargins ( 0, 0, 0, 0 ); + pLabelGrid->setContentsMargins ( 0, 0, 0, 0 ); pLabelGrid->setSpacing ( 2 ); // only minimal space between picture and text // add user controls to the grids pLabelPictGrid->addWidget ( plblCountryFlag, 0, Qt::AlignHCenter ); pLabelPictGrid->addWidget ( plblInstrument, 0, Qt::AlignHCenter ); + pLabelGrid->addLayout ( pLabelPictGrid ); pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); - pMainGrid->addWidget ( pFader, 0, Qt::AlignHCenter ); - pMainGrid->addWidget ( pcbMute, 0, Qt::AlignLeft ); - pMainGrid->addWidget ( pcbSolo, 0, Qt::AlignLeft ); + pLevelsGrid->addWidget ( plbrChannelLevel, 0, Qt::AlignRight ); + pLevelsGrid->addWidget ( pFader, 0, Qt::AlignLeft ); + + pMuteSoloGrid->addWidget ( pcbMute, 0, Qt::AlignLeft ); + pMuteSoloGrid->addWidget ( pcbSolo, 0, Qt::AlignLeft ); + + pMainGrid->addWidget ( pLevelsBox, 0, Qt::AlignHCenter ); + pMainGrid->addWidget ( pMuteSoloBox, 0, Qt::AlignHCenter ); pMainGrid->addWidget ( pLabelInstBox ); // add fader frame to audio mixer board layout @@ -83,17 +107,23 @@ CChannelFader::CChannelFader ( QWidget* pNW, Reset(); // add help text to controls + plbrChannelLevel->setWhatsThis ( tr ( "Channel Level: Displays the " + "pre-fader audio level of this channel. All connected clients at the " + "server will be assigned an audio level, the same value for each client." ) ); + plbrChannelLevel->setAccessibleName ( tr ( "Input level of the current audio " + "channel at the server" ) ); + pFader->setWhatsThis ( tr ( "Mixer Fader: Adjusts the audio level of " "this channel. All connected clients at the server will be assigned " - "an audio fader at each client." ) ); - pFader->setAccessibleName ( tr ( "Mixer level setting of the connected client " - "at the server" ) ); + "an audio fader at each client, adjusting the local mix." ) ); + pFader->setAccessibleName ( tr ( "Local mix level setting of the current audio " + "channel at the server" ) ); - pcbMute->setWhatsThis ( tr ( "Mute: With the Mute checkbox, the current " + pcbMute->setWhatsThis ( tr ( "Mute: With the Mute checkbox, the " "audio channel can be muted." ) ); pcbMute->setAccessibleName ( tr ( "Mute button" ) ); - pcbSolo->setWhatsThis ( tr ( "Solo: With the Solo checkbox, the current " + pcbSolo->setWhatsThis ( tr ( "Solo: With the Solo checkbox, the " "audio channel can be set to solo which means that all other channels " "except of the current channel are muted. It is possible to set more than " "one channel to solo." ) ); @@ -158,6 +188,11 @@ void CChannelFader::SetGUIDesign ( const EGUIDesign eNewDesign ) } } +void CChannelFader::SetDisplayChannelLevel ( const bool eNDCL ) +{ + plbrChannelLevel->setHidden( !eNDCL ); +} + void CChannelFader::SetupFaderTag ( const ESkillLevel eSkillLevel ) { // setup group box for label/instrument picture: set a thick black border @@ -306,6 +341,11 @@ void CChannelFader::UpdateSoloState ( const bool bNewOtherSoloState ) } } +void CChannelFader::SetChannelLevel ( const uint16_t iLevel ) +{ + plbrChannelLevel->setValue ( iLevel ); +} + void CChannelFader::SetText ( const CChannelInfo& ChanInfo ) { // store original received name @@ -634,6 +674,17 @@ void CAudioMixerBoard::SetGUIDesign ( const EGUIDesign eNewDesign ) } } +void CAudioMixerBoard::SetDisplayChannelLevels ( const bool eNDCL ) +{ + bDisplayChannelLevels = eNDCL; + + // apply preference to child GUI controls + for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) + { + vecpChanFader[i]->SetDisplayChannelLevel ( bDisplayChannelLevels ); + } +} + void CAudioMixerBoard::HideAll() { // make all controls invisible @@ -710,6 +761,9 @@ void CAudioMixerBoard::ApplyNewConClientList ( CVector& vecChanInf } } + // At some future time a new level will arrive -- ??? + vecpChanFader[i]->SetChannelLevel ( 0 ); + // set the text in the fader vecpChanFader[i]->SetText ( vecChanInfo[j] ); @@ -857,3 +911,17 @@ bool CAudioMixerBoard::GetStoredFaderSettings ( const CChannelInfo& ChanInfo, // return "not OK" since we did not find matching fader settings return false; } + +void CAudioMixerBoard::SetChannelLevels ( const CVector& vecChannelLevel ) +{ + const int iNumChannelLevels = vecChannelLevel.Size(); + int i = 0; + + for ( int iChId = 0; iChId < MAX_NUM_CHANNELS; iChId++ ) + { + if ( vecpChanFader[iChId]->IsVisible() && i < iNumChannelLevels ) + { + vecpChanFader[iChId]->SetChannelLevel ( vecChannelLevel[i++] ); + } + } +} diff --git a/src/audiomixerboard.h b/src/audiomixerboard.h index e22c485d..6ce15c94 100755 --- a/src/audiomixerboard.h +++ b/src/audiomixerboard.h @@ -36,6 +36,7 @@ #include #include "global.h" #include "util.h" +#include "multicolorledbar.h" /* Classes ********************************************************************/ @@ -55,6 +56,7 @@ public: bool IsSolo() { return pcbSolo->isChecked(); } bool IsMute() { return pcbMute->isChecked(); } void SetGUIDesign ( const EGUIDesign eNewDesign ); + void SetDisplayChannelLevel ( const bool eNDCL ); void UpdateSoloState ( const bool bNewOtherSoloState ); void SetFaderLevel ( const int iLevel ); @@ -62,6 +64,7 @@ public: void SetFaderIsMute ( const bool bIsMute ); int GetFaderLevel() { return pFader->value(); } void Reset(); + void SetChannelLevel ( const uint16_t iLevel ); protected: double CalcFaderGain ( const int value ); @@ -69,18 +72,24 @@ protected: void SendFaderLevelToServer ( const int iLevel ); void SetupFaderTag ( const ESkillLevel eSkillLevel ); - QFrame* pFrame; - QGroupBox* pLabelInstBox; - QSlider* pFader; - QCheckBox* pcbMute; - QCheckBox* pcbSolo; - QLabel* plblLabel; - QLabel* plblInstrument; - QLabel* plblCountryFlag; + QFrame* pFrame; - QString strReceivedName; + QWidget* pLevelsBox; + QWidget* pMuteSoloBox; + CMultiColorLEDBar* plbrChannelLevel; + QSlider* pFader; - bool bOtherChannelIsSolo; + QCheckBox* pcbMute; + QCheckBox* pcbSolo; + + QGroupBox* pLabelInstBox; + QLabel* plblLabel; + QLabel* plblInstrument; + QLabel* plblCountryFlag; + + QString strReceivedName; + + bool bOtherChannelIsSolo; public slots: void OnLevelValueChanged ( int value ) { SendFaderLevelToServer ( value ); } @@ -103,10 +112,13 @@ public: void ApplyNewConClientList ( CVector& vecChanInfo ); void SetServerName ( const QString& strNewServerName ); void SetGUIDesign ( const EGUIDesign eNewDesign ); + void SetDisplayChannelLevels ( const bool eNDCL ); void SetFaderLevel ( const int iChannelIdx, const int iValue ); + void SetChannelLevels ( const CVector& vecChannelLevel ); + // settings CVector vecStoredFaderTags; CVector vecStoredFaderLevels; @@ -129,6 +141,7 @@ protected: CVector vecpChanFader; QGroupBox* pGroupBox; QHBoxLayout* pMainLayout; + bool bDisplayChannelLevels; bool bNoFaderVisible; public slots: diff --git a/src/channel.cpp b/src/channel.cpp index 75829284..284d1cc0 100755 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -102,6 +102,10 @@ qRegisterMetaType ( "CHostAddress" ); QObject::connect( &Protocol, SIGNAL ( LicenceRequired ( ELicenceType ) ), SIGNAL ( LicenceRequired ( ELicenceType ) ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ReqChannelLevelList ( bool ) ), + this, SLOT ( OnReqChannelLevelList ( bool ) ) ); } bool CChannel::ProtocolIsEnabled() diff --git a/src/channel.h b/src/channel.h index 98160756..2b3086f0 100755 --- a/src/channel.h +++ b/src/channel.h @@ -159,12 +159,18 @@ public: void CreateReqConnClientsList() { Protocol.CreateReqConnClientsList(); } void CreateChatTextMes ( const QString& strChatText ) { Protocol.CreateChatTextMes ( strChatText ); } void CreateLicReqMes ( const ELicenceType eLicenceType ) { Protocol.CreateLicenceRequiredMes ( eLicenceType ); } + void CreateReqChannelLevelListMes ( bool bOptIn ) { Protocol.CreateReqChannelLevelListMes ( bOptIn ); } void CreateConClientListMes ( const CVector& vecChanInfo ) { Protocol.CreateConClientListMes ( vecChanInfo ); } CNetworkTransportProps GetNetworkTransportPropsFromCurrentSettings(); + bool ChannelLevelsRequired() const { return bChannelLevelsRequired; } + + double GetPrevLevel() const { return dPrevLevel; } + void SetPrevLevel ( const double nPL ) { dPrevLevel = nPL; } + protected: bool ProtocolIsEnabled(); @@ -177,6 +183,8 @@ protected: iNetwFrameSizeFact = FRAME_SIZE_FACTOR_PREFERRED; iNetwFrameSize = CELT_MINIMUM_NUM_BYTES; iNumAudioChannels = 1; // mono + + dPrevLevel = 0.0; } // connection parameters @@ -216,6 +224,9 @@ protected: QMutex MutexSocketBuf; QMutex MutexConvBuf; + bool bChannelLevelsRequired; + double dPrevLevel; + public slots: void OnSendProtMessage ( CVector vecMessage ); void OnJittBufSizeChange ( int iNewJitBufSize ); @@ -249,6 +260,8 @@ public slots: void OnNewConnection() { emit NewConnection(); } + void OnReqChannelLevelList ( bool bOptIn ) { bChannelLevelsRequired = bOptIn; } + signals: void MessReadyForSending ( CVector vecMessage ); void NewConnection(); diff --git a/src/client.cpp b/src/client.cpp index aa8338de..372088ae 100755 --- a/src/client.cpp +++ b/src/client.cpp @@ -65,6 +65,7 @@ CClient::CClient ( const quint16 iPortNumber, bFraSiFactDefSupported ( false ), bFraSiFactSafeSupported ( false ), eGUIDesign ( GD_ORIGINAL ), + bDisplayChannelLevels ( true ), bJitterBufferOK ( true ), strCentralServerAddress ( "" ), bUseDefaultCentralServerAddress ( true ), @@ -191,6 +192,10 @@ CClient::CClient ( const quint16 iPortNumber, SIGNAL ( CLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType, QString ) ) ); #endif + QObject::connect ( &ConnLessProtocol, + SIGNAL ( CLChannelLevelListReceived ( CHostAddress, CVector ) ), + this, SLOT ( OnCLChannelLevelListReceived ( CHostAddress, CVector ) ) ); + // other QObject::connect ( &Sound, SIGNAL ( ReinitRequest ( int ) ), this, SLOT ( OnSndCrdReinitRequest ( int ) ) ); @@ -279,6 +284,9 @@ void CClient::OnNewConnection() // Same problem is with the jitter buffer message. Channel.CreateReqConnClientsList(); CreateServerJitterBufferMessage(); + + // send opt-in / out for Channel Level updates + Channel.CreateReqChannelLevelListMes ( bDisplayChannelLevels ); } void CClient::CreateServerJitterBufferMessage() @@ -387,6 +395,14 @@ bool CClient::GetAndResetbJitterBufferOKFlag() return bSocketJitBufOKFlag; } +void CClient::SetDisplayChannelLevels ( const bool bNDCL ) +{ + bDisplayChannelLevels = bNDCL; + + // tell any connected server about the change + Channel.CreateReqChannelLevelListMes ( bDisplayChannelLevels ); +} + void CClient::SetSndCrdPrefFrameSizeFactor ( const int iNewFactor ) { // first check new input parameter @@ -598,6 +614,12 @@ void CClient::OnSndCrdReinitRequest ( int iSndCrdResetType ) } } +void CClient::OnCLChannelLevelListReceived ( CHostAddress InetAddr, + CVector vecLevelList ) +{ + emit CLChannelLevelListReceived ( InetAddr, vecLevelList ); +} + void CClient::Start() { // always use the OPUS codec diff --git a/src/client.h b/src/client.h index 62ea9ec7..f32ad5c0 100755 --- a/src/client.h +++ b/src/client.h @@ -128,6 +128,9 @@ public: EGUIDesign GetGUIDesign() const { return eGUIDesign; } void SetGUIDesign ( const EGUIDesign eNGD ) { eGUIDesign = eNGD; } + bool GetDisplayChannelLevels() const { return bDisplayChannelLevels; } + void SetDisplayChannelLevels ( const bool bNDCL ); + EAudioQuality GetAudioQuality() const { return eAudioQuality; } void SetAudioQuality ( const EAudioQuality eNAudioQuality ); @@ -359,6 +362,7 @@ protected: int iStereoBlockSizeSam; EGUIDesign eGUIDesign; + bool bDisplayChannelLevels; bool bJitterBufferOK; @@ -398,6 +402,9 @@ public slots: void OnSndCrdReinitRequest ( int iSndCrdResetType ); + void OnCLChannelLevelListReceived ( CHostAddress InetAddr, + CVector vecLevelList ); + signals: void ConClientListMesReceived ( CVector vecChanInfo ); void ChatTextReceived ( QString strChatText ); @@ -420,6 +427,9 @@ signals: QString strVersion ); #endif + void CLChannelLevelListReceived ( CHostAddress InetAddr, + CVector vecLevelList ); + void Disconnected(); void ControllerInFaderLevel ( int iChannelIdx, int iValue ); }; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 06b90e97..6606baf8 100755 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -185,6 +185,9 @@ CClientDlg::CClientDlg ( CClient* pNCliP, // reset mixer board MainMixerBoard->HideAll(); + // restore channel level display preference + MainMixerBoard->SetDisplayChannelLevels ( pClient->GetDisplayChannelLevels() ); + // restore fader settings MainMixerBoard->vecStoredFaderTags = pClient->vecStoredFaderTags; MainMixerBoard->vecStoredFaderLevels = pClient->vecStoredFaderLevels; @@ -483,6 +486,10 @@ CClientDlg::CClientDlg ( CClient* pNCliP, SIGNAL ( ControllerInFaderLevel ( int, int ) ), this, SLOT ( OnControllerInFaderLevel ( int, int ) ) ); + QObject::connect ( pClient, + SIGNAL ( CLChannelLevelListReceived ( CHostAddress, CVector ) ), + this, SLOT ( OnCLChannelLevelListReceived ( CHostAddress, CVector ) ) ); + #ifdef ENABLE_CLIENT_VERSION_AND_OS_DEBUGGING QObject::connect ( pClient, SIGNAL ( CLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType, QString ) ), @@ -495,6 +502,9 @@ CClientDlg::CClientDlg ( CClient* pNCliP, QObject::connect ( &ClientSettingsDlg, SIGNAL ( GUIDesignChanged() ), this, SLOT ( OnGUIDesignChanged() ) ); + QObject::connect ( &ClientSettingsDlg, SIGNAL ( DisplayChannelLevelsChanged() ), + this, SLOT ( OnDisplayChannelLevelsChanged() ) ); + QObject::connect ( &ClientSettingsDlg, SIGNAL ( AudioChannelsChanged() ), this, SLOT ( OnAudioChannelsChanged() ) ); @@ -906,7 +916,7 @@ void CClientDlg::OnTimerSigMet() // linear transformation of the input level range to the progress-bar // range dCurSigLevelL -= LOW_BOUND_SIG_METER; - dCurSigLevelL *= NUM_STEPS_INP_LEV_METER / + dCurSigLevelL *= NUM_STEPS_LED_BAR / ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ); // lower bound the signal @@ -916,7 +926,7 @@ void CClientDlg::OnTimerSigMet() } dCurSigLevelR -= LOW_BOUND_SIG_METER; - dCurSigLevelR *= NUM_STEPS_INP_LEV_METER / + dCurSigLevelR *= NUM_STEPS_LED_BAR / ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ); // lower bound the signal diff --git a/src/clientdlg.h b/src/clientdlg.h index ffa0571a..79549c8d 100755 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -60,10 +60,6 @@ #define BUFFER_LED_UPDATE_TIME_MS 300 // ms #define LED_BAR_UPDATE_TIME_MS 1000 // ms -// range for signal level meter -#define LOW_BOUND_SIG_METER ( -50.0 ) // dB -#define UPPER_BOUND_SIG_METER ( 0.0 ) // dB - // number of ping times > upper bound until error message is shown #define NUM_HIGH_PINGS_UNTIL_ERROR 5 @@ -196,12 +192,19 @@ public slots: CVector vecChanInfo ) { ConnectDlg.SetConnClientsList ( InetAddr, vecChanInfo ); } + void OnCLChannelLevelListReceived ( CHostAddress /* unused */, + CVector vecLevelList ) + { MainMixerBoard->SetChannelLevels ( vecLevelList ); } + void OnConnectDlgAccepted(); void OnDisconnected(); void OnGUIDesignChanged() { SetGUIDesign ( pClient->GetGUIDesign() ); } + void OnDisplayChannelLevelsChanged() + { MainMixerBoard->SetDisplayChannelLevels ( pClient->GetDisplayChannelLevels() ); } + void OnAudioChannelsChanged() { UpdateRevSelection(); } void OnNumClientsChanged ( int iNewNumClients ); void OnNewClientLevelChanged() { MainMixerBoard->iNewClientFaderLevel = pClient->iNewClientFaderLevel; } diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 91385ae5..98f8a1f3 100755 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -189,6 +189,12 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, QWidget* parent, chbGUIDesignFancy->setAccessibleName ( tr ( "Fancy skin check box" ) ); + // display channel levels + chbDisplayChannelLevels->setWhatsThis ( tr ( "Display Channel Levels: " + "If enabled, each client channel will display a pre-fader level bar." ) ); + + chbDisplayChannelLevels->setAccessibleName ( tr ( "Display channel levels check box" ) ); + // audio channels QString strAudioChannels = tr ( "Audio Channels: " "Select the number of audio channels to be used. There are three " @@ -323,6 +329,9 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, QWidget* parent, chbGUIDesignFancy->setCheckState ( Qt::Checked ); } + // Display Channel Levels check box + chbDisplayChannelLevels->setCheckState ( pClient->GetDisplayChannelLevels() ? Qt::Checked : Qt::Unchecked ); + // "Audio Channels" combo box cbxAudioChannels->clear(); cbxAudioChannels->addItem ( "Mono" ); // CC_MONO @@ -386,6 +395,9 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, QWidget* parent, QObject::connect ( chbGUIDesignFancy, SIGNAL ( stateChanged ( int ) ), this, SLOT ( OnGUIDesignFancyStateChanged ( int ) ) ); + QObject::connect ( chbDisplayChannelLevels, SIGNAL ( stateChanged ( int ) ), + this, SLOT ( OnDisplayChannelLevelsStateChanged ( int ) ) ); + QObject::connect ( chbAutoJitBuf, SIGNAL ( stateChanged ( int ) ), this, SLOT ( OnAutoJitBufStateChanged ( int ) ) ); @@ -704,6 +716,12 @@ void CClientSettingsDlg::OnGUIDesignFancyStateChanged ( int value ) UpdateDisplay(); } +void CClientSettingsDlg::OnDisplayChannelLevelsStateChanged ( int value ) +{ + pClient->SetDisplayChannelLevels ( value != Qt::Unchecked ); + emit DisplayChannelLevelsChanged(); +} + void CClientSettingsDlg::OnDefaultCentralServerStateChanged ( int value ) { // apply new setting to the client diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index cf50826a..ce478cdf 100755 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -90,6 +90,7 @@ protected: void OnSliderSndCrdBufferDelay ( int value ); void OnAutoJitBufStateChanged ( int value ); void OnGUIDesignFancyStateChanged ( int value ); + void OnDisplayChannelLevelsStateChanged ( int value ); void OnDefaultCentralServerStateChanged ( int value ); void OnCentralServerAddressEditingFinished(); void OnNewClientLevelEditingFinished(); @@ -105,6 +106,7 @@ protected: signals: void GUIDesignChanged(); + void DisplayChannelLevelsChanged(); void AudioChannelsChanged(); void NewClientLevelChanged(); }; diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui index 06d4fc41..d53654a7 100755 --- a/src/clientsettingsdlgbase.ui +++ b/src/clientsettingsdlgbase.ui @@ -530,11 +530,22 @@ - - - Fancy Skin - - + + + + + Fancy Skin + + + + + + + Display Channel Levels + + + + @@ -719,6 +730,7 @@ cbxAudioQuality edtNewClientLevel chbGUIDesignFancy + chbDisplayChannelFaders chbDefaultCentralServer edtCentralServerAddress diff --git a/src/global.h b/src/global.h index 493febdc..a1baa8a7 100755 --- a/src/global.h +++ b/src/global.h @@ -169,10 +169,14 @@ LED bar: lbr // maximum number of fader settings to be stored (together with the fader tags) #define MAX_NUM_STORED_FADER_SETTINGS 100 -// defines for LED input level meter -#define NUM_STEPS_INP_LEV_METER 8 -#define RED_BOUND_INP_LEV_METER 7 -#define YELLOW_BOUND_INP_LEV_METER 5 +// defines for LED level meter CMultiColorLEDBar +#define NUM_STEPS_LED_BAR 8 +#define RED_BOUND_LED_BAR 7 +#define YELLOW_BOUND_LED_BAR 5 + +// range for signal level meter +#define LOW_BOUND_SIG_METER ( -50.0 ) // dB +#define UPPER_BOUND_SIG_METER ( 0.0 ) // dB // Maximum number of connected clients at the server. If you want to change this // paramter you have to modify the code on some places, too! The code tag @@ -195,6 +199,9 @@ LED bar: lbr // list #define PING_UPDATE_TIME_SERVER_LIST_MS 2000 // ms +// defines the interval between Channel Level updates from the server +#define CHANNEL_LEVEL_UPDATE_INTERVAL 100 // number of frames + // time-out until a registered server is deleted from the server list if no // new registering was made in minutes #define SERVLIST_TIME_OUT_MINUTES 60 // minutes diff --git a/src/multicolorledbar.cpp b/src/multicolorledbar.cpp index b27114c3..8baedfb8 100755 --- a/src/multicolorledbar.cpp +++ b/src/multicolorledbar.cpp @@ -33,7 +33,7 @@ CMultiColorLEDBar::CMultiColorLEDBar ( QWidget* parent, Qt::WindowFlags f ) : QFrame ( parent, f ) { // set total number of LEDs - iNumLEDs = NUM_STEPS_INP_LEV_METER; + iNumLEDs = NUM_STEPS_LED_BAR; // create layout and set spacing to zero pMainLayout = new QVBoxLayout ( this ); @@ -105,14 +105,14 @@ void CMultiColorLEDBar::setValue ( const int value ) if ( iLEDIdx < value ) { // check which color we should use (green, yellow or red) - if ( iLEDIdx < YELLOW_BOUND_INP_LEV_METER ) + if ( iLEDIdx < YELLOW_BOUND_LED_BAR ) { // green region vecpLEDs[iLEDIdx]->setColor ( cLED::RL_GREEN ); } else { - if ( iLEDIdx < RED_BOUND_INP_LEV_METER ) + if ( iLEDIdx < RED_BOUND_LED_BAR ) { // yellow region vecpLEDs[iLEDIdx]->setColor ( cLED::RL_YELLOW ); diff --git a/src/protocol.cpp b/src/protocol.cpp index de15bb84..f9c37aa5 100755 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -153,6 +153,14 @@ MESSAGES (with connection) | 1 byte licence type | +---------------------+ +- PROTMESSID_CLM_REQ_CHANNEL_LEVEL_LIST: Opt in or out of the channel level list + + +---------------+ + | 1 byte option | + +---------------+ + + option is boolean, true to opt in, false to opt out + // #### COMPATIBILITY OLD VERSION, TO BE REMOVED #### - PROTMESSID_OPUS_SUPPORTED: Informs that OPUS codec is supported @@ -276,6 +284,27 @@ CONNECTION LESS MESSAGES note: does not have any data -> n = 0 +- PROTMESSID_CLM_CHANNEL_LEVEL_LIST: The channel level list + + +----------------------------------+ + | ( ( n + 1 ) / 2 ) * 4 bit values | + +----------------------------------+ + + n is number of connected clients + + the values are the maximum channel levels for a client frame converted + to the range of CMultiColorLEDBar in 4 bits, two entries per byte + with the earlier channel in the lower half of the byte + + where an odd number of clients is connected, there will be four unused + upper bits in the final byte, containing 0xF (which is out of range) + + the server may compute them message when any client has used + PROTMESSID_CLM_REQ_CHANNEL_LEVEL_LIST to opt in + + the server may issue to message only to a client that has used + PROTMESSID_CLM_REQ_CHANNEL_LEVEL_LIST to opt in + ****************************************************************************** * @@ -549,6 +578,10 @@ if ( rand() < ( RAND_MAX / 2 ) ) return false; case PROTMESSID_LICENCE_REQUIRED: bRet = EvaluateLicenceRequiredMes ( vecbyMesBodyData ); break; + + case PROTMESSID_REQ_CHANNEL_LEVEL_LIST: + bRet = EvaluateReqChannelLevelListMes ( vecbyMesBodyData ); + break; } // immediately send acknowledge message @@ -634,6 +667,10 @@ if ( rand() < ( RAND_MAX / 2 ) ) return false; case PROTMESSID_CLM_REQ_CONN_CLIENTS_LIST: bRet = EvaluateCLReqConnClientsListMes ( InetAddr ); break; + + case PROTMESSID_CLM_CHANNEL_LEVEL_LIST: + bRet = EvaluateCLChannelLevelListMes ( InetAddr, vecbyMesBodyData ); + break; } } else @@ -1234,6 +1271,40 @@ void CProtocol::CreateOpusSupportedMes() CVector ( 0 ) ); } +void CProtocol::CreateReqChannelLevelListMes ( const bool bRCL ) +{ + CVector vecData ( 1 ); // 1 byte of data + int iPos = 0; // init position pointer + PutValOnStream ( vecData, iPos, + static_cast ( bRCL ), 1 ); + + CreateAndSendMessage ( PROTMESSID_REQ_CHANNEL_LEVEL_LIST, vecData ); +} + +bool CProtocol::EvaluateReqChannelLevelListMes ( const CVector& vecData ) +{ + int iPos = 0; // init position pointer + + // check size + if ( vecData.Size() != 1 ) + { + return true; // return error code + } + + // extract opt in / out for channel levels + uint32_t val = GetValFromStream ( vecData, iPos, 1 ); + + if ( val != 0 && val != 1 ) + { + return true; // return error code + } + + // invoke message action + emit ReqChannelLevelList ( static_cast ( val ) ); + + return false; // no error +} + // Connection less messages ---------------------------------------------------- void CProtocol::CreateCLPingMes ( const CHostAddress& InetAddr, const int iMs ) @@ -1936,6 +2007,70 @@ bool CProtocol::EvaluateCLReqConnClientsListMes ( const CHostAddress& InetAddr ) return false; // no error } +void CProtocol::CreateCLChannelLevelListMes ( const CHostAddress& InetAddr, + const CVector& vecLevelList, + const int iNumClients ) +{ + // This must be a multiple of bytes at four bits per client + const int iNumBytes = ( iNumClients + 1 ) / 2; + CVector vecData( iNumBytes ); + int iPos = 0; // init position pointer + + for ( int i = 0, j = 0; i < iNumClients; i += 2 /* pack two per byte */, j++ ) + { + uint16_t levelLo = vecLevelList[i] & 0x0F; + uint16_t levelHi = ( i + 1 < iNumClients ) ? vecLevelList[i + 1] & 0x0F : 0x0F; + uint8_t byte = static_cast ( levelLo | ( levelHi << 4 ) ); + + PutValOnStream ( vecData, iPos, + static_cast ( byte ), 1 ); + } + + CreateAndImmSendConLessMessage ( PROTMESSID_CLM_CHANNEL_LEVEL_LIST, + vecData, + InetAddr ); +} + +bool CProtocol::EvaluateCLChannelLevelListMes ( const CHostAddress& InetAddr, + const CVector& vecData ) +{ + int iPos = 0; // init position pointer + const int iDataLen = vecData.Size(); // four bits per channel, 2 channels per byte + // may have one too many entries, last being 0xF + int iVecLen = iDataLen * 2; // one ushort per channel + + if ( iVecLen > MAX_NUM_CHANNELS ) + { + return true; // return error code + } + + CVector vecLevelList ( iVecLen ); + + for (int i = 0, j = 0; i < iDataLen; i++, j += 2 ) + { + uint8_t byte = static_cast ( GetValFromStream ( vecData, iPos, 1 ) ); + uint16_t levelLo = byte & 0x0F; + uint16_t levelHi = ( byte >> 4 ) & 0x0F; + + vecLevelList[j] = levelLo; + + if ( levelHi != 0x0F ) + { + vecLevelList[j + 1] = levelHi; + } + else + { + vecLevelList.resize ( iVecLen - 1 ); + break; + } + } + + // invoke message action + emit CLChannelLevelListReceived ( InetAddr, vecLevelList ); + + return false; // no error +} + /******************************************************************************\ * Message generation and parsing * diff --git a/src/protocol.h b/src/protocol.h index 617092c0..91dfa77d 100755 --- a/src/protocol.h +++ b/src/protocol.h @@ -54,6 +54,7 @@ #define PROTMESSID_CHANNEL_INFOS 25 // set channel infos #define PROTMESSID_OPUS_SUPPORTED 26 // tells that OPUS codec is supported #define PROTMESSID_LICENCE_REQUIRED 27 // licence required +#define PROTMESSID_REQ_CHANNEL_LEVEL_LIST 28 // request the channel level list // message IDs of connection less messages (CLM) // DEFINITION -> start at 1000, end at 1999, see IsConnectionLessMessageID @@ -71,6 +72,7 @@ #define PROTMESSID_CLM_REQ_VERSION_AND_OS 1012 // request version number and operating system #define PROTMESSID_CLM_CONN_CLIENTS_LIST 1013 // channel infos for connected clients #define PROTMESSID_CLM_REQ_CONN_CLIENTS_LIST 1014 // request the connected clients list +#define PROTMESSID_CLM_CHANNEL_LEVEL_LIST 1015 // channel level list // lengths of message as defined in protocol.cpp file #define MESS_HEADER_LENGTH_BYTE 7 // TAG (2), ID (2), cnt (1), length (2) @@ -102,6 +104,7 @@ public: void CreateReqNetwTranspPropsMes(); void CreateLicenceRequiredMes ( const ELicenceType eLicenceType ); void CreateOpusSupportedMes(); + void CreateReqChannelLevelListMes ( const bool bRCL ); void CreateCLPingMes ( const CHostAddress& InetAddr, const int iMs ); void CreateCLPingWithNumClientsMes ( const CHostAddress& InetAddr, @@ -123,6 +126,9 @@ public: void CreateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecChanInfo ); void CreateCLReqConnClientsListMes ( const CHostAddress& InetAddr ); + void CreateCLChannelLevelListMes ( const CHostAddress& InetAddr, + const CVector& vecLevelList, + const int iNumClients ); static bool ParseMessageFrame ( const CVector& vecbyData, const int iNumBytesIn, @@ -205,17 +211,18 @@ protected: const CVector& vecData, const CHostAddress& InetAddr ); - bool EvaluateJitBufMes ( const CVector& vecData ); + bool EvaluateJitBufMes ( const CVector& vecData ); bool EvaluateReqJitBufMes(); - bool EvaluateChanGainMes ( const CVector& vecData ); - bool EvaluateConClientListMes ( const CVector& vecData ); + bool EvaluateChanGainMes ( const CVector& vecData ); + bool EvaluateConClientListMes ( const CVector& vecData ); bool EvaluateReqConnClientsList(); - bool EvaluateChanInfoMes ( const CVector& vecData ); + bool EvaluateChanInfoMes ( const CVector& vecData ); bool EvaluateReqChanInfoMes(); - bool EvaluateChatTextMes ( const CVector& vecData ); - bool EvaluateNetwTranspPropsMes ( const CVector& vecData ); + bool EvaluateChatTextMes ( const CVector& vecData ); + bool EvaluateNetwTranspPropsMes ( const CVector& vecData ); bool EvaluateReqNetwTranspPropsMes(); - bool EvaluateLicenceRequiredMes ( const CVector& vecData ); + bool EvaluateLicenceRequiredMes ( const CVector& vecData ); + bool EvaluateReqChannelLevelListMes ( const CVector& vecData ); bool EvaluateCLPingMes ( const CHostAddress& InetAddr, const CVector& vecData ); @@ -236,6 +243,8 @@ protected: bool EvaluateCLConnClientsListMes ( const CHostAddress& InetAddr, const CVector& vecData ); bool EvaluateCLReqConnClientsListMes ( const CHostAddress& InetAddr ); + bool EvaluateCLChannelLevelListMes ( const CHostAddress& InetAddr, + const CVector& vecData ); int iOldRecID; int iOldRecCnt; @@ -270,6 +279,7 @@ signals: void NetTranspPropsReceived ( CNetworkTransportProps NetworkTransportProps ); void ReqNetTranspProps(); void LicenceRequired ( ELicenceType eLicenceType ); + void ReqChannelLevelList ( bool bOptIn ); void CLPingReceived ( CHostAddress InetAddr, int iMs ); @@ -291,4 +301,6 @@ signals: void CLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo ); void CLReqConnClientsList ( CHostAddress InetAddr ); + void CLChannelLevelListReceived ( CHostAddress InetAddr, + CVector vecLevelList ); }; diff --git a/src/res/homepage/displaychannellevels.png b/src/res/homepage/displaychannellevels.png new file mode 100644 index 00000000..a759d433 Binary files /dev/null and b/src/res/homepage/displaychannellevels.png differ diff --git a/src/res/homepage/faders.jpg b/src/res/homepage/faders.jpg index 1c4152c4..9621b9e5 100644 Binary files a/src/res/homepage/faders.jpg and b/src/res/homepage/faders.jpg differ diff --git a/src/res/homepage/main.jpg b/src/res/homepage/main.jpg index a91209f2..2a860b72 100644 Binary files a/src/res/homepage/main.jpg and b/src/res/homepage/main.jpg differ diff --git a/src/res/homepage/manual.md b/src/res/homepage/manual.md index 2f2e2f80..6b88035a 100644 --- a/src/res/homepage/manual.md +++ b/src/res/homepage/manual.md @@ -81,11 +81,11 @@ The reverberation effect requires significant CPU so that it should only be used level fader is set to minimum (which is the default setting), the reverberation effect is switched off and does not cause any additional CPU usage. -### Local audio input fader +### Local audio pan / balance control -![Local audio input fader](audiofader.jpg) +![Local audio pan / balance control](audiofader.jpg) -With the audio fader, the relative levels of the left and right local audio channels can be changed. For a mono signal +With the balance control, the relative levels of the left and right local audio channels can be changed. For a mono signal it acts like a panning between the two channels. If, e.g., a microphone is connected to the right input channel and an instrument is connected to the left input channel which is much louder than the microphone, move the audio fader in a direction where the label above the fader shows L -x, where x is the current attenuation indicator. @@ -94,11 +94,13 @@ in a direction where the label above the fader shows L -x, where x is the curren ![Audio faders](faders.jpg) -In the audio mixer frame, a fader for each connected client at the server is shown. This includes a fader for the own signal. -With the faders, the audio level of each client can be modified individually. +In the audio mixer frame, a fader is shown for each connected client at the server, including yourself. +The faders allow you to adjust the level of what you hear without affecting what others hear. +The VU meter shows the input level at the server - that is, what you are sending. -With the Mute checkbox, the current audio channel can be muted. With the Solo checkbox, the current audio channel can -be set to solo which means that all other channels except of the current channel are muted. +Using the Mute checkbox prevents the indicated channel being heard in your local mix. + +The solo checkboxes allow you to hear only one, or several, channels, with those not soloed being muted. Settings Window --------------- @@ -206,6 +208,12 @@ that client was already stored. If enabled, a fancy skin will be applied to the main window. +### Display channel levels + +![Display channel levels](displaychannellevels.png) + +If enabled, the channel input level for each connected client will be displayed in the mixer. + ### Central server address ![Central server address](centralserveraddress.png) diff --git a/src/res/homepage/settings.jpg b/src/res/homepage/settings.jpg index b9836bd4..47597a73 100644 Binary files a/src/res/homepage/settings.jpg and b/src/res/homepage/settings.jpg differ diff --git a/src/server.cpp b/src/server.cpp index d759bd6d..3a6192f1 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -335,6 +335,8 @@ CServer::CServer ( const int iNewMaxNumChan, // allocate worst case memory for the coded data vecbyCodedData.Init ( MAX_SIZE_BYTES_NETW_BUF ); + // allocate worst case memory for the channel levels + vecChannelLevels.Init ( iMaxNumChannels ); // enable history graph (if requested) if ( !strHistoryFileName.isEmpty() ) @@ -864,6 +866,7 @@ JitterMeas.Measure(); // some inits int iNumClients = 0; // init connected client counter bool bChannelIsNowDisconnected = false; + bool bSendChannelLevels = false; // Make put and get calls thread safe. Do not forget to unlock mutex // afterwards! @@ -1007,6 +1010,29 @@ JitterMeas.Measure(); // one client is connected. if ( iNumClients > 0 ) { + + // Low frequency updates + if ( iFrameCount > CHANNEL_LEVEL_UPDATE_INTERVAL ) + { + iFrameCount = 0; + + // Calculate channel levels if any client has requested them + for ( int i = 0; i < iNumClients; i++ ) + { + if ( vecChannels[ vecChanIDsCurConChan[i] ].ChannelLevelsRequired() ) + { + bSendChannelLevels = true; + + CreateLevelsForAllConChannels ( iNumClients, + vecNumAudioChannels, + vecvecsData, + vecChannelLevels ); + break; + } + } + } + iFrameCount++; + for ( int i = 0; i < iNumClients; i++ ) { // get actual ID of current channel @@ -1095,6 +1121,12 @@ opus_custom_encoder_ctl ( CurOpusEncoder, // update socket buffer size vecChannels[iCurChanID].UpdateSocketBufferSize(); + + // send channel levels + if ( bSendChannelLevels && vecChannels[iCurChanID].ChannelLevelsRequired() ) + { + ConnLessProtocol.CreateCLChannelLevelListMes ( vecChannels[iCurChanID].GetAddress(), vecChannelLevels, iNumClients ); + } } } else @@ -1574,3 +1606,69 @@ void CServer::customEvent ( QEvent* pEvent ) } } } + +/// @brief Compute frame peak level for each client +void CServer::CreateLevelsForAllConChannels ( const int iNumClients, + const CVector& vecNumAudioChannels, + const CVector > vecvecsData, + CVector& vecLevelsOut ) +{ + int i, j, k; + + // init return vector with zeros since we mix all channels on that vector + vecLevelsOut.Reset ( 0 ); + + for ( j = 0; j < iNumClients; j++ ) + { + // get a reference to the audio data + const CVector& vecsData = vecvecsData[j]; + + double dCurLevel = 0.0; + if ( vecNumAudioChannels[j] == 1 ) + { + // mono + for ( i = 0; i < SYSTEM_FRAME_SIZE_SAMPLES; i += 3 ) + { + dCurLevel = std::max ( dCurLevel, std::abs ( static_cast ( vecsData[i] ) ) ); + } + } + else + { + // stereo: apply stereo-to-mono attenuation + for ( i = 0, k = 0; i < SYSTEM_FRAME_SIZE_SAMPLES; i += 3, k += 6 ) + { + double sMix = ( static_cast ( vecsData[k] ) + vecsData[k + 1] ) / 2; + dCurLevel = std::max ( dCurLevel, std::abs ( sMix ) ); + } + } + + // smoothing + int iChId = vecChanIDsCurConChan [ j ]; + dCurLevel = std::max ( dCurLevel, vecChannels[ iChId ].GetPrevLevel() * 0.5 ); + vecChannels[ iChId ].SetPrevLevel ( dCurLevel ); + + // logarithmic measure + const double dNormChanLevel = dCurLevel / _MAXSHORT; + double dCurSigLevel; + if ( dNormChanLevel > 0 ) + { + dCurSigLevel = 20.0 * log10 ( dNormChanLevel ); + } + else + { + dCurSigLevel = -100000.0; // large negative value + } + + // map to signal level meter + dCurSigLevel -= LOW_BOUND_SIG_METER; + dCurSigLevel *= NUM_STEPS_LED_BAR / + ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ); + + if ( dCurSigLevel < 0 ) + { + dCurSigLevel = 0; + } + + vecLevelsOut[j] = static_cast ( ceil ( dCurSigLevel ) ); + } +} diff --git a/src/server.h b/src/server.h index 6494bd50..e04436e0 100755 --- a/src/server.h +++ b/src/server.h @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef USE_OPUS_SHARED_LIB # include "opus/opus_custom.h" #else @@ -225,6 +226,11 @@ protected: // if server mode is normal or double system frame size bool bUseDoubleSystemFrameSize; + void CreateLevelsForAllConChannels ( const int iNumClients, + const CVector& vecNumAudioChannels, + const CVector > vecvecsData, + CVector& vecLevelsOut ); + // do not use the vector class since CChannel does not have appropriate // copy constructor/operator CChannel vecChannels[MAX_NUM_CHANNELS]; @@ -255,12 +261,18 @@ protected: CVector vecsSendData; CVector vecbyCodedData; + // Channel levels + CVector vecChannelLevels; + // actual working objects CHighPrioSocket Socket; // logging CServerLogging Logging; + // channel level update frame interval counter + uint16_t iFrameCount; + // recording thread recorder::CJamRecorder JamRecorder; bool bEnableRecording; diff --git a/src/settings.cpp b/src/settings.cpp index df592e16..9b9a4460 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -250,6 +250,12 @@ void CSettings::Load() pClient->SetGUIDesign ( static_cast ( iValue ) ); } + // display channel levels preference + if ( GetFlagIniSet ( IniXMLDocument, "client", "displaychannellevels", bValue ) ) + { + pClient->SetDisplayChannelLevels ( bValue ); + } + // audio channels if ( GetNumericIniSet ( IniXMLDocument, "client", "audiochannels", 0, 2 /* CC_STEREO */, iValue ) ) @@ -490,6 +496,10 @@ void CSettings::Save() SetNumericIniSet ( IniXMLDocument, "client", "guidesign", static_cast ( pClient->GetGUIDesign() ) ); + // display channel levels preference + SetFlagIniSet ( IniXMLDocument, "client", "displaychannellevels", + pClient->GetDisplayChannelLevels() ); + // audio channels SetNumericIniSet ( IniXMLDocument, "client", "audiochannels", static_cast ( pClient->GetAudioChannels() ) );