1 /******************************************************************************\
2 * Copyright (c) 2004-2020
3 *
4 * Author(s):
5 * Volker Fischer
6 *
7 ******************************************************************************
8 *
9 * This program is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License as published by the Free Software
11 * Foundation; either version 2 of the License, or (at your option) any later
12 * version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 *
23 \******************************************************************************/
24
25 #include "audiomixerboard.h"
26
27 /******************************************************************************\
28 * CChanneFader *
29 \******************************************************************************/
CChannelFader(QWidget * pNW)30 CChannelFader::CChannelFader ( QWidget* pNW ) :
31 eDesign ( GD_STANDARD ),
32 BitmapMutedIcon ( QString::fromUtf8 ( ":/png/fader/res/mutediconorange.png" ) )
33 {
34 // create new GUI control objects and store pointers to them (note that
35 // QWidget takes the ownership of the pMainGrid so that this only has
36 // to be created locally in this constructor)
37 pFrame = new QFrame ( pNW );
38
39 pLevelsBox = new QWidget ( pFrame );
40 plbrChannelLevel = new CLevelMeter ( pLevelsBox );
41 pFader = new QSlider ( Qt::Vertical, pLevelsBox );
42 pPan = new QDial ( pLevelsBox );
43 pPanLabel = new QLabel ( tr ( "Pan" ), pLevelsBox );
44 pInfoLabel = new QLabel ( "", pLevelsBox );
45
46 pMuteSoloBox = new QWidget ( pFrame );
47 pcbMute = new QCheckBox ( tr ( "Mute" ), pMuteSoloBox );
48 pcbSolo = new QCheckBox ( tr ( "Solo" ), pMuteSoloBox );
49 pcbGroup = new QCheckBox ( "", pMuteSoloBox );
50
51 pLabelInstBox = new QGroupBox ( pFrame );
52 plblLabel = new QLabel ( "", pFrame );
53 plblInstrument = new QLabel ( pFrame );
54 plblCountryFlag = new QLabel ( pFrame );
55
56 QVBoxLayout* pMainGrid = new QVBoxLayout ( pFrame );
57 QHBoxLayout* pLevelsGrid = new QHBoxLayout ( pLevelsBox );
58 QVBoxLayout* pMuteSoloGrid = new QVBoxLayout ( pMuteSoloBox );
59 pLabelGrid = new QHBoxLayout ( pLabelInstBox );
60 pLabelPictGrid = new QVBoxLayout();
61 QVBoxLayout* pPanGrid = new QVBoxLayout();
62 QHBoxLayout* pPanInfoGrid = new QHBoxLayout();
63
64 // define the popup menu for the group checkbox
65 pGroupPopupMenu = new QMenu ( "", pcbGroup );
66 pGroupPopupMenu->addAction ( tr ( "&No grouping" ), this, [=] { OnGroupMenuGrp ( INVALID_INDEX ); } );
67 for ( int iGrp = 0; iGrp < MAX_NUM_FADER_GROUPS; iGrp++ )
68 {
69 pGroupPopupMenu->addAction ( tr ( "Assign to group" ) + ( QString ( " &%1" ).arg ( iGrp + 1 ) ), this, [=] { OnGroupMenuGrp ( iGrp ); } );
70 }
71 #if ( MAX_NUM_FADER_GROUPS != 8 )
72 # error "MAX_NUM_FADER_GROUPS must be set to 8, see implementation in CChannelFader()"
73 #endif
74
75 // setup channel level
76 plbrChannelLevel->setContentsMargins ( 0, 3, 2, 3 );
77
78 // setup slider
79 pFader->setPageStep ( 1 );
80 pFader->setRange ( 0, AUD_MIX_FADER_MAX );
81 pFader->setTickInterval ( AUD_MIX_FADER_MAX / 9 );
82
83 // setup panning control and info label
84 pPan->setRange ( 0, AUD_MIX_PAN_MAX );
85 pPan->setValue ( AUD_MIX_PAN_MAX / 2 );
86 pPan->setNotchesVisible ( true );
87 pInfoLabel->setMinimumHeight ( 14 ); // prevents jitter when muting/unmuting (#811)
88 pInfoLabel->setAlignment ( Qt::AlignTop );
89 pPanInfoGrid->addWidget ( pPanLabel, 0, Qt::AlignLeft | Qt::AlignTop );
90 pPanInfoGrid->addWidget ( pInfoLabel, 0, Qt::AlignHCenter | Qt::AlignTop );
91 pPanGrid->addLayout ( pPanInfoGrid );
92 pPanGrid->addWidget ( pPan, 0, Qt::AlignHCenter );
93
94 // setup fader tag label (black bold text which is centered)
95 plblLabel->setTextFormat ( Qt::PlainText );
96 plblLabel->setAlignment ( Qt::AlignHCenter | Qt::AlignVCenter );
97
98 // set margins of the layouts to zero to get maximum space for the controls
99 pMainGrid->setContentsMargins ( 0, 0, 0, 0 );
100
101 pPanGrid->setContentsMargins ( 0, 0, 0, 0 );
102 pPanGrid->setSpacing ( 0 ); // only minimal space
103
104 pLevelsGrid->setContentsMargins ( 0, 0, 0, 0 );
105 pLevelsGrid->setSpacing ( 0 ); // only minimal space
106
107 pMuteSoloGrid->setContentsMargins ( 0, 0, 0, 0 );
108 pMuteSoloGrid->setSpacing ( 0 ); // only minimal space
109
110 pLabelGrid->setContentsMargins ( 0, 0, 0, 0 );
111 pLabelGrid->setSpacing ( 2 ); // only minimal space between picture and text
112
113 // add user controls to the grids
114 pLabelPictGrid->addWidget ( plblCountryFlag, 0, Qt::AlignHCenter );
115 pLabelPictGrid->addWidget ( plblInstrument, 0, Qt::AlignHCenter );
116
117 pLabelGrid->addLayout ( pLabelPictGrid );
118 pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // note: just initial add, may be changed later
119
120 pLevelsGrid->addWidget ( plbrChannelLevel, 0, Qt::AlignRight );
121 pLevelsGrid->addWidget ( pFader, 0, Qt::AlignLeft );
122
123 pMuteSoloGrid->addWidget ( pcbGroup, 0, Qt::AlignLeft );
124 pMuteSoloGrid->addWidget ( pcbMute, 0, Qt::AlignLeft );
125 pMuteSoloGrid->addWidget ( pcbSolo, 0, Qt::AlignLeft );
126
127 pMainGrid->addLayout ( pPanGrid );
128 pMainGrid->addWidget ( pLevelsBox, 0, Qt::AlignHCenter );
129 pMainGrid->addWidget ( pMuteSoloBox, 0, Qt::AlignHCenter );
130 pMainGrid->addWidget ( pLabelInstBox );
131
132 // reset current fader
133 strGroupBaseText = "Grp"; // this will most probably overwritten by SetGUIDesign()
134 iInstrPicMaxWidth = INVALID_INDEX; // this will most probably overwritten by SetGUIDesign()
135 Reset();
136
137 // add help text to controls
138 plbrChannelLevel->setWhatsThis ( "<b>" + tr ( "Channel Level" ) + ":</b> " +
139 tr ( "Displays the pre-fader audio level of this channel. All clients connected to the "
140 "server will be assigned an audio level, the same value for every client." ) );
141 plbrChannelLevel->setAccessibleName ( tr ( "Input level of the current audio "
142 "channel at the server" ) );
143
144 pFader->setWhatsThis ( "<b>" + tr ( "Mixer Fader" ) + ":</b> " +
145 tr ( "Adjusts the audio level of this channel. All clients connected to the server "
146 "will be assigned an audio fader, displayed at each client, to adjust the local mix." ) );
147 pFader->setAccessibleName ( tr ( "Local mix level setting of the current audio "
148 "channel at the server" ) );
149
150 pInfoLabel->setWhatsThis ( "<b>" + tr ( "Status Indicator" ) + ":</b> " +
151 tr ( "Shows a status indication about the client which is assigned to this channel. "
152 "Supported indicators are:" ) +
153 "<ul><li>" + tr ( "Speaker with cancellation stroke: Indicates that another client has muted you." ) + "</li></ul>" );
154 pInfoLabel->setAccessibleName ( tr ( "Status indicator label" ) );
155
156 pPan->setWhatsThis ( "<b>" + tr ( "Panning" ) + ":</b> " +
157 tr ( "Sets the pan from Left to Right of the channel. "
158 "Works only in stereo or preferably mono in/stereo out mode." ) );
159 pPan->setAccessibleName ( tr ( "Local panning position of the current audio channel at the server" ) );
160
161 pcbMute->setWhatsThis ( "<b>" + tr ( "Mute" ) + ":</b> " + tr ( "With the Mute checkbox, the audio channel can be muted." ) );
162 pcbMute->setAccessibleName ( tr ( "Mute button" ) );
163
164 pcbSolo->setWhatsThis ( "<b>" + tr ( "Solo" ) + ":</b> " +
165 tr ( "With the Solo checkbox, the "
166 "audio channel can be set to solo which means that all other channels "
167 "except the soloed channel are muted. It is possible to set more than "
168 "one channel to solo." ) );
169 pcbSolo->setAccessibleName ( tr ( "Solo button" ) );
170
171 pcbGroup->setWhatsThis ( "<b>" + tr ( "Group" ) + ":</b> " +
172 tr ( "With the Grp checkbox, a "
173 "group of audio channels can be defined. All channel faders in a group are moved "
174 "in proportional synchronization if any one of the group faders are moved." ) );
175 pcbGroup->setAccessibleName ( tr ( "Group button" ) );
176
177 QString strFaderText = "<b>" + tr ( "Fader Tag" ) + ":</b> " +
178 tr ( "The fader tag "
179 "identifies the connected client. The tag name, a picture of your "
180 "instrument and the flag of your country can be set in the main window." );
181
182 plblInstrument->setWhatsThis ( strFaderText );
183 plblInstrument->setAccessibleName ( tr ( "Mixer channel instrument picture" ) );
184 plblLabel->setWhatsThis ( strFaderText );
185 plblLabel->setAccessibleName ( tr ( "Mixer channel label (fader tag)" ) );
186 plblCountryFlag->setWhatsThis ( strFaderText );
187 plblCountryFlag->setAccessibleName ( tr ( "Mixer channel country flag" ) );
188
189 // Connections -------------------------------------------------------------
190 QObject::connect ( pFader, &QSlider::valueChanged, this, &CChannelFader::OnLevelValueChanged );
191
192 QObject::connect ( pPan, &QDial::valueChanged, this, &CChannelFader::OnPanValueChanged );
193
194 QObject::connect ( pcbMute, &QCheckBox::stateChanged, this, &CChannelFader::OnMuteStateChanged );
195
196 QObject::connect ( pcbSolo, &QCheckBox::stateChanged, this, &CChannelFader::soloStateChanged );
197
198 QObject::connect ( pcbGroup, &QCheckBox::stateChanged, this, &CChannelFader::OnGroupStateChanged );
199 }
200
SetGUIDesign(const EGUIDesign eNewDesign)201 void CChannelFader::SetGUIDesign ( const EGUIDesign eNewDesign )
202 {
203 eDesign = eNewDesign;
204
205 switch ( eNewDesign )
206 {
207 case GD_ORIGINAL:
208 pFader->setStyleSheet ( "QSlider { width: 45px;"
209 " border-image: url(:/png/fader/res/faderbackground.png) repeat;"
210 " border-top: 10px transparent;"
211 " border-bottom: 10px transparent;"
212 " border-left: 20px transparent;"
213 " border-right: -25px transparent; }"
214 "QSlider::groove { image: url(:/png/fader/res/transparent1x1.png);"
215 " padding-left: -34px;"
216 " padding-top: -10px;"
217 " padding-bottom: -15px; }"
218 "QSlider::handle { image: url(:/png/fader/res/faderhandle.png); }" );
219
220 pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons
221 pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures
222 pFader->setMinimumHeight ( 120 ); // if this value is too small, the fader might not be movable with the mouse for fancy skin (#292)
223 pPan->setFixedSize ( 50, 50 );
224 pPanLabel->setText ( tr ( "PAN" ) );
225 pcbMute->setText ( tr ( "MUTE" ) );
226 pcbSolo->setText ( tr ( "SOLO" ) );
227 strGroupBaseText = tr ( "GRP" );
228 plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_LED );
229 iInstrPicMaxWidth = INVALID_INDEX; // no instrument picture scaling
230 break;
231
232 case GD_SLIMFADER:
233 pLabelPictGrid->addWidget ( plblLabel, 0, Qt::AlignHCenter ); // label below icons
234 pLabelInstBox->setMinimumHeight ( 130 ); // maximum height of the instrument+flag+label
235 pFader->setMinimumHeight ( 85 );
236 pPan->setFixedSize ( 28, 28 );
237 pFader->setTickPosition ( QSlider::NoTicks );
238 pFader->setStyleSheet ( "" );
239 pPanLabel->setText ( tr ( "Pan" ) );
240 pcbMute->setText ( tr ( "M" ) );
241 pcbSolo->setText ( tr ( "S" ) );
242 strGroupBaseText = tr ( "G" );
243 plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_SLIM_BAR );
244 iInstrPicMaxWidth = 18; // scale instrument picture to avoid enlarging the width by the picture
245 break;
246
247 default:
248 // reset style sheet and set original parameters
249 pFader->setTickPosition ( QSlider::TicksBothSides );
250 pFader->setStyleSheet ( "" );
251 pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons
252 pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures
253 pFader->setMinimumHeight ( 85 );
254 pPan->setFixedSize ( 50, 50 );
255 pPanLabel->setText ( tr ( "Pan" ) );
256 pcbMute->setText ( tr ( "Mute" ) );
257 pcbSolo->setText ( tr ( "Solo" ) );
258 strGroupBaseText = tr ( "Grp" );
259 plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_BAR );
260 iInstrPicMaxWidth = INVALID_INDEX; // no instrument picture scaling
261 break;
262 }
263
264 // we need to update since we changed the checkbox text
265 UpdateGroupIDDependencies();
266
267 // the instrument picture might need scaling after a style change
268 SetChannelInfos ( cReceivedChanInfo );
269 }
270
SetDisplayChannelLevel(const bool eNDCL)271 void CChannelFader::SetDisplayChannelLevel ( const bool eNDCL ) { plbrChannelLevel->setHidden ( !eNDCL ); }
272
GetDisplayChannelLevel()273 bool CChannelFader::GetDisplayChannelLevel() { return !plbrChannelLevel->isHidden(); }
274
SetDisplayPans(const bool eNDP)275 void CChannelFader::SetDisplayPans ( const bool eNDP )
276 {
277 pPanLabel->setHidden ( !eNDP );
278 pPan->setHidden ( !eNDP );
279 }
280
SetupFaderTag(const ESkillLevel eSkillLevel)281 void CChannelFader::SetupFaderTag ( const ESkillLevel eSkillLevel )
282 {
283 // Should never happen here
284 if ( iGroupID >= MAX_NUM_FADER_GROUPS )
285 {
286 SetGroupID ( INVALID_INDEX );
287 }
288
289 // the group ID defines the border color and style
290 QString strBorderColor = "black";
291 QString strBorderStyle = "solid";
292
293 if ( iGroupID != INVALID_INDEX )
294 {
295 switch ( iGroupID % 4 )
296 {
297 case 0:
298 strBorderColor = "#C43AC5";
299 break;
300
301 case 1:
302 strBorderColor = "#2B93D4";
303 break;
304
305 case 2:
306 strBorderColor = "#3BC53A";
307 break;
308
309 case 3:
310 strBorderColor = "#D46C2B";
311 break;
312
313 default:
314 break;
315 }
316
317 switch ( iGroupID / 4 )
318 {
319 case 0:
320 strBorderStyle = "solid";
321 break;
322
323 case 1:
324 strBorderStyle = "dashed";
325 break;
326
327 case 2:
328 strBorderStyle = "dotted";
329 break;
330
331 case 3:
332 strBorderStyle = "double";
333 break;
334
335 default:
336 break;
337 }
338 }
339
340 // setup group box for label/instrument picture: set a thick black border
341 // with nice round edges
342 QString strStile = "QGroupBox { border: 2px " + strBorderStyle + " " + strBorderColor +
343 ";"
344 " border-radius: 4px;"
345 " padding: 3px;";
346
347 // the background color depends on the skill level
348 switch ( eSkillLevel )
349 {
350 case SL_BEGINNER:
351 strStile +=
352 QString ( "background-color: rgb(%1, %2, %3); }" ).arg ( RGBCOL_R_SL_BEGINNER ).arg ( RGBCOL_G_SL_BEGINNER ).arg ( RGBCOL_B_SL_BEGINNER );
353 break;
354
355 case SL_INTERMEDIATE:
356 strStile += QString ( "background-color: rgb(%1, %2, %3); }" )
357 .arg ( RGBCOL_R_SL_INTERMEDIATE )
358 .arg ( RGBCOL_G_SL_INTERMEDIATE )
359 .arg ( RGBCOL_B_SL_INTERMEDIATE );
360 break;
361
362 case SL_PROFESSIONAL:
363 strStile += QString ( "background-color: rgb(%1, %2, %3); }" )
364 .arg ( RGBCOL_R_SL_SL_PROFESSIONAL )
365 .arg ( RGBCOL_G_SL_SL_PROFESSIONAL )
366 .arg ( RGBCOL_B_SL_SL_PROFESSIONAL );
367 break;
368
369 default:
370 strStile +=
371 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 );
372 break;
373 }
374
375 pLabelInstBox->setStyleSheet ( strStile );
376 }
377
Reset()378 void CChannelFader::Reset()
379 {
380 // it is important to reset the group index first (#611)
381 iGroupID = INVALID_INDEX;
382
383 // general initializations
384 SetRemoteFaderIsMute ( false );
385
386 // init gain and pan value -> maximum value as definition according to server
387 pFader->setValue ( AUD_MIX_FADER_MAX );
388 dPreviousFaderLevel = AUD_MIX_FADER_MAX;
389 pPan->setValue ( AUD_MIX_PAN_MAX / 2 );
390
391 // reset mute/solo/group check boxes and level meter
392 pcbMute->setChecked ( false );
393 pcbSolo->setChecked ( false );
394 plbrChannelLevel->SetValue ( 0 );
395 plbrChannelLevel->ClipReset();
396
397 // clear instrument picture, country flag, tool tips and label text
398 plblLabel->setText ( "" );
399 plblLabel->setToolTip ( "" );
400 plblInstrument->setVisible ( false );
401 plblInstrument->setToolTip ( "" );
402 plblCountryFlag->setVisible ( false );
403 plblCountryFlag->setToolTip ( "" );
404 cReceivedChanInfo = CChannelInfo();
405 SetupFaderTag ( SL_NOT_SET );
406
407 // set a defined tool tip time out
408 const int iToolTipDurMs = 30000;
409 plblLabel->setToolTipDuration ( iToolTipDurMs );
410 plblInstrument->setToolTipDuration ( iToolTipDurMs );
411 plblCountryFlag->setToolTipDuration ( iToolTipDurMs );
412
413 bOtherChannelIsSolo = false;
414 bIsMyOwnFader = false;
415 bIsMutedAtServer = false;
416 iRunningNewClientCnt = 0;
417
418 UpdateGroupIDDependencies();
419 }
420
SetFaderLevel(const double dLevel,const bool bIsGroupUpdate)421 void CChannelFader::SetFaderLevel ( const double dLevel, const bool bIsGroupUpdate )
422 {
423 // first make a range check
424 if ( dLevel >= 0 )
425 {
426 // we set the new fader level in the GUI (slider control) and also tell the
427 // server about the change (block the signal of the fader since we want to
428 // call SendFaderLevelToServer with a special additional parameter)
429 pFader->blockSignals ( true );
430 pFader->setValue ( std::min ( AUD_MIX_FADER_MAX, MathUtils::round ( dLevel ) ) );
431 pFader->blockSignals ( false );
432
433 SendFaderLevelToServer ( std::min ( static_cast<double> ( AUD_MIX_FADER_MAX ), dLevel ), bIsGroupUpdate );
434
435 if ( dLevel > AUD_MIX_FADER_MAX )
436 {
437 // If the level is above the maximum, we have to store it for the purpose
438 // of group fader movement. If you move a fader which has lower volume than
439 // this one and this clips at max, we want to retain the ratio between this
440 // fader and the others in the group.
441 dPreviousFaderLevel = dLevel;
442 }
443 }
444 }
445
SetPanValue(const int iPan)446 void CChannelFader::SetPanValue ( const int iPan )
447 {
448 // first make a range check
449 if ( ( iPan >= 0 ) && ( iPan <= AUD_MIX_PAN_MAX ) )
450 {
451 // we set the new fader level in the GUI (slider control) which then
452 // emits to signal to tell the server about the change (implicitly)
453 pPan->setValue ( iPan );
454 pPan->setAccessibleName ( QString::number ( iPan ) );
455 }
456 }
457
SetFaderIsSolo(const bool bIsSolo)458 void CChannelFader::SetFaderIsSolo ( const bool bIsSolo )
459 {
460 // changing the state automatically emits the signal, too
461 pcbSolo->setChecked ( bIsSolo );
462 }
463
SetFaderIsMute(const bool bIsMute)464 void CChannelFader::SetFaderIsMute ( const bool bIsMute )
465 {
466 // changing the state automatically emits the signal, too
467 pcbMute->setChecked ( bIsMute );
468 }
469
SetRemoteFaderIsMute(const bool bIsMute)470 void CChannelFader::SetRemoteFaderIsMute ( const bool bIsMute )
471 {
472 if ( bIsMute )
473 {
474 // show muted icon orange
475 pInfoLabel->setPixmap ( BitmapMutedIcon );
476 }
477 else
478 {
479 pInfoLabel->setPixmap ( QPixmap() );
480 }
481 }
482
SendFaderLevelToServer(const double dLevel,const bool bIsGroupUpdate)483 void CChannelFader::SendFaderLevelToServer ( const double dLevel, const bool bIsGroupUpdate )
484 {
485 // if mute flag is set or other channel is on solo, do not apply the new
486 // fader value to the server (exception: we are on solo, in that case we
487 // ignore the "other channel is on solo" flag)
488 const bool bSuppressServerUpdate = !( ( pcbMute->checkState() == Qt::Unchecked ) && ( !bOtherChannelIsSolo || IsSolo() ) );
489
490 // emit signal for new fader gain value
491 emit gainValueChanged ( MathUtils::CalcFaderGain ( static_cast<float> ( dLevel ) ),
492 bIsMyOwnFader,
493 bIsGroupUpdate,
494 bSuppressServerUpdate,
495 dLevel / dPreviousFaderLevel );
496
497 // update previous fader level since the level has changed, avoid to use
498 // the zero value not to have division by zero and also to retain the ratio
499 // after the fader is moved up again from the zero position
500 if ( dLevel > 0 )
501 {
502 dPreviousFaderLevel = dLevel;
503 }
504 }
505
SendPanValueToServer(const int iPan)506 void CChannelFader::SendPanValueToServer ( const int iPan ) { emit panValueChanged ( static_cast<float> ( iPan ) / AUD_MIX_PAN_MAX ); }
507
OnPanValueChanged(int value)508 void CChannelFader::OnPanValueChanged ( int value )
509 {
510 // on shift-click the pan shall reset to 0 L/R (#707)
511 if ( QGuiApplication::keyboardModifiers() == Qt::ShiftModifier )
512 {
513 // correct the value to the center position
514 value = AUD_MIX_PAN_MAX / 2;
515
516 // set the GUI control in the center position while deactivating
517 // the signals to avoid an infinite loop
518 pPan->blockSignals ( true );
519 pPan->setValue ( value );
520 pPan->blockSignals ( false );
521 }
522
523 pPan->setAccessibleName ( QString::number ( value ) );
524 SendPanValueToServer ( value );
525 }
526
OnMuteStateChanged(int value)527 void CChannelFader::OnMuteStateChanged ( int value )
528 {
529 // call muting function
530 SetMute ( static_cast<Qt::CheckState> ( value ) == Qt::Checked );
531 }
532
SetGroupID(const int iNGroupID)533 void CChannelFader::SetGroupID ( const int iNGroupID )
534 {
535 iGroupID = iNGroupID;
536 UpdateGroupIDDependencies();
537 }
538
UpdateGroupIDDependencies()539 void CChannelFader::UpdateGroupIDDependencies()
540 {
541 // update the group checkbox according the current group ID setting
542 pcbGroup->blockSignals ( true ); // make sure no signals are fired
543 if ( iGroupID == INVALID_INDEX )
544 {
545 pcbGroup->setCheckState ( Qt::Unchecked );
546 }
547 else
548 {
549 pcbGroup->setCheckState ( Qt::Checked );
550 }
551 pcbGroup->blockSignals ( false );
552
553 // update group checkbox text
554 if ( iGroupID != INVALID_INDEX )
555 {
556 pcbGroup->setText ( strGroupBaseText + QString::number ( iGroupID + 1 ) );
557 }
558 else
559 {
560 pcbGroup->setText ( strGroupBaseText );
561 }
562
563 // if the group is disable for this fader, reset the previous fader level
564 if ( iGroupID == INVALID_INDEX )
565 {
566 // for the special case that the fader is all the way down, use a small
567 // value instead
568 if ( GetFaderLevel() > 0 )
569 {
570 dPreviousFaderLevel = GetFaderLevel();
571 }
572 else
573 {
574 dPreviousFaderLevel = 1; // small value
575 }
576 }
577
578 // the fader tag border color is set according to the selected group
579 SetupFaderTag ( cReceivedChanInfo.eSkillLevel );
580 }
581
OnGroupStateChanged(int)582 void CChannelFader::OnGroupStateChanged ( int )
583 {
584 // we want a popup menu shown if the user presses the group checkbox but
585 // we want to make sure that the checkbox state represents the current group
586 // setting and not the current click state since the user might not click
587 // on the menu but at one other place and then the popup menu disappears but
588 // the checkobx state would be on an invalid state
589 UpdateGroupIDDependencies();
590 pGroupPopupMenu->popup ( QCursor::pos() );
591 }
592
SetMute(const bool bState)593 void CChannelFader::SetMute ( const bool bState )
594 {
595 if ( bState )
596 {
597 if ( !bIsMutedAtServer )
598 {
599 // mute channel -> send gain of 0
600 emit gainValueChanged ( 0, bIsMyOwnFader, false, false, -1 ); // set level ratio to in invalid value
601 bIsMutedAtServer = true;
602 }
603 }
604 else
605 {
606 // only unmute if we are not solot but an other channel is on solo
607 if ( ( !bOtherChannelIsSolo || IsSolo() ) && bIsMutedAtServer )
608 {
609 // mute was unchecked, get current fader value and apply
610 emit gainValueChanged ( MathUtils::CalcFaderGain ( GetFaderLevel() ),
611 bIsMyOwnFader,
612 false,
613 false,
614 -1 ); // set level ratio to in invalid value
615 bIsMutedAtServer = false;
616 }
617 }
618 }
619
UpdateSoloState(const bool bNewOtherSoloState)620 void CChannelFader::UpdateSoloState ( const bool bNewOtherSoloState )
621 {
622 // store state (must be done before the SetMute() call!)
623 bOtherChannelIsSolo = bNewOtherSoloState;
624
625 // mute overwrites solo -> if mute is active, do not change anything
626 if ( !pcbMute->isChecked() )
627 {
628 // mute channel if we are not solo but another channel is solo
629 SetMute ( bOtherChannelIsSolo && !IsSolo() );
630 }
631 }
632
SetChannelLevel(const uint16_t iLevel)633 void CChannelFader::SetChannelLevel ( const uint16_t iLevel ) { plbrChannelLevel->SetValue ( iLevel ); }
634
SetChannelInfos(const CChannelInfo & cChanInfo)635 void CChannelFader::SetChannelInfos ( const CChannelInfo& cChanInfo )
636 {
637 // store received channel info
638 cReceivedChanInfo = cChanInfo;
639
640 // init properties for the tool tip
641 int iTTInstrument = CInstPictures::GetNotUsedInstrument();
642 QLocale::Country eTTCountry = QLocale::AnyCountry;
643
644 // Label text --------------------------------------------------------------
645
646 QString strModText = cChanInfo.strName;
647
648 // apply break position and font size depending on the selected design
649 if ( eDesign == GD_SLIMFADER )
650 {
651 // in slim mode use a non-bold font (smaller width font)
652 plblLabel->setStyleSheet ( "QLabel { color: black; }" );
653
654 // break at every 4th character
655 for ( int iInsPos = 4; iInsPos <= strModText.size() - 1; iInsPos += 4 + 1 )
656 {
657 strModText.insert ( iInsPos, "\n" );
658 }
659 }
660 else
661 {
662 // in normal mode use bold font
663 plblLabel->setStyleSheet ( "QLabel { color: black; font: bold; }" );
664
665 // break text at predefined position
666 const int iBreakPos = MAX_LEN_FADER_TAG / 2;
667
668 if ( strModText.length() > iBreakPos )
669 {
670 strModText.insert ( iBreakPos, QString ( "\n" ) );
671 }
672 }
673
674 plblLabel->setText ( strModText );
675
676 // Instrument picture ------------------------------------------------------
677 // get the resource reference string for this instrument
678 const QString strCurResourceRef = CInstPictures::GetResourceReference ( cChanInfo.iInstrument );
679
680 // first check if instrument picture is used or not and if it is valid
681 if ( CInstPictures::IsNotUsedInstrument ( cChanInfo.iInstrument ) || strCurResourceRef.isEmpty() )
682 {
683 // disable instrument picture
684 plblInstrument->setVisible ( false );
685 }
686 else
687 {
688 // set correct picture
689 QPixmap pixInstr ( strCurResourceRef );
690
691 if ( ( iInstrPicMaxWidth != INVALID_INDEX ) && ( pixInstr.width() > iInstrPicMaxWidth ) )
692 {
693 // scale instrument picture on request (scale to the width with correct aspect ratio)
694 plblInstrument->setPixmap ( pixInstr.scaledToWidth ( iInstrPicMaxWidth, Qt::SmoothTransformation ) );
695 }
696 else
697 {
698 plblInstrument->setPixmap ( pixInstr );
699 }
700 iTTInstrument = cChanInfo.iInstrument;
701
702 // enable instrument picture
703 plblInstrument->setVisible ( true );
704 }
705
706 // Country flag icon -------------------------------------------------------
707 if ( cChanInfo.eCountry != QLocale::AnyCountry )
708 {
709 // try to load the country flag icon
710 QPixmap CountryFlagPixmap ( CLocale::GetCountryFlagIconsResourceReference ( cChanInfo.eCountry ) );
711
712 // first check if resource reference was valid
713 if ( CountryFlagPixmap.isNull() )
714 {
715 // disable country flag
716 plblCountryFlag->setVisible ( false );
717 }
718 else
719 {
720 // set correct picture
721 plblCountryFlag->setPixmap ( CountryFlagPixmap );
722 eTTCountry = cChanInfo.eCountry;
723
724 // enable country flag
725 plblCountryFlag->setVisible ( true );
726 }
727 }
728 else
729 {
730 // disable country flag
731 plblCountryFlag->setVisible ( false );
732 }
733
734 // Skill level background color --------------------------------------------
735 SetupFaderTag ( cChanInfo.eSkillLevel );
736
737 // Tool tip ----------------------------------------------------------------
738 // complete musician profile in the tool tip
739 QString strToolTip = "";
740 QString strAliasAccessible = "";
741 QString strInstrumentAccessible = "";
742 QString strLocationAccessible = "";
743
744 // alias/name
745 if ( !cChanInfo.strName.isEmpty() )
746 {
747 strToolTip += "<h4>" + tr ( "Alias/Name" ) + "</h4>" + cChanInfo.strName;
748 strAliasAccessible += cChanInfo.strName;
749 }
750
751 // instrument
752 if ( !CInstPictures::IsNotUsedInstrument ( iTTInstrument ) )
753 {
754 strToolTip += "<h4>" + tr ( "Instrument" ) + "</h4>" + CInstPictures::GetName ( iTTInstrument );
755
756 strInstrumentAccessible += CInstPictures::GetName ( iTTInstrument );
757 }
758
759 // location
760 if ( ( eTTCountry != QLocale::AnyCountry ) || ( !cChanInfo.strCity.isEmpty() ) )
761 {
762 strToolTip += "<h4>" + tr ( "Location" ) + "</h4>";
763
764 if ( !cChanInfo.strCity.isEmpty() )
765 {
766 strToolTip += cChanInfo.strCity;
767 strLocationAccessible += cChanInfo.strCity;
768
769 if ( eTTCountry != QLocale::AnyCountry )
770 {
771 strToolTip += ", ";
772 strLocationAccessible += ", ";
773 }
774 }
775
776 if ( eTTCountry != QLocale::AnyCountry )
777 {
778 strToolTip += QLocale::countryToString ( eTTCountry );
779 strLocationAccessible += QLocale::countryToString ( eTTCountry );
780 }
781 }
782
783 // skill level
784 QString strSkillLevel;
785
786 switch ( cChanInfo.eSkillLevel )
787 {
788 case SL_BEGINNER:
789 strSkillLevel = tr ( "Beginner" );
790 strToolTip += "<h4>" + tr ( "Skill Level" ) + "</h4>" + strSkillLevel;
791 strInstrumentAccessible += ", " + strSkillLevel;
792 break;
793
794 case SL_INTERMEDIATE:
795 strSkillLevel = tr ( "Intermediate" );
796 strToolTip += "<h4>" + tr ( "Skill Level" ) + "</h4>" + strSkillLevel;
797 strInstrumentAccessible += ", " + strSkillLevel;
798 break;
799
800 case SL_PROFESSIONAL:
801 strSkillLevel = tr ( "Expert" );
802 strToolTip += "<h4>" + tr ( "Skill Level" ) + "</h4>" + strSkillLevel;
803 strInstrumentAccessible += ", " + strSkillLevel;
804 break;
805
806 case SL_NOT_SET:
807 // skill level not set, do not add this entry
808 break;
809 }
810
811 // if no information is given, leave the tool tip empty, otherwise add header
812 if ( !strToolTip.isEmpty() )
813 {
814 strToolTip.prepend ( "<h3>" + tr ( "Musician Profile" ) + "</h3>" );
815 }
816
817 plblCountryFlag->setToolTip ( strToolTip );
818 plblCountryFlag->setAccessibleDescription ( strLocationAccessible );
819 plblInstrument->setToolTip ( strToolTip );
820 plblInstrument->setAccessibleDescription ( strInstrumentAccessible );
821 plblLabel->setToolTip ( strToolTip );
822 plblLabel->setAccessibleName ( strAliasAccessible );
823 plblLabel->setAccessibleDescription ( tr ( "Alias" ) );
824 }
825
826 /******************************************************************************\
827 * CAudioMixerBoard *
828 \******************************************************************************/
CAudioMixerBoard(QWidget * parent)829 CAudioMixerBoard::CAudioMixerBoard ( QWidget* parent ) :
830 QGroupBox ( parent ),
831 pSettings ( nullptr ),
832 bDisplayPans ( false ),
833 bIsPanSupported ( false ),
834 bNoFaderVisible ( true ),
835 iMyChannelID ( INVALID_INDEX ),
836 iRunningNewClientCnt ( 0 ),
837 iNumMixerPanelRows ( 1 ), // pSettings->iNumMixerPanelRows is not yet available
838 strServerName ( "" ),
839 eRecorderState ( RS_UNDEFINED ),
840 eChSortType ( ST_NO_SORT )
841 {
842 // add group box and hboxlayout
843 QHBoxLayout* pGroupBoxLayout = new QHBoxLayout ( this );
844 QWidget* pMixerWidget = new QWidget(); // will be added to the scroll area which is then the parent
845 pScrollArea = new CMixerBoardScrollArea ( this );
846 pMainLayout = new QGridLayout ( pMixerWidget );
847
848 setAccessibleName ( "Personal Mix at the Server groupbox" );
849 setWhatsThis ( "<b>" + tr ( "Personal Mix at the Server" ) + ":</b> " +
850 tr ( "When connected to a server, the controls here allow you to set your "
851 "local mix without affecting what others hear from you. The title shows "
852 "the server name and, when known, whether it is actively recording." ) );
853
854 // set title text (default: no server given)
855 SetServerName ( "" );
856
857 // create all mixer controls and make them invisible
858 vecpChanFader.Init ( MAX_NUM_CHANNELS );
859
860 vecAvgLevels.Init ( MAX_NUM_CHANNELS, 0.0f );
861
862 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
863 {
864 vecpChanFader[i] = new CChannelFader ( this );
865 vecpChanFader[i]->Hide();
866 }
867
868 // insert horizontal spacer (at position MAX_NUM_CHANNELS+1 which is index MAX_NUM_CHANNELS)
869 pMainLayout->addItem ( new QSpacerItem ( 0, 0, QSizePolicy::Expanding ), 0, MAX_NUM_CHANNELS );
870
871 // set margins of the layout to zero to get maximum space for the controls
872 pGroupBoxLayout->setContentsMargins ( 0, 0, 0, 1 ); // note: to avoid problems at the bottom, use a small margin for that
873
874 // add the group box to the scroll area
875 pScrollArea->setMinimumWidth ( 200 ); // at least two faders shall be visible
876 pScrollArea->setWidget ( pMixerWidget );
877 pScrollArea->setWidgetResizable ( true ); // make sure it fills the entire scroll area
878 pScrollArea->setFrameShape ( QFrame::NoFrame );
879 pGroupBoxLayout->addWidget ( pScrollArea );
880
881 // Connections -------------------------------------------------------------
882 connectFaderSignalsToMixerBoardSlots<MAX_NUM_CHANNELS>();
883 }
884
~CAudioMixerBoard()885 CAudioMixerBoard::~CAudioMixerBoard()
886 {
887 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
888 {
889 delete vecpChanFader[i];
890 }
891 }
892
893 template<unsigned int slotId>
connectFaderSignalsToMixerBoardSlots()894 inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots()
895 {
896 int iCurChanID = slotId - 1;
897
898 void ( CAudioMixerBoard::*pGainValueChanged ) ( float, bool, bool, bool, double ) = &CAudioMixerBoardSlots<slotId>::OnChGainValueChanged;
899
900 void ( CAudioMixerBoard::*pPanValueChanged ) ( float ) = &CAudioMixerBoardSlots<slotId>::OnChPanValueChanged;
901
902 QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::soloStateChanged, this, &CAudioMixerBoard::UpdateSoloStates );
903
904 QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::gainValueChanged, this, pGainValueChanged );
905
906 QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::panValueChanged, this, pPanValueChanged );
907
908 connectFaderSignalsToMixerBoardSlots<slotId - 1>();
909 }
910
911 template<>
connectFaderSignalsToMixerBoardSlots()912 inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots<0>()
913 {}
914
SetServerName(const QString & strNewServerName)915 void CAudioMixerBoard::SetServerName ( const QString& strNewServerName )
916 {
917 // store the current server name
918 strServerName = strNewServerName;
919
920 if ( strServerName.isEmpty() )
921 {
922 // no connection or connection was reset: show default title
923 setTitle ( tr ( "Server" ) );
924 }
925 else
926 {
927 // Do not set the server name directly but first show a label which indicates
928 // that we are trying to connect the server. First if a connected client
929 // list was received, the connection was successful and the title is updated
930 // with the correct server name. Make sure to choose a "try to connect" title
931 // which is most striking (we use filled blocks and upper case letters).
932 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" );
933 }
934 }
935
SetGUIDesign(const EGUIDesign eNewDesign)936 void CAudioMixerBoard::SetGUIDesign ( const EGUIDesign eNewDesign )
937 {
938 // move the channels tighter together in slim fader mode
939 if ( eNewDesign == GD_SLIMFADER )
940 {
941 pMainLayout->setSpacing ( 2 );
942 }
943 else
944 {
945 pMainLayout->setSpacing ( 6 ); // Qt default spacing value
946 }
947
948 // apply GUI design to child GUI controls
949 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
950 {
951 vecpChanFader[i]->SetGUIDesign ( eNewDesign );
952 }
953 }
954
SetDisplayPans(const bool eNDP)955 void CAudioMixerBoard::SetDisplayPans ( const bool eNDP )
956 {
957 bDisplayPans = eNDP;
958
959 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
960 {
961 vecpChanFader[i]->SetDisplayPans ( eNDP && bIsPanSupported );
962 }
963 }
964
SetPanIsSupported()965 void CAudioMixerBoard::SetPanIsSupported()
966 {
967 bIsPanSupported = true;
968 SetDisplayPans ( bDisplayPans );
969 }
970
HideAll()971 void CAudioMixerBoard::HideAll()
972 {
973 // before hiding the faders, store their settings
974 StoreAllFaderSettings();
975
976 // make all controls invisible
977 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
978 {
979 vecpChanFader[i]->SetChannelLevel ( 0 );
980 vecpChanFader[i]->SetDisplayChannelLevel ( false );
981 vecpChanFader[i]->SetDisplayPans ( false );
982 vecpChanFader[i]->Hide();
983 }
984
985 // initialize flags and other parameters
986 bIsPanSupported = false;
987 bNoFaderVisible = true;
988 eRecorderState = RS_UNDEFINED;
989 iMyChannelID = INVALID_INDEX;
990 iRunningNewClientCnt = 0; // reset running counter on new server connection
991
992 // use original order of channel (by server ID)
993 ChangeFaderOrder ( ST_NO_SORT );
994
995 // Reset recording indication styleSheet
996 setStyleSheet ( "" );
997
998 // emit status of connected clients
999 emit NumClientsChanged ( 0 ); // -> no clients connected
1000 }
1001
SetNumMixerPanelRows(const int iNNumMixerPanelRows)1002 void CAudioMixerBoard::SetNumMixerPanelRows ( const int iNNumMixerPanelRows )
1003 {
1004 // store new value and immediately initiate the sorting
1005 iNumMixerPanelRows = iNNumMixerPanelRows;
1006 ChangeFaderOrder ( eChSortType );
1007 }
1008
SetFaderSorting(const EChSortType eNChSortType)1009 void CAudioMixerBoard::SetFaderSorting ( const EChSortType eNChSortType )
1010 {
1011 // store new sort type and update the fader order
1012 eChSortType = eNChSortType;
1013 ChangeFaderOrder ( eNChSortType );
1014 }
1015
ChangeFaderOrder(const EChSortType eChSortType)1016 void CAudioMixerBoard::ChangeFaderOrder ( const EChSortType eChSortType )
1017 {
1018 QMutexLocker locker ( &Mutex );
1019
1020 // create a pair list of lower strings and fader ID for each channel
1021 QList<QPair<QString, int>> PairList;
1022 int iNumVisibleFaders = 0;
1023
1024 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1025 {
1026 if ( eChSortType == ST_BY_NAME )
1027 {
1028 PairList << QPair<QString, int> ( vecpChanFader[i]->GetReceivedName().toLower(), i );
1029 }
1030 else if ( eChSortType == ST_BY_CITY )
1031 {
1032 PairList << QPair<QString, int> ( vecpChanFader[i]->GetReceivedCity().toLower(), i );
1033 }
1034 else if ( eChSortType == ST_BY_INSTRUMENT )
1035 {
1036 // sort first "by instrument" and second "by name" by adding the name after the instrument
1037 PairList << QPair<QString, int> ( CInstPictures::GetName ( vecpChanFader[i]->GetReceivedInstrument() ) +
1038 vecpChanFader[i]->GetReceivedName().toLower(),
1039 i );
1040 }
1041 else if ( eChSortType == ST_BY_GROUPID )
1042 {
1043 if ( vecpChanFader[i]->GetGroupID() == INVALID_INDEX )
1044 {
1045 // put channels without a group at the end
1046 PairList << QPair<QString, int> ( "z", i ); // group IDs are numbers, use letter to put it at the end
1047 }
1048 else
1049 {
1050 PairList << QPair<QString, int> ( QString::number ( vecpChanFader[i]->GetGroupID() ), i );
1051 }
1052 }
1053 else // ST_NO_SORT
1054 {
1055 // per definition for no sort: faders are sorted in the order they appeared (note that we
1056 // pad to a total of 11 characters with zeros to make sure the sorting is done correctly)
1057 PairList << QPair<QString, int> ( QString ( "%1" ).arg ( vecpChanFader[i]->GetRunningNewClientCnt(), 11, 10, QLatin1Char ( '0' ) ), i );
1058 }
1059
1060 // count the number of visible faders
1061 if ( vecpChanFader[i]->IsVisible() )
1062 {
1063 iNumVisibleFaders++;
1064 }
1065 }
1066
1067 // sort the channels according to the first of the pair
1068 std::stable_sort ( PairList.begin(), PairList.end() );
1069
1070 // we want to distribute iNumVisibleFaders across the first row, then the next, etc
1071 // up to iNumMixerPanelRows. So row wants to start at 0 until we get to some number,
1072 // then increase, where "some number" means we get no more than iNumMixerPanelRows.
1073 const int iNumFadersFirstRow = ( iNumVisibleFaders + iNumMixerPanelRows - 1 ) / iNumMixerPanelRows;
1074
1075 // add channels to the layout in the new order, note that it is not required to remove
1076 // the widget from the layout first but it is moved to the new position automatically
1077 int iVisibleFaderCnt = 0;
1078
1079 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1080 {
1081 const int iCurFaderID = PairList[i].second;
1082
1083 if ( vecpChanFader[iCurFaderID]->IsVisible() )
1084 {
1085 // channels are added row-first, up to iNumFadersFirstRow, then onto
1086 // the next row.
1087 pMainLayout->addWidget ( vecpChanFader[iCurFaderID]->GetMainWidget(),
1088 iVisibleFaderCnt / iNumFadersFirstRow,
1089 iVisibleFaderCnt % iNumFadersFirstRow );
1090
1091 iVisibleFaderCnt++;
1092 }
1093 }
1094 }
1095
UpdateTitle()1096 void CAudioMixerBoard::UpdateTitle()
1097 {
1098 QString strTitlePrefix = "";
1099
1100 if ( eRecorderState == RS_RECORDING )
1101 {
1102 strTitlePrefix = "[" + tr ( "RECORDING ACTIVE" ) + "] ";
1103 }
1104
1105 // replace & signs with && (See Qt documentation for QLabel)
1106 // if strServerName includes an "&" sign, this is interpreted as keyboard shortcut (#1886)
1107 // it might be possible to find a more elegant solution here?
1108
1109 QString strEscServerName = strServerName;
1110 strEscServerName.replace ( "&", "&&" );
1111
1112 setTitle ( strTitlePrefix + tr ( "Personal Mix at: " ) + strEscServerName );
1113 setAccessibleName ( title() );
1114 }
1115
SetRecorderState(const ERecorderState newRecorderState)1116 void CAudioMixerBoard::SetRecorderState ( const ERecorderState newRecorderState )
1117 {
1118 // store the new recorder state and update the title
1119 eRecorderState = newRecorderState;
1120 UpdateTitle();
1121 }
1122
ApplyNewConClientList(CVector<CChannelInfo> & vecChanInfo)1123 void CAudioMixerBoard::ApplyNewConClientList ( CVector<CChannelInfo>& vecChanInfo )
1124 {
1125 // get number of connected clients
1126 const int iNumConnectedClients = vecChanInfo.Size();
1127
1128 Mutex.lock();
1129 {
1130 // we want to set the server name only if the very first faders appear
1131 // in the audio mixer board to show a "try to connect" before
1132 if ( bNoFaderVisible )
1133 {
1134 UpdateTitle();
1135 }
1136
1137 // search for channels with are already present and preserve their gain
1138 // setting, for all other channels reset gain
1139 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1140 {
1141 bool bFaderIsUsed = false;
1142
1143 for ( int j = 0; j < iNumConnectedClients; j++ )
1144 {
1145 // check if current fader is used
1146 if ( vecChanInfo[j].iChanID == i )
1147 {
1148 // check if fader was already in use -> preserve gain value
1149 if ( !vecpChanFader[i]->IsVisible() )
1150 {
1151 // the fader was not in use, reset everything for new client
1152 vecpChanFader[i]->Reset();
1153 vecAvgLevels[i] = 0.0f;
1154
1155 // check if this is my own fader and set fader property
1156 if ( i == iMyChannelID )
1157 {
1158 vecpChanFader[i]->SetIsMyOwnFader();
1159 }
1160
1161 // set and increment the running new client counter needed for sorting (per definition:
1162 // a fader for a new client shall always be inserted at the right-hand-side if no other
1163 // sorting type is selected (i.e. "no sorting" is active) (#673)
1164 vecpChanFader[i]->SetRunningNewClientCnt ( iRunningNewClientCnt++ );
1165
1166 // show fader
1167 vecpChanFader[i]->Show();
1168
1169 // Set the default initial fader level. Check first that
1170 // this is not the initialization (i.e. previously there
1171 // were no faders visible) to avoid that our own level is
1172 // adjusted. If we have received our own channel ID, then
1173 // we can adjust the level even if no fader was visible.
1174 // The fader level of 100 % is the default in the
1175 // server, in that case we do not have to do anything here.
1176 if ( ( !bNoFaderVisible || ( ( iMyChannelID != INVALID_INDEX ) && ( iMyChannelID != i ) ) ) &&
1177 ( pSettings->iNewClientFaderLevel != 100 ) )
1178 {
1179 // the value is in percent -> convert range
1180 vecpChanFader[i]->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX );
1181 }
1182 }
1183
1184 // restore gain (if new name is different from the current one)
1185 if ( vecpChanFader[i]->GetReceivedName().compare ( vecChanInfo[j].strName ) )
1186 {
1187 // the text has actually changed, search in the list of
1188 // stored settings if we have a matching entry
1189 int iStoredFaderLevel;
1190 int iStoredPanValue;
1191 bool bStoredFaderIsSolo;
1192 bool bStoredFaderIsMute;
1193 int iGroupID;
1194
1195 if ( GetStoredFaderSettings ( vecChanInfo[j].strName,
1196 iStoredFaderLevel,
1197 iStoredPanValue,
1198 bStoredFaderIsSolo,
1199 bStoredFaderIsMute,
1200 iGroupID ) )
1201 {
1202 vecpChanFader[i]->SetFaderLevel ( iStoredFaderLevel, true ); // suppress group update
1203 vecpChanFader[i]->SetPanValue ( iStoredPanValue );
1204 vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo );
1205 vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute );
1206 vecpChanFader[i]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader!
1207 }
1208 }
1209
1210 // set the channel infos
1211 vecpChanFader[i]->SetChannelInfos ( vecChanInfo[j] );
1212
1213 bFaderIsUsed = true;
1214 }
1215 }
1216
1217 // if current fader is not used, hide it
1218 if ( !bFaderIsUsed )
1219 {
1220 // before hiding the fader, store its level (if some conditions are fulfilled)
1221 StoreFaderSettings ( vecpChanFader[i] );
1222
1223 vecpChanFader[i]->Hide();
1224 }
1225 }
1226
1227 // update the solo states since if any channel was on solo and a new client
1228 // has just connected, the new channel must be muted
1229 UpdateSoloStates();
1230
1231 // update flag for "all faders are invisible"
1232 bNoFaderVisible = ( iNumConnectedClients == 0 );
1233 }
1234 Mutex.unlock(); // release mutex
1235
1236 // sort the channels according to the selected sorting type
1237 ChangeFaderOrder ( eChSortType );
1238
1239 // emit status of connected clients
1240 emit NumClientsChanged ( iNumConnectedClients );
1241 }
1242
SetFaderLevel(const int iChannelIdx,const int iValue)1243 void CAudioMixerBoard::SetFaderLevel ( const int iChannelIdx, const int iValue )
1244 {
1245 // only apply new fader level if channel index is valid and the fader is visible
1246 if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
1247 {
1248 if ( vecpChanFader[iChannelIdx]->IsVisible() )
1249 {
1250 vecpChanFader[iChannelIdx]->SetFaderLevel ( iValue );
1251 }
1252 }
1253 }
1254
SetPanValue(const int iChannelIdx,const int iValue)1255 void CAudioMixerBoard::SetPanValue ( const int iChannelIdx, const int iValue )
1256 {
1257 // only apply new pan value if channel index is valid and the panner is visible
1258 if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) && bDisplayPans )
1259 {
1260 if ( vecpChanFader[iChannelIdx]->IsVisible() )
1261 {
1262 vecpChanFader[iChannelIdx]->SetPanValue ( iValue );
1263 }
1264 }
1265 }
1266
SetFaderIsSolo(const int iChannelIdx,const bool bIsSolo)1267 void CAudioMixerBoard::SetFaderIsSolo ( const int iChannelIdx, const bool bIsSolo )
1268 {
1269 // only apply solo if channel index is valid and the fader is visible
1270 if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
1271
1272 {
1273 if ( vecpChanFader[iChannelIdx]->IsVisible() )
1274 {
1275 vecpChanFader[iChannelIdx]->SetFaderIsSolo ( bIsSolo );
1276 }
1277 }
1278 }
1279
SetFaderIsMute(const int iChannelIdx,const bool bIsMute)1280 void CAudioMixerBoard::SetFaderIsMute ( const int iChannelIdx, const bool bIsMute )
1281 {
1282 // only apply mute if channel index is valid and the fader is visible
1283 if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
1284 {
1285 if ( vecpChanFader[iChannelIdx]->IsVisible() )
1286 {
1287 vecpChanFader[iChannelIdx]->SetFaderIsMute ( bIsMute );
1288 }
1289 }
1290 }
1291
SetAllFaderLevelsToNewClientLevel()1292 void CAudioMixerBoard::SetAllFaderLevelsToNewClientLevel()
1293 {
1294 QMutexLocker locker ( &Mutex );
1295
1296 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1297 {
1298 // only apply to visible faders and not to my own channel fader
1299 if ( vecpChanFader[i]->IsVisible() && ( i != iMyChannelID ) )
1300 {
1301 // the value is in percent -> convert range, also use the group
1302 // update flag to make sure the group values are all set to the
1303 // same fader level now
1304 vecpChanFader[i]->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX, true );
1305 }
1306 }
1307 }
1308
AutoAdjustAllFaderLevels()1309 void CAudioMixerBoard::AutoAdjustAllFaderLevels()
1310 {
1311 QMutexLocker locker ( &Mutex );
1312
1313 // initialize variables used for statistics
1314 float vecMaxLevel[MAX_NUM_FADER_GROUPS + 1];
1315 int vecChannelsPerGroup[MAX_NUM_FADER_GROUPS + 1];
1316 for ( int i = 0; i < MAX_NUM_FADER_GROUPS + 1; ++i )
1317 {
1318 vecMaxLevel[i] = LOW_BOUND_SIG_METER;
1319 vecChannelsPerGroup[i] = 0;
1320 }
1321 CVector<CVector<float>> levels;
1322 levels.resize ( MAX_NUM_FADER_GROUPS + 1 );
1323
1324 // compute min/max level per group and number of channels per group
1325 for ( int i = 0; i < MAX_NUM_CHANNELS; ++i )
1326 {
1327 // only apply to visible faders (and not to my own channel fader)
1328 if ( vecpChanFader[i]->IsVisible() && ( i != iMyChannelID ) )
1329 {
1330 // map averaged meter output level to decibels
1331 // (invert CStereoSignalLevelMeter::CalcLogResultForMeter)
1332 float leveldB = vecAvgLevels[i] * ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ) / NUM_STEPS_LED_BAR + LOW_BOUND_SIG_METER;
1333
1334 int group = vecpChanFader[i]->GetGroupID();
1335 if ( group == INVALID_INDEX )
1336 {
1337 group = MAX_NUM_FADER_GROUPS;
1338 }
1339
1340 if ( leveldB >= AUTO_FADER_NOISE_THRESHOLD_DB )
1341 {
1342 vecMaxLevel[group] = fmax ( vecMaxLevel[group], leveldB );
1343 levels[group].Add ( leveldB );
1344 }
1345 ++vecChannelsPerGroup[group];
1346 }
1347 }
1348
1349 // sort levels for later median computation
1350 for ( int i = 0; i < MAX_NUM_FADER_GROUPS + 1; ++i )
1351 {
1352 std::sort ( levels[i].begin(), levels[i].end() );
1353 }
1354
1355 // compute the number of active groups (at least one channel)
1356 int cntActiveGroups = 0;
1357 for ( int i = 0; i < MAX_NUM_FADER_GROUPS; ++i )
1358 {
1359 cntActiveGroups += vecChannelsPerGroup[i] > 0;
1360 }
1361
1362 // only my channel is active, nothing to do
1363 if ( cntActiveGroups == 0 && vecChannelsPerGroup[MAX_NUM_FADER_GROUPS] == 0 )
1364 {
1365 return;
1366 }
1367
1368 // compute target level for each group
1369 // (prevent clipping when each group contributes at maximum level)
1370 float targetLevelPerGroup = -20.0f * log10 ( std::max ( cntActiveGroups, 1 ) );
1371
1372 // compute target levels for the channels of each group individually
1373 float vecTargetChannelLevel[MAX_NUM_FADER_GROUPS + 1];
1374 float levelOffset = 0.0f;
1375 float minFader = 0.0f;
1376 for ( int i = 0; i < MAX_NUM_FADER_GROUPS + 1; ++i )
1377 {
1378 // compute the target level for each channel in the current group
1379 // (prevent clipping when each channel in this group contributes at
1380 // the maximum level)
1381 vecTargetChannelLevel[i] = vecChannelsPerGroup[i] > 0 ? targetLevelPerGroup - 20.0f * log10 ( vecChannelsPerGroup[i] ) : 0.0f;
1382
1383 // get median level
1384 int cntChannels = levels[i].Size();
1385 if ( cntChannels == 0 )
1386 {
1387 continue;
1388 }
1389 float refLevel = levels[i][cntChannels / 2];
1390
1391 // since we can only attenuate channels but not amplify, we have to
1392 // check that the reference channel can be brought to the target
1393 // level
1394 if ( refLevel < vecTargetChannelLevel[i] )
1395 {
1396 // otherwise, we adjust the level offset in such a way that
1397 // the level can be reached
1398 levelOffset = fmin ( levelOffset, refLevel - vecTargetChannelLevel[i] );
1399
1400 // compute the minimum necessary fader setting
1401 minFader = fmin ( minFader, -vecMaxLevel[i] + vecTargetChannelLevel[i] + levelOffset );
1402 }
1403 }
1404
1405 // take minimum fader value into account
1406 // very weak channels would actually require strong channels to be
1407 // attenuated to a large amount; however, the attenuation is limited by
1408 // the faders
1409 if ( minFader < -AUD_MIX_FADER_RANGE_DB )
1410 {
1411 levelOffset += -AUD_MIX_FADER_RANGE_DB - minFader;
1412 }
1413
1414 // adjust all levels
1415 for ( int i = 0; i < MAX_NUM_CHANNELS; ++i )
1416 {
1417 // only apply to visible faders (and not to my own channel fader)
1418 if ( vecpChanFader[i]->IsVisible() && ( i != iMyChannelID ) )
1419 {
1420 // map averaged meter output level to decibels
1421 // (invert CStereoSignalLevelMeter::CalcLogResultForMeter)
1422 float leveldB = vecAvgLevels[i] * ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ) / NUM_STEPS_LED_BAR + LOW_BOUND_SIG_METER;
1423
1424 int group = vecpChanFader[i]->GetGroupID();
1425 if ( group == INVALID_INDEX )
1426 {
1427 if ( cntActiveGroups > 0 )
1428 {
1429 // do not adjust the channels without group in group mode
1430 continue;
1431 }
1432 else
1433 {
1434 group = MAX_NUM_FADER_GROUPS;
1435 }
1436 }
1437
1438 // do not adjust channels with almost zero level to full level since
1439 // the channel might simply be silent at the moment
1440 if ( leveldB >= AUTO_FADER_NOISE_THRESHOLD_DB )
1441 {
1442 // compute new level
1443 float newdBLevel = -leveldB + vecTargetChannelLevel[group] + levelOffset;
1444
1445 // map range from decibels to fader level
1446 // (this inverts MathUtils::CalcFaderGain)
1447 float newFaderLevel = ( newdBLevel / AUD_MIX_FADER_RANGE_DB + 1.0f ) * AUD_MIX_FADER_MAX;
1448
1449 // limit fader
1450 newFaderLevel = fmin ( fmax ( newFaderLevel, 0.0f ), float ( AUD_MIX_FADER_MAX ) );
1451
1452 // set fader level
1453 vecpChanFader[i]->SetFaderLevel ( newFaderLevel, true );
1454 }
1455 }
1456 }
1457 }
1458
StoreAllFaderSettings()1459 void CAudioMixerBoard::StoreAllFaderSettings()
1460 {
1461 QMutexLocker locker ( &Mutex );
1462
1463 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1464 {
1465 StoreFaderSettings ( vecpChanFader[i] );
1466 }
1467 }
1468
LoadAllFaderSettings()1469 void CAudioMixerBoard::LoadAllFaderSettings()
1470 {
1471 QMutexLocker locker ( &Mutex );
1472
1473 int iStoredFaderLevel;
1474 int iStoredPanValue;
1475 bool bStoredFaderIsSolo;
1476 bool bStoredFaderIsMute;
1477 int iGroupID;
1478
1479 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1480 {
1481 if ( GetStoredFaderSettings ( vecpChanFader[i]->GetReceivedName(),
1482 iStoredFaderLevel,
1483 iStoredPanValue,
1484 bStoredFaderIsSolo,
1485 bStoredFaderIsMute,
1486 iGroupID ) )
1487 {
1488 vecpChanFader[i]->SetFaderLevel ( iStoredFaderLevel, true ); // suppress group update
1489 vecpChanFader[i]->SetPanValue ( iStoredPanValue );
1490 vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo );
1491 vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute );
1492 vecpChanFader[i]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader!
1493 }
1494 }
1495 }
1496
SetRemoteFaderIsMute(const int iChannelIdx,const bool bIsMute)1497 void CAudioMixerBoard::SetRemoteFaderIsMute ( const int iChannelIdx, const bool bIsMute )
1498 {
1499 // only apply remote mute state if channel index is valid and the fader is visible
1500 if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) )
1501 {
1502 if ( vecpChanFader[iChannelIdx]->IsVisible() )
1503 {
1504 vecpChanFader[iChannelIdx]->SetRemoteFaderIsMute ( bIsMute );
1505 }
1506 }
1507 }
1508
UpdateSoloStates()1509 void CAudioMixerBoard::UpdateSoloStates()
1510 {
1511 // first check if any channel has a solo state active
1512 bool bAnyChannelIsSolo = false;
1513
1514 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1515 {
1516 // check if fader is in use and has solo state active
1517 if ( vecpChanFader[i]->IsVisible() && vecpChanFader[i]->IsSolo() )
1518 {
1519 bAnyChannelIsSolo = true;
1520 continue;
1521 }
1522 }
1523
1524 // now update the solo state of all active faders
1525 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1526 {
1527 if ( vecpChanFader[i]->IsVisible() )
1528 {
1529 vecpChanFader[i]->UpdateSoloState ( bAnyChannelIsSolo );
1530 }
1531 }
1532 }
1533
UpdateGainValue(const int iChannelIdx,const float fValue,const bool bIsMyOwnFader,const bool bIsGroupUpdate,const bool bSuppressServerUpdate,const double dLevelRatio)1534 void CAudioMixerBoard::UpdateGainValue ( const int iChannelIdx,
1535 const float fValue,
1536 const bool bIsMyOwnFader,
1537 const bool bIsGroupUpdate,
1538 const bool bSuppressServerUpdate,
1539 const double dLevelRatio )
1540 {
1541 // update current gain
1542 if ( !bSuppressServerUpdate )
1543 {
1544 emit ChangeChanGain ( iChannelIdx, fValue, bIsMyOwnFader );
1545 }
1546
1547 // if this fader is selected, all other in the group must be updated as
1548 // well (note that we do not have to update if this is already a group update
1549 // to avoid an infinite loop)
1550 if ( ( vecpChanFader[iChannelIdx]->GetGroupID() != INVALID_INDEX ) && !bIsGroupUpdate )
1551 {
1552 for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
1553 {
1554 // update rest of faders selected
1555 if ( vecpChanFader[i]->IsVisible() && ( vecpChanFader[i]->GetGroupID() == vecpChanFader[iChannelIdx]->GetGroupID() ) &&
1556 ( i != iChannelIdx ) && ( dLevelRatio >= 0 ) )
1557 {
1558 // synchronize faders with moving fader level (it is important
1559 // to set the group flag to avoid infinite looping)
1560 vecpChanFader[i]->SetFaderLevel ( vecpChanFader[i]->GetPreviousFaderLevel() * dLevelRatio, true );
1561 }
1562 }
1563 }
1564 }
1565
UpdatePanValue(const int iChannelIdx,const float fValue)1566 void CAudioMixerBoard::UpdatePanValue ( const int iChannelIdx, const float fValue ) { emit ChangeChanPan ( iChannelIdx, fValue ); }
1567
StoreFaderSettings(CChannelFader * pChanFader)1568 void CAudioMixerBoard::StoreFaderSettings ( CChannelFader* pChanFader )
1569 {
1570 // if the fader was visible and the name is not empty, we store the old gain
1571 if ( pChanFader->IsVisible() && !pChanFader->GetReceivedName().isEmpty() )
1572 {
1573 CVector<int> viOldStoredFaderLevels ( pSettings->vecStoredFaderLevels );
1574 CVector<int> viOldStoredPanValues ( pSettings->vecStoredPanValues );
1575 CVector<int> vbOldStoredFaderIsSolo ( pSettings->vecStoredFaderIsSolo );
1576 CVector<int> vbOldStoredFaderIsMute ( pSettings->vecStoredFaderIsMute );
1577 CVector<int> vbOldStoredFaderGroupID ( pSettings->vecStoredFaderGroupID );
1578
1579 // put new value on the top of the list
1580 const int iOldIdx = pSettings->vecStoredFaderTags.StringFiFoWithCompare ( pChanFader->GetReceivedName() );
1581
1582 // current fader level and solo state is at the top of the list
1583 pSettings->vecStoredFaderLevels[0] = pChanFader->GetFaderLevel();
1584 pSettings->vecStoredPanValues[0] = pChanFader->GetPanValue();
1585 pSettings->vecStoredFaderIsSolo[0] = pChanFader->IsSolo();
1586 pSettings->vecStoredFaderIsMute[0] = pChanFader->IsMute();
1587 pSettings->vecStoredFaderGroupID[0] = pChanFader->GetGroupID();
1588 int iTempListCnt = 1; // current fader is on top, other faders index start at 1
1589
1590 for ( int iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ )
1591 {
1592 // first check if we still have space in our data storage
1593 if ( iTempListCnt < MAX_NUM_STORED_FADER_SETTINGS )
1594 {
1595 // check for the old index of the current entry (this has to be
1596 // skipped), note that per definition: the old index is an illegal
1597 // index in case the entry was not present in the vector before
1598 if ( iIdx != iOldIdx )
1599 {
1600 pSettings->vecStoredFaderLevels[iTempListCnt] = viOldStoredFaderLevels[iIdx];
1601 pSettings->vecStoredPanValues[iTempListCnt] = viOldStoredPanValues[iIdx];
1602 pSettings->vecStoredFaderIsSolo[iTempListCnt] = vbOldStoredFaderIsSolo[iIdx];
1603 pSettings->vecStoredFaderIsMute[iTempListCnt] = vbOldStoredFaderIsMute[iIdx];
1604 pSettings->vecStoredFaderGroupID[iTempListCnt] = vbOldStoredFaderGroupID[iIdx];
1605
1606 iTempListCnt++;
1607 }
1608 }
1609 }
1610 }
1611 }
1612
GetStoredFaderSettings(const QString & strName,int & iStoredFaderLevel,int & iStoredPanValue,bool & bStoredFaderIsSolo,bool & bStoredFaderIsMute,int & iGroupID)1613 bool CAudioMixerBoard::GetStoredFaderSettings ( const QString& strName,
1614 int& iStoredFaderLevel,
1615 int& iStoredPanValue,
1616 bool& bStoredFaderIsSolo,
1617 bool& bStoredFaderIsMute,
1618 int& iGroupID )
1619 {
1620 // only do the check if the name string is not empty
1621 if ( !strName.isEmpty() )
1622 {
1623 for ( int iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ )
1624 {
1625 // check if fader text is already known in the list
1626 if ( !pSettings->vecStoredFaderTags[iIdx].compare ( strName ) )
1627 {
1628 // copy stored settings values
1629 iStoredFaderLevel = pSettings->vecStoredFaderLevels[iIdx];
1630 iStoredPanValue = pSettings->vecStoredPanValues[iIdx];
1631 bStoredFaderIsSolo = pSettings->vecStoredFaderIsSolo[iIdx] != 0;
1632 bStoredFaderIsMute = pSettings->vecStoredFaderIsMute[iIdx] != 0;
1633 iGroupID = pSettings->vecStoredFaderGroupID[iIdx];
1634
1635 // values found and copied, return OK
1636 return true;
1637 }
1638 }
1639 }
1640
1641 // return "not OK" since we did not find matching fader settings
1642 return false;
1643 }
1644
SetChannelLevels(const CVector<uint16_t> & vecChannelLevel)1645 void CAudioMixerBoard::SetChannelLevels ( const CVector<uint16_t>& vecChannelLevel )
1646 {
1647 const int iNumChannelLevels = vecChannelLevel.Size();
1648 int i = 0;
1649
1650 for ( int iChId = 0; iChId < MAX_NUM_CHANNELS; iChId++ )
1651 {
1652 if ( vecpChanFader[iChId]->IsVisible() && ( i < iNumChannelLevels ) )
1653 {
1654 // compute exponential moving average
1655 vecAvgLevels[iChId] = ( 1.0f - AUTO_FADER_ADJUST_ALPHA ) * vecAvgLevels[iChId] + AUTO_FADER_ADJUST_ALPHA * vecChannelLevel[i];
1656
1657 vecpChanFader[iChId]->SetChannelLevel ( vecChannelLevel[i++] );
1658
1659 // show level only if we successfully received levels from the
1660 // server (if server does not support levels, do not show levels)
1661 if ( !vecpChanFader[iChId]->GetDisplayChannelLevel() )
1662 {
1663 vecpChanFader[iChId]->SetDisplayChannelLevel ( true );
1664 }
1665 }
1666 }
1667 }
1668
MuteMyChannel()1669 void CAudioMixerBoard::MuteMyChannel() { SetFaderIsMute ( iMyChannelID, true ); }
1670