/******************************************************************************\ * Copyright (c) 2004-2020 * * Author(s): * Volker Fischer * ****************************************************************************** * * 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. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * \******************************************************************************/ #include "audiomixerboard.h" /******************************************************************************\ * CChanneFader * \******************************************************************************/ 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 ); pLevelsBox = new QWidget ( pFrame ); plbrChannelLevel = new CLevelMeter ( pLevelsBox ); pFader = new QSlider ( Qt::Vertical, pLevelsBox ); pPan = new QDial ( pLevelsBox ); pPanLabel = new QLabel ( tr ( "Pan" ), pLevelsBox ); pInfoLabel = new QLabel ( "", pLevelsBox ); pMuteSoloBox = new QWidget ( pFrame ); pcbMute = new QCheckBox ( tr ( "Mute" ), pMuteSoloBox ); pcbSolo = new QCheckBox ( tr ( "Solo" ), pMuteSoloBox ); pcbGroup = new QCheckBox ( tr ( "Grp" ), 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 ); pLabelGrid = new QHBoxLayout ( pLabelInstBox ); pLabelPictGrid = new QVBoxLayout ( ); QVBoxLayout* pPanGrid = new QVBoxLayout ( ); QHBoxLayout* pPanInfoGrid = new QHBoxLayout ( ); // setup channel level plbrChannelLevel->setContentsMargins ( 0, 3, 2, 3 ); // setup slider pFader->setPageStep ( 1 ); pFader->setRange ( 0, AUD_MIX_FADER_MAX ); pFader->setTickInterval ( AUD_MIX_FADER_MAX / 9 ); // setup panning control pPan->setRange ( 0, AUD_MIX_PAN_MAX ); pPan->setValue ( AUD_MIX_PAN_MAX / 2 ); pPan->setNotchesVisible ( true ); pPanInfoGrid->addWidget ( pPanLabel, 0, Qt::AlignLeft ); pPanInfoGrid->addWidget ( pInfoLabel ); pPanGrid->addLayout ( pPanInfoGrid ); pPanGrid->addWidget ( pPan, 0, Qt::AlignHCenter ); // setup fader tag label (black bold text which is centered) plblLabel->setTextFormat ( Qt::PlainText ); plblLabel->setAlignment ( Qt::AlignHCenter | Qt::AlignVCenter ); plblLabel->setStyleSheet ( "QLabel { color: black; font: bold; }" ); // set margins of the layouts to zero to get maximum space for the controls pMainGrid->setContentsMargins ( 0, 0, 0, 0 ); pPanGrid->setContentsMargins ( 0, 0, 0, 0 ); pPanGrid->setSpacing ( 0 ); // only minimal space pLevelsGrid->setContentsMargins ( 0, 0, 0, 0 ); pLevelsGrid->setSpacing ( 0 ); // only minimal space pMuteSoloGrid->setContentsMargins ( 0, 0, 0, 0 ); pMuteSoloGrid->setSpacing ( 0 ); // only minimal space 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 ); // note: just initial add, may be changed later pLevelsGrid->addWidget ( plbrChannelLevel, 0, Qt::AlignRight ); pLevelsGrid->addWidget ( pFader, 0, Qt::AlignLeft ); pMuteSoloGrid->addWidget ( pcbGroup, 0, Qt::AlignLeft ); pMuteSoloGrid->addWidget ( pcbMute, 0, Qt::AlignLeft ); pMuteSoloGrid->addWidget ( pcbSolo, 0, Qt::AlignLeft ); pMainGrid->addLayout ( pPanGrid ); pMainGrid->addWidget ( pLevelsBox, 0, Qt::AlignHCenter ); pMainGrid->addWidget ( pMuteSoloBox, 0, Qt::AlignHCenter ); pMainGrid->addWidget ( pLabelInstBox ); // reset current fader Reset(); // add help text to controls plbrChannelLevel->setWhatsThis ( "" + tr ( "Channel Level" ) + ": " + tr ( "Displays the pre-fader audio level of this channel. All clients connected to the " "server will be assigned an audio level, the same value for every client." ) ); plbrChannelLevel->setAccessibleName ( tr ( "Input level of the current audio " "channel at the server" ) ); pFader->setWhatsThis ( "" + tr ( "Mixer Fader" ) + ": " + tr ( "Adjusts the audio level of this channel. All clients connected to the server " "will be assigned an audio fader, displayed at each client, to adjust the local mix." ) ); pFader->setAccessibleName ( tr ( "Local mix level setting of the current audio " "channel at the server" ) ); pInfoLabel->setWhatsThis ( "" + tr ( "Status Indicator" ) + ": " + tr ( "Shows a status indication about the client which is assigned to this channel. " "Supported indicators are:" ) + "" ); pInfoLabel->setAccessibleName ( tr ( "Status indicator label" ) ); pPan->setWhatsThis ( "" + tr ( "Panning" ) + ": " + tr ( "Sets the pan from Left to Right of the channel. " "Works only in stereo or preferably mono in/stereo out mode." ) ); pPan->setAccessibleName ( tr ( "Local panning position of the current audio channel at the server" ) ); pcbMute->setWhatsThis ( "" + tr ( "Mute" ) + ": " + tr ( "With the Mute checkbox, the audio channel can be muted." ) ); pcbMute->setAccessibleName ( tr ( "Mute button" ) ); pcbSolo->setWhatsThis ( "" + tr ( "Solo" ) + ": " + tr ( "With the Solo checkbox, the " "audio channel can be set to solo which means that all other channels " "except the soloed channel are muted. It is possible to set more than " "one channel to solo." ) ); pcbSolo->setAccessibleName ( tr ( "Solo button" ) ); pcbGroup->setWhatsThis ( "" + tr ( "Group" ) + ": " + tr ( "With the Grp checkbox, a " "group of audio channels can be defined. All channel faders in a group are moved " "in proportional synchronization if any one of the group faders are moved." ) ); pcbGroup->setAccessibleName ( tr ( "Group button" ) ); QString strFaderText = "" + tr ( "Fader Tag" ) + ": " + tr ( "The fader tag " "identifies the connected client. The tag name, a picture of your " "instrument and the flag of your country can be set in the main window." ); plblInstrument->setWhatsThis ( strFaderText ); plblInstrument->setAccessibleName ( tr ( "Mixer channel instrument picture" ) ); plblLabel->setWhatsThis ( strFaderText ); plblLabel->setAccessibleName ( tr ( "Mixer channel label (fader tag)" ) ); plblCountryFlag->setWhatsThis ( strFaderText ); plblCountryFlag->setAccessibleName ( tr ( "Mixer channel country flag" ) ); // Connections ------------------------------------------------------------- QObject::connect ( pFader, &QSlider::valueChanged, this, &CChannelFader::OnLevelValueChanged ); QObject::connect ( pPan, &QDial::valueChanged, this, &CChannelFader::OnPanValueChanged ); QObject::connect ( pcbMute, &QCheckBox::stateChanged, this, &CChannelFader::OnMuteStateChanged ); QObject::connect ( pcbSolo, &QCheckBox::stateChanged, this, &CChannelFader::soloStateChanged ); } void CChannelFader::SetGUIDesign ( const EGUIDesign eNewDesign ) { switch ( eNewDesign ) { case GD_ORIGINAL: pFader->setStyleSheet ( "QSlider { width: 45px;" " border-image: url(:/png/fader/res/faderbackground.png) repeat;" " border-top: 10px transparent;" " border-bottom: 10px transparent;" " border-left: 20px transparent;" " border-right: -25px transparent; }" "QSlider::groove { image: url();" " padding-left: -38px;" " padding-top: -10px;" " padding-bottom: -15px; }" "QSlider::handle { image: url(:/png/fader/res/faderhandle.png); }" ); pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures pFader->setMinimumHeight ( 120 ); // if this value is too small, the fader might not be movable with the mouse for fancy skin (#292) pPan->setFixedSize ( 50, 50 ); pPanLabel->setText ( tr ( "PAN" ) ); pcbMute->setText ( tr ( "MUTE" ) ); pcbSolo->setText ( tr ( "SOLO" ) ); pcbGroup->setText ( tr ( "GRP" ) ); plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_LED ); break; case GD_SLIMFADER: pLabelPictGrid->addWidget ( plblLabel, 0, Qt::AlignHCenter ); // label below icons pLabelInstBox->setMinimumHeight ( 84 ); // maximum height of the instrument+flag+label pFader->setMinimumHeight ( 85 ); pPan->setFixedSize ( 28, 28 ); pFader->setTickPosition ( QSlider::NoTicks ); pFader->setStyleSheet ( "" ); pPanLabel->setText ( tr ( "Pan" ) ); pcbMute->setText ( tr ( "M" ) ); pcbSolo->setText ( tr ( "S" ) ); pcbGroup->setText ( tr ( "G" ) ); plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_SLIM_BAR ); break; default: // reset style sheet and set original parameters pFader->setTickPosition ( QSlider::TicksBothSides ); pFader->setStyleSheet ( "" ); pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures pFader->setMinimumHeight ( 85 ); pPan->setFixedSize ( 50, 50 ); pPanLabel->setText ( tr ( "Pan" ) ); pcbMute->setText ( tr ( "Mute" ) ); pcbSolo->setText ( tr ( "Solo" ) ); pcbGroup->setText ( tr ( "Grp" ) ); plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_BAR ); break; } } void CChannelFader::SetDisplayChannelLevel ( const bool eNDCL ) { plbrChannelLevel->setHidden ( !eNDCL ); } bool CChannelFader::GetDisplayChannelLevel() { return !plbrChannelLevel->isHidden(); } void CChannelFader::SetDisplayPans ( const bool eNDP ) { pInfoLabel->setHidden ( !eNDP ); pPanLabel->setHidden ( !eNDP ); pPan->setHidden ( !eNDP ); } void CChannelFader::SetupFaderTag ( const ESkillLevel eSkillLevel ) { // setup group box for label/instrument picture: set a thick black border // with nice round edges QString strStile = "QGroupBox { border: 2px solid black;" " border-radius: 4px;" " padding: 3px;"; // the background color depends on the skill level switch ( eSkillLevel ) { case SL_BEGINNER: strStile += QString ( "background-color: rgb(%1, %2, %3); }" ). arg ( RGBCOL_R_SL_BEGINNER ). arg ( RGBCOL_G_SL_BEGINNER ). arg ( RGBCOL_B_SL_BEGINNER ); break; case SL_INTERMEDIATE: strStile += QString ( "background-color: rgb(%1, %2, %3); }" ). arg ( RGBCOL_R_SL_INTERMEDIATE ). arg ( RGBCOL_G_SL_INTERMEDIATE ). arg ( RGBCOL_B_SL_INTERMEDIATE ); break; case SL_PROFESSIONAL: strStile += QString ( "background-color: rgb(%1, %2, %3); }" ). arg ( RGBCOL_R_SL_SL_PROFESSIONAL ). arg ( RGBCOL_G_SL_SL_PROFESSIONAL ). arg ( RGBCOL_B_SL_SL_PROFESSIONAL ); break; default: strStile += QString ( "background-color: rgb(%1, %2, %3); }" ). arg ( RGBCOL_R_SL_NOT_SET ). arg ( RGBCOL_G_SL_NOT_SET ). arg ( RGBCOL_B_SL_NOT_SET ); break; } pLabelInstBox->setStyleSheet ( strStile ); } void CChannelFader::Reset() { // general initializations SetRemoteFaderIsMute ( false ); // init gain and pan value -> maximum value as definition according to server pFader->setValue ( AUD_MIX_FADER_MAX ); dPreviousFaderLevel = AUD_MIX_FADER_MAX; pPan->setValue ( AUD_MIX_PAN_MAX / 2 ); // reset mute/solo/group check boxes and level meter pcbMute->setChecked ( false ); pcbSolo->setChecked ( false ); pcbGroup->setChecked ( false ); plbrChannelLevel->SetValue ( 0 ); plbrChannelLevel->ClipReset(); // clear instrument picture, country flag, tool tips and label text plblLabel->setText ( "" ); plblLabel->setToolTip ( "" ); plblInstrument->setVisible ( false ); plblInstrument->setToolTip ( "" ); plblCountryFlag->setVisible ( false ); plblCountryFlag->setToolTip ( "" ); cReceivedChanInfo = CChannelInfo(); SetupFaderTag ( SL_NOT_SET ); // set a defined tool tip time out const int iToolTipDurMs = 30000; plblLabel->setToolTipDuration ( iToolTipDurMs ); plblInstrument->setToolTipDuration ( iToolTipDurMs ); plblCountryFlag->setToolTipDuration ( iToolTipDurMs ); bOtherChannelIsSolo = false; bIsMyOwnFader = false; } void CChannelFader::SetFaderLevel ( const double dLevel, const bool bIsGroupUpdate ) { // first make a range check if ( ( dLevel >= 0 ) && ( dLevel <= AUD_MIX_FADER_MAX ) ) { // we set the new fader level in the GUI (slider control) and also tell the // server about the change (block the signal of the fader since we want to // call SendFaderLevelToServer with a special additional parameter) pFader->blockSignals ( true ); pFader->setValue ( MathUtils::round ( dLevel ) ); pFader->blockSignals ( false ); SendFaderLevelToServer ( dLevel, bIsGroupUpdate ); } else if ( dLevel >= 0 ) { // If the level is above the maximum, we have to store it for the purpose // of group fader movement. If you move a fader which has lower volume than // this one and this clips at max, we want to retain the ratio between this // fader and the others in the group. dPreviousFaderLevel = dLevel; } } void CChannelFader::SetPanValue ( const int iPan ) { // first make a range check if ( ( iPan >= 0 ) && ( iPan <= AUD_MIX_PAN_MAX ) ) { // we set the new fader level in the GUI (slider control) which then // emits to signal to tell the server about the change (implicitly) pPan->setValue ( iPan ); } } void CChannelFader::SetFaderIsSelect ( const bool bIsSelected ) { pcbGroup->setChecked ( bIsSelected ); } void CChannelFader::SetFaderIsSolo ( const bool bIsSolo ) { // changing the state automatically emits the signal, too pcbSolo->setChecked ( bIsSolo ); } void CChannelFader::SetFaderIsMute ( const bool bIsMute ) { // changing the state automatically emits the signal, too pcbMute->setChecked ( bIsMute ); } void CChannelFader::SetRemoteFaderIsMute ( const bool bIsMute ) { if ( bIsMute ) { // show orange utf8 SPEAKER WITH CANCELLATION STROKE (U+1F507) pInfoLabel->setText ( "🔇" ); } else { pInfoLabel->setText ( "" ); } } void CChannelFader::SendFaderLevelToServer ( const double dLevel, const bool bIsGroupUpdate ) { // if mute flag is set or other channel is on solo, do not apply the new // fader value to the server (exception: we are on solo, in that case we // ignore the "other channel is on solo" flag) const bool bSuppressServerUpdate = !( ( pcbMute->checkState() == Qt::Unchecked ) && ( !bOtherChannelIsSolo || IsSolo() ) ); // emit signal for new fader gain value emit gainValueChanged ( CalcFaderGain ( dLevel ), bIsMyOwnFader, bIsGroupUpdate, bSuppressServerUpdate, dLevel / dPreviousFaderLevel ); // update previous fader level since the level has changed if ( dLevel > 0 ) { dPreviousFaderLevel = dLevel; } } void CChannelFader::SendPanValueToServer ( const int iPan ) { emit panValueChanged ( static_cast ( iPan ) / AUD_MIX_PAN_MAX ); } void CChannelFader::OnMuteStateChanged ( int value ) { // call muting function SetMute ( static_cast ( value ) == Qt::Checked ); } void CChannelFader::SetMute ( const bool bState ) { if ( bState ) { // mute channel -> send gain of 0 emit gainValueChanged ( 0, bIsMyOwnFader, false, false, -1 ); // set level ratio to in invalid value } else { // only unmute if we are not solot but an other channel is on solo if ( !bOtherChannelIsSolo || IsSolo() ) { // mute was unchecked, get current fader value and apply emit gainValueChanged ( CalcFaderGain ( GetFaderLevel() ), bIsMyOwnFader, false, false, -1 ); // set level ratio to in invalid value // TODO When mute or solo is activated, the group synchronization does not work anymore. // To get a smoother experience, we adjust the previous level as soon as the mute is // again set to off (if we would not do that, on the next move of the fader the other // faders in the group would jump which is very bad). dPreviousFaderLevel = GetFaderLevel(); } } } void CChannelFader::UpdateSoloState ( const bool bNewOtherSoloState ) { // store state (must be done before the SetMute() call!) bOtherChannelIsSolo = bNewOtherSoloState; // mute overwrites solo -> if mute is active, do not change anything if ( !pcbMute->isChecked() ) { // mute channel if we are not solo but another channel is solo SetMute ( bOtherChannelIsSolo && !IsSolo() ); } } void CChannelFader::SetChannelLevel ( const uint16_t iLevel ) { plbrChannelLevel->SetValue ( iLevel ); } void CChannelFader::SetChannelInfos ( const CChannelInfo& cChanInfo ) { // store received channel info cReceivedChanInfo = cChanInfo; // init properties for the tool tip int iTTInstrument = CInstPictures::GetNotUsedInstrument(); QLocale::Country eTTCountry = QLocale::AnyCountry; // Label text -------------------------------------------------------------- // break text at predefined position const int iBreakPos = MAX_LEN_FADER_TAG / 2; QString strModText = cChanInfo.strName; if ( strModText.length() > iBreakPos ) { strModText.insert ( iBreakPos, QString ( "\n" ) ); } plblLabel->setText ( strModText ); // Instrument picture ------------------------------------------------------ // get the resource reference string for this instrument const QString strCurResourceRef = CInstPictures::GetResourceReference ( cChanInfo.iInstrument ); // first check if instrument picture is used or not and if it is valid if ( CInstPictures::IsNotUsedInstrument ( cChanInfo.iInstrument ) || strCurResourceRef.isEmpty() ) { // disable instrument picture plblInstrument->setVisible ( false ); } else { // set correct picture plblInstrument->setPixmap ( QPixmap ( strCurResourceRef ) ); iTTInstrument = cChanInfo.iInstrument; // enable instrument picture plblInstrument->setVisible ( true ); } // Country flag icon ------------------------------------------------------- if ( cChanInfo.eCountry != QLocale::AnyCountry ) { // try to load the country flag icon QPixmap CountryFlagPixmap ( CLocale::GetCountryFlagIconsResourceReference ( cChanInfo.eCountry ) ); // first check if resource reference was valid if ( CountryFlagPixmap.isNull() ) { // disable country flag plblCountryFlag->setVisible ( false ); } else { // set correct picture plblCountryFlag->setPixmap ( CountryFlagPixmap ); eTTCountry = cChanInfo.eCountry; // enable country flag plblCountryFlag->setVisible ( true ); } } else { // disable country flag plblCountryFlag->setVisible ( false ); } // Skill level background color -------------------------------------------- SetupFaderTag ( cChanInfo.eSkillLevel ); // Tool tip ---------------------------------------------------------------- // complete musician profile in the tool tip QString strToolTip = ""; // alias/name if ( !cChanInfo.strName.isEmpty() ) { strToolTip += "

