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 "sound.h"
26
27 /* Implementation *************************************************************/
CSound(void (* fpNewProcessCallback)(CVector<short> & psData,void * arg),void * arg,const QString & strMIDISetup,const bool,const QString &)28 CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector<short>& psData, void* arg ),
29 void* arg,
30 const QString& strMIDISetup,
31 const bool,
32 const QString& ) :
33 CSoundBase ( "CoreAudio", fpNewProcessCallback, arg, strMIDISetup ),
34 midiInPortRef ( static_cast<MIDIPortRef> ( NULL ) )
35 {
36 // Apple Mailing Lists: Subject: GUI Apps should set kAudioHardwarePropertyRunLoop
37 // in the HAL, From: Jeff Moore, Date: Fri, 6 Dec 2002
38 // Most GUI applciations have several threads on which they receive
39 // notifications already, so the having the HAL's thread around is wasteful.
40 // Here is what you should do: On the thread you want the HAL to use for
41 // notifications (for most apps, this will be the main thread), add the
42 // following lines of code:
43 // tell the HAL to use the current thread as it's run loop
44 CFRunLoopRef theRunLoop = CFRunLoopGetCurrent();
45 AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
46
47 AudioObjectSetPropertyData ( kAudioObjectSystemObject, &property, 0, NULL, sizeof ( CFRunLoopRef ), &theRunLoop );
48
49 // initial query for available input/output sound devices in the system
50 GetAvailableInOutDevices();
51
52 // init device index as not initialized (invalid)
53 lCurDev = INVALID_INDEX;
54 CurrentAudioInputDeviceID = 0;
55 CurrentAudioOutputDeviceID = 0;
56 iNumInChan = 0;
57 iNumInChanPlusAddChan = 0;
58 iNumOutChan = 0;
59 iSelInputLeftChannel = 0;
60 iSelInputRightChannel = 0;
61 iSelOutputLeftChannel = 0;
62 iSelOutputRightChannel = 0;
63
64 // Optional MIDI initialization --------------------------------------------
65 if ( iCtrlMIDIChannel != INVALID_MIDI_CH )
66 {
67 // create client and ports
68 MIDIClientRef midiClient = static_cast<MIDIClientRef> ( NULL );
69 MIDIClientCreate ( CFSTR ( APP_NAME ), NULL, NULL, &midiClient );
70 MIDIInputPortCreate ( midiClient, CFSTR ( "Input port" ), callbackMIDI, this, &midiInPortRef );
71
72 // open connections from all sources
73 const int iNMIDISources = MIDIGetNumberOfSources();
74
75 for ( int i = 0; i < iNMIDISources; i++ )
76 {
77 MIDIEndpointRef src = MIDIGetSource ( i );
78 MIDIPortConnectSource ( midiInPortRef, src, NULL );
79 }
80 }
81 }
82
GetAvailableInOutDevices()83 void CSound::GetAvailableInOutDevices()
84 {
85 UInt32 iPropertySize = 0;
86 AudioObjectPropertyAddress stPropertyAddress;
87
88 stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
89 stPropertyAddress.mElement = kAudioObjectPropertyElementMaster;
90
91 // first get property size of devices array and allocate memory
92 stPropertyAddress.mSelector = kAudioHardwarePropertyDevices;
93
94 AudioObjectGetPropertyDataSize ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize );
95
96 CVector<AudioDeviceID> vAudioDevices ( iPropertySize );
97
98 // now actually query all devices present in the system
99 AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &vAudioDevices[0] );
100
101 // calculate device count based on size of returned data array
102 const UInt32 iDeviceCount = iPropertySize / sizeof ( AudioDeviceID );
103
104 // always add system default devices for input and output as first entry
105 lNumDevs = 0;
106 strDriverNames[lNumDevs] = "System Default In/Out Devices";
107
108 iPropertySize = sizeof ( AudioDeviceID );
109 stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
110
111 if ( AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &audioInputDevice[lNumDevs] ) )
112 {
113 throw CGenErr ( tr ( "No sound card is available in your system. "
114 "CoreAudio input AudioHardwareGetProperty call failed." ) );
115 }
116
117 iPropertySize = sizeof ( AudioDeviceID );
118 stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
119
120 if ( AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &audioOutputDevice[lNumDevs] ) )
121 {
122 throw CGenErr ( tr ( "No sound card is available in the system. "
123 "CoreAudio output AudioHardwareGetProperty call failed." ) );
124 }
125
126 lNumDevs++; // next device
127
128 // add detected devices
129 //
130 // we add combined entries for input and output for each device so that we
131 // do not need two combo boxes in the GUI for input and output (therefore
132 // all possible combinations are required which can be a large number)
133 for ( UInt32 i = 0; i < iDeviceCount; i++ )
134 {
135 for ( UInt32 j = 0; j < iDeviceCount; j++ )
136 {
137 // get device infos for both current devices
138 QString strDeviceName_i;
139 QString strDeviceName_j;
140 bool bIsInput_i;
141 bool bIsInput_j;
142 bool bIsOutput_i;
143 bool bIsOutput_j;
144
145 GetAudioDeviceInfos ( vAudioDevices[i], strDeviceName_i, bIsInput_i, bIsOutput_i );
146
147 GetAudioDeviceInfos ( vAudioDevices[j], strDeviceName_j, bIsInput_j, bIsOutput_j );
148
149 // check if i device is input and j device is output and that we are
150 // in range
151 if ( bIsInput_i && bIsOutput_j && ( lNumDevs < MAX_NUMBER_SOUND_CARDS ) )
152 {
153 strDriverNames[lNumDevs] = "in: " + strDeviceName_i + "/out: " + strDeviceName_j;
154
155 // store audio device IDs
156 audioInputDevice[lNumDevs] = vAudioDevices[i];
157 audioOutputDevice[lNumDevs] = vAudioDevices[j];
158
159 lNumDevs++; // next device
160 }
161 }
162 }
163 }
164
GetAudioDeviceInfos(const AudioDeviceID DeviceID,QString & strDeviceName,bool & bIsInput,bool & bIsOutput)165 void CSound::GetAudioDeviceInfos ( const AudioDeviceID DeviceID, QString& strDeviceName, bool& bIsInput, bool& bIsOutput )
166 {
167 UInt32 iPropertySize;
168 AudioObjectPropertyAddress stPropertyAddress;
169
170 // init return values
171 bIsInput = false;
172 bIsOutput = false;
173
174 // check if device is input or output or both (is that possible?)
175 stPropertyAddress.mSelector = kAudioDevicePropertyStreams;
176 stPropertyAddress.mElement = kAudioObjectPropertyElementMaster;
177
178 // input check
179 iPropertySize = 0;
180 stPropertyAddress.mScope = kAudioDevicePropertyScopeInput;
181
182 AudioObjectGetPropertyDataSize ( DeviceID, &stPropertyAddress, 0, NULL, &iPropertySize );
183
184 bIsInput = ( iPropertySize > 0 ); // check if any input streams are available
185
186 // output check
187 iPropertySize = 0;
188 stPropertyAddress.mScope = kAudioDevicePropertyScopeOutput;
189
190 AudioObjectGetPropertyDataSize ( DeviceID, &stPropertyAddress, 0, NULL, &iPropertySize );
191
192 bIsOutput = ( iPropertySize > 0 ); // check if any output streams are available
193
194 // get property name
195 CFStringRef sPropertyStringValue = NULL;
196
197 stPropertyAddress.mSelector = kAudioObjectPropertyName;
198 stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
199 iPropertySize = sizeof ( CFStringRef );
200
201 AudioObjectGetPropertyData ( DeviceID, &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue );
202
203 // convert string
204 if ( !ConvertCFStringToQString ( sPropertyStringValue, strDeviceName ) )
205 {
206 // use a default name in case the conversion did not succeed
207 strDeviceName = "UNKNOWN";
208 }
209 }
210
CountChannels(AudioDeviceID devID,bool isInput)211 int CSound::CountChannels ( AudioDeviceID devID, bool isInput )
212 {
213 OSStatus err;
214 UInt32 propSize;
215 int result = 0;
216
217 if ( isInput )
218 {
219 vecNumInBufChan.Init ( 0 );
220 }
221 else
222 {
223 vecNumOutBufChan.Init ( 0 );
224 }
225
226 // it seems we have multiple buffers where each buffer has only one channel,
227 // in that case we assume that each input channel has its own buffer
228 AudioObjectPropertyScope theScope = isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
229
230 AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyStreamConfiguration, theScope, 0 };
231
232 AudioObjectGetPropertyDataSize ( devID, &theAddress, 0, NULL, &propSize );
233
234 AudioBufferList* buflist = (AudioBufferList*) malloc ( propSize );
235
236 err = AudioObjectGetPropertyData ( devID, &theAddress, 0, NULL, &propSize, buflist );
237
238 if ( !err )
239 {
240 for ( UInt32 i = 0; i < buflist->mNumberBuffers; ++i )
241 {
242 // The correct value mNumberChannels for an AudioBuffer can be derived from the mChannelsPerFrame
243 // and the interleaved flag. For non interleaved formats, mNumberChannels is always 1.
244 // For interleaved formats, mNumberChannels is equal to mChannelsPerFrame.
245 result += buflist->mBuffers[i].mNumberChannels;
246
247 if ( isInput )
248 {
249 vecNumInBufChan.Add ( buflist->mBuffers[i].mNumberChannels );
250 }
251 else
252 {
253 vecNumOutBufChan.Add ( buflist->mBuffers[i].mNumberChannels );
254 }
255 }
256 }
257 free ( buflist );
258
259 return result;
260 }
261
LoadAndInitializeDriver(QString strDriverName,bool)262 QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool )
263 {
264 // secure lNumDevs/strDriverNames access
265 QMutexLocker locker ( &Mutex );
266
267 // reload the driver list of available sound devices
268 GetAvailableInOutDevices();
269
270 // find driver index from given driver name
271 int iDriverIdx = INVALID_INDEX; // initialize with an invalid index
272
273 for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ )
274 {
275 if ( strDriverName.compare ( strDriverNames[i] ) == 0 )
276 {
277 iDriverIdx = i;
278 }
279 }
280
281 // if the selected driver was not found, return an error message
282 if ( iDriverIdx == INVALID_INDEX )
283 {
284 return tr ( "The currently selected audio device is no longer present. Please check your audio device." );
285 }
286
287 // check device capabilities if it fulfills our requirements
288 const QString strStat = CheckDeviceCapabilities ( iDriverIdx );
289
290 // check if device is capable and if not the same device is used
291 if ( strStat.isEmpty() && ( strCurDevName.compare ( strDriverNames[iDriverIdx] ) != 0 ) )
292 {
293 AudioObjectPropertyAddress stPropertyAddress;
294
295 // unregister callbacks if previous device was valid
296 if ( lCurDev != INVALID_INDEX )
297 {
298 stPropertyAddress.mElement = kAudioObjectPropertyElementMaster;
299 stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
300
301 // unregister callback functions for device property changes
302 stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
303
304 AudioObjectRemovePropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this );
305
306 stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
307
308 AudioObjectRemovePropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this );
309
310 stPropertyAddress.mSelector = kAudioDevicePropertyDeviceHasChanged;
311
312 AudioObjectRemovePropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
313
314 AudioObjectRemovePropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
315
316 stPropertyAddress.mSelector = kAudioDevicePropertyDeviceIsAlive;
317
318 AudioObjectRemovePropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
319
320 AudioObjectRemovePropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
321 }
322
323 // store ID of selected driver if initialization was successful
324 lCurDev = iDriverIdx;
325 CurrentAudioInputDeviceID = audioInputDevice[iDriverIdx];
326 CurrentAudioOutputDeviceID = audioOutputDevice[iDriverIdx];
327
328 // register callbacks
329 stPropertyAddress.mElement = kAudioObjectPropertyElementMaster;
330 stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
331
332 // setup callbacks for device property changes
333 stPropertyAddress.mSelector = kAudioDevicePropertyDeviceHasChanged;
334
335 AudioObjectAddPropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
336
337 AudioObjectAddPropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
338
339 stPropertyAddress.mSelector = kAudioDevicePropertyDeviceIsAlive;
340
341 AudioObjectAddPropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
342
343 AudioObjectAddPropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this );
344
345 stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
346
347 AudioObjectAddPropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this );
348
349 stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
350
351 AudioObjectAddPropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this );
352
353 // the device has changed, per definition we reset the channel
354 // mapping to the defaults (first two available channels)
355 SetLeftInputChannel ( 0 );
356 SetRightInputChannel ( 1 );
357 SetLeftOutputChannel ( 0 );
358 SetRightOutputChannel ( 1 );
359
360 // store the current name of the driver
361 strCurDevName = strDriverNames[iDriverIdx];
362 }
363
364 return strStat;
365 }
366
CheckDeviceCapabilities(const int iDriverIdx)367 QString CSound::CheckDeviceCapabilities ( const int iDriverIdx )
368 {
369 UInt32 iPropertySize;
370 AudioStreamBasicDescription CurDevStreamFormat;
371 Float64 inputSampleRate = 0;
372 Float64 outputSampleRate = 0;
373 const Float64 fSystemSampleRate = static_cast<Float64> ( SYSTEM_SAMPLE_RATE_HZ );
374 AudioObjectPropertyAddress stPropertyAddress;
375
376 stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
377 stPropertyAddress.mElement = kAudioObjectPropertyElementMaster;
378
379 // check input device sample rate
380 stPropertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate;
381 iPropertySize = sizeof ( Float64 );
382
383 if ( AudioObjectGetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &inputSampleRate ) )
384 {
385 return QString ( tr ( "The audio input device is no longer available. Please check if your input device is connected correctly." ) );
386 }
387
388 if ( inputSampleRate != fSystemSampleRate )
389 {
390 // try to change the sample rate
391 if ( AudioObjectSetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, sizeof ( Float64 ), &fSystemSampleRate ) !=
392 noErr )
393 {
394 return QString ( tr ( "The sample rate on the current input device isn't %1 Hz and is therefore incompatible. "
395 "Please select another device or try setting the sample rate to %1 Hz "
396 "manually via Audio-MIDI-Setup (in Applications->Utilities)." ) )
397 .arg ( SYSTEM_SAMPLE_RATE_HZ );
398 }
399 }
400
401 // check output device sample rate
402 iPropertySize = sizeof ( Float64 );
403
404 if ( AudioObjectGetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &outputSampleRate ) )
405 {
406 return QString ( tr ( "The audio output device is no longer available. Please check if your output device is connected correctly." ) );
407 }
408
409 if ( outputSampleRate != fSystemSampleRate )
410 {
411 // try to change the sample rate
412 if ( AudioObjectSetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, sizeof ( Float64 ), &fSystemSampleRate ) !=
413 noErr )
414 {
415 return QString ( tr ( "The sample rate on the current output device isn't %1 Hz and is therefore incompatible. "
416 "Please select another device or try setting the sample rate to %1 Hz "
417 "manually via Audio-MIDI-Setup (in Applications->Utilities)." ) )
418 .arg ( SYSTEM_SAMPLE_RATE_HZ );
419 }
420 }
421
422 // get the stream ID of the input device (at least one stream must always exist)
423 iPropertySize = 0;
424 stPropertyAddress.mSelector = kAudioDevicePropertyStreams;
425 stPropertyAddress.mScope = kAudioObjectPropertyScopeInput;
426
427 AudioObjectGetPropertyDataSize ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize );
428
429 CVector<AudioStreamID> vInputStreamIDList ( iPropertySize );
430
431 AudioObjectGetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &vInputStreamIDList[0] );
432
433 const AudioStreamID inputStreamID = vInputStreamIDList[0];
434
435 // get the stream ID of the output device (at least one stream must always exist)
436 iPropertySize = 0;
437 stPropertyAddress.mSelector = kAudioDevicePropertyStreams;
438 stPropertyAddress.mScope = kAudioObjectPropertyScopeOutput;
439
440 AudioObjectGetPropertyDataSize ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize );
441
442 CVector<AudioStreamID> vOutputStreamIDList ( iPropertySize );
443
444 AudioObjectGetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &vOutputStreamIDList[0] );
445
446 const AudioStreamID outputStreamID = vOutputStreamIDList[0];
447
448 // According to the AudioHardware documentation: "If the format is a linear PCM
449 // format, the data will always be presented as 32 bit, native endian floating
450 // point. All conversions to and from the true physical format of the hardware
451 // is handled by the devices driver.".
452 // check the input
453 iPropertySize = sizeof ( AudioStreamBasicDescription );
454 stPropertyAddress.mSelector = kAudioStreamPropertyVirtualFormat;
455 stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
456
457 AudioObjectGetPropertyData ( inputStreamID, &stPropertyAddress, 0, NULL, &iPropertySize, &CurDevStreamFormat );
458
459 if ( ( CurDevStreamFormat.mFormatID != kAudioFormatLinearPCM ) || ( CurDevStreamFormat.mFramesPerPacket != 1 ) ||
460 ( CurDevStreamFormat.mBitsPerChannel != 32 ) || ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsFloat ) ) ||
461 ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsPacked ) ) )
462 {
463 return QString ( tr ( "The stream format on the current input device isn't "
464 "compatible with this software. Please select another device." ) )
465 .arg ( APP_NAME );
466 }
467
468 // check the output
469 AudioObjectGetPropertyData ( outputStreamID, &stPropertyAddress, 0, NULL, &iPropertySize, &CurDevStreamFormat );
470
471 if ( ( CurDevStreamFormat.mFormatID != kAudioFormatLinearPCM ) || ( CurDevStreamFormat.mFramesPerPacket != 1 ) ||
472 ( CurDevStreamFormat.mBitsPerChannel != 32 ) || ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsFloat ) ) ||
473 ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsPacked ) ) )
474 {
475 return QString ( tr ( "The stream format on the current output device isn't "
476 "compatible with %1. Please select another device." ) )
477 .arg ( APP_NAME );
478 }
479
480 // store the input and out number of channels for this device
481 iNumInChan = CountChannels ( audioInputDevice[iDriverIdx], true );
482 iNumOutChan = CountChannels ( audioOutputDevice[iDriverIdx], false );
483
484 // clip the number of input/output channels to our allowed maximum
485 if ( iNumInChan > MAX_NUM_IN_OUT_CHANNELS )
486 {
487 iNumInChan = MAX_NUM_IN_OUT_CHANNELS;
488 }
489 if ( iNumOutChan > MAX_NUM_IN_OUT_CHANNELS )
490 {
491 iNumOutChan = MAX_NUM_IN_OUT_CHANNELS;
492 }
493
494 // get the channel names of the input device
495 for ( int iCurInCH = 0; iCurInCH < iNumInChan; iCurInCH++ )
496 {
497 CFStringRef sPropertyStringValue = NULL;
498
499 stPropertyAddress.mSelector = kAudioObjectPropertyElementName;
500 stPropertyAddress.mElement = iCurInCH + 1;
501 stPropertyAddress.mScope = kAudioObjectPropertyScopeInput;
502 iPropertySize = sizeof ( CFStringRef );
503
504 AudioObjectGetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue );
505
506 // convert string
507 const bool bConvOK = ConvertCFStringToQString ( sPropertyStringValue, sChannelNamesInput[iCurInCH] );
508
509 // add the "[n]:" at the beginning as is in the Audio-Midi-Setup
510 if ( !bConvOK || ( iPropertySize == 0 ) )
511 {
512 // use a default name in case there was an error or the name is empty
513 sChannelNamesInput[iCurInCH] = QString ( "%1: Channel %1" ).arg ( iCurInCH + 1 );
514 }
515 else
516 {
517 sChannelNamesInput[iCurInCH].prepend ( QString ( "%1: " ).arg ( iCurInCH + 1 ) );
518 }
519 }
520
521 // get the channel names of the output device
522 for ( int iCurOutCH = 0; iCurOutCH < iNumOutChan; iCurOutCH++ )
523 {
524 CFStringRef sPropertyStringValue = NULL;
525
526 stPropertyAddress.mSelector = kAudioObjectPropertyElementName;
527 stPropertyAddress.mElement = iCurOutCH + 1;
528 stPropertyAddress.mScope = kAudioObjectPropertyScopeOutput;
529 iPropertySize = sizeof ( CFStringRef );
530
531 AudioObjectGetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue );
532
533 // convert string
534 const bool bConvOK = ConvertCFStringToQString ( sPropertyStringValue, sChannelNamesOutput[iCurOutCH] );
535
536 // add the "[n]:" at the beginning as is in the Audio-Midi-Setup
537 if ( !bConvOK || ( iPropertySize == 0 ) )
538 {
539 // use a default name in case there was an error or the name is empty
540 sChannelNamesOutput[iCurOutCH] = QString ( "%1: Channel %1" ).arg ( iCurOutCH + 1 );
541 }
542 else
543 {
544 sChannelNamesOutput[iCurOutCH].prepend ( QString ( "%1: " ).arg ( iCurOutCH + 1 ) );
545 }
546 }
547
548 // special case with 4 input channels: support adding channels
549 if ( iNumInChan == 4 )
550 {
551 // add four mixed channels (i.e. 4 normal, 4 mixed channels)
552 iNumInChanPlusAddChan = 8;
553
554 for ( int iCh = 0; iCh < iNumInChanPlusAddChan; iCh++ )
555 {
556 int iSelCH, iSelAddCH;
557
558 GetSelCHAndAddCH ( iCh, iNumInChan, iSelCH, iSelAddCH );
559
560 if ( iSelAddCH >= 0 )
561 {
562 // for mixed channels, show both audio channel names to be mixed
563 sChannelNamesInput[iCh] = sChannelNamesInput[iSelCH] + " + " + sChannelNamesInput[iSelAddCH];
564 }
565 }
566 }
567 else
568 {
569 // regular case: no mixing input channels used
570 iNumInChanPlusAddChan = iNumInChan;
571 }
572
573 // everything is ok, return empty string for "no error" case
574 return "";
575 }
576
UpdateChSelection()577 void CSound::UpdateChSelection()
578 {
579 // calculate the selected input/output buffer and the selected interleaved
580 // channel index in the buffer, note that each buffer can have a different
581 // number of interleaved channels
582 int iChCnt;
583 int iSelCHLeft, iSelAddCHLeft;
584 int iSelCHRight, iSelAddCHRight;
585
586 // initialize all buffer indexes with an invalid value
587 iSelInBufferLeft = INVALID_INDEX;
588 iSelInBufferRight = INVALID_INDEX;
589 iSelAddInBufferLeft = INVALID_INDEX; // if no additional channel used, this will stay on the invalid value
590 iSelAddInBufferRight = INVALID_INDEX; // if no additional channel used, this will stay on the invalid value
591 iSelOutBufferLeft = INVALID_INDEX;
592 iSelOutBufferRight = INVALID_INDEX;
593
594 // input
595 GetSelCHAndAddCH ( iSelInputLeftChannel, iNumInChan, iSelCHLeft, iSelAddCHLeft );
596 GetSelCHAndAddCH ( iSelInputRightChannel, iNumInChan, iSelCHRight, iSelAddCHRight );
597
598 iChCnt = 0;
599
600 for ( int iBuf = 0; iBuf < vecNumInBufChan.Size(); iBuf++ )
601 {
602 iChCnt += vecNumInBufChan[iBuf];
603
604 if ( ( iSelInBufferLeft < 0 ) && ( iChCnt > iSelCHLeft ) )
605 {
606 iSelInBufferLeft = iBuf;
607 iSelInInterlChLeft = iSelCHLeft - iChCnt + vecNumInBufChan[iBuf];
608 }
609
610 if ( ( iSelInBufferRight < 0 ) && ( iChCnt > iSelCHRight ) )
611 {
612 iSelInBufferRight = iBuf;
613 iSelInInterlChRight = iSelCHRight - iChCnt + vecNumInBufChan[iBuf];
614 }
615
616 if ( ( iSelAddCHLeft >= 0 ) && ( iSelAddInBufferLeft < 0 ) && ( iChCnt > iSelAddCHLeft ) )
617 {
618 iSelAddInBufferLeft = iBuf;
619 iSelAddInInterlChLeft = iSelAddCHLeft - iChCnt + vecNumInBufChan[iBuf];
620 }
621
622 if ( ( iSelAddCHRight >= 0 ) && ( iSelAddInBufferRight < 0 ) && ( iChCnt > iSelAddCHRight ) )
623 {
624 iSelAddInBufferRight = iBuf;
625 iSelAddInInterlChRight = iSelAddCHRight - iChCnt + vecNumInBufChan[iBuf];
626 }
627 }
628
629 // output
630 GetSelCHAndAddCH ( iSelOutputLeftChannel, iNumOutChan, iSelCHLeft, iSelAddCHLeft );
631 GetSelCHAndAddCH ( iSelOutputRightChannel, iNumOutChan, iSelCHRight, iSelAddCHRight );
632
633 iChCnt = 0;
634
635 for ( int iBuf = 0; iBuf < vecNumOutBufChan.Size(); iBuf++ )
636 {
637 iChCnt += vecNumOutBufChan[iBuf];
638
639 if ( ( iSelOutBufferLeft < 0 ) && ( iChCnt > iSelCHLeft ) )
640 {
641 iSelOutBufferLeft = iBuf;
642 iSelOutInterlChLeft = iSelCHLeft - iChCnt + vecNumOutBufChan[iBuf];
643 }
644
645 if ( ( iSelOutBufferRight < 0 ) && ( iChCnt > iSelCHRight ) )
646 {
647 iSelOutBufferRight = iBuf;
648 iSelOutInterlChRight = iSelCHRight - iChCnt + vecNumOutBufChan[iBuf];
649 }
650 }
651 }
652
SetLeftInputChannel(const int iNewChan)653 void CSound::SetLeftInputChannel ( const int iNewChan )
654 {
655 // apply parameter after input parameter check
656 if ( ( iNewChan >= 0 ) && ( iNewChan < iNumInChanPlusAddChan ) )
657 {
658 iSelInputLeftChannel = iNewChan;
659 UpdateChSelection();
660 }
661 }
662
SetRightInputChannel(const int iNewChan)663 void CSound::SetRightInputChannel ( const int iNewChan )
664 {
665 // apply parameter after input parameter check
666 if ( ( iNewChan >= 0 ) && ( iNewChan < iNumInChanPlusAddChan ) )
667 {
668 iSelInputRightChannel = iNewChan;
669 UpdateChSelection();
670 }
671 }
672
SetLeftOutputChannel(const int iNewChan)673 void CSound::SetLeftOutputChannel ( const int iNewChan )
674 {
675 // apply parameter after input parameter check
676 if ( ( iNewChan >= 0 ) && ( iNewChan < iNumOutChan ) )
677 {
678 iSelOutputLeftChannel = iNewChan;
679 UpdateChSelection();
680 }
681 }
682
SetRightOutputChannel(const int iNewChan)683 void CSound::SetRightOutputChannel ( const int iNewChan )
684 {
685 // apply parameter after input parameter check
686 if ( ( iNewChan >= 0 ) && ( iNewChan < iNumOutChan ) )
687 {
688 iSelOutputRightChannel = iNewChan;
689 UpdateChSelection();
690 }
691 }
692
Start()693 void CSound::Start()
694 {
695 // register the callback function for input and output
696 AudioDeviceCreateIOProcID ( audioInputDevice[lCurDev], callbackIO, this, &audioInputProcID );
697
698 AudioDeviceCreateIOProcID ( audioOutputDevice[lCurDev], callbackIO, this, &audioOutputProcID );
699
700 // start the audio stream
701 AudioDeviceStart ( audioInputDevice[lCurDev], audioInputProcID );
702 AudioDeviceStart ( audioOutputDevice[lCurDev], audioOutputProcID );
703
704 // call base class
705 CSoundBase::Start();
706 }
707
Stop()708 void CSound::Stop()
709 {
710 // stop the audio stream
711 AudioDeviceStop ( audioInputDevice[lCurDev], audioInputProcID );
712 AudioDeviceStop ( audioOutputDevice[lCurDev], audioOutputProcID );
713
714 // unregister the callback function for input and output
715 AudioDeviceDestroyIOProcID ( audioInputDevice[lCurDev], audioInputProcID );
716 AudioDeviceDestroyIOProcID ( audioOutputDevice[lCurDev], audioOutputProcID );
717
718 // call base class
719 CSoundBase::Stop();
720 }
721
Init(const int iNewPrefMonoBufferSize)722 int CSound::Init ( const int iNewPrefMonoBufferSize )
723 {
724 UInt32 iActualMonoBufferSize;
725
726 // Error message string: in case buffer sizes on input and output cannot be
727 // set to the same value
728 const QString strErrBufSize = tr ( "The buffer sizes of the current "
729 "input and output audio device can't be set to a common value. Please "
730 "select different input/output devices in your system settings." );
731
732 // try to set input buffer size
733 iActualMonoBufferSize = SetBufferSize ( audioInputDevice[lCurDev], true, iNewPrefMonoBufferSize );
734
735 if ( iActualMonoBufferSize != static_cast<UInt32> ( iNewPrefMonoBufferSize ) )
736 {
737 // try to set the input buffer size to the output so that we
738 // have a matching pair
739 if ( SetBufferSize ( audioOutputDevice[lCurDev], false, iActualMonoBufferSize ) != iActualMonoBufferSize )
740 {
741 throw CGenErr ( strErrBufSize );
742 }
743 }
744 else
745 {
746 // try to set output buffer size
747 if ( SetBufferSize ( audioOutputDevice[lCurDev], false, iNewPrefMonoBufferSize ) != static_cast<UInt32> ( iNewPrefMonoBufferSize ) )
748 {
749 throw CGenErr ( strErrBufSize );
750 }
751 }
752
753 // store buffer size
754 iCoreAudioBufferSizeMono = iActualMonoBufferSize;
755
756 // init base class
757 CSoundBase::Init ( iCoreAudioBufferSizeMono );
758
759 // set internal buffer size value and calculate stereo buffer size
760 iCoreAudioBufferSizeStereo = 2 * iCoreAudioBufferSizeMono;
761
762 // create memory for intermediate audio buffer
763 vecsTmpAudioSndCrdStereo.Init ( iCoreAudioBufferSizeStereo );
764
765 return iCoreAudioBufferSizeMono;
766 }
767
SetBufferSize(AudioDeviceID & audioDeviceID,const bool bIsInput,UInt32 iPrefBufferSize)768 UInt32 CSound::SetBufferSize ( AudioDeviceID& audioDeviceID, const bool bIsInput, UInt32 iPrefBufferSize )
769 {
770 AudioObjectPropertyAddress stPropertyAddress;
771 stPropertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize;
772
773 if ( bIsInput )
774 {
775 stPropertyAddress.mScope = kAudioDevicePropertyScopeInput;
776 }
777 else
778 {
779 stPropertyAddress.mScope = kAudioDevicePropertyScopeOutput;
780 }
781
782 stPropertyAddress.mElement = kAudioObjectPropertyElementMaster;
783
784 // first set the value
785 UInt32 iSizeBufValue = sizeof ( UInt32 );
786
787 AudioObjectSetPropertyData ( audioDeviceID, &stPropertyAddress, 0, NULL, iSizeBufValue, &iPrefBufferSize );
788
789 // read back which value is actually used
790 UInt32 iActualMonoBufferSize = 0;
791
792 AudioObjectGetPropertyData ( audioDeviceID, &stPropertyAddress, 0, NULL, &iSizeBufValue, &iActualMonoBufferSize );
793
794 return iActualMonoBufferSize;
795 }
796
deviceNotification(AudioDeviceID,UInt32,const AudioObjectPropertyAddress * inAddresses,void * inRefCon)797 OSStatus CSound::deviceNotification ( AudioDeviceID, UInt32, const AudioObjectPropertyAddress* inAddresses, void* inRefCon )
798 {
799 CSound* pSound = static_cast<CSound*> ( inRefCon );
800
801 if ( ( inAddresses->mSelector == kAudioDevicePropertyDeviceHasChanged ) || ( inAddresses->mSelector == kAudioDevicePropertyDeviceIsAlive ) ||
802 ( inAddresses->mSelector == kAudioHardwarePropertyDefaultOutputDevice ) ||
803 ( inAddresses->mSelector == kAudioHardwarePropertyDefaultInputDevice ) )
804 {
805 if ( ( inAddresses->mSelector == kAudioDevicePropertyDeviceHasChanged ) || ( inAddresses->mSelector == kAudioDevicePropertyDeviceIsAlive ) )
806 {
807 // if any property of the device has changed, do a full reload
808 pSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT );
809 }
810 else
811 {
812 // for any other change in audio devices, just initiate a restart which
813 // triggers an update of the sound device selection combo box
814 pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART );
815 }
816 }
817
818 return noErr;
819 }
820
callbackIO(AudioDeviceID inDevice,const AudioTimeStamp *,const AudioBufferList * inInputData,const AudioTimeStamp *,AudioBufferList * outOutputData,const AudioTimeStamp *,void * inRefCon)821 OSStatus CSound::callbackIO ( AudioDeviceID inDevice,
822 const AudioTimeStamp*,
823 const AudioBufferList* inInputData,
824 const AudioTimeStamp*,
825 AudioBufferList* outOutputData,
826 const AudioTimeStamp*,
827 void* inRefCon )
828 {
829 CSound* pSound = static_cast<CSound*> ( inRefCon );
830
831 // both, the input and output device use the same callback function
832 QMutexLocker locker ( &pSound->MutexAudioProcessCallback );
833
834 const int iCoreAudioBufferSizeMono = pSound->iCoreAudioBufferSizeMono;
835 const int iSelInBufferLeft = pSound->iSelInBufferLeft;
836 const int iSelInBufferRight = pSound->iSelInBufferRight;
837 const int iSelInInterlChLeft = pSound->iSelInInterlChLeft;
838 const int iSelInInterlChRight = pSound->iSelInInterlChRight;
839 const int iSelAddInBufferLeft = pSound->iSelAddInBufferLeft;
840 const int iSelAddInBufferRight = pSound->iSelAddInBufferRight;
841 const int iSelAddInInterlChLeft = pSound->iSelAddInInterlChLeft;
842 const int iSelAddInInterlChRight = pSound->iSelAddInInterlChRight;
843 const int iSelOutBufferLeft = pSound->iSelOutBufferLeft;
844 const int iSelOutBufferRight = pSound->iSelOutBufferRight;
845 const int iSelOutInterlChLeft = pSound->iSelOutInterlChLeft;
846 const int iSelOutInterlChRight = pSound->iSelOutInterlChRight;
847 const CVector<int>& vecNumInBufChan = pSound->vecNumInBufChan;
848 const CVector<int>& vecNumOutBufChan = pSound->vecNumOutBufChan;
849
850 if ( ( inDevice == pSound->CurrentAudioInputDeviceID ) && inInputData && pSound->bRun )
851 {
852 // check sizes (note that float32 has four bytes)
853 if ( ( iSelInBufferLeft >= 0 ) && ( iSelInBufferLeft < static_cast<int> ( inInputData->mNumberBuffers ) ) && ( iSelInBufferRight >= 0 ) &&
854 ( iSelInBufferRight < static_cast<int> ( inInputData->mNumberBuffers ) ) &&
855 ( iSelAddInBufferLeft < static_cast<int> ( inInputData->mNumberBuffers ) ) &&
856 ( iSelAddInBufferRight < static_cast<int> ( inInputData->mNumberBuffers ) ) &&
857 ( inInputData->mBuffers[iSelInBufferLeft].mDataByteSize ==
858 static_cast<UInt32> ( vecNumInBufChan[iSelInBufferLeft] * iCoreAudioBufferSizeMono * 4 ) ) &&
859 ( inInputData->mBuffers[iSelInBufferRight].mDataByteSize ==
860 static_cast<UInt32> ( vecNumInBufChan[iSelInBufferRight] * iCoreAudioBufferSizeMono * 4 ) ) )
861 {
862 Float32* pLeftData = static_cast<Float32*> ( inInputData->mBuffers[iSelInBufferLeft].mData );
863 Float32* pRightData = static_cast<Float32*> ( inInputData->mBuffers[iSelInBufferRight].mData );
864 int iNumChanPerFrameLeft = vecNumInBufChan[iSelInBufferLeft];
865 int iNumChanPerFrameRight = vecNumInBufChan[iSelInBufferRight];
866
867 // copy input data
868 for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ )
869 {
870 // copy left and right channels separately
871 pSound->vecsTmpAudioSndCrdStereo[2 * i] = (short) ( pLeftData[iNumChanPerFrameLeft * i + iSelInInterlChLeft] * _MAXSHORT );
872 pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = (short) ( pRightData[iNumChanPerFrameRight * i + iSelInInterlChRight] * _MAXSHORT );
873 }
874
875 // add an additional optional channel
876 if ( iSelAddInBufferLeft >= 0 )
877 {
878 pLeftData = static_cast<Float32*> ( inInputData->mBuffers[iSelAddInBufferLeft].mData );
879 iNumChanPerFrameLeft = vecNumInBufChan[iSelAddInBufferLeft];
880
881 for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ )
882 {
883 pSound->vecsTmpAudioSndCrdStereo[2 * i] = Float2Short ( pSound->vecsTmpAudioSndCrdStereo[2 * i] +
884 pLeftData[iNumChanPerFrameLeft * i + iSelAddInInterlChLeft] * _MAXSHORT );
885 }
886 }
887
888 if ( iSelAddInBufferRight >= 0 )
889 {
890 pRightData = static_cast<Float32*> ( inInputData->mBuffers[iSelAddInBufferRight].mData );
891 iNumChanPerFrameRight = vecNumInBufChan[iSelAddInBufferRight];
892
893 for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ )
894 {
895 pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = Float2Short (
896 pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] + pRightData[iNumChanPerFrameRight * i + iSelAddInInterlChRight] * _MAXSHORT );
897 }
898 }
899 }
900 else
901 {
902 // incompatible sizes, clear work buffer
903 pSound->vecsTmpAudioSndCrdStereo.Reset ( 0 );
904 }
905
906 // call processing callback function
907 pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo );
908 }
909
910 if ( ( inDevice == pSound->CurrentAudioOutputDeviceID ) && outOutputData && pSound->bRun )
911 {
912 // check sizes (note that float32 has four bytes)
913 if ( ( iSelOutBufferLeft >= 0 ) && ( iSelOutBufferLeft < static_cast<int> ( outOutputData->mNumberBuffers ) ) &&
914 ( iSelOutBufferRight >= 0 ) && ( iSelOutBufferRight < static_cast<int> ( outOutputData->mNumberBuffers ) ) &&
915 ( outOutputData->mBuffers[iSelOutBufferLeft].mDataByteSize ==
916 static_cast<UInt32> ( vecNumOutBufChan[iSelOutBufferLeft] * iCoreAudioBufferSizeMono * 4 ) ) &&
917 ( outOutputData->mBuffers[iSelOutBufferRight].mDataByteSize ==
918 static_cast<UInt32> ( vecNumOutBufChan[iSelOutBufferRight] * iCoreAudioBufferSizeMono * 4 ) ) )
919 {
920 Float32* pLeftData = static_cast<Float32*> ( outOutputData->mBuffers[iSelOutBufferLeft].mData );
921 Float32* pRightData = static_cast<Float32*> ( outOutputData->mBuffers[iSelOutBufferRight].mData );
922 int iNumChanPerFrameLeft = vecNumOutBufChan[iSelOutBufferLeft];
923 int iNumChanPerFrameRight = vecNumOutBufChan[iSelOutBufferRight];
924
925 // copy output data
926 for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ )
927 {
928 // copy left and right channels separately
929 pLeftData[iNumChanPerFrameLeft * i + iSelOutInterlChLeft] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i] / _MAXSHORT;
930 pRightData[iNumChanPerFrameRight * i + iSelOutInterlChRight] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] / _MAXSHORT;
931 }
932 }
933 }
934
935 return kAudioHardwareNoError;
936 }
937
callbackMIDI(const MIDIPacketList * pktlist,void * refCon,void *)938 void CSound::callbackMIDI ( const MIDIPacketList* pktlist, void* refCon, void* )
939 {
940 CSound* pSound = static_cast<CSound*> ( refCon );
941
942 if ( pSound->midiInPortRef != static_cast<MIDIPortRef> ( NULL ) )
943 {
944 MIDIPacket* midiPacket = const_cast<MIDIPacket*> ( pktlist->packet );
945
946 for ( unsigned int j = 0; j < pktlist->numPackets; j++ )
947 {
948 // copy packet and send it to the MIDI parser
949 CVector<uint8_t> vMIDIPaketBytes ( midiPacket->length );
950 for ( int i = 0; i < midiPacket->length; i++ )
951 {
952 vMIDIPaketBytes[i] = static_cast<uint8_t> ( midiPacket->data[i] );
953 }
954 pSound->ParseMIDIMessage ( vMIDIPaketBytes );
955
956 midiPacket = MIDIPacketNext ( midiPacket );
957 }
958 }
959 }
960
ConvertCFStringToQString(const CFStringRef stringRef,QString & sOut)961 bool CSound::ConvertCFStringToQString ( const CFStringRef stringRef, QString& sOut )
962 {
963 // check if the string reference is a valid pointer
964 if ( stringRef != NULL )
965 {
966 // first check if the string is not empty
967 if ( CFStringGetLength ( stringRef ) > 0 )
968 {
969 // convert CFString in c-string (quick hack!) and then in QString
970 char* sC_strPropValue = (char*) malloc ( CFStringGetLength ( stringRef ) * 3 + 1 );
971
972 if ( CFStringGetCString ( stringRef, sC_strPropValue, CFStringGetLength ( stringRef ) * 3 + 1, kCFStringEncodingUTF8 ) )
973 {
974 sOut = sC_strPropValue;
975 free ( sC_strPropValue );
976
977 return true; // OK
978 }
979 }
980
981 // release the string reference because it is not needed anymore
982 CFRelease ( stringRef );
983 }
984
985 return false; // not OK
986 }
987