2013-01-23 11:41:13 +01:00
|
|
|
/******************************************************************************\
|
2020-01-01 15:41:43 +01:00
|
|
|
* Copyright (c) 2004-2020
|
2013-01-23 11:41:13 +01:00
|
|
|
*
|
|
|
|
* 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.,
|
2020-06-08 22:58:11 +02:00
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
2013-01-23 11:41:13 +01:00
|
|
|
*
|
|
|
|
\******************************************************************************/
|
|
|
|
|
|
|
|
#include "audiomixerboard.h"
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************\
|
|
|
|
* CChanneFader *
|
|
|
|
\******************************************************************************/
|
2020-06-08 17:34:45 +02:00
|
|
|
CChannelFader::CChannelFader ( QWidget* pNW )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
|
|
|
// 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)
|
2020-06-21 20:09:13 +02:00
|
|
|
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 );
|
2020-07-04 10:46:31 +02:00
|
|
|
pcbGroup = new QCheckBox ( "", pMuteSoloBox );
|
2020-06-21 20:09:13 +02:00
|
|
|
|
|
|
|
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 ( );
|
2020-03-29 23:40:53 +02:00
|
|
|
|
2020-07-04 09:45:46 +02:00
|
|
|
// define the popup menu for the group checkbox
|
|
|
|
pGroupPopupMenu = new QMenu ( "", pcbGroup );
|
|
|
|
pGroupPopupMenu->addAction ( tr ( "No grouping" ), this, SLOT ( OnGroupMenuGrpNone() ) );
|
|
|
|
pGroupPopupMenu->addAction ( tr ( "Assign to group" ) + " 1", this, SLOT ( OnGroupMenuGrp1() ) );
|
|
|
|
pGroupPopupMenu->addAction ( tr ( "Assign to group" ) + " 2", this, SLOT ( OnGroupMenuGrp2() ) );
|
|
|
|
pGroupPopupMenu->addAction ( tr ( "Assign to group" ) + " 3", this, SLOT ( OnGroupMenuGrp3() ) );
|
|
|
|
pGroupPopupMenu->addAction ( tr ( "Assign to group" ) + " 4", this, SLOT ( OnGroupMenuGrp4() ) );
|
|
|
|
#if ( MAX_NUM_FADER_GROUPS != 4 )
|
|
|
|
# error "MAX_NUM_FADER_GROUPS must be set to 4, see implementation in CChannelFader()"
|
|
|
|
#endif
|
|
|
|
|
2020-03-29 23:40:53 +02:00
|
|
|
// setup channel level
|
2020-04-22 21:19:55 +02:00
|
|
|
plbrChannelLevel->setContentsMargins ( 0, 3, 2, 3 );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
|
|
|
// setup slider
|
2020-06-20 21:11:31 +02:00
|
|
|
pFader->setPageStep ( 1 );
|
|
|
|
pFader->setRange ( 0, AUD_MIX_FADER_MAX );
|
|
|
|
pFader->setTickInterval ( AUD_MIX_FADER_MAX / 9 );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-05-19 18:28:52 +02:00
|
|
|
// setup panning control
|
2020-05-22 20:26:04 +02:00
|
|
|
pPan->setRange ( 0, AUD_MIX_PAN_MAX );
|
|
|
|
pPan->setValue ( AUD_MIX_PAN_MAX / 2 );
|
2020-05-19 18:28:52 +02:00
|
|
|
pPan->setNotchesVisible ( true );
|
2020-05-22 20:26:04 +02:00
|
|
|
pPanInfoGrid->addWidget ( pPanLabel, 0, Qt::AlignLeft );
|
2020-05-21 23:20:53 +02:00
|
|
|
pPanInfoGrid->addWidget ( pInfoLabel );
|
2020-05-22 20:26:04 +02:00
|
|
|
pPanGrid->addLayout ( pPanInfoGrid );
|
|
|
|
pPanGrid->addWidget ( pPan, 0, Qt::AlignHCenter );
|
2020-04-26 00:55:28 +02:00
|
|
|
|
2013-02-10 09:44:01 +01:00
|
|
|
// setup fader tag label (black bold text which is centered)
|
2020-06-11 16:42:50 +02:00
|
|
|
plblLabel->setTextFormat ( Qt::PlainText );
|
|
|
|
plblLabel->setAlignment ( Qt::AlignHCenter | Qt::AlignVCenter );
|
|
|
|
plblLabel->setStyleSheet ( "QLabel { color: black; font: bold; }" );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2013-02-10 09:44:01 +01:00
|
|
|
// set margins of the layouts to zero to get maximum space for the controls
|
2013-01-23 11:41:13 +01:00
|
|
|
pMainGrid->setContentsMargins ( 0, 0, 0, 0 );
|
2020-03-29 23:40:53 +02:00
|
|
|
|
2020-05-20 20:58:11 +02:00
|
|
|
pPanGrid->setContentsMargins ( 0, 0, 0, 0 );
|
|
|
|
pPanGrid->setSpacing ( 0 ); // only minimal space
|
|
|
|
|
2020-03-29 23:40:53 +02:00
|
|
|
pLevelsGrid->setContentsMargins ( 0, 0, 0, 0 );
|
|
|
|
pLevelsGrid->setSpacing ( 0 ); // only minimal space
|
|
|
|
|
|
|
|
pMuteSoloGrid->setContentsMargins ( 0, 0, 0, 0 );
|
2020-06-20 21:55:33 +02:00
|
|
|
pMuteSoloGrid->setSpacing ( 0 ); // only minimal space
|
2020-03-29 23:40:53 +02:00
|
|
|
|
2013-02-10 09:44:01 +01:00
|
|
|
pLabelGrid->setContentsMargins ( 0, 0, 0, 0 );
|
2013-02-11 16:53:52 +01:00
|
|
|
pLabelGrid->setSpacing ( 2 ); // only minimal space between picture and text
|
2013-02-10 09:44:01 +01:00
|
|
|
|
|
|
|
// add user controls to the grids
|
2015-01-25 10:57:13 +01:00
|
|
|
pLabelPictGrid->addWidget ( plblCountryFlag, 0, Qt::AlignHCenter );
|
|
|
|
pLabelPictGrid->addWidget ( plblInstrument, 0, Qt::AlignHCenter );
|
2020-03-29 23:40:53 +02:00
|
|
|
|
2015-01-25 10:57:13 +01:00
|
|
|
pLabelGrid->addLayout ( pLabelPictGrid );
|
2020-06-11 16:42:50 +02:00
|
|
|
pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // note: just initial add, may be changed later
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-03-29 23:40:53 +02:00
|
|
|
pLevelsGrid->addWidget ( plbrChannelLevel, 0, Qt::AlignRight );
|
|
|
|
pLevelsGrid->addWidget ( pFader, 0, Qt::AlignLeft );
|
|
|
|
|
2020-06-20 17:44:34 +02:00
|
|
|
pMuteSoloGrid->addWidget ( pcbGroup, 0, Qt::AlignLeft );
|
2020-06-20 17:51:03 +02:00
|
|
|
pMuteSoloGrid->addWidget ( pcbMute, 0, Qt::AlignLeft );
|
|
|
|
pMuteSoloGrid->addWidget ( pcbSolo, 0, Qt::AlignLeft );
|
2020-03-29 23:40:53 +02:00
|
|
|
|
2020-05-20 20:58:11 +02:00
|
|
|
pMainGrid->addLayout ( pPanGrid );
|
2020-03-29 23:40:53 +02:00
|
|
|
pMainGrid->addWidget ( pLevelsBox, 0, Qt::AlignHCenter );
|
|
|
|
pMainGrid->addWidget ( pMuteSoloBox, 0, Qt::AlignHCenter );
|
2013-02-10 09:44:01 +01:00
|
|
|
pMainGrid->addWidget ( pLabelInstBox );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
|
|
|
// reset current fader
|
2020-07-07 22:07:19 +02:00
|
|
|
strGroupBaseText = "Grp"; // this will most probably overwritten by SetGUIDesign()
|
|
|
|
iInstrPicMaxWidth = INVALID_INDEX; // this will most probably overwritten by SetGUIDesign()
|
2013-01-23 11:41:13 +01:00
|
|
|
Reset();
|
|
|
|
|
|
|
|
// add help text to controls
|
2020-05-06 21:56:22 +02:00
|
|
|
plbrChannelLevel->setWhatsThis ( "<b>" + tr ( "Channel Level" ) + ":</b> " +
|
2020-06-04 11:53:04 +02:00
|
|
|
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." ) );
|
2020-03-29 23:40:53 +02:00
|
|
|
plbrChannelLevel->setAccessibleName ( tr ( "Input level of the current audio "
|
|
|
|
"channel at the server" ) );
|
|
|
|
|
2020-05-06 21:56:22 +02:00
|
|
|
pFader->setWhatsThis ( "<b>" + tr ( "Mixer Fader" ) + ":</b> " + tr (
|
2020-06-04 11:53:04 +02:00
|
|
|
"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." ) );
|
2020-03-29 23:40:53 +02:00
|
|
|
pFader->setAccessibleName ( tr ( "Local mix level setting of the current audio "
|
|
|
|
"channel at the server" ) );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-05-21 18:26:33 +02:00
|
|
|
pInfoLabel->setWhatsThis ( "<b>" + tr ( "Status Indicator" ) + ":</b> " + tr (
|
|
|
|
"Shows a status indication about the client which is assigned to this channel. "
|
|
|
|
"Supported indicators are:" ) + "<ul><li>" + tr (
|
2020-06-04 11:53:04 +02:00
|
|
|
"Speaker with cancellation stroke: Indicates that another client has muted you." ) +
|
2020-05-21 18:26:33 +02:00
|
|
|
"</li></ul>" );
|
|
|
|
pInfoLabel->setAccessibleName ( tr ( "Status indicator label" ) );
|
|
|
|
|
|
|
|
pPan->setWhatsThis ( "<b>" + tr ( "Panning" ) + ":</b> " + tr (
|
2020-06-04 11:53:04 +02:00
|
|
|
"Sets the pan from Left to Right of the channel. "
|
2020-05-21 15:28:04 +02:00
|
|
|
"Works only in stereo or preferably mono in/stereo out mode." ) );
|
2020-04-26 00:55:28 +02:00
|
|
|
pPan->setAccessibleName ( tr ( "Local panning position of the current audio channel at the server" ) );
|
|
|
|
|
2020-05-06 21:56:22 +02:00
|
|
|
pcbMute->setWhatsThis ( "<b>" + tr ( "Mute" ) + ":</b> " + tr (
|
|
|
|
"With the Mute checkbox, the audio channel can be muted." ) );
|
2013-02-10 09:44:01 +01:00
|
|
|
pcbMute->setAccessibleName ( tr ( "Mute button" ) );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-05-06 21:56:22 +02:00
|
|
|
pcbSolo->setWhatsThis ( "<b>" + tr ( "Solo" ) + ":</b> " + tr ( "With the Solo checkbox, the "
|
2013-01-23 11:41:13 +01:00
|
|
|
"audio channel can be set to solo which means that all other channels "
|
2020-06-04 11:53:04 +02:00
|
|
|
"except the soloed channel are muted. It is possible to set more than "
|
2015-01-23 20:34:10 +01:00
|
|
|
"one channel to solo." ) );
|
2013-02-10 09:44:01 +01:00
|
|
|
pcbSolo->setAccessibleName ( tr ( "Solo button" ) );
|
|
|
|
|
2020-06-20 22:02:46 +02:00
|
|
|
pcbGroup->setWhatsThis ( "<b>" + tr ( "Group" ) + ":</b> " + 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." ) );
|
2020-06-20 17:44:34 +02:00
|
|
|
pcbGroup->setAccessibleName ( tr ( "Group button" ) );
|
2020-06-20 16:20:27 +02:00
|
|
|
|
2020-05-06 21:56:22 +02:00
|
|
|
QString strFaderText = "<b>" + tr ( "Fader Tag" ) + ":</b> " + tr ( "The fader tag "
|
2020-06-04 11:53:04 +02:00
|
|
|
"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." );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2015-01-23 22:38:24 +01:00
|
|
|
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" ) );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
// Connections -------------------------------------------------------------
|
2020-06-06 17:19:25 +02:00
|
|
|
QObject::connect ( pFader, &QSlider::valueChanged,
|
|
|
|
this, &CChannelFader::OnLevelValueChanged );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-06-06 17:19:25 +02:00
|
|
|
QObject::connect ( pPan, &QDial::valueChanged,
|
|
|
|
this, &CChannelFader::OnPanValueChanged );
|
2020-04-26 00:55:28 +02:00
|
|
|
|
2020-06-06 17:19:25 +02:00
|
|
|
QObject::connect ( pcbMute, &QCheckBox::stateChanged,
|
|
|
|
this, &CChannelFader::OnMuteStateChanged );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-06-06 17:19:25 +02:00
|
|
|
QObject::connect ( pcbSolo, &QCheckBox::stateChanged,
|
|
|
|
this, &CChannelFader::soloStateChanged );
|
2020-07-04 09:45:46 +02:00
|
|
|
|
|
|
|
QObject::connect ( pcbGroup, &QCheckBox::stateChanged,
|
|
|
|
this, &CChannelFader::OnGroupStateChanged );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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); }" );
|
|
|
|
|
2020-06-11 16:42:50 +02:00
|
|
|
pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons
|
2020-06-29 21:18:54 +02:00
|
|
|
pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures
|
2020-06-20 21:11:31 +02:00
|
|
|
pFader->setMinimumHeight ( 120 ); // if this value is too small, the fader might not be movable with the mouse for fancy skin (#292)
|
2020-06-11 16:42:50 +02:00
|
|
|
pPan->setFixedSize ( 50, 50 );
|
2020-05-19 18:28:52 +02:00
|
|
|
pPanLabel->setText ( tr ( "PAN" ) );
|
2020-04-22 21:19:55 +02:00
|
|
|
pcbMute->setText ( tr ( "MUTE" ) );
|
|
|
|
pcbSolo->setText ( tr ( "SOLO" ) );
|
2020-07-04 10:46:31 +02:00
|
|
|
strGroupBaseText = tr ( "GRP" );
|
2020-06-21 20:09:13 +02:00
|
|
|
plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_LED );
|
2020-07-07 22:07:19 +02:00
|
|
|
iInstrPicMaxWidth = INVALID_INDEX; // no instrument picture scaling
|
2013-01-23 11:41:13 +01:00
|
|
|
break;
|
|
|
|
|
2020-06-11 16:42:50 +02:00
|
|
|
case GD_SLIMFADER:
|
|
|
|
pLabelPictGrid->addWidget ( plblLabel, 0, Qt::AlignHCenter ); // label below icons
|
2020-07-05 09:01:32 +02:00
|
|
|
pLabelInstBox->setMinimumHeight ( 94 ); // maximum height of the instrument+flag+label
|
2020-06-20 21:11:31 +02:00
|
|
|
pFader->setMinimumHeight ( 85 );
|
2020-06-11 16:42:50 +02:00
|
|
|
pPan->setFixedSize ( 28, 28 );
|
|
|
|
pFader->setTickPosition ( QSlider::NoTicks );
|
|
|
|
pFader->setStyleSheet ( "" );
|
|
|
|
pPanLabel->setText ( tr ( "Pan" ) );
|
|
|
|
pcbMute->setText ( tr ( "M" ) );
|
|
|
|
pcbSolo->setText ( tr ( "S" ) );
|
2020-07-04 10:46:31 +02:00
|
|
|
strGroupBaseText = tr ( "G" );
|
2020-06-21 20:09:13 +02:00
|
|
|
plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_SLIM_BAR );
|
2020-07-07 22:07:19 +02:00
|
|
|
iInstrPicMaxWidth = 18; // scale instrument picture to avoid enlarging the width by the picture
|
2020-06-11 16:42:50 +02:00
|
|
|
break;
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
default:
|
2020-06-29 21:18:54 +02:00
|
|
|
// reset style sheet and set original parameters
|
2020-06-11 16:42:50 +02:00
|
|
|
pFader->setTickPosition ( QSlider::TicksBothSides );
|
2020-04-22 21:19:55 +02:00
|
|
|
pFader->setStyleSheet ( "" );
|
2020-06-11 16:42:50 +02:00
|
|
|
pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons
|
2020-06-29 21:18:54 +02:00
|
|
|
pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures
|
2020-06-20 21:11:31 +02:00
|
|
|
pFader->setMinimumHeight ( 85 );
|
2020-06-11 16:42:50 +02:00
|
|
|
pPan->setFixedSize ( 50, 50 );
|
2020-05-19 18:28:52 +02:00
|
|
|
pPanLabel->setText ( tr ( "Pan" ) );
|
2020-04-22 21:19:55 +02:00
|
|
|
pcbMute->setText ( tr ( "Mute" ) );
|
|
|
|
pcbSolo->setText ( tr ( "Solo" ) );
|
2020-07-04 10:46:31 +02:00
|
|
|
strGroupBaseText = tr ( "Grp" );
|
2020-06-21 20:09:13 +02:00
|
|
|
plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_BAR );
|
2020-07-07 22:07:19 +02:00
|
|
|
iInstrPicMaxWidth = INVALID_INDEX; // no instrument picture scaling
|
2013-01-23 11:41:13 +01:00
|
|
|
break;
|
|
|
|
}
|
2020-07-04 10:46:31 +02:00
|
|
|
|
|
|
|
// we need to update since we changed the checkbox text
|
|
|
|
UpdateGroupIDDependencies();
|
2020-07-05 09:01:32 +02:00
|
|
|
|
|
|
|
// the instrument picture might need scaling after a style change
|
|
|
|
SetChannelInfos ( cReceivedChanInfo );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
2020-03-29 23:40:53 +02:00
|
|
|
void CChannelFader::SetDisplayChannelLevel ( const bool eNDCL )
|
|
|
|
{
|
2020-04-07 00:20:32 +02:00
|
|
|
plbrChannelLevel->setHidden ( !eNDCL );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CChannelFader::GetDisplayChannelLevel()
|
|
|
|
{
|
|
|
|
return !plbrChannelLevel->isHidden();
|
2020-03-29 23:40:53 +02:00
|
|
|
}
|
|
|
|
|
2020-05-19 18:28:52 +02:00
|
|
|
void CChannelFader::SetDisplayPans ( const bool eNDP )
|
|
|
|
{
|
2020-05-21 21:24:37 +02:00
|
|
|
pInfoLabel->setHidden ( !eNDP );
|
|
|
|
pPanLabel->setHidden ( !eNDP );
|
|
|
|
pPan->setHidden ( !eNDP );
|
2020-05-19 18:28:52 +02:00
|
|
|
}
|
|
|
|
|
2015-01-31 09:58:19 +01:00
|
|
|
void CChannelFader::SetupFaderTag ( const ESkillLevel eSkillLevel )
|
|
|
|
{
|
2020-07-04 12:07:51 +02:00
|
|
|
// the group ID defines the border color
|
|
|
|
QString strBorderColor;
|
|
|
|
|
|
|
|
switch ( iGroupID )
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
strBorderColor = "red";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
strBorderColor = "blue";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
strBorderColor = "green";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
strBorderColor = "yellow";
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
strBorderColor = "black";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-01-31 09:58:19 +01:00
|
|
|
// setup group box for label/instrument picture: set a thick black border
|
|
|
|
// with nice round edges
|
|
|
|
QString strStile =
|
2020-07-04 12:07:51 +02:00
|
|
|
"QGroupBox { border: 2px solid " + strBorderColor + ";"
|
2020-06-11 16:42:50 +02:00
|
|
|
" border-radius: 4px;"
|
|
|
|
" padding: 3px;";
|
2015-01-31 09:58:19 +01:00
|
|
|
|
|
|
|
// the background color depends on the skill level
|
|
|
|
switch ( eSkillLevel )
|
|
|
|
{
|
|
|
|
case SL_BEGINNER:
|
2015-02-04 17:07:21 +01:00
|
|
|
strStile += QString ( "background-color: rgb(%1, %2, %3); }" ).
|
|
|
|
arg ( RGBCOL_R_SL_BEGINNER ).
|
|
|
|
arg ( RGBCOL_G_SL_BEGINNER ).
|
|
|
|
arg ( RGBCOL_B_SL_BEGINNER );
|
2015-01-31 09:58:19 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SL_INTERMEDIATE:
|
2015-02-04 17:07:21 +01:00
|
|
|
strStile += QString ( "background-color: rgb(%1, %2, %3); }" ).
|
|
|
|
arg ( RGBCOL_R_SL_INTERMEDIATE ).
|
|
|
|
arg ( RGBCOL_G_SL_INTERMEDIATE ).
|
|
|
|
arg ( RGBCOL_B_SL_INTERMEDIATE );
|
2015-01-31 09:58:19 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SL_PROFESSIONAL:
|
2015-02-04 17:07:21 +01:00
|
|
|
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 );
|
2015-01-31 09:58:19 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2015-02-04 17:07:21 +01:00
|
|
|
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 );
|
2015-01-31 09:58:19 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pLabelInstBox->setStyleSheet ( strStile );
|
|
|
|
}
|
|
|
|
|
2013-02-27 22:28:38 +01:00
|
|
|
void CChannelFader::Reset()
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-05-21 18:26:33 +02:00
|
|
|
// general initializations
|
|
|
|
SetRemoteFaderIsMute ( false );
|
|
|
|
|
2020-04-26 00:55:28 +02:00
|
|
|
// init gain and pan value -> maximum value as definition according to server
|
2013-02-27 22:28:38 +01:00
|
|
|
pFader->setValue ( AUD_MIX_FADER_MAX );
|
2020-06-30 21:37:36 +02:00
|
|
|
dPreviousFaderLevel = AUD_MIX_FADER_MAX;
|
2020-05-18 20:46:46 +02:00
|
|
|
pPan->setValue ( AUD_MIX_PAN_MAX / 2 );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-06-20 20:29:43 +02:00
|
|
|
// reset mute/solo/group check boxes and level meter
|
2013-01-23 11:41:13 +01:00
|
|
|
pcbMute->setChecked ( false );
|
|
|
|
pcbSolo->setChecked ( false );
|
2020-06-21 22:39:15 +02:00
|
|
|
plbrChannelLevel->SetValue ( 0 );
|
2020-06-22 22:18:34 +02:00
|
|
|
plbrChannelLevel->ClipReset();
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2015-01-23 22:38:24 +01:00
|
|
|
// clear instrument picture, country flag, tool tips and label text
|
2015-01-30 21:53:43 +01:00
|
|
|
plblLabel->setText ( "" );
|
|
|
|
plblLabel->setToolTip ( "" );
|
2015-01-23 22:38:24 +01:00
|
|
|
plblInstrument->setVisible ( false );
|
|
|
|
plblInstrument->setToolTip ( "" );
|
|
|
|
plblCountryFlag->setVisible ( false );
|
|
|
|
plblCountryFlag->setToolTip ( "" );
|
2020-06-13 08:31:28 +02:00
|
|
|
cReceivedChanInfo = CChannelInfo();
|
2015-01-31 09:58:19 +01:00
|
|
|
SetupFaderTag ( SL_NOT_SET );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-05-14 21:12:06 +02:00
|
|
|
// set a defined tool tip time out
|
2015-01-30 21:53:43 +01:00
|
|
|
const int iToolTipDurMs = 30000;
|
2015-02-04 07:59:28 +01:00
|
|
|
plblLabel->setToolTipDuration ( iToolTipDurMs );
|
|
|
|
plblInstrument->setToolTipDuration ( iToolTipDurMs );
|
2015-01-30 21:53:43 +01:00
|
|
|
plblCountryFlag->setToolTipDuration ( iToolTipDurMs );
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
bOtherChannelIsSolo = false;
|
2020-05-26 20:45:10 +02:00
|
|
|
bIsMyOwnFader = false;
|
2020-07-04 09:45:46 +02:00
|
|
|
|
|
|
|
iGroupID = INVALID_INDEX;
|
2020-07-04 10:46:31 +02:00
|
|
|
UpdateGroupIDDependencies();
|
2013-02-27 22:28:38 +01:00
|
|
|
}
|
2013-02-24 10:03:57 +01:00
|
|
|
|
2020-06-30 21:37:36 +02:00
|
|
|
void CChannelFader::SetFaderLevel ( const double dLevel,
|
|
|
|
const bool bIsGroupUpdate )
|
2013-02-27 22:28:38 +01:00
|
|
|
{
|
|
|
|
// first make a range check
|
2020-07-02 17:55:59 +02:00
|
|
|
if ( dLevel >= 0 )
|
2013-02-24 10:03:57 +01:00
|
|
|
{
|
2013-02-27 22:28:38 +01:00
|
|
|
// we set the new fader level in the GUI (slider control) and also tell the
|
2020-06-20 20:29:43 +02:00
|
|
|
// server about the change (block the signal of the fader since we want to
|
|
|
|
// call SendFaderLevelToServer with a special additional parameter)
|
|
|
|
pFader->blockSignals ( true );
|
2020-07-02 17:55:59 +02:00
|
|
|
pFader->setValue ( min ( AUD_MIX_FADER_MAX, MathUtils::round ( dLevel ) ) );
|
2020-06-20 20:29:43 +02:00
|
|
|
pFader->blockSignals ( false );
|
|
|
|
|
2020-07-02 17:55:59 +02:00
|
|
|
SendFaderLevelToServer ( min ( static_cast<double> ( AUD_MIX_FADER_MAX ), dLevel ), bIsGroupUpdate );
|
|
|
|
|
|
|
|
if ( dLevel > AUD_MIX_FADER_MAX )
|
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|
2020-07-01 15:20:28 +02:00
|
|
|
}
|
2020-04-26 00:55:28 +02:00
|
|
|
}
|
|
|
|
|
2020-05-18 20:46:46 +02:00
|
|
|
void CChannelFader::SetPanValue ( const int iPan )
|
2020-04-26 00:55:28 +02:00
|
|
|
{
|
|
|
|
// first make a range check
|
2020-05-12 23:11:06 +02:00
|
|
|
if ( ( iPan >= 0 ) && ( iPan <= AUD_MIX_PAN_MAX ) )
|
2020-04-26 00:55:28 +02:00
|
|
|
{
|
2020-06-20 20:29:43 +02:00
|
|
|
// 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 );
|
2013-02-24 10:03:57 +01:00
|
|
|
}
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 11:51:31 +01:00
|
|
|
void CChannelFader::SetFaderIsSolo ( const bool bIsSolo )
|
|
|
|
{
|
|
|
|
// changing the state automatically emits the signal, too
|
|
|
|
pcbSolo->setChecked ( bIsSolo );
|
|
|
|
}
|
|
|
|
|
2020-04-05 17:57:39 +02:00
|
|
|
void CChannelFader::SetFaderIsMute ( const bool bIsMute )
|
|
|
|
{
|
2020-04-04 17:14:59 +02:00
|
|
|
// changing the state automatically emits the signal, too
|
|
|
|
pcbMute->setChecked ( bIsMute );
|
|
|
|
}
|
|
|
|
|
2020-05-21 18:26:33 +02:00
|
|
|
void CChannelFader::SetRemoteFaderIsMute ( const bool bIsMute )
|
|
|
|
{
|
|
|
|
if ( bIsMute )
|
|
|
|
{
|
2020-05-21 20:27:01 +02:00
|
|
|
// show orange utf8 SPEAKER WITH CANCELLATION STROKE (U+1F507)
|
2020-05-21 18:26:33 +02:00
|
|
|
pInfoLabel->setText ( "<font color=""orange"">🔇</font>" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pInfoLabel->setText ( "" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-30 21:37:36 +02:00
|
|
|
void CChannelFader::SendFaderLevelToServer ( const double dLevel,
|
|
|
|
const bool bIsGroupUpdate )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
|
|
|
// if mute flag is set or other channel is on solo, do not apply the new
|
2020-06-30 21:37:36 +02:00
|
|
|
// 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 );
|
|
|
|
|
2020-07-01 15:22:51 +02:00
|
|
|
// update previous fader level since the level has changed, avoid to use
|
|
|
|
// the zero value not to have division by zero and also to retain the ratio
|
|
|
|
// after the fader is moved up again from the zero position
|
2020-06-30 21:37:36 +02:00
|
|
|
if ( dLevel > 0 )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-06-30 21:37:36 +02:00
|
|
|
dPreviousFaderLevel = dLevel;
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-26 00:55:28 +02:00
|
|
|
void CChannelFader::SendPanValueToServer ( const int iPan )
|
2020-06-20 20:29:43 +02:00
|
|
|
{
|
2020-05-18 20:46:46 +02:00
|
|
|
emit panValueChanged ( static_cast<double> ( iPan ) / AUD_MIX_PAN_MAX );
|
2020-04-26 00:55:28 +02:00
|
|
|
}
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
void CChannelFader::OnMuteStateChanged ( int value )
|
|
|
|
{
|
|
|
|
// call muting function
|
|
|
|
SetMute ( static_cast<Qt::CheckState> ( value ) == Qt::Checked );
|
|
|
|
}
|
|
|
|
|
2020-07-04 09:45:46 +02:00
|
|
|
void CChannelFader::SetGroupID ( const int iNGroupID )
|
|
|
|
{
|
|
|
|
iGroupID = iNGroupID;
|
2020-07-04 10:46:31 +02:00
|
|
|
UpdateGroupIDDependencies();
|
2020-07-04 09:45:46 +02:00
|
|
|
}
|
|
|
|
|
2020-07-04 10:46:31 +02:00
|
|
|
void CChannelFader::UpdateGroupIDDependencies()
|
2020-07-04 09:45:46 +02:00
|
|
|
{
|
|
|
|
// update the group checkbox according the current group ID setting
|
2020-07-07 22:02:44 +02:00
|
|
|
pcbGroup->blockSignals ( true ); // make sure no signals are fired
|
2020-07-04 09:45:46 +02:00
|
|
|
if ( iGroupID == INVALID_INDEX )
|
|
|
|
{
|
|
|
|
pcbGroup->setCheckState ( Qt::Unchecked );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pcbGroup->setCheckState ( Qt::Checked );
|
|
|
|
}
|
|
|
|
pcbGroup->blockSignals ( false );
|
2020-07-04 10:46:31 +02:00
|
|
|
|
|
|
|
// update group checkbox text
|
|
|
|
if ( iGroupID != INVALID_INDEX )
|
|
|
|
{
|
|
|
|
pcbGroup->setText ( strGroupBaseText + QString::number ( iGroupID + 1 ) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pcbGroup->setText ( strGroupBaseText );
|
|
|
|
}
|
2020-07-04 12:07:51 +02:00
|
|
|
|
2020-07-07 22:02:44 +02:00
|
|
|
// if the group is disable for this fader, reset the previous fader level
|
|
|
|
if ( iGroupID == INVALID_INDEX )
|
|
|
|
{
|
|
|
|
// for the special case that the fader is all the way down, use a small
|
|
|
|
// value instead
|
|
|
|
if ( GetFaderLevel() > 0 )
|
|
|
|
{
|
|
|
|
dPreviousFaderLevel = GetFaderLevel();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dPreviousFaderLevel = 1; // small value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-04 12:07:51 +02:00
|
|
|
// the fader tag border color is set according to the selected group
|
|
|
|
SetupFaderTag ( cReceivedChanInfo.eSkillLevel );
|
2020-07-04 09:45:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CChannelFader::OnGroupStateChanged ( int )
|
|
|
|
{
|
|
|
|
// we want a popup menu shown if the user presses the group checkbox but
|
|
|
|
// we want to make sure that the checkbox state represents the current group
|
|
|
|
// setting and not the current click state since the user might not click
|
|
|
|
// on the menu but at one other place and then the popup menu disappears but
|
|
|
|
// the checkobx state would be on an invalid state
|
2020-07-04 10:46:31 +02:00
|
|
|
UpdateGroupIDDependencies();
|
2020-07-04 09:45:46 +02:00
|
|
|
pGroupPopupMenu->popup ( QCursor::pos() );
|
|
|
|
}
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
void CChannelFader::SetMute ( const bool bState )
|
|
|
|
{
|
|
|
|
if ( bState )
|
|
|
|
{
|
|
|
|
// mute channel -> send gain of 0
|
2020-07-01 15:20:28 +02:00
|
|
|
emit gainValueChanged ( 0, bIsMyOwnFader, false, false, -1 ); // set level ratio to in invalid value
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-09-10 18:24:55 +02:00
|
|
|
// only unmute if we are not solot but an other channel is on solo
|
|
|
|
if ( !bOtherChannelIsSolo || IsSolo() )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
|
|
|
// mute was unchecked, get current fader value and apply
|
2020-07-01 15:20:28 +02:00
|
|
|
emit gainValueChanged ( CalcFaderGain ( GetFaderLevel() ), bIsMyOwnFader, false, false, -1 ); // set level ratio to in invalid value
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-10 18:24:55 +02:00
|
|
|
void CChannelFader::UpdateSoloState ( const bool bNewOtherSoloState )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
|
|
|
// store state (must be done before the SetMute() call!)
|
2013-09-10 18:24:55 +02:00
|
|
|
bOtherChannelIsSolo = bNewOtherSoloState;
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2013-09-10 18:24:55 +02:00
|
|
|
// mute overwrites solo -> if mute is active, do not change anything
|
2013-01-23 11:41:13 +01:00
|
|
|
if ( !pcbMute->isChecked() )
|
|
|
|
{
|
2013-09-10 18:24:55 +02:00
|
|
|
// mute channel if we are not solo but another channel is solo
|
2014-01-19 11:51:31 +01:00
|
|
|
SetMute ( bOtherChannelIsSolo && !IsSolo() );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 23:40:53 +02:00
|
|
|
void CChannelFader::SetChannelLevel ( const uint16_t iLevel )
|
|
|
|
{
|
2020-06-21 22:39:15 +02:00
|
|
|
plbrChannelLevel->SetValue ( iLevel );
|
2020-03-29 23:40:53 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 17:34:45 +02:00
|
|
|
void CChannelFader::SetChannelInfos ( const CChannelInfo& cChanInfo )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-06-13 08:31:28 +02:00
|
|
|
// store received channel info
|
|
|
|
cReceivedChanInfo = cChanInfo;
|
|
|
|
|
2020-06-08 17:34:45 +02:00
|
|
|
// init properties for the tool tip
|
|
|
|
int iTTInstrument = CInstPictures::GetNotUsedInstrument();
|
|
|
|
QLocale::Country eTTCountry = QLocale::AnyCountry;
|
|
|
|
|
|
|
|
|
|
|
|
// Label text --------------------------------------------------------------
|
2015-01-23 22:38:24 +01:00
|
|
|
// break text at predefined position
|
2013-02-27 22:28:38 +01:00
|
|
|
const int iBreakPos = MAX_LEN_FADER_TAG / 2;
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-06-08 17:34:45 +02:00
|
|
|
QString strModText = cChanInfo.strName;
|
2013-02-27 22:28:38 +01:00
|
|
|
|
|
|
|
if ( strModText.length() > iBreakPos )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2013-02-27 22:28:38 +01:00
|
|
|
strModText.insert ( iBreakPos, QString ( "\n" ) );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
2015-01-23 22:38:24 +01:00
|
|
|
plblLabel->setText ( strModText );
|
2015-01-30 21:53:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
// Instrument picture ------------------------------------------------------
|
2013-02-11 16:53:52 +01:00
|
|
|
// get the resource reference string for this instrument
|
|
|
|
const QString strCurResourceRef =
|
2015-01-30 21:53:43 +01:00
|
|
|
CInstPictures::GetResourceReference ( cChanInfo.iInstrument );
|
2013-02-11 16:53:52 +01:00
|
|
|
|
|
|
|
// first check if instrument picture is used or not and if it is valid
|
2015-01-30 21:53:43 +01:00
|
|
|
if ( CInstPictures::IsNotUsedInstrument ( cChanInfo.iInstrument ) ||
|
2013-02-11 18:55:36 +01:00
|
|
|
strCurResourceRef.isEmpty() )
|
2013-02-11 16:53:52 +01:00
|
|
|
{
|
2015-01-30 21:53:43 +01:00
|
|
|
// disable instrument picture
|
2015-01-23 22:38:24 +01:00
|
|
|
plblInstrument->setVisible ( false );
|
2013-02-11 16:53:52 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-01-30 21:53:43 +01:00
|
|
|
// set correct picture
|
2020-07-05 16:18:07 +02:00
|
|
|
QPixmap pixInstr ( strCurResourceRef );
|
|
|
|
|
2020-07-07 22:07:19 +02:00
|
|
|
if ( ( iInstrPicMaxWidth != INVALID_INDEX ) && ( pixInstr.width() > iInstrPicMaxWidth ) )
|
2020-07-05 09:01:32 +02:00
|
|
|
{
|
|
|
|
// scale instrument picture on request (scale to the width with correct aspect ratio)
|
2020-07-07 22:07:19 +02:00
|
|
|
plblInstrument->setPixmap ( pixInstr.scaledToWidth ( iInstrPicMaxWidth, Qt::SmoothTransformation ) );
|
2020-07-05 09:01:32 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-05 16:18:07 +02:00
|
|
|
plblInstrument->setPixmap ( pixInstr );
|
2020-07-05 09:01:32 +02:00
|
|
|
}
|
2015-01-30 21:53:43 +01:00
|
|
|
iTTInstrument = cChanInfo.iInstrument;
|
2013-02-11 16:53:52 +01:00
|
|
|
|
|
|
|
// enable instrument picture
|
2015-01-23 22:38:24 +01:00
|
|
|
plblInstrument->setVisible ( true );
|
|
|
|
}
|
|
|
|
|
2015-01-30 21:53:43 +01:00
|
|
|
|
|
|
|
// Country flag icon -------------------------------------------------------
|
|
|
|
if ( cChanInfo.eCountry != QLocale::AnyCountry )
|
2015-01-23 22:38:24 +01:00
|
|
|
{
|
|
|
|
// try to load the country flag icon
|
2020-04-11 10:31:26 +02:00
|
|
|
QPixmap CountryFlagPixmap ( CLocale::GetCountryFlagIconsResourceReference ( cChanInfo.eCountry ) );
|
2015-01-23 22:38:24 +01:00
|
|
|
|
|
|
|
// first check if resource reference was valid
|
|
|
|
if ( CountryFlagPixmap.isNull() )
|
|
|
|
{
|
2015-01-30 21:53:43 +01:00
|
|
|
// disable country flag
|
2015-01-23 22:38:24 +01:00
|
|
|
plblCountryFlag->setVisible ( false );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-01-30 21:53:43 +01:00
|
|
|
// set correct picture
|
2015-01-23 22:38:24 +01:00
|
|
|
plblCountryFlag->setPixmap ( CountryFlagPixmap );
|
2015-01-30 21:53:43 +01:00
|
|
|
eTTCountry = cChanInfo.eCountry;
|
2015-01-23 22:38:24 +01:00
|
|
|
|
|
|
|
// enable country flag
|
|
|
|
plblCountryFlag->setVisible ( true );
|
|
|
|
}
|
2013-02-11 16:53:52 +01:00
|
|
|
}
|
2015-01-24 16:38:39 +01:00
|
|
|
else
|
|
|
|
{
|
2015-01-30 21:53:43 +01:00
|
|
|
// disable country flag
|
2015-01-24 16:38:39 +01:00
|
|
|
plblCountryFlag->setVisible ( false );
|
|
|
|
}
|
2015-01-30 21:53:43 +01:00
|
|
|
|
|
|
|
|
2015-01-31 09:58:19 +01:00
|
|
|
// Skill level background color --------------------------------------------
|
|
|
|
SetupFaderTag ( cChanInfo.eSkillLevel );
|
|
|
|
|
|
|
|
|
2015-01-30 21:53:43 +01:00
|
|
|
// Tool tip ----------------------------------------------------------------
|
|
|
|
// complete musician profile in the tool tip
|
|
|
|
QString strToolTip = "";
|
|
|
|
|
|
|
|
// alias/name
|
2020-06-13 08:31:28 +02:00
|
|
|
if ( !cChanInfo.strName.isEmpty() )
|
2015-01-30 21:53:43 +01:00
|
|
|
{
|
2020-06-13 08:31:28 +02:00
|
|
|
strToolTip += "<h4>" + tr ( "Alias/Name" ) + "</h4>" + cChanInfo.strName;
|
2015-01-30 21:53:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// instrument
|
|
|
|
if ( !CInstPictures::IsNotUsedInstrument ( iTTInstrument ) )
|
|
|
|
{
|
2020-05-06 19:33:27 +02:00
|
|
|
strToolTip += "<h4>" + tr ( "Instrument" ) + "</h4>" +
|
2015-01-30 21:53:43 +01:00
|
|
|
CInstPictures::GetName ( iTTInstrument );
|
|
|
|
}
|
|
|
|
|
|
|
|
// location
|
|
|
|
if ( ( eTTCountry != QLocale::AnyCountry ) ||
|
|
|
|
( !cChanInfo.strCity.isEmpty() ) )
|
|
|
|
{
|
2020-05-06 19:33:27 +02:00
|
|
|
strToolTip += "<h4>" + tr ( "Location" ) + "</h4>";
|
2015-01-30 21:53:43 +01:00
|
|
|
|
|
|
|
if ( !cChanInfo.strCity.isEmpty() )
|
|
|
|
{
|
|
|
|
strToolTip += cChanInfo.strCity;
|
|
|
|
|
|
|
|
if ( eTTCountry != QLocale::AnyCountry )
|
|
|
|
{
|
|
|
|
strToolTip += ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( eTTCountry != QLocale::AnyCountry )
|
|
|
|
{
|
|
|
|
strToolTip += QLocale::countryToString ( eTTCountry );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// skill level
|
2015-02-15 09:44:49 +01:00
|
|
|
switch ( cChanInfo.eSkillLevel )
|
2015-01-30 21:53:43 +01:00
|
|
|
{
|
2015-02-15 09:44:49 +01:00
|
|
|
case SL_BEGINNER:
|
2020-05-06 19:33:27 +02:00
|
|
|
strToolTip += "<h4>" + tr ( "Skill Level" ) + "</h4>" + tr ( "Beginner" );
|
2015-02-15 09:44:49 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SL_INTERMEDIATE:
|
2020-05-06 19:33:27 +02:00
|
|
|
strToolTip += "<h4>" + tr ( "Skill Level" ) + "</h4>" + tr ( "Intermediate" );
|
2015-02-15 09:44:49 +01:00
|
|
|
break;
|
2015-01-30 21:53:43 +01:00
|
|
|
|
2015-02-15 09:44:49 +01:00
|
|
|
case SL_PROFESSIONAL:
|
2020-05-06 19:33:27 +02:00
|
|
|
strToolTip += "<h4>" + tr ( "Skill Level" ) + "</h4>" + tr ( "Expert" );
|
2015-02-15 09:44:49 +01:00
|
|
|
break;
|
2015-01-30 21:53:43 +01:00
|
|
|
|
2015-02-15 09:44:49 +01:00
|
|
|
case SL_NOT_SET:
|
|
|
|
// skill level not set, do not add this entry
|
|
|
|
break;
|
2015-01-30 21:53:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// if no information is given, leave the tool tip empty, otherwise add header
|
|
|
|
if ( !strToolTip.isEmpty() )
|
|
|
|
{
|
2020-05-06 19:33:27 +02:00
|
|
|
strToolTip.prepend ( "<h3>" + tr ( "Musician Profile" ) + "</h3>" );
|
2015-01-30 21:53:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
plblCountryFlag->setToolTip ( strToolTip );
|
|
|
|
plblInstrument->setToolTip ( strToolTip );
|
|
|
|
plblLabel->setToolTip ( strToolTip );
|
2013-02-11 16:53:52 +01:00
|
|
|
}
|
|
|
|
|
2020-06-30 21:37:36 +02:00
|
|
|
double CChannelFader::CalcFaderGain ( const double dValue )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
|
|
|
// convert actual slider range in gain values
|
|
|
|
// and normalize so that maximum gain is 1
|
2020-06-30 21:37:36 +02:00
|
|
|
const double dInValueRange0_1 = dValue / AUD_MIX_FADER_MAX;
|
2020-04-13 21:00:26 +02:00
|
|
|
|
2020-05-26 20:58:01 +02:00
|
|
|
// map range from 0..1 to range -35..0 dB and calculate linear gain
|
2020-06-30 21:37:36 +02:00
|
|
|
if ( dValue == 0 )
|
2020-04-13 21:00:26 +02:00
|
|
|
{
|
|
|
|
return 0; // -infinity
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-26 20:58:01 +02:00
|
|
|
return pow ( 10, ( dInValueRange0_1 * 35 - 35 ) / 20 );
|
2020-04-13 21:00:26 +02:00
|
|
|
}
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************\
|
|
|
|
* CAudioMixerBoard *
|
|
|
|
\******************************************************************************/
|
|
|
|
CAudioMixerBoard::CAudioMixerBoard ( QWidget* parent, Qt::WindowFlags ) :
|
2020-07-12 10:38:18 +02:00
|
|
|
QGroupBox ( parent ),
|
|
|
|
pSettings ( nullptr ),
|
|
|
|
bDisplayPans ( false ),
|
|
|
|
bIsPanSupported ( false ),
|
|
|
|
bNoFaderVisible ( true ),
|
|
|
|
iMyChannelID ( INVALID_INDEX ),
|
|
|
|
strServerName ( "" ),
|
|
|
|
eRecorderState ( RS_UNDEFINED )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-03-30 19:53:30 +02:00
|
|
|
// add group box and hboxlayout
|
2020-05-20 22:28:52 +02:00
|
|
|
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 );
|
2020-03-30 19:53:30 +02:00
|
|
|
|
2020-06-14 23:02:13 +02:00
|
|
|
setAccessibleName ( "Personal Mix at the Server groupbox" );
|
2020-06-18 21:06:39 +02:00
|
|
|
setWhatsThis ( "<b>" + tr ( "Personal Mix at the Server" ) + ":</b> " + tr (
|
2020-06-15 22:45:11 +02:00
|
|
|
"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." ) );
|
2020-06-14 23:02:13 +02:00
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
// set title text (default: no server given)
|
|
|
|
SetServerName ( "" );
|
|
|
|
|
|
|
|
// create all mixer controls and make them invisible
|
|
|
|
vecpChanFader.Init ( MAX_NUM_CHANNELS );
|
2013-02-24 10:03:57 +01:00
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
2020-06-08 17:34:45 +02:00
|
|
|
vecpChanFader[i] = new CChannelFader ( this );
|
2013-01-23 11:41:13 +01:00
|
|
|
vecpChanFader[i]->Hide();
|
2020-06-08 17:34:45 +02:00
|
|
|
|
|
|
|
// add fader frame to audio mixer board layout
|
|
|
|
pMainLayout->addWidget ( vecpChanFader[i]->GetMainWidget() );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// insert horizontal spacer
|
|
|
|
pMainLayout->addItem ( new QSpacerItem ( 0, 0, QSizePolicy::Expanding ) );
|
|
|
|
|
2020-05-20 22:28:52 +02:00
|
|
|
// set margins of the layout to zero to get maximum space for the controls
|
2020-06-29 21:18:54 +02:00
|
|
|
pGroupBoxLayout->setContentsMargins ( 0, 0, 0, 1 ); // note: to avoid problems at the bottom, use a small margin for that
|
2020-05-20 22:28:52 +02:00
|
|
|
|
2020-03-30 19:53:30 +02:00
|
|
|
// add the group box to the scroll area
|
2020-05-20 22:28:52 +02:00
|
|
|
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 );
|
2020-03-30 19:53:30 +02:00
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
|
|
|
|
// Connections -------------------------------------------------------------
|
2020-04-18 12:20:31 +02:00
|
|
|
connectFaderSignalsToMixerBoardSlots<MAX_NUM_CHANNELS>();
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
2020-06-21 14:55:08 +02:00
|
|
|
CAudioMixerBoard::~CAudioMixerBoard()
|
|
|
|
{
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
delete vecpChanFader[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-18 12:20:31 +02:00
|
|
|
template<unsigned int slotId>
|
|
|
|
inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots()
|
|
|
|
{
|
|
|
|
int iCurChanID = slotId - 1;
|
|
|
|
|
2020-06-30 21:37:36 +02:00
|
|
|
void ( CAudioMixerBoard::* pGainValueChanged )( double, bool, bool, bool, double ) =
|
2020-04-18 12:20:31 +02:00
|
|
|
&CAudioMixerBoardSlots<slotId>::OnChGainValueChanged;
|
|
|
|
|
2020-05-16 18:18:58 +02:00
|
|
|
void ( CAudioMixerBoard::* pPanValueChanged )( double ) =
|
|
|
|
&CAudioMixerBoardSlots<slotId>::OnChPanValueChanged;
|
|
|
|
|
2020-04-18 12:20:31 +02:00
|
|
|
QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::soloStateChanged,
|
2020-06-09 17:42:32 +02:00
|
|
|
this, &CAudioMixerBoard::UpdateSoloStates );
|
2020-04-18 12:20:31 +02:00
|
|
|
|
|
|
|
QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::gainValueChanged,
|
2020-06-09 17:42:32 +02:00
|
|
|
this, pGainValueChanged );
|
2020-04-18 12:20:31 +02:00
|
|
|
|
2020-05-16 18:18:58 +02:00
|
|
|
QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::panValueChanged,
|
2020-06-09 17:42:32 +02:00
|
|
|
this, pPanValueChanged );
|
2020-05-16 18:18:58 +02:00
|
|
|
|
2020-04-18 12:20:31 +02:00
|
|
|
connectFaderSignalsToMixerBoardSlots<slotId - 1>();
|
2020-06-06 11:02:31 +02:00
|
|
|
}
|
2020-04-18 12:20:31 +02:00
|
|
|
|
|
|
|
template<>
|
2020-06-06 11:02:31 +02:00
|
|
|
inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots<0>() {}
|
2020-04-18 12:20:31 +02:00
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
void CAudioMixerBoard::SetServerName ( const QString& strNewServerName )
|
|
|
|
{
|
2020-04-12 18:56:58 +02:00
|
|
|
// store the current server name
|
|
|
|
strServerName = strNewServerName;
|
|
|
|
|
|
|
|
if ( strServerName.isEmpty() )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-04-12 18:56:58 +02:00
|
|
|
// no connection or connection was reset: show default title
|
2020-05-20 22:28:52 +02:00
|
|
|
setTitle ( tr ( "Server" ) );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-12 18:56:58 +02:00
|
|
|
// 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).
|
2020-05-20 22:28:52 +02:00
|
|
|
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" );
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CAudioMixerBoard::SetGUIDesign ( const EGUIDesign eNewDesign )
|
|
|
|
{
|
2020-06-15 22:45:11 +02:00
|
|
|
// move the channels tighter together in slim fader mode
|
|
|
|
if ( eNewDesign == GD_SLIMFADER )
|
|
|
|
{
|
|
|
|
pMainLayout->setSpacing ( 2 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pMainLayout->setSpacing ( 6 ); // Qt default spacing value
|
|
|
|
}
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
// apply GUI design to child GUI controls
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
vecpChanFader[i]->SetGUIDesign ( eNewDesign );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 23:40:53 +02:00
|
|
|
void CAudioMixerBoard::SetDisplayChannelLevels ( const bool eNDCL )
|
|
|
|
{
|
|
|
|
bDisplayChannelLevels = eNDCL;
|
|
|
|
|
2020-04-07 00:20:32 +02:00
|
|
|
// only update hiding the levels immediately, showing the levels
|
|
|
|
// is only applied if the server actually transmits levels
|
|
|
|
if ( !bDisplayChannelLevels )
|
2020-03-29 23:40:53 +02:00
|
|
|
{
|
2020-04-07 00:20:32 +02:00
|
|
|
// hide all level meters
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
vecpChanFader[i]->SetDisplayChannelLevel ( false );
|
|
|
|
}
|
2020-03-29 23:40:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 20:26:04 +02:00
|
|
|
void CAudioMixerBoard::SetDisplayPans ( const bool eNDP )
|
2020-05-19 18:28:52 +02:00
|
|
|
{
|
2020-05-22 20:26:04 +02:00
|
|
|
bDisplayPans = eNDP;
|
|
|
|
|
2020-05-19 18:28:52 +02:00
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
2020-05-22 20:26:04 +02:00
|
|
|
vecpChanFader[i]->SetDisplayPans ( eNDP && bIsPanSupported );
|
2020-05-19 18:28:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 20:26:04 +02:00
|
|
|
void CAudioMixerBoard::SetPanIsSupported()
|
|
|
|
{
|
|
|
|
bIsPanSupported = true;
|
|
|
|
SetDisplayPans ( bDisplayPans );
|
|
|
|
}
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
void CAudioMixerBoard::HideAll()
|
|
|
|
{
|
|
|
|
// make all controls invisible
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
2020-06-29 21:18:54 +02:00
|
|
|
// before hiding the fader, store its level (if some conditions are fulfilled)
|
2014-01-19 11:51:31 +01:00
|
|
|
StoreFaderSettings ( vecpChanFader[i] );
|
2013-02-24 10:03:57 +01:00
|
|
|
|
2020-04-22 21:41:33 +02:00
|
|
|
vecpChanFader[i]->SetChannelLevel ( 0 );
|
2020-04-07 00:20:32 +02:00
|
|
|
vecpChanFader[i]->SetDisplayChannelLevel ( false );
|
2020-05-19 18:28:52 +02:00
|
|
|
vecpChanFader[i]->SetDisplayPans ( false );
|
2013-01-23 11:41:13 +01:00
|
|
|
vecpChanFader[i]->Hide();
|
|
|
|
}
|
|
|
|
|
2020-05-22 20:26:04 +02:00
|
|
|
// set flags
|
|
|
|
bIsPanSupported = false;
|
2015-11-25 16:52:00 +01:00
|
|
|
bNoFaderVisible = true;
|
2020-06-16 16:58:45 +02:00
|
|
|
eRecorderState = RS_UNDEFINED;
|
2020-05-26 17:28:44 +02:00
|
|
|
iMyChannelID = INVALID_INDEX;
|
2015-11-25 16:52:00 +01:00
|
|
|
|
2020-06-08 17:34:45 +02:00
|
|
|
// use original order of channel (by server ID)
|
2020-06-13 08:51:05 +02:00
|
|
|
ChangeFaderOrder ( false, ST_BY_NAME );
|
2020-06-08 17:34:45 +02:00
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
// emit status of connected clients
|
|
|
|
emit NumClientsChanged ( 0 ); // -> no clients connected
|
|
|
|
}
|
|
|
|
|
2020-06-13 08:51:05 +02:00
|
|
|
void CAudioMixerBoard::ChangeFaderOrder ( const bool bDoSort,
|
|
|
|
const EChSortType eChSortType )
|
2020-06-08 17:34:45 +02:00
|
|
|
{
|
2020-06-09 17:42:32 +02:00
|
|
|
// create a pair list of lower strings and fader ID for each channel
|
|
|
|
QList<QPair<QString, int> > PairList;
|
|
|
|
|
2020-06-08 17:34:45 +02:00
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
2020-06-13 08:51:05 +02:00
|
|
|
if ( eChSortType == ST_BY_NAME )
|
|
|
|
{
|
|
|
|
PairList << QPair<QString, int> ( vecpChanFader[i]->GetReceivedName().toLower(), i );
|
|
|
|
}
|
2020-07-04 11:52:09 +02:00
|
|
|
else if ( eChSortType == ST_BY_INSTRUMENT )
|
2020-06-13 08:51:05 +02:00
|
|
|
{
|
2020-06-14 19:17:50 +02:00
|
|
|
PairList << QPair<QString, int> ( CInstPictures::GetName ( vecpChanFader[i]->GetReceivedInstrument() ), i );
|
2020-06-13 08:51:05 +02:00
|
|
|
}
|
2020-07-04 11:52:09 +02:00
|
|
|
else // ST_BY_GROUPID
|
|
|
|
{
|
|
|
|
if ( vecpChanFader[i]->GetGroupID() == INVALID_INDEX )
|
|
|
|
{
|
|
|
|
// put channels without a group at the end
|
|
|
|
PairList << QPair<QString, int> ( "z", i ); // group IDs are numbers, use letter to put it at the end
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PairList << QPair<QString, int> ( QString::number ( vecpChanFader[i]->GetGroupID() ), i );
|
|
|
|
}
|
|
|
|
}
|
2020-06-08 17:34:45 +02:00
|
|
|
}
|
2020-06-09 17:42:32 +02:00
|
|
|
|
|
|
|
// if requested, sort the channels
|
2020-06-08 17:34:45 +02:00
|
|
|
if ( bDoSort )
|
|
|
|
{
|
2020-06-09 17:42:32 +02:00
|
|
|
qStableSort ( PairList.begin(), PairList.end() );
|
2020-06-08 17:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// add channels to the layout in the new order (since we insert on the left, we
|
2020-06-09 17:42:32 +02:00
|
|
|
// 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
|
2020-06-08 17:34:45 +02:00
|
|
|
for ( int i = MAX_NUM_CHANNELS - 1; i >= 0; i-- )
|
|
|
|
{
|
2020-06-09 17:42:32 +02:00
|
|
|
pMainLayout->insertWidget ( 0, vecpChanFader[PairList[i].second]->GetMainWidget() );
|
2020-06-08 17:34:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 21:48:30 +02:00
|
|
|
void CAudioMixerBoard::UpdateTitle()
|
2020-06-14 23:02:13 +02:00
|
|
|
{
|
2020-06-15 21:48:30 +02:00
|
|
|
QString strTitlePrefix = "";
|
|
|
|
|
2020-06-14 23:02:13 +02:00
|
|
|
if ( eRecorderState == RS_RECORDING )
|
|
|
|
{
|
2020-06-15 21:48:30 +02:00
|
|
|
strTitlePrefix = "[" + tr ( "RECORDING ACTIVE" ) + "] ";
|
2020-06-14 23:02:13 +02:00
|
|
|
}
|
2020-06-15 21:48:30 +02:00
|
|
|
|
|
|
|
setTitle ( strTitlePrefix + tr ( "Personal Mix at: " ) + strServerName );
|
2020-06-14 23:02:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CAudioMixerBoard::SetRecorderState ( const ERecorderState newRecorderState )
|
|
|
|
{
|
2020-06-15 21:48:30 +02:00
|
|
|
// store the new recorder state and update the title
|
2020-06-14 23:02:13 +02:00
|
|
|
eRecorderState = newRecorderState;
|
2020-06-15 21:48:30 +02:00
|
|
|
UpdateTitle();
|
2020-06-14 23:02:13 +02:00
|
|
|
}
|
|
|
|
|
2013-02-11 16:53:52 +01:00
|
|
|
void CAudioMixerBoard::ApplyNewConClientList ( CVector<CChannelInfo>& vecChanInfo )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-04-12 18:56:58 +02:00
|
|
|
// 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
|
2020-05-16 11:44:00 +02:00
|
|
|
if ( bNoFaderVisible )
|
2020-04-12 18:56:58 +02:00
|
|
|
{
|
2020-06-15 21:48:30 +02:00
|
|
|
UpdateTitle();
|
2020-04-12 18:56:58 +02:00
|
|
|
}
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
// get number of connected clients
|
|
|
|
const int iNumConnectedClients = vecChanInfo.Size();
|
|
|
|
|
2013-02-10 09:44:01 +01:00
|
|
|
// search for channels with are already present and preserve their gain
|
|
|
|
// setting, for all other channels reset gain
|
2013-01-23 11:41:13 +01:00
|
|
|
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() )
|
|
|
|
{
|
2013-09-10 18:24:55 +02:00
|
|
|
// the fader was not in use, reset everything for new client
|
2013-02-27 22:28:38 +01:00
|
|
|
vecpChanFader[i]->Reset();
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2020-05-26 20:45:10 +02:00
|
|
|
// check if this is my own fader and set fader property
|
|
|
|
if ( i == iMyChannelID )
|
|
|
|
{
|
|
|
|
vecpChanFader[i]->SetIsMyOwnFader();
|
|
|
|
}
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
// show fader
|
|
|
|
vecpChanFader[i]->Show();
|
2015-11-25 16:52:00 +01:00
|
|
|
|
|
|
|
// 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
|
2020-05-26 17:28:44 +02:00
|
|
|
// 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
|
2015-11-25 16:52:00 +01:00
|
|
|
// server, in that case we do not have to do anything here.
|
2020-05-26 17:28:44 +02:00
|
|
|
if ( ( !bNoFaderVisible ||
|
|
|
|
( ( iMyChannelID != INVALID_INDEX ) && ( iMyChannelID != i ) ) ) &&
|
2020-07-12 10:38:18 +02:00
|
|
|
( pSettings->iNewClientFaderLevel != 100 ) )
|
2015-11-25 16:52:00 +01:00
|
|
|
{
|
|
|
|
// the value is in percent -> convert range
|
2020-06-30 21:37:36 +02:00
|
|
|
vecpChanFader[i]->SetFaderLevel (
|
2020-07-12 10:38:18 +02:00
|
|
|
pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX );
|
2015-11-25 16:52:00 +01:00
|
|
|
}
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
2013-03-01 18:15:29 +01:00
|
|
|
// restore gain (if new name is different from the current one)
|
2013-02-27 22:28:38 +01:00
|
|
|
if ( vecpChanFader[i]->GetReceivedName().compare ( vecChanInfo[j].strName ) )
|
|
|
|
{
|
|
|
|
// the text has actually changed, search in the list of
|
2014-01-19 11:51:31 +01:00
|
|
|
// stored settings if we have a matching entry
|
|
|
|
int iStoredFaderLevel;
|
2020-05-16 19:11:54 +02:00
|
|
|
int iStoredPanValue;
|
2014-01-19 11:51:31 +01:00
|
|
|
bool bStoredFaderIsSolo;
|
2020-04-04 17:14:59 +02:00
|
|
|
bool bStoredFaderIsMute;
|
2020-07-02 17:04:05 +02:00
|
|
|
int iGroupID;
|
2013-02-27 22:28:38 +01:00
|
|
|
|
2014-01-19 11:51:31 +01:00
|
|
|
if ( GetStoredFaderSettings ( vecChanInfo[j],
|
|
|
|
iStoredFaderLevel,
|
2020-05-16 19:11:54 +02:00
|
|
|
iStoredPanValue,
|
2020-04-04 17:14:59 +02:00
|
|
|
bStoredFaderIsSolo,
|
2020-07-02 17:04:05 +02:00
|
|
|
bStoredFaderIsMute,
|
|
|
|
iGroupID ) )
|
2013-02-27 22:28:38 +01:00
|
|
|
{
|
2020-04-22 21:19:55 +02:00
|
|
|
vecpChanFader[i]->SetFaderLevel ( iStoredFaderLevel );
|
2020-05-18 20:46:46 +02:00
|
|
|
vecpChanFader[i]->SetPanValue ( iStoredPanValue );
|
2014-01-19 11:51:31 +01:00
|
|
|
vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo );
|
2020-04-04 17:14:59 +02:00
|
|
|
vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute );
|
2020-07-02 17:04:05 +02:00
|
|
|
vecpChanFader[i]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader!
|
2013-02-27 22:28:38 +01:00
|
|
|
}
|
|
|
|
}
|
2013-02-11 16:53:52 +01:00
|
|
|
|
2020-06-08 17:34:45 +02:00
|
|
|
// set the channel infos
|
2019-05-17 22:06:48 +02:00
|
|
|
vecpChanFader[i]->SetChannelInfos ( vecChanInfo[j] );
|
2013-01-23 11:41:13 +01:00
|
|
|
|
|
|
|
bFaderIsUsed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if current fader is not used, hide it
|
|
|
|
if ( !bFaderIsUsed )
|
|
|
|
{
|
2020-06-29 21:18:54 +02:00
|
|
|
// before hiding the fader, store its level (if some conditions are fulfilled)
|
2014-01-19 11:51:31 +01:00
|
|
|
StoreFaderSettings ( vecpChanFader[i] );
|
2013-02-24 10:03:57 +01:00
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
vecpChanFader[i]->Hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-10 18:24:55 +02:00
|
|
|
// 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();
|
|
|
|
|
2015-11-25 16:52:00 +01:00
|
|
|
// update flag for "all faders are invisible"
|
|
|
|
bNoFaderVisible = ( iNumConnectedClients == 0 );
|
|
|
|
|
2013-01-23 11:41:13 +01:00
|
|
|
// emit status of connected clients
|
|
|
|
emit NumClientsChanged ( iNumConnectedClients );
|
|
|
|
}
|
|
|
|
|
2019-01-12 13:59:16 +01:00
|
|
|
void CAudioMixerBoard::SetFaderLevel ( const int iChannelIdx,
|
|
|
|
const int iValue )
|
2019-01-12 13:45:08 +01:00
|
|
|
{
|
|
|
|
// 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() )
|
|
|
|
{
|
2019-01-12 13:59:16 +01:00
|
|
|
vecpChanFader[iChannelIdx]->SetFaderLevel ( iValue );
|
2019-01-12 13:45:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 18:26:33 +02:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-10 18:24:55 +02:00
|
|
|
void CAudioMixerBoard::UpdateSoloStates()
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2013-09-10 18:24:55 +02:00
|
|
|
// first check if any channel has a solo state active
|
|
|
|
bool bAnyChannelIsSolo = false;
|
2013-01-23 11:41:13 +01:00
|
|
|
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
2013-09-10 18:24:55 +02:00
|
|
|
// check if fader is in use and has solo state active
|
|
|
|
if ( vecpChanFader[i]->IsVisible() && vecpChanFader[i]->IsSolo() )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2013-09-10 18:24:55 +02:00
|
|
|
bAnyChannelIsSolo = true;
|
|
|
|
continue;
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-10 18:24:55 +02:00
|
|
|
// 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 );
|
|
|
|
}
|
|
|
|
}
|
2013-01-23 11:41:13 +01:00
|
|
|
}
|
|
|
|
|
2020-04-18 12:20:31 +02:00
|
|
|
void CAudioMixerBoard::UpdateGainValue ( const int iChannelIdx,
|
2020-05-26 20:45:10 +02:00
|
|
|
const double dValue,
|
2020-06-20 19:04:50 +02:00
|
|
|
const bool bIsMyOwnFader,
|
2020-06-20 20:29:43 +02:00
|
|
|
const bool bIsGroupUpdate,
|
2020-06-30 21:37:36 +02:00
|
|
|
const bool bSuppressServerUpdate,
|
|
|
|
const double dLevelRatio )
|
2013-02-23 21:15:22 +01:00
|
|
|
{
|
2020-06-20 17:44:34 +02:00
|
|
|
// update current gain
|
2020-06-30 21:37:36 +02:00
|
|
|
if ( !bSuppressServerUpdate )
|
|
|
|
{
|
|
|
|
emit ChangeChanGain ( iChannelIdx, dValue, bIsMyOwnFader );
|
|
|
|
}
|
2020-06-20 17:44:34 +02:00
|
|
|
|
2020-06-20 20:29:43 +02:00
|
|
|
// 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)
|
2020-07-02 17:04:05 +02:00
|
|
|
if ( ( vecpChanFader[iChannelIdx]->GetGroupID() != INVALID_INDEX ) && !bIsGroupUpdate )
|
2020-06-20 16:20:27 +02:00
|
|
|
{
|
2020-06-20 19:49:58 +02:00
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
2020-06-20 16:20:27 +02:00
|
|
|
{
|
|
|
|
// update rest of faders selected
|
2020-06-30 21:37:36 +02:00
|
|
|
if ( vecpChanFader[i]->IsVisible() &&
|
2020-07-02 17:04:05 +02:00
|
|
|
( vecpChanFader[i]->GetGroupID() == vecpChanFader[iChannelIdx]->GetGroupID() ) &&
|
2020-06-30 21:37:36 +02:00
|
|
|
( i != iChannelIdx ) &&
|
|
|
|
( dLevelRatio >= 0 ) )
|
2020-06-20 16:20:27 +02:00
|
|
|
{
|
2020-06-20 20:29:43 +02:00
|
|
|
// synchronize faders with moving fader level (it is important
|
2020-06-29 21:18:54 +02:00
|
|
|
// to set the group flag to avoid infinite looping)
|
2020-06-30 21:37:36 +02:00
|
|
|
vecpChanFader[i]->SetFaderLevel ( vecpChanFader[i]->GetPreviousFaderLevel() * dLevelRatio, true );
|
2020-06-20 16:20:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-02-23 21:15:22 +01:00
|
|
|
}
|
|
|
|
|
2020-05-16 18:18:58 +02:00
|
|
|
void CAudioMixerBoard::UpdatePanValue ( const int iChannelIdx,
|
2020-05-18 20:46:46 +02:00
|
|
|
const double dValue )
|
2020-05-16 18:18:58 +02:00
|
|
|
{
|
|
|
|
emit ChangeChanPan ( iChannelIdx, dValue );
|
|
|
|
}
|
|
|
|
|
2014-01-19 11:51:31 +01:00
|
|
|
void CAudioMixerBoard::StoreFaderSettings ( CChannelFader* pChanFader )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2013-02-27 22:28:38 +01:00
|
|
|
// if the fader was visible and the name is not empty, we store the old gain
|
|
|
|
if ( pChanFader->IsVisible() &&
|
|
|
|
!pChanFader->GetReceivedName().isEmpty() )
|
2013-01-23 11:41:13 +01:00
|
|
|
{
|
2020-07-12 10:38:18 +02:00
|
|
|
CVector<int> viOldStoredFaderLevels ( pSettings->vecStoredFaderLevels );
|
|
|
|
CVector<int> viOldStoredPanValues ( pSettings->vecStoredPanValues );
|
|
|
|
CVector<int> vbOldStoredFaderIsSolo ( pSettings->vecStoredFaderIsSolo );
|
|
|
|
CVector<int> vbOldStoredFaderIsMute ( pSettings->vecStoredFaderIsMute );
|
|
|
|
CVector<int> vbOldStoredFaderGroupID ( pSettings->vecStoredFaderGroupID );
|
2013-02-28 21:50:09 +01:00
|
|
|
|
2013-02-27 22:28:38 +01:00
|
|
|
// init temporary list count (may be overwritten later on)
|
|
|
|
int iTempListCnt = 0;
|
2013-01-23 11:41:13 +01:00
|
|
|
|
2015-11-25 16:52:00 +01:00
|
|
|
// put new value on the top of the list
|
2013-02-28 21:50:09 +01:00
|
|
|
const int iOldIdx =
|
2020-07-12 10:38:18 +02:00
|
|
|
pSettings->vecStoredFaderTags.StringFiFoWithCompare ( pChanFader->GetReceivedName(),
|
|
|
|
true );
|
2013-02-28 21:50:09 +01:00
|
|
|
|
2015-11-25 16:52:00 +01:00
|
|
|
// current fader level and solo state is at the top of the list
|
2020-07-12 10:38:18 +02:00
|
|
|
pSettings->vecStoredFaderLevels[0] = pChanFader->GetFaderLevel();
|
|
|
|
pSettings->vecStoredPanValues[0] = pChanFader->GetPanValue();
|
|
|
|
pSettings->vecStoredFaderIsSolo[0] = pChanFader->IsSolo();
|
|
|
|
pSettings->vecStoredFaderIsMute[0] = pChanFader->IsMute();
|
|
|
|
pSettings->vecStoredFaderGroupID[0] = pChanFader->GetGroupID();
|
2020-07-02 17:04:05 +02:00
|
|
|
iTempListCnt = 1;
|
2013-02-27 22:28:38 +01:00
|
|
|
|
2014-01-19 11:51:31 +01:00
|
|
|
for ( int iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ )
|
2013-02-27 22:28:38 +01:00
|
|
|
{
|
|
|
|
// first check if we still have space in our data storage
|
2014-01-19 11:51:31 +01:00
|
|
|
if ( iTempListCnt < MAX_NUM_STORED_FADER_SETTINGS )
|
2013-02-27 22:28:38 +01:00
|
|
|
{
|
|
|
|
// 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 )
|
|
|
|
{
|
2020-07-12 10:38:18 +02:00
|
|
|
pSettings->vecStoredFaderLevels[iTempListCnt] = viOldStoredFaderLevels[iIdx];
|
|
|
|
pSettings->vecStoredPanValues[iTempListCnt] = viOldStoredPanValues[iIdx];
|
|
|
|
pSettings->vecStoredFaderIsSolo[iTempListCnt] = vbOldStoredFaderIsSolo[iIdx];
|
|
|
|
pSettings->vecStoredFaderIsMute[iTempListCnt] = vbOldStoredFaderIsMute[iIdx];
|
|
|
|
pSettings->vecStoredFaderGroupID[iTempListCnt] = vbOldStoredFaderGroupID[iIdx];
|
2013-02-27 22:28:38 +01:00
|
|
|
|
|
|
|
iTempListCnt++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-02-24 10:03:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-19 11:51:31 +01:00
|
|
|
bool CAudioMixerBoard::GetStoredFaderSettings ( const CChannelInfo& ChanInfo,
|
|
|
|
int& iStoredFaderLevel,
|
2020-05-16 19:11:54 +02:00
|
|
|
int& iStoredPanValue,
|
2020-04-04 17:14:59 +02:00
|
|
|
bool& bStoredFaderIsSolo,
|
2020-07-02 17:04:05 +02:00
|
|
|
bool& bStoredFaderIsMute,
|
|
|
|
int& iGroupID )
|
2013-02-24 10:03:57 +01:00
|
|
|
{
|
|
|
|
// only do the check if the name string is not empty
|
|
|
|
if ( !ChanInfo.strName.isEmpty() )
|
|
|
|
{
|
2014-01-19 11:51:31 +01:00
|
|
|
for ( int iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ )
|
2013-02-24 10:03:57 +01:00
|
|
|
{
|
|
|
|
// check if fader text is already known in the list
|
2020-07-12 10:38:18 +02:00
|
|
|
if ( !pSettings->vecStoredFaderTags[iIdx].compare ( ChanInfo.strName ) )
|
2013-02-24 10:03:57 +01:00
|
|
|
{
|
2014-01-19 11:51:31 +01:00
|
|
|
// copy stored settings values
|
2020-07-12 10:38:18 +02:00
|
|
|
iStoredFaderLevel = pSettings->vecStoredFaderLevels[iIdx];
|
|
|
|
iStoredPanValue = pSettings->vecStoredPanValues[iIdx];
|
|
|
|
bStoredFaderIsSolo = pSettings->vecStoredFaderIsSolo[iIdx] != 0;
|
|
|
|
bStoredFaderIsMute = pSettings->vecStoredFaderIsMute[iIdx] != 0;
|
|
|
|
iGroupID = pSettings->vecStoredFaderGroupID[iIdx];
|
2014-01-19 11:51:31 +01:00
|
|
|
|
|
|
|
// values found and copied, return OK
|
|
|
|
return true;
|
2013-02-24 10:03:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-19 11:51:31 +01:00
|
|
|
// return "not OK" since we did not find matching fader settings
|
|
|
|
return false;
|
2013-02-24 10:03:57 +01:00
|
|
|
}
|
2020-03-29 23:40:53 +02:00
|
|
|
|
|
|
|
void CAudioMixerBoard::SetChannelLevels ( const CVector<uint16_t>& vecChannelLevel )
|
|
|
|
{
|
|
|
|
const int iNumChannelLevels = vecChannelLevel.Size();
|
2020-04-07 00:20:32 +02:00
|
|
|
int i = 0;
|
2020-03-29 23:40:53 +02:00
|
|
|
|
|
|
|
for ( int iChId = 0; iChId < MAX_NUM_CHANNELS; iChId++ )
|
|
|
|
{
|
2020-06-30 21:37:36 +02:00
|
|
|
if ( vecpChanFader[iChId]->IsVisible() && ( i < iNumChannelLevels ) )
|
2020-03-29 23:40:53 +02:00
|
|
|
{
|
|
|
|
vecpChanFader[iChId]->SetChannelLevel ( vecChannelLevel[i++] );
|
2020-04-07 00:20:32 +02:00
|
|
|
|
|
|
|
// 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 );
|
|
|
|
}
|
2020-03-29 23:40:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|