" + tr ( "Alias/Name" ) + "

" + cChanInfo.strName; } // instrument if ( !CInstPictures::IsNotUsedInstrument ( iTTInstrument ) ) { strToolTip += "

" + tr ( "Instrument" ) + "

" + CInstPictures::GetName ( iTTInstrument ); } // location if ( ( eTTCountry != QLocale::AnyCountry ) || ( !cChanInfo.strCity.isEmpty() ) ) { strToolTip += "

" + tr ( "Location" ) + "

"; if ( !cChanInfo.strCity.isEmpty() ) { strToolTip += cChanInfo.strCity; if ( eTTCountry != QLocale::AnyCountry ) { strToolTip += ", "; } } if ( eTTCountry != QLocale::AnyCountry ) { strToolTip += QLocale::countryToString ( eTTCountry ); } } // skill level switch ( cChanInfo.eSkillLevel ) { case SL_BEGINNER: strToolTip += "

" + tr ( "Skill Level" ) + "

" + tr ( "Beginner" ); break; case SL_INTERMEDIATE: strToolTip += "

" + tr ( "Skill Level" ) + "

" + tr ( "Intermediate" ); break; case SL_PROFESSIONAL: strToolTip += "

" + tr ( "Skill Level" ) + "

" + tr ( "Expert" ); break; case SL_NOT_SET: // skill level not set, do not add this entry break; } // if no information is given, leave the tool tip empty, otherwise add header if ( !strToolTip.isEmpty() ) { strToolTip.prepend ( "

