-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathsoundbase.cpp
More file actions
381 lines (331 loc) · 13.9 KB
/
soundbase.cpp
File metadata and controls
381 lines (331 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/******************************************************************************\
* Copyright (c) 2004-2026
*
* Author(s):
* Volker Fischer
*
******************************************************************************
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
\******************************************************************************/
#include "soundbase.h"
// This is used as a lookup table for parsing option letters, mapping
// a single character to an EMidiCtlType
char const sMidiCtlChar[] = {
// Has to follow order of EMidiCtlType
/* [EMidiCtlType::Fader] = */ 'f',
/* [EMidiCtlType::Pan] = */ 'p',
/* [EMidiCtlType::Solo] = */ 's',
/* [EMidiCtlType::Mute] = */ 'm',
/* [EMidiCtlType::MuteMyself] = */ 'o',
/* [EMidiCtlType::Device] = */ 'd',
/* [EMidiCtlType::None] = */ '\0' };
/* Implementation *************************************************************/
CSoundBase::CSoundBase ( const QString& strNewSystemDriverTechniqueName,
void ( *fpNewProcessCallback ) ( CVector<int16_t>& psData, void* pParg ),
void* pParg ) :
fpProcessCallback ( fpNewProcessCallback ),
pProcessCallbackArg ( pParg ),
bRun ( false ),
bCallbackEntered ( false ),
strSystemDriverTechniqueName ( strNewSystemDriverTechniqueName ),
iCtrlMIDIChannel ( INVALID_MIDI_CH ),
aMidiCtls ( 128 )
{
// initializations for the sound card names (default)
lNumDevs = 1;
strDriverNames[0] = strSystemDriverTechniqueName;
// set current device
strCurDevName = ""; // default device
}
void CSoundBase::Stop()
{
// set flag so that thread can leave the main loop
bRun = false;
// wait for draining the audio process callback
QMutexLocker locker ( &MutexAudioProcessCallback );
}
/******************************************************************************\
* Device handling *
\******************************************************************************/
QStringList CSoundBase::GetDevNames()
{
QMutexLocker locker ( &MutexDevProperties );
QStringList slDevNames;
// put all device names in the string list
for ( int iDev = 0; iDev < lNumDevs; iDev++ )
{
slDevNames << strDriverNames[iDev];
}
return slDevNames;
}
QString CSoundBase::SetDev ( const QString strDevName )
{
QMutexLocker locker ( &MutexDevProperties );
// init return parameter with "no error"
QString strReturn = "";
// init flag for "load any driver"
bool bTryLoadAnyDriver = false;
// check if an ASIO driver was already initialized
if ( !strCurDevName.isEmpty() )
{
// a device was already been initialized and is used, first clean up
// driver
UnloadCurrentDriver();
const QString strErrorMessage = LoadAndInitializeDriver ( strDevName, false );
if ( !strErrorMessage.isEmpty() )
{
if ( strDevName != strCurDevName )
{
// loading and initializing the new driver failed, go back to
// original driver and create error message
LoadAndInitializeDriver ( strCurDevName, false );
// store error return message
strReturn = QString ( tr ( "Can't use the selected audio device "
"because of the following error: %1 "
"The previous driver will be selected." )
.arg ( strErrorMessage ) );
}
else
{
// loading and initializing the current driver failed, try to find
// at least one usable driver
bTryLoadAnyDriver = true;
}
}
}
else
{
if ( !strDevName.isEmpty() )
{
// This is the first time a driver is to be initialized, we first
// try to load the selected driver, if this fails, we try to load
// the first available driver in the system. If this fails, too, we
// throw an error that no driver is available -> it does not make
// sense to start the software if no audio hardware is available.
if ( !LoadAndInitializeDriver ( strDevName, false ).isEmpty() )
{
// loading and initializing the new driver failed, try to find
// at least one usable driver
bTryLoadAnyDriver = true;
}
}
else
{
// try to find one usable driver (select the first valid driver)
bTryLoadAnyDriver = true;
}
}
if ( bTryLoadAnyDriver )
{
// if a driver was previously selected, show a warning message
if ( !strDevName.isEmpty() )
{
strReturn = tr ( "The previously selected audio device "
"is no longer available or the driver has changed to an incompatible state. "
"We'll attempt to find a valid audio device, but this new audio device may cause feedback. "
"Before connecting to a server, please check your audio device settings." );
}
// try to load and initialize any valid driver
QVector<QString> vsErrorList = LoadAndInitializeFirstValidDriver();
if ( !vsErrorList.isEmpty() )
{
// create error message with all details
QString sErrorMessage =
tr ( "<b>%1 couldn't find a usable %2 audio device.</b><br><br>" ).arg ( APP_NAME ).arg ( strSystemDriverTechniqueName );
for ( int i = 0; i < lNumDevs; i++ )
{
sErrorMessage += "<b>" + GetDeviceName ( i ) + "</b>: " + vsErrorList[i] + "<br><br>";
}
#if defined( _WIN32 ) && !defined( WITH_JACK )
// to be able to access the ASIO driver setup for changing, e.g., the sample rate, we
// offer the user under Windows that we open the driver setups of all registered
// ASIO drivers
sErrorMessage += "<br>" + tr ( "You may be able to fix errors in the driver settings. Do you want to open these settings now?" );
if ( QMessageBox::Yes == QMessageBox::information ( nullptr, APP_NAME, sErrorMessage, QMessageBox::Yes | QMessageBox::No ) )
{
LoadAndInitializeFirstValidDriver ( true );
}
sErrorMessage = QString ( tr ( "Can't start %1. Please restart %1 and check/reconfigure your audio settings." ) ).arg ( APP_NAME );
#endif
throw CGenErr ( sErrorMessage );
}
}
return strReturn;
}
QVector<QString> CSoundBase::LoadAndInitializeFirstValidDriver ( const bool bOpenDriverSetup )
{
QVector<QString> vsErrorList;
// load and initialize first valid ASIO driver
bool bValidDriverDetected = false;
int iDriverCnt = 0;
// try all available drivers in the system ("lNumDevs" devices)
while ( !bValidDriverDetected && ( iDriverCnt < lNumDevs ) )
{
// try to load and initialize current driver, store error message
const QString strCurError = LoadAndInitializeDriver ( GetDeviceName ( iDriverCnt ), bOpenDriverSetup );
vsErrorList.append ( strCurError );
if ( strCurError.isEmpty() )
{
// initialization was successful
bValidDriverDetected = true;
// store ID of selected driver
strCurDevName = GetDeviceName ( iDriverCnt );
// empty error list shows that init was successful
vsErrorList.clear();
}
// try next driver
iDriverCnt++;
}
return vsErrorList;
}
/******************************************************************************\
* MIDI handling *
\******************************************************************************/
void CSoundBase::SetMIDIControllerMapping ( int iFaderOffset,
int iFaderCount,
int iPanOffset,
int iPanCount,
int iSoloOffset,
int iSoloCount,
int iMuteOffset,
int iMuteCount,
int iMuteMyselfCC )
{
// Clear all previous MIDI mappings
for ( int i = 0; i < aMidiCtls.size(); ++i )
{
aMidiCtls[i] = { None, 0 };
}
// Map fader controllers
for ( int i = 0; i < iFaderCount && i < MAX_NUM_CHANNELS; ++i )
{
int iCC = iFaderOffset + i;
if ( iCC >= 0 && iCC < 128 )
{
aMidiCtls[iCC] = { Fader, i };
}
}
// Map pan controllers
for ( int i = 0; i < iPanCount && i < MAX_NUM_CHANNELS; ++i )
{
int iCC = iPanOffset + i;
if ( iCC >= 0 && iCC < 128 )
{
aMidiCtls[iCC] = { Pan, i };
}
}
// Map solo controllers
for ( int i = 0; i < iSoloCount && i < MAX_NUM_CHANNELS; ++i )
{
int iCC = iSoloOffset + i;
if ( iCC >= 0 && iCC < 128 )
{
aMidiCtls[iCC] = { Solo, i };
}
}
// Map mute controllers
for ( int i = 0; i < iMuteCount && i < MAX_NUM_CHANNELS; ++i )
{
int iCC = iMuteOffset + i;
if ( iCC >= 0 && iCC < 128 )
{
aMidiCtls[iCC] = { Mute, i };
}
}
// Map mute myself controller
if ( iMuteMyselfCC >= 0 && iMuteMyselfCC < 128 )
{
aMidiCtls[iMuteMyselfCC] = { MuteMyself, 0 };
}
}
void CSoundBase::ParseMIDIMessage ( const CVector<uint8_t>& vMIDIPaketBytes )
{
if ( vMIDIPaketBytes.Size() > 0 )
{
const uint8_t iStatusByte = vMIDIPaketBytes[0];
// check if status byte is correct
if ( ( iStatusByte >= 0x80 ) && ( iStatusByte < 0xF0 ) )
{
// zero-based MIDI channel number (i.e. range 0-15)
const int iMIDIChannelZB = iStatusByte & 0x0F;
/*
// debugging
printf ( "%02X: ", iMIDIChannelZB );
for ( int i = 0; i < vMIDIPaketBytes.Size(); i++ )
{
printf ( "%02X ", vMIDIPaketBytes[i] );
}
printf ( "\n" );
*/
// per definition if MIDI channel is 0, we listen to all channels
// note that iCtrlMIDIChannel is one-based channel number
if ( ( iCtrlMIDIChannel == 0 ) || ( iCtrlMIDIChannel - 1 == iMIDIChannelZB ) )
{
// we only want to parse controller messages
if ( ( iStatusByte >= 0xB0 ) && ( iStatusByte < 0xC0 ) )
{
// make sure packet is long enough
if ( vMIDIPaketBytes.Size() > 2 && vMIDIPaketBytes[1] <= uint8_t ( 127 ) && vMIDIPaketBytes[2] <= uint8_t ( 127 ) )
{
const CMidiCtlEntry& cCtrl = aMidiCtls[vMIDIPaketBytes[1]];
const int iValue = vMIDIPaketBytes[2];
emit MidiCCReceived ( vMIDIPaketBytes[1] );
switch ( cCtrl.eType )
{
case Fader:
{
// we are assuming that the controller number is the same
// as the audio fader index and the range is 0-127
const int iFaderLevel = static_cast<int> ( static_cast<double> ( iValue ) / 127 * AUD_MIX_FADER_MAX );
// consider offset for the faders
emit ControllerInFaderLevel ( cCtrl.iChannel, iFaderLevel );
}
break;
case Pan:
{
// Pan levels need to be symmetric between 1 and 127
const int iPanValue = static_cast<int> ( static_cast<double> ( qMax ( iValue, 1 ) - 1 ) / 126 * AUD_MIX_PAN_MAX );
emit ControllerInPanValue ( cCtrl.iChannel, iPanValue );
}
break;
case Solo:
{
// We depend on toggles reflecting the desired state
emit ControllerInFaderIsSolo ( cCtrl.iChannel, iValue >= 0x40 );
}
break;
case Mute:
{
// We depend on toggles reflecting the desired state
emit ControllerInFaderIsMute ( cCtrl.iChannel, iValue >= 0x40 );
}
break;
case MuteMyself:
{
// We depend on toggles reflecting the desired state to Mute Myself
emit ControllerInMuteMyself ( iValue >= 0x40 );
}
break;
default:
break;
}
}
}
}
}
}
}