1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 AudioIOBase.cpp
6 
7 Paul Licameli split from AudioIO.cpp
8 
9 **********************************************************************/
10 
11 
12 #include "AudioIOBase.h"
13 
14 #include <wx/log.h>
15 #include <wx/sstream.h>
16 #include <wx/txtstrm.h>
17 
18 #include "Meter.h"
19 #include "Prefs.h"
20 
21 #if USE_PORTMIXER
22 #include "portmixer.h"
23 #endif
24 
25 int AudioIOBase::mCachedPlaybackIndex = -1;
26 std::vector<long> AudioIOBase::mCachedPlaybackRates;
27 int AudioIOBase::mCachedCaptureIndex = -1;
28 std::vector<long> AudioIOBase::mCachedCaptureRates;
29 std::vector<long> AudioIOBase::mCachedSampleRates;
30 double AudioIOBase::mCachedBestRateIn = 0.0;
31 
32 const int AudioIOBase::StandardRates[] = {
33    8000,
34    11025,
35    16000,
36    22050,
37    32000,
38    44100,
39    48000,
40    88200,
41    96000,
42    176400,
43    192000,
44    352800,
45    384000
46 };
47 
48 const int AudioIOBase::NumStandardRates = WXSIZEOF(AudioIOBase::StandardRates);
49 
50 const int AudioIOBase::RatesToTry[] = {
51    8000,
52    9600,
53    11025,
54    12000,
55    15000,
56    16000,
57    22050,
58    24000,
59    32000,
60    44100,
61    48000,
62    88200,
63    96000,
64    176400,
65    192000,
66    352800,
67    384000
68 };
69 const int AudioIOBase::NumRatesToTry = WXSIZEOF(AudioIOBase::RatesToTry);
70 
DeviceName(const PaDeviceInfo * info)71 wxString AudioIOBase::DeviceName(const PaDeviceInfo* info)
72 {
73    wxString infoName = wxSafeConvertMB2WX(info->name);
74 
75    return infoName;
76 }
77 
HostName(const PaDeviceInfo * info)78 wxString AudioIOBase::HostName(const PaDeviceInfo* info)
79 {
80    wxString hostapiName = wxSafeConvertMB2WX(Pa_GetHostApiInfo(info->hostApi)->name);
81 
82    return hostapiName;
83 }
84 
85 std::unique_ptr<AudioIOBase> AudioIOBase::ugAudioIO;
86 
87 AudioIOExtBase::~AudioIOExtBase() = default;
88 
Get()89 AudioIOBase *AudioIOBase::Get()
90 {
91    return ugAudioIO.get();
92 }
93 
94 AudioIOBase::AudioIOBase() = default;
95 
96 AudioIOBase::~AudioIOBase() = default;
97 
SetMixer(int inputSource)98 void AudioIOBase::SetMixer(int inputSource)
99 {
100 #if defined(USE_PORTMIXER)
101    int oldRecordSource = Px_GetCurrentInputSource(mPortMixer);
102    if ( inputSource != oldRecordSource )
103          Px_SetCurrentInputSource(mPortMixer, inputSource);
104 #endif
105 }
106 
HandleDeviceChange()107 void AudioIOBase::HandleDeviceChange()
108 {
109    // This should not happen, but it would screw things up if it did.
110    // Vaughan, 2010-10-08: But it *did* happen, due to a bug, and nobody
111    // caught it because this method just returned. Added wxASSERT().
112    wxASSERT(!IsStreamActive());
113    if (IsStreamActive())
114       return;
115 
116    // get the selected record and playback devices
117    const int playDeviceNum = getPlayDevIndex();
118    const int recDeviceNum = getRecordDevIndex();
119 
120    // If no change needed, return
121    if (mCachedPlaybackIndex == playDeviceNum &&
122        mCachedCaptureIndex == recDeviceNum)
123        return;
124 
125    // cache playback/capture rates
126    mCachedPlaybackRates = GetSupportedPlaybackRates(playDeviceNum);
127    mCachedCaptureRates = GetSupportedCaptureRates(recDeviceNum);
128    mCachedSampleRates = GetSupportedSampleRates(playDeviceNum, recDeviceNum);
129    mCachedPlaybackIndex = playDeviceNum;
130    mCachedCaptureIndex = recDeviceNum;
131    mCachedBestRateIn = 0.0;
132 
133 #if defined(USE_PORTMIXER)
134 
135    // if we have a PortMixer object, close it down
136    if (mPortMixer) {
137       #if __WXMAC__
138       // on the Mac we must make sure that we restore the hardware playthrough
139       // state of the sound device to what it was before, because there isn't
140       // a UI for this (!)
141       if (Px_SupportsPlaythrough(mPortMixer) && mPreviousHWPlaythrough >= 0.0)
142          Px_SetPlaythrough(mPortMixer, mPreviousHWPlaythrough);
143          mPreviousHWPlaythrough = -1.0;
144       #endif
145       Px_CloseMixer(mPortMixer);
146       mPortMixer = NULL;
147    }
148 
149    // that might have given us no rates whatsoever, so we have to guess an
150    // answer to do the next bit
151    int numrates = mCachedSampleRates.size();
152    int highestSampleRate;
153    if (numrates > 0)
154    {
155       highestSampleRate = mCachedSampleRates[numrates - 1];
156    }
157    else
158    {  // we don't actually have any rates that work for Rec and Play. Guess one
159       // to use for messing with the mixer, which doesn't actually do either
160       highestSampleRate = 44100;
161       // mCachedSampleRates is still empty, but it's not used again, so
162       // can ignore
163    }
164 
165    mInputMixerWorks = false;
166 
167    int error;
168    // This tries to open the device with the samplerate worked out above, which
169    // will be the highest available for play and record on the device, or
170    // 44.1kHz if the info cannot be fetched.
171 
172    PaStream *stream;
173 
174    PaStreamParameters playbackParameters;
175 
176    playbackParameters.device = playDeviceNum;
177    playbackParameters.sampleFormat = paFloat32;
178    playbackParameters.hostApiSpecificStreamInfo = NULL;
179    playbackParameters.channelCount = 1;
180    if (Pa_GetDeviceInfo(playDeviceNum))
181       playbackParameters.suggestedLatency =
182          Pa_GetDeviceInfo(playDeviceNum)->defaultLowOutputLatency;
183    else
184       playbackParameters.suggestedLatency =
185          AudioIOLatencyCorrection.GetDefault()/1000.0;
186 
187    PaStreamParameters captureParameters;
188 
189    captureParameters.device = recDeviceNum;
190    captureParameters.sampleFormat = paFloat32;;
191    captureParameters.hostApiSpecificStreamInfo = NULL;
192    captureParameters.channelCount = 1;
193    if (Pa_GetDeviceInfo(recDeviceNum))
194       captureParameters.suggestedLatency =
195          Pa_GetDeviceInfo(recDeviceNum)->defaultLowInputLatency;
196    else
197       captureParameters.suggestedLatency =
198          AudioIOLatencyCorrection.GetDefault()/1000.0;
199 
200    // try opening for record and playback
201    // Not really doing I/O so pass nullptr for the callback function
202    error = Pa_OpenStream(&stream,
203                          &captureParameters, &playbackParameters,
204                          highestSampleRate, paFramesPerBufferUnspecified,
205                          paClipOff | paDitherOff,
206                          nullptr, NULL);
207 
208    if (!error) {
209       // Try portmixer for this stream
210       mPortMixer = Px_OpenMixer(stream, recDeviceNum, playDeviceNum, 0);
211       if (!mPortMixer) {
212          Pa_CloseStream(stream);
213          error = true;
214       }
215    }
216 
217    // if that failed, try just for record
218    if( error ) {
219       error = Pa_OpenStream(&stream,
220                             &captureParameters, NULL,
221                             highestSampleRate, paFramesPerBufferUnspecified,
222                             paClipOff | paDitherOff,
223                             nullptr, NULL);
224 
225       if (!error) {
226          mPortMixer = Px_OpenMixer(stream, recDeviceNum, playDeviceNum, 0);
227          if (!mPortMixer) {
228             Pa_CloseStream(stream);
229             error = true;
230          }
231       }
232    }
233 
234    // finally, try just for playback
235    if ( error ) {
236       error = Pa_OpenStream(&stream,
237                             NULL, &playbackParameters,
238                             highestSampleRate, paFramesPerBufferUnspecified,
239                             paClipOff | paDitherOff,
240                             nullptr, NULL);
241 
242       if (!error) {
243          mPortMixer = Px_OpenMixer(stream, recDeviceNum, playDeviceNum, 0);
244          if (!mPortMixer) {
245             Pa_CloseStream(stream);
246             error = true;
247          }
248       }
249    }
250 
251    // FIXME: TRAP_ERR errors in HandleDeviceChange not reported.
252    // if it's still not working, give up
253    if( error )
254       return;
255 
256    // Set input source
257 #if USE_PORTMIXER
258    auto sourceIndex = AudioIORecordingSourceIndex.Read(); // defaults to -1
259    if (sourceIndex >= 0) {
260       //the current index of our source may be different because the stream
261       //is a combination of two devices, so update it.
262       sourceIndex = getRecordSourceIndex(mPortMixer);
263       if (sourceIndex >= 0)
264          SetMixer(sourceIndex);
265    }
266 #endif
267 
268    // Determine mixer capabilities - if it doesn't support control of output
269    // signal level, we emulate it (by multiplying this value by all outgoing
270    // samples)
271 
272    float inputVol = Px_GetInputVolume(mPortMixer);
273    mInputMixerWorks = true;   // assume it works unless proved wrong
274    Px_SetInputVolume(mPortMixer, 0.0);
275    if (Px_GetInputVolume(mPortMixer) > 0.1)
276       mInputMixerWorks = false;  // can't set to zero
277    Px_SetInputVolume(mPortMixer, 0.2f);
278    if (Px_GetInputVolume(mPortMixer) < 0.1 ||
279        Px_GetInputVolume(mPortMixer) > 0.3)
280       mInputMixerWorks = false;  // can't set level accurately
281    Px_SetInputVolume(mPortMixer, inputVol);
282 
283    Pa_CloseStream(stream);
284 
285 
286    #if 0
287    wxPrintf("PortMixer: Recording: %s\n"
288           mInputMixerWorks? "hardware": "no control");
289    #endif
290 #endif   // USE_PORTMIXER
291 }
292 
SetCaptureMeter(const std::shared_ptr<AudacityProject> & project,const std::weak_ptr<Meter> & wMeter)293 void AudioIOBase::SetCaptureMeter(
294    const std::shared_ptr<AudacityProject> &project, const std::weak_ptr<Meter> &wMeter)
295 {
296    if (auto pOwningProject = mOwningProject.lock();
297        ( pOwningProject ) && ( pOwningProject != project))
298       return;
299 
300    auto meter = wMeter.lock();
301    if (meter)
302    {
303       mInputMeter = meter;
304       meter->Reset(mRate, true);
305    }
306    else
307       mInputMeter.reset();
308 }
309 
SetPlaybackMeter(const std::shared_ptr<AudacityProject> & project,const std::weak_ptr<Meter> & wMeter)310 void AudioIOBase::SetPlaybackMeter(
311    const std::shared_ptr<AudacityProject> &project, const std::weak_ptr<Meter> &wMeter)
312 {
313    if (auto pOwningProject = mOwningProject.lock();
314        ( pOwningProject ) && ( pOwningProject != project))
315       return;
316 
317    auto meter = wMeter.lock();
318    if (meter)
319    {
320       mOutputMeter = meter;
321       meter->Reset(mRate, true);
322    }
323    else
324       mOutputMeter.reset();
325 }
326 
IsPaused() const327 bool AudioIOBase::IsPaused() const
328 {
329    return mPaused;
330 }
331 
IsBusy() const332 bool AudioIOBase::IsBusy() const
333 {
334    if (mStreamToken != 0)
335       return true;
336 
337    return false;
338 }
339 
IsStreamActive() const340 bool AudioIOBase::IsStreamActive() const
341 {
342    bool isActive = false;
343    // JKC: Not reporting any Pa error, but that looks OK.
344    if( mPortStreamV19 )
345       isActive = (Pa_IsStreamActive( mPortStreamV19 ) > 0);
346 
347    isActive = isActive ||
348       std::any_of(mAudioIOExt.begin(), mAudioIOExt.end(),
349          [](auto &pExt){ return pExt && pExt->IsOtherStreamActive(); });
350    return isActive;
351 }
352 
IsStreamActive(int token) const353 bool AudioIOBase::IsStreamActive(int token) const
354 {
355    return (this->IsStreamActive() && this->IsAudioTokenActive(token));
356 }
357 
IsAudioTokenActive(int token) const358 bool AudioIOBase::IsAudioTokenActive(int token) const
359 {
360    return ( token > 0 && token == mStreamToken );
361 }
362 
IsMonitoring() const363 bool AudioIOBase::IsMonitoring() const
364 {
365    return ( mPortStreamV19 && mStreamToken==0 );
366 }
367 
GetSupportedPlaybackRates(int devIndex,double rate)368 std::vector<long> AudioIOBase::GetSupportedPlaybackRates(int devIndex, double rate)
369 {
370    if (devIndex == -1)
371    {  // weren't given a device index, get the prefs / default one
372       devIndex = getPlayDevIndex();
373    }
374 
375    // Check if we can use the cached rates
376    if (mCachedPlaybackIndex != -1 && devIndex == mCachedPlaybackIndex
377          && (rate == 0.0 || make_iterator_range(mCachedPlaybackRates).contains(rate)))
378    {
379       return mCachedPlaybackRates;
380    }
381 
382    std::vector<long> supported;
383    int irate = (int)rate;
384    const PaDeviceInfo* devInfo = NULL;
385    int i;
386 
387    devInfo = Pa_GetDeviceInfo(devIndex);
388 
389    if (!devInfo)
390    {
391       wxLogDebug(wxT("GetSupportedPlaybackRates() Could not get device info!"));
392       return supported;
393    }
394 
395    // LLL: Remove when a proper method of determining actual supported
396    //      DirectSound rate is devised.
397    const PaHostApiInfo* hostInfo = Pa_GetHostApiInfo(devInfo->hostApi);
398    bool isDirectSound = (hostInfo && hostInfo->type == paDirectSound);
399 
400    PaStreamParameters pars;
401 
402    pars.device = devIndex;
403    pars.channelCount = 1;
404    pars.sampleFormat = paFloat32;
405    pars.suggestedLatency = devInfo->defaultHighOutputLatency;
406    pars.hostApiSpecificStreamInfo = NULL;
407 
408    // JKC: PortAudio Errors handled OK here.  No need to report them
409    for (i = 0; i < NumRatesToTry; i++)
410    {
411       // LLL: Remove when a proper method of determining actual supported
412       //      DirectSound rate is devised.
413       if (!(isDirectSound && RatesToTry[i] > 200000)){
414          if (Pa_IsFormatSupported(NULL, &pars, RatesToTry[i]) == 0)
415             supported.push_back(RatesToTry[i]);
416          Pa_Sleep( 10 );// There are ALSA drivers that don't like being probed
417          // too quickly.
418       }
419    }
420 
421    if (irate != 0 && !make_iterator_range(supported).contains(irate))
422    {
423       // LLL: Remove when a proper method of determining actual supported
424       //      DirectSound rate is devised.
425       if (!(isDirectSound && RatesToTry[i] > 200000))
426          if (Pa_IsFormatSupported(NULL, &pars, irate) == 0)
427             supported.push_back(irate);
428    }
429 
430    return supported;
431 }
432 
GetSupportedCaptureRates(int devIndex,double rate)433 std::vector<long> AudioIOBase::GetSupportedCaptureRates(int devIndex, double rate)
434 {
435    if (devIndex == -1)
436    {  // not given a device, look up in prefs / default
437       devIndex = getRecordDevIndex();
438    }
439 
440    // Check if we can use the cached rates
441    if (mCachedCaptureIndex != -1 && devIndex == mCachedCaptureIndex
442          && (rate == 0.0 || make_iterator_range(mCachedCaptureRates).contains(rate)))
443    {
444       return mCachedCaptureRates;
445    }
446 
447    std::vector<long> supported;
448    int irate = (int)rate;
449    const PaDeviceInfo* devInfo = NULL;
450    int i;
451 
452    devInfo = Pa_GetDeviceInfo(devIndex);
453 
454    if (!devInfo)
455    {
456       wxLogDebug(wxT("GetSupportedCaptureRates() Could not get device info!"));
457       return supported;
458    }
459 
460    auto latencyDuration = AudioIOLatencyDuration.Read();
461    // Why not defaulting to 2 as elsewhere?
462    auto recordChannels = AudioIORecordChannels.ReadWithDefault(1);
463 
464    // LLL: Remove when a proper method of determining actual supported
465    //      DirectSound rate is devised.
466    const PaHostApiInfo* hostInfo = Pa_GetHostApiInfo(devInfo->hostApi);
467    bool isDirectSound = (hostInfo && hostInfo->type == paDirectSound);
468 
469    PaStreamParameters pars;
470 
471    pars.device = devIndex;
472    pars.channelCount = recordChannels;
473    pars.sampleFormat = paFloat32;
474    pars.suggestedLatency = latencyDuration / 1000.0;
475    pars.hostApiSpecificStreamInfo = NULL;
476 
477    for (i = 0; i < NumRatesToTry; i++)
478    {
479       // LLL: Remove when a proper method of determining actual supported
480       //      DirectSound rate is devised.
481       if (!(isDirectSound && RatesToTry[i] > 200000))
482       {
483          if (Pa_IsFormatSupported(&pars, NULL, RatesToTry[i]) == 0)
484             supported.push_back(RatesToTry[i]);
485          Pa_Sleep( 10 );// There are ALSA drivers that don't like being probed
486          // too quickly.
487       }
488    }
489 
490    if (irate != 0 && !make_iterator_range(supported).contains(irate))
491    {
492       // LLL: Remove when a proper method of determining actual supported
493       //      DirectSound rate is devised.
494       if (!(isDirectSound && RatesToTry[i] > 200000))
495          if (Pa_IsFormatSupported(&pars, NULL, irate) == 0)
496             supported.push_back(irate);
497    }
498 
499    return supported;
500 }
501 
GetSupportedSampleRates(int playDevice,int recDevice,double rate)502 std::vector<long> AudioIOBase::GetSupportedSampleRates(
503    int playDevice, int recDevice, double rate)
504 {
505    // Not given device indices, look up prefs
506    if (playDevice == -1) {
507       playDevice = getPlayDevIndex();
508    }
509    if (recDevice == -1) {
510       recDevice = getRecordDevIndex();
511    }
512 
513    // Check if we can use the cached rates
514    if (mCachedPlaybackIndex != -1 && mCachedCaptureIndex != -1 &&
515          playDevice == mCachedPlaybackIndex &&
516          recDevice == mCachedCaptureIndex &&
517          (rate == 0.0 || make_iterator_range(mCachedSampleRates).contains(rate)))
518    {
519       return mCachedSampleRates;
520    }
521 
522    auto playback = GetSupportedPlaybackRates(playDevice, rate);
523    auto capture = GetSupportedCaptureRates(recDevice, rate);
524    int i;
525 
526    // Return only sample rates which are in both arrays
527    std::vector<long> result;
528 
529    for (i = 0; i < (int)playback.size(); i++)
530       if (make_iterator_range(capture).contains(playback[i]))
531          result.push_back(playback[i]);
532 
533    // If this yields no results, use the default sample rates nevertheless
534 /*   if (result.empty())
535    {
536       for (i = 0; i < NumStandardRates; i++)
537          result.push_back(StandardRates[i]);
538    }*/
539 
540    return result;
541 }
542 
543 /** \todo: should this take into account PortAudio's value for
544  * PaDeviceInfo::defaultSampleRate? In principal this should let us work out
545  * which rates are "real" and which resampled in the drivers, and so prefer
546  * the real rates. */
GetOptimalSupportedSampleRate()547 int AudioIOBase::GetOptimalSupportedSampleRate()
548 {
549    auto rates = GetSupportedSampleRates();
550 
551    if (make_iterator_range(rates).contains(44100))
552       return 44100;
553 
554    if (make_iterator_range(rates).contains(48000))
555       return 48000;
556 
557    // if there are no supported rates, the next bit crashes. So check first,
558    // and give them a "sensible" value if there are no valid values. They
559    // will still get an error later, but with any luck may have changed
560    // something by then. It's no worse than having an invalid default rate
561    // stored in the preferences, which we don't check for
562    if (rates.empty()) return 44100;
563 
564    return rates.back();
565 }
566 
567 #if USE_PORTMIXER
getRecordSourceIndex(PxMixer * portMixer)568 int AudioIOBase::getRecordSourceIndex(PxMixer *portMixer)
569 {
570    int i;
571    auto sourceName = AudioIORecordingSource.Read();
572    int numSources = Px_GetNumInputSources(portMixer);
573    for (i = 0; i < numSources; i++) {
574       if (sourceName == wxString(wxSafeConvertMB2WX(Px_GetInputSourceName(portMixer, i))))
575          return i;
576    }
577    return -1;
578 }
579 #endif
580 
getPlayDevIndex(const wxString & devNameArg)581 int AudioIOBase::getPlayDevIndex(const wxString &devNameArg)
582 {
583    wxString devName(devNameArg);
584    // if we don't get given a device, look up the preferences
585    if (devName.empty())
586       devName = AudioIOPlaybackDevice.Read();
587 
588    auto hostName = AudioIOHost.Read();
589    PaHostApiIndex hostCnt = Pa_GetHostApiCount();
590    PaHostApiIndex hostNum;
591    for (hostNum = 0; hostNum < hostCnt; hostNum++)
592    {
593       const PaHostApiInfo *hinfo = Pa_GetHostApiInfo(hostNum);
594       if (hinfo && wxString(wxSafeConvertMB2WX(hinfo->name)) == hostName)
595       {
596          for (PaDeviceIndex hostDevice = 0; hostDevice < hinfo->deviceCount; hostDevice++)
597          {
598             PaDeviceIndex deviceNum = Pa_HostApiDeviceIndexToDeviceIndex(hostNum, hostDevice);
599 
600             const PaDeviceInfo *dinfo = Pa_GetDeviceInfo(deviceNum);
601             if (dinfo && DeviceName(dinfo) == devName && dinfo->maxOutputChannels > 0 )
602             {
603                // this device name matches the stored one, and works.
604                // So we say this is the answer and return it
605                return deviceNum;
606             }
607          }
608 
609          // The device wasn't found so use the default for this host.
610          // LL:  At this point, preferences and active no longer match.
611          return hinfo->defaultOutputDevice;
612       }
613    }
614 
615    // The host wasn't found, so use the default output device.
616    // FIXME: TRAP_ERR PaErrorCode not handled well (this code is similar to input code
617    // and the input side has more comments.)
618 
619    PaDeviceIndex deviceNum = Pa_GetDefaultOutputDevice();
620 
621    // Sometimes PortAudio returns -1 if it cannot find a suitable default
622    // device, so we just use the first one available
623    //
624    // LL:  At this point, preferences and active no longer match
625    //
626    //      And I can't imagine how far we'll get specifying an "invalid" index later
627    //      on...are we certain "0" even exists?
628    if (deviceNum < 0) {
629       wxASSERT(false);
630       deviceNum = 0;
631    }
632 
633    return deviceNum;
634 }
635 
getRecordDevIndex(const wxString & devNameArg)636 int AudioIOBase::getRecordDevIndex(const wxString &devNameArg)
637 {
638    wxString devName(devNameArg);
639    // if we don't get given a device, look up the preferences
640    if (devName.empty())
641       devName = AudioIORecordingDevice.Read();
642 
643    auto hostName = AudioIOHost.Read();
644    PaHostApiIndex hostCnt = Pa_GetHostApiCount();
645    PaHostApiIndex hostNum;
646    for (hostNum = 0; hostNum < hostCnt; hostNum++)
647    {
648       const PaHostApiInfo *hinfo = Pa_GetHostApiInfo(hostNum);
649       if (hinfo && wxString(wxSafeConvertMB2WX(hinfo->name)) == hostName)
650       {
651          for (PaDeviceIndex hostDevice = 0; hostDevice < hinfo->deviceCount; hostDevice++)
652          {
653             PaDeviceIndex deviceNum = Pa_HostApiDeviceIndexToDeviceIndex(hostNum, hostDevice);
654 
655             const PaDeviceInfo *dinfo = Pa_GetDeviceInfo(deviceNum);
656             if (dinfo && DeviceName(dinfo) == devName && dinfo->maxInputChannels > 0 )
657             {
658                // this device name matches the stored one, and works.
659                // So we say this is the answer and return it
660                return deviceNum;
661             }
662          }
663 
664          // The device wasn't found so use the default for this host.
665          // LL:  At this point, preferences and active no longer match.
666          return hinfo->defaultInputDevice;
667       }
668    }
669 
670    // The host wasn't found, so use the default input device.
671    // FIXME: TRAP_ERR PaErrorCode not handled well in getRecordDevIndex()
672    PaDeviceIndex deviceNum = Pa_GetDefaultInputDevice();
673 
674    // Sometimes PortAudio returns -1 if it cannot find a suitable default
675    // device, so we just use the first one available
676    // PortAudio has an error reporting function.  We should log/report the error?
677    //
678    // LL:  At this point, preferences and active no longer match
679    //
680    //      And I can't imagine how far we'll get specifying an "invalid" index later
681    //      on...are we certain "0" even exists?
682    if (deviceNum < 0) {
683       // JKC: This ASSERT will happen if you run with no config file
684       // This happens once.  Config file will exist on the next run.
685       // TODO: Look into this a bit more.  Could be relevant to blank Device Toolbar.
686       wxASSERT(false);
687       deviceNum = 0;
688    }
689 
690    return deviceNum;
691 }
692 
GetDeviceInfo() const693 wxString AudioIOBase::GetDeviceInfo() const
694 {
695    wxStringOutputStream o;
696    wxTextOutputStream s(o, wxEOL_UNIX);
697 
698    if (IsStreamActive()) {
699       return XO("Stream is active ... unable to gather information.\n")
700          .Translation();
701    }
702 
703 
704    // FIXME: TRAP_ERR PaErrorCode not handled.  3 instances in GetDeviceInfo().
705    int recDeviceNum = Pa_GetDefaultInputDevice();
706    int playDeviceNum = Pa_GetDefaultOutputDevice();
707    int cnt = Pa_GetDeviceCount();
708 
709    // PRL:  why only into the log?
710    wxLogDebug(wxT("Portaudio reports %d audio devices"),cnt);
711 
712    s << wxT("==============================\n");
713    s << XO("Default recording device number: %d\n").Format( recDeviceNum );
714    s << XO("Default playback device number: %d\n").Format( playDeviceNum);
715 
716    auto recDevice = AudioIORecordingDevice.Read();
717    auto playDevice = AudioIOPlaybackDevice.Read();
718    int j;
719 
720    // This gets info on all available audio devices (input and output)
721    if (cnt <= 0) {
722       s << XO("No devices found\n");
723       return o.GetString();
724    }
725 
726    const PaDeviceInfo* info;
727 
728    for (j = 0; j < cnt; j++) {
729       s << wxT("==============================\n");
730 
731       info = Pa_GetDeviceInfo(j);
732       if (!info) {
733          s << XO("Device info unavailable for: %d\n").Format( j );
734          continue;
735       }
736 
737       wxString name = DeviceName(info);
738       s << XO("Device ID: %d\n").Format( j );
739       s << XO("Device name: %s\n").Format( name );
740       s << XO("Host name: %s\n").Format( HostName(info) );
741       s << XO("Recording channels: %d\n").Format( info->maxInputChannels );
742       s << XO("Playback channels: %d\n").Format( info->maxOutputChannels );
743       s << XO("Low Recording Latency: %g\n").Format( info->defaultLowInputLatency );
744       s << XO("Low Playback Latency: %g\n").Format( info->defaultLowOutputLatency );
745       s << XO("High Recording Latency: %g\n").Format( info->defaultHighInputLatency );
746       s << XO("High Playback Latency: %g\n").Format( info->defaultHighOutputLatency );
747 
748       auto rates = GetSupportedPlaybackRates(j, 0.0);
749 
750       /* i18n-hint: Supported, meaning made available by the system */
751       s << XO("Supported Rates:\n");
752       for (int k = 0; k < (int) rates.size(); k++) {
753          s << wxT("    ") << (int)rates[k] << wxT("\n");
754       }
755 
756       if (name == playDevice && info->maxOutputChannels > 0)
757          playDeviceNum = j;
758 
759       if (name == recDevice && info->maxInputChannels > 0)
760          recDeviceNum = j;
761 
762       // Sometimes PortAudio returns -1 if it cannot find a suitable default
763       // device, so we just use the first one available
764       if (recDeviceNum < 0 && info->maxInputChannels > 0){
765          recDeviceNum = j;
766       }
767       if (playDeviceNum < 0 && info->maxOutputChannels > 0){
768          playDeviceNum = j;
769       }
770    }
771 
772    bool haveRecDevice = (recDeviceNum >= 0);
773    bool havePlayDevice = (playDeviceNum >= 0);
774 
775    s << wxT("==============================\n");
776    if (haveRecDevice)
777       s << XO("Selected recording device: %d - %s\n").Format( recDeviceNum, recDevice );
778    else
779       s << XO("No recording device found for '%s'.\n").Format( recDevice );
780 
781    if (havePlayDevice)
782       s << XO("Selected playback device: %d - %s\n").Format( playDeviceNum, playDevice );
783    else
784       s << XO("No playback device found for '%s'.\n").Format( playDevice );
785 
786    std::vector<long> supportedSampleRates;
787 
788    if (havePlayDevice && haveRecDevice) {
789       supportedSampleRates = GetSupportedSampleRates(playDeviceNum, recDeviceNum);
790 
791       s << XO("Supported Rates:\n");
792       for (int k = 0; k < (int) supportedSampleRates.size(); k++) {
793          s << wxT("    ") << (int)supportedSampleRates[k] << wxT("\n");
794       }
795    }
796    else {
797       s << XO("Cannot check mutual sample rates without both devices.\n");
798       return o.GetString();
799    }
800 
801 #if defined(USE_PORTMIXER)
802    if (supportedSampleRates.size() > 0)
803       {
804       int highestSampleRate = supportedSampleRates.back();
805       bool EmulateMixerInputVol = true;
806       float MixerInputVol = 1.0;
807       float MixerOutputVol = 1.0;
808 
809       int error;
810 
811       PaStream *stream;
812 
813       PaStreamParameters playbackParameters;
814 
815       playbackParameters.device = playDeviceNum;
816       playbackParameters.sampleFormat = paFloat32;
817       playbackParameters.hostApiSpecificStreamInfo = NULL;
818       playbackParameters.channelCount = 1;
819       if (Pa_GetDeviceInfo(playDeviceNum)){
820          playbackParameters.suggestedLatency =
821             Pa_GetDeviceInfo(playDeviceNum)->defaultLowOutputLatency;
822       }
823       else
824          playbackParameters.suggestedLatency =
825             AudioIOLatencyCorrection.GetDefault()/1000.0;
826 
827       PaStreamParameters captureParameters;
828 
829       captureParameters.device = recDeviceNum;
830       captureParameters.sampleFormat = paFloat32;;
831       captureParameters.hostApiSpecificStreamInfo = NULL;
832       captureParameters.channelCount = 1;
833       if (Pa_GetDeviceInfo(recDeviceNum)){
834          captureParameters.suggestedLatency =
835             Pa_GetDeviceInfo(recDeviceNum)->defaultLowInputLatency;
836       }
837       else
838          captureParameters.suggestedLatency =
839             AudioIOLatencyCorrection.GetDefault()/1000.0;
840 
841       // Not really doing I/O so pass nullptr for the callback function
842       error = Pa_OpenStream(&stream,
843                          &captureParameters, &playbackParameters,
844                          highestSampleRate, paFramesPerBufferUnspecified,
845                          paClipOff | paDitherOff,
846                          nullptr, NULL);
847 
848       if (error) {
849          error = Pa_OpenStream(&stream,
850                             &captureParameters, NULL,
851                             highestSampleRate, paFramesPerBufferUnspecified,
852                             paClipOff | paDitherOff,
853                             nullptr, NULL);
854       }
855 
856       if (error) {
857          s << XO("Received %d while opening devices\n").Format( error );
858          return o.GetString();
859       }
860 
861       PxMixer *PortMixer = Px_OpenMixer(stream, recDeviceNum, playDeviceNum, 0);
862 
863       if (!PortMixer) {
864          s << XO("Unable to open Portmixer\n");
865          Pa_CloseStream(stream);
866          return o.GetString();
867       }
868 
869       s << wxT("==============================\n");
870       s << XO("Available mixers:\n");
871 
872       // FIXME: ? PortMixer errors on query not reported in GetDeviceInfo
873       cnt = Px_GetNumMixers(stream);
874       for (int i = 0; i < cnt; i++) {
875          wxString name = wxSafeConvertMB2WX(Px_GetMixerName(stream, i));
876          s << XO("%d - %s\n").Format( i, name );
877       }
878 
879       s << wxT("==============================\n");
880       s << XO("Available recording sources:\n");
881       cnt = Px_GetNumInputSources(PortMixer);
882       for (int i = 0; i < cnt; i++) {
883          wxString name = wxSafeConvertMB2WX(Px_GetInputSourceName(PortMixer, i));
884          s << XO("%d - %s\n").Format( i, name );
885       }
886 
887       s << wxT("==============================\n");
888       s << XO("Available playback volumes:\n");
889       cnt = Px_GetNumOutputVolumes(PortMixer);
890       for (int i = 0; i < cnt; i++) {
891          wxString name = wxSafeConvertMB2WX(Px_GetOutputVolumeName(PortMixer, i));
892          s << XO("%d - %s\n").Format( i, name );
893       }
894 
895      // Check, if PortMixer supports adjusting input levels on the interface
896 
897       MixerInputVol = Px_GetInputVolume(PortMixer);
898       EmulateMixerInputVol = false;
899       Px_SetInputVolume(PortMixer, 0.0);
900       if (Px_GetInputVolume(PortMixer) > 0.1)
901          EmulateMixerInputVol = true;
902       Px_SetInputVolume(PortMixer, 0.2f);
903       if (Px_GetInputVolume(PortMixer) < 0.1 ||
904           Px_GetInputVolume(PortMixer) > 0.3)
905          EmulateMixerInputVol = true;
906       Px_SetInputVolume(PortMixer, MixerInputVol);
907 
908       Pa_CloseStream(stream);
909 
910       s << wxT("==============================\n");
911       s << ( EmulateMixerInputVol
912          ? XO("Recording volume is emulated\n")
913          : XO("Recording volume is native\n") );
914 
915       Px_CloseMixer(PortMixer);
916 
917       }  //end of massive if statement if a valid sample rate has been found
918 #endif
919    return o.GetString();
920 }
921 
GetAllDeviceInfo()922 auto AudioIOBase::GetAllDeviceInfo() -> std::vector<AudioIODiagnostics>
923 {
924    std::vector<AudioIODiagnostics> result;
925    result.push_back({
926       wxT("audiodev.txt"), GetDeviceInfo(), wxT("Audio Device Info") });
927    for( auto &pExt : mAudioIOExt )
928       if ( pExt )
929          result.emplace_back(pExt->Dump());
930    return result;
931 }
932 
933 StringSetting AudioIOHost{
934    L"/AudioIO/Host", L"" };
935 DoubleSetting AudioIOLatencyCorrection{
936    L"/AudioIO/LatencyCorrection", -130.0 };
937 DoubleSetting AudioIOLatencyDuration{
938    L"/AudioIO/LatencyDuration", 100.0 };
939 StringSetting AudioIOPlaybackDevice{
940    L"/AudioIO/PlaybackDevice", L"" };
941 DoubleSetting AudioIOPlaybackVolume {
942    L"/AudioIO/PlaybackVolume", 1.0 };
943 IntSetting AudioIORecordChannels{
944    L"/AudioIO/RecordChannels", 2 };
945 StringSetting AudioIORecordingDevice{
946    L"/AudioIO/RecordingDevice", L"" };
947 StringSetting AudioIORecordingSource{
948    L"/AudioIO/RecordingSource", L"" };
949 IntSetting AudioIORecordingSourceIndex{
950    L"/AudioIO/RecordingSourceIndex", -1 };
951