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