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