" + tr ( "Musician Profile" ) + "

" ); } plblCountryFlag->setToolTip ( strToolTip ); plblInstrument->setToolTip ( strToolTip ); plblLabel->setToolTip ( strToolTip ); } double CChannelFader::CalcFaderGain ( const double dValue ) { // convert actual slider range in gain values // and normalize so that maximum gain is 1 const double dInValueRange0_1 = dValue / AUD_MIX_FADER_MAX; // map range from 0..1 to range -35..0 dB and calculate linear gain if ( dValue == 0 ) { return 0; // -infinity } else { return pow ( 10, ( dInValueRange0_1 * 35 - 35 ) / 20 ); } } /******************************************************************************\ * CAudioMixerBoard * \******************************************************************************/ CAudioMixerBoard::CAudioMixerBoard ( QWidget* parent, Qt::WindowFlags ) : QGroupBox ( parent ), vecStoredFaderTags ( MAX_NUM_STORED_FADER_SETTINGS, "" ), vecStoredFaderLevels ( MAX_NUM_STORED_FADER_SETTINGS, AUD_MIX_FADER_MAX ), vecStoredPanValues ( MAX_NUM_STORED_FADER_SETTINGS, AUD_MIX_PAN_MAX / 2 ), vecStoredFaderIsSolo ( MAX_NUM_STORED_FADER_SETTINGS, false ), vecStoredFaderIsMute ( MAX_NUM_STORED_FADER_SETTINGS, false ), iNewClientFaderLevel ( 100 ), bDisplayPans ( false ), bIsPanSupported ( false ), bNoFaderVisible ( true ), iMyChannelID ( INVALID_INDEX ), strServerName ( "" ), eRecorderState ( RS_UNDEFINED ) { // add group box and hboxlayout QHBoxLayout* pGroupBoxLayout = new QHBoxLayout ( this ); QWidget* pMixerWidget = new QWidget(); // will be added to the scroll area which is then the parent pScrollArea = new CMixerBoardScrollArea ( this ); pMainLayout = new QHBoxLayout ( pMixerWidget ); setAccessibleName ( "Personal Mix at the Server groupbox" ); setWhatsThis ( "" + tr ( "Personal Mix at the Server" ) + ": " + tr ( "When connected to a server, the controls here allow you to set your " "local mix without affecting what others hear from you. The title shows " "the server name and, when known, whether it is actively recording." ) ); // set title text (default: no server given) SetServerName ( "" ); // create all mixer controls and make them invisible vecpChanFader.Init ( MAX_NUM_CHANNELS ); for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { vecpChanFader[i] = new CChannelFader ( this ); vecpChanFader[i]->Hide(); // add fader frame to audio mixer board layout pMainLayout->addWidget ( vecpChanFader[i]->GetMainWidget() ); } // insert horizontal spacer pMainLayout->addItem ( new QSpacerItem ( 0, 0, QSizePolicy::Expanding ) ); // set margins of the layout to zero to get maximum space for the controls pGroupBoxLayout->setContentsMargins ( 0, 0, 0, 1 ); // note: to avoid problems at the bottom, use a small margin for that // add the group box to the scroll area pScrollArea->setMinimumWidth ( 200 ); // at least two faders shall be visible pScrollArea->setWidget ( pMixerWidget ); pScrollArea->setWidgetResizable ( true ); // make sure it fills the entire scroll area pScrollArea->setFrameShape ( QFrame::NoFrame ); pGroupBoxLayout->addWidget ( pScrollArea ); // Connections ------------------------------------------------------------- connectFaderSignalsToMixerBoardSlots(); } CAudioMixerBoard::~CAudioMixerBoard() { for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { delete vecpChanFader[i]; } } template inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots() { int iCurChanID = slotId - 1; void ( CAudioMixerBoard::* pGainValueChanged )( double, bool, bool, bool, double ) = &CAudioMixerBoardSlots::OnChGainValueChanged; void ( CAudioMixerBoard::* pPanValueChanged )( double ) = &CAudioMixerBoardSlots::OnChPanValueChanged; QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::soloStateChanged, this, &CAudioMixerBoard::UpdateSoloStates ); QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::gainValueChanged, this, pGainValueChanged ); QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::panValueChanged, this, pPanValueChanged ); connectFaderSignalsToMixerBoardSlots(); } template<> inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots<0>() {} void CAudioMixerBoard::SetServerName ( const QString& strNewServerName ) { // store the current server name strServerName = strNewServerName; if ( strServerName.isEmpty() ) { // no connection or connection was reset: show default title setTitle ( tr ( "Server" ) ); } else { // Do not set the server name directly but first show a label which indicates // that we are trying to connect the server. First if a connected client // list was received, the connection was successful and the title is updated // with the correct server name. Make sure to choose a "try to connect" title // which is most striking (we use filled blocks and upper case letters). setTitle ( u8"\u2588\u2588\u2588\u2588\u2588 " + tr ( "T R Y I N G T O C O N N E C T" ) + u8" \u2588\u2588\u2588\u2588\u2588" ); } } void CAudioMixerBoard::SetGUIDesign ( const EGUIDesign eNewDesign ) { // move the channels tighter together in slim fader mode if ( eNewDesign == GD_SLIMFADER ) { pMainLayout->setSpacing ( 2 ); } else { pMainLayout->setSpacing ( 6 ); // Qt default spacing value } // apply GUI design to child GUI controls for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { vecpChanFader[i]->SetGUIDesign ( eNewDesign ); } } void CAudioMixerBoard::SetDisplayChannelLevels ( const bool eNDCL ) { bDisplayChannelLevels = eNDCL; // only update hiding the levels immediately, showing the levels // is only applied if the server actually transmits levels if ( !bDisplayChannelLevels ) { // hide all level meters for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { vecpChanFader[i]->SetDisplayChannelLevel ( false ); } } } void CAudioMixerBoard::SetDisplayPans ( const bool eNDP ) { bDisplayPans = eNDP; for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { vecpChanFader[i]->SetDisplayPans ( eNDP && bIsPanSupported ); } } void CAudioMixerBoard::SetPanIsSupported() { bIsPanSupported = true; SetDisplayPans ( bDisplayPans ); } void CAudioMixerBoard::HideAll() { // make all controls invisible for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { // before hiding the fader, store its level (if some conditions are fulfilled) StoreFaderSettings ( vecpChanFader[i] ); vecpChanFader[i]->SetChannelLevel ( 0 ); vecpChanFader[i]->SetDisplayChannelLevel ( false ); vecpChanFader[i]->SetDisplayPans ( false ); vecpChanFader[i]->Hide(); } // set flags bIsPanSupported = false; bNoFaderVisible = true; eRecorderState = RS_UNDEFINED; iMyChannelID = INVALID_INDEX; // use original order of channel (by server ID) ChangeFaderOrder ( false, ST_BY_NAME ); // emit status of connected clients emit NumClientsChanged ( 0 ); // -> no clients connected } void CAudioMixerBoard::ChangeFaderOrder ( const bool bDoSort, const EChSortType eChSortType ) { // create a pair list of lower strings and fader ID for each channel QList > PairList; for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { if ( eChSortType == ST_BY_NAME ) { PairList << QPair ( vecpChanFader[i]->GetReceivedName().toLower(), i ); } else // ST_BY_INSTRUMENT { PairList << QPair ( CInstPictures::GetName ( vecpChanFader[i]->GetReceivedInstrument() ), i ); } } // if requested, sort the channels if ( bDoSort ) { qStableSort ( PairList.begin(), PairList.end() ); } // add channels to the layout in the new order (since we insert on the left, we // have to use a backwards counting loop), note that it is not required to remove // the widget from the layout first but it is moved to the new position automatically for ( int i = MAX_NUM_CHANNELS - 1; i >= 0; i-- ) { pMainLayout->insertWidget ( 0, vecpChanFader[PairList[i].second]->GetMainWidget() ); } } void CAudioMixerBoard::UpdateTitle() { QString strTitlePrefix = ""; if ( eRecorderState == RS_RECORDING ) { strTitlePrefix = "[" + tr ( "RECORDING ACTIVE" ) + "] "; } setTitle ( strTitlePrefix + tr ( "Personal Mix at: " ) + strServerName ); } void CAudioMixerBoard::SetRecorderState ( const ERecorderState newRecorderState ) { // store the new recorder state and update the title eRecorderState = newRecorderState; UpdateTitle(); } void CAudioMixerBoard::ApplyNewConClientList ( CVector& vecChanInfo ) { // we want to set the server name only if the very first faders appear // in the audio mixer board to show a "try to connect" before if ( bNoFaderVisible ) { UpdateTitle(); } // get number of connected clients const int iNumConnectedClients = vecChanInfo.Size(); // search for channels with are already present and preserve their gain // setting, for all other channels reset gain for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { bool bFaderIsUsed = false; for ( int j = 0; j < iNumConnectedClients; j++ ) { // check if current fader is used if ( vecChanInfo[j].iChanID == i ) { // check if fader was already in use -> preserve gain value if ( !vecpChanFader[i]->IsVisible() ) { // the fader was not in use, reset everything for new client vecpChanFader[i]->Reset(); // check if this is my own fader and set fader property if ( i == iMyChannelID ) { vecpChanFader[i]->SetIsMyOwnFader(); } // show fader vecpChanFader[i]->Show(); // Set the default initial fader level. Check first that // this is not the initialization (i.e. previously there // were no faders visible) to avoid that our own level is // adjusted. If we have received our own channel ID, then // we can adjust the level even if no fader was visible. // The fader level of 100 % is the default in the // server, in that case we do not have to do anything here. if ( ( !bNoFaderVisible || ( ( iMyChannelID != INVALID_INDEX ) && ( iMyChannelID != i ) ) ) && ( iNewClientFaderLevel != 100 ) ) { // the value is in percent -> convert range vecpChanFader[i]->SetFaderLevel ( iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX ); } } // restore gain (if new name is different from the current one) if ( vecpChanFader[i]->GetReceivedName().compare ( vecChanInfo[j].strName ) ) { // the text has actually changed, search in the list of // stored settings if we have a matching entry int iStoredFaderLevel; int iStoredPanValue; bool bStoredFaderIsSolo; bool bStoredFaderIsMute; if ( GetStoredFaderSettings ( vecChanInfo[j], iStoredFaderLevel, iStoredPanValue, bStoredFaderIsSolo, bStoredFaderIsMute ) ) { vecpChanFader[i]->SetFaderLevel ( iStoredFaderLevel ); vecpChanFader[i]->SetPanValue ( iStoredPanValue ); vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo ); vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute ); } } // set the channel infos vecpChanFader[i]->SetChannelInfos ( vecChanInfo[j] ); bFaderIsUsed = true; } } // if current fader is not used, hide it if ( !bFaderIsUsed ) { // before hiding the fader, store its level (if some conditions are fulfilled) StoreFaderSettings ( vecpChanFader[i] ); vecpChanFader[i]->Hide(); } } // update the solo states since if any channel was on solo and a new client // has just connected, the new channel must be muted UpdateSoloStates(); // update flag for "all faders are invisible" bNoFaderVisible = ( iNumConnectedClients == 0 ); // emit status of connected clients emit NumClientsChanged ( iNumConnectedClients ); } void CAudioMixerBoard::SetFaderLevel ( const int iChannelIdx, const int iValue ) { // only apply new fader level if channel index is valid and the fader is visible if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) ) { if ( vecpChanFader[iChannelIdx]->IsVisible() ) { vecpChanFader[iChannelIdx]->SetFaderLevel ( iValue ); } } } void CAudioMixerBoard::SetRemoteFaderIsMute ( const int iChannelIdx, const bool bIsMute ) { // only apply remote mute state if channel index is valid and the fader is visible if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) ) { if ( vecpChanFader[iChannelIdx]->IsVisible() ) { vecpChanFader[iChannelIdx]->SetRemoteFaderIsMute ( bIsMute ); } } } void CAudioMixerBoard::UpdateSoloStates() { // first check if any channel has a solo state active bool bAnyChannelIsSolo = false; for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { // check if fader is in use and has solo state active if ( vecpChanFader[i]->IsVisible() && vecpChanFader[i]->IsSolo() ) { bAnyChannelIsSolo = true; continue; } } // now update the solo state of all active faders for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { if ( vecpChanFader[i]->IsVisible() ) { vecpChanFader[i]->UpdateSoloState ( bAnyChannelIsSolo ); } } } void CAudioMixerBoard::UpdateGainValue ( const int iChannelIdx, const double dValue, const bool bIsMyOwnFader, const bool bIsGroupUpdate, const bool bSuppressServerUpdate, const double dLevelRatio ) { // update current gain if ( !bSuppressServerUpdate ) { emit ChangeChanGain ( iChannelIdx, dValue, bIsMyOwnFader ); } // if this fader is selected, all other in the group must be updated as // well (note that we do not have to update if this is already a group update // to avoid an infinite loop) if ( vecpChanFader[iChannelIdx]->IsSelect() && !bIsGroupUpdate ) { for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { // update rest of faders selected if ( vecpChanFader[i]->IsVisible() && vecpChanFader[i]->IsSelect() && ( i != iChannelIdx ) && ( dLevelRatio >= 0 ) ) { // synchronize faders with moving fader level (it is important // to set the group flag to avoid infinite looping) vecpChanFader[i]->SetFaderLevel ( vecpChanFader[i]->GetPreviousFaderLevel() * dLevelRatio, true ); } } } } void CAudioMixerBoard::UpdatePanValue ( const int iChannelIdx, const double dValue ) { emit ChangeChanPan ( iChannelIdx, dValue ); } void CAudioMixerBoard::StoreFaderSettings ( CChannelFader* pChanFader ) { // if the fader was visible and the name is not empty, we store the old gain if ( pChanFader->IsVisible() && !pChanFader->GetReceivedName().isEmpty() ) { CVector viOldStoredFaderLevels ( vecStoredFaderLevels ); CVector viOldStoredPanValues ( vecStoredPanValues ); CVector vbOldStoredFaderIsSolo ( vecStoredFaderIsSolo ); CVector vbOldStoredFaderIsMute ( vecStoredFaderIsMute ); // init temporary list count (may be overwritten later on) int iTempListCnt = 0; // put new value on the top of the list const int iOldIdx = vecStoredFaderTags.StringFiFoWithCompare ( pChanFader->GetReceivedName(), true ); // current fader level and solo state is at the top of the list vecStoredFaderLevels[0] = pChanFader->GetFaderLevel(); vecStoredPanValues[0] = pChanFader->GetPanValue(); vecStoredFaderIsSolo[0] = pChanFader->IsSolo(); vecStoredFaderIsMute[0] = pChanFader->IsMute(); iTempListCnt = 1; for ( int iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ ) { // first check if we still have space in our data storage if ( iTempListCnt < MAX_NUM_STORED_FADER_SETTINGS ) { // check for the old index of the current entry (this has to be // skipped), note that per definition: the old index is an illegal // index in case the entry was not present in the vector before if ( iIdx != iOldIdx ) { vecStoredFaderLevels[iTempListCnt] = viOldStoredFaderLevels[iIdx]; vecStoredPanValues[iTempListCnt] = viOldStoredPanValues[iIdx]; vecStoredFaderIsSolo[iTempListCnt] = vbOldStoredFaderIsSolo[iIdx]; vecStoredFaderIsMute[iTempListCnt] = vbOldStoredFaderIsMute[iIdx]; iTempListCnt++; } } } } } bool CAudioMixerBoard::GetStoredFaderSettings ( const CChannelInfo& ChanInfo, int& iStoredFaderLevel, int& iStoredPanValue, bool& bStoredFaderIsSolo, bool& bStoredFaderIsMute ) { // only do the check if the name string is not empty if ( !ChanInfo.strName.isEmpty() ) { for ( int iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ ) { // check if fader text is already known in the list if ( !vecStoredFaderTags[iIdx].compare ( ChanInfo.strName ) ) { // copy stored settings values iStoredFaderLevel = vecStoredFaderLevels[iIdx]; iStoredPanValue = vecStoredPanValues[iIdx]; bStoredFaderIsSolo = vecStoredFaderIsSolo[iIdx] != 0; bStoredFaderIsMute = vecStoredFaderIsMute[iIdx] != 0; // values found and copied, return OK return true; } } } // 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++] ); // show level only if we successfully received levels from the // server (if server does not support levels, do not show levels) if ( bDisplayChannelLevels && !vecpChanFader[iChId]->GetDisplayChannelLevel() ) { vecpChanFader[iChId]->SetDisplayChannelLevel ( true ); } } } }