Audacity 3.2.0
DevicePrefs.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 DevicePrefs.cpp
6
7 Joshua Haberman
8 Dominic Mazzoni
9 James Crook
10
11*******************************************************************//********************************************************************/
24#include "DevicePrefs.h"
25#include "AudioIOBase.h"
26
27#include "IteratorX.h"
28#include "RecordingPrefs.h"
29
30#include <wx/defs.h>
31
32#include <wx/choice.h>
33#include <wx/combobox.h>
34#include <wx/log.h>
35#include <wx/textctrl.h>
36#include <wx/bmpbuttn.h>
37
38#include "portaudio.h"
39
40#include "Prefs.h"
41#include "ShuttleGui.h"
42#include "DeviceManager.h"
43#include "ProjectRate.h"
44
45#include "QualityPrefs.h"
46#include "QualitySettings.h"
47
48#include "AllThemeResources.h"
49#include "Theme.h"
50
51#define ID_DEFAULT_SAMPLE_RATE_CHOICE 7001
52
53enum {
54 HostID = 10000,
60};
61
62BEGIN_EVENT_TABLE(DevicePrefs, PrefsPanel)
63 EVT_CHOICE(HostID, DevicePrefs::OnHost)
68
69DevicePrefs::DevicePrefs(wxWindow * parent, wxWindowID winid, AudacityProject* project)
70: PrefsPanel(parent, winid, XO("Audio Settings"))
71, mProject(project)
72{
73 Populate();
74}
75
77{
78}
79
80
82{
84}
85
87{
88 return XO("Audio Settings");
89}
90
92{
93 return "Devices_Preferences";
94}
95
97{
98 // First any pre-processing for constructing the GUI.
100
103 ProjectRate::Get(*mProject).GetRate() :
105
106 auto it = std::find(
109
111 mSampleRateNames.size() - 1 :
112 std::distance(mSampleRateValues.begin(), it);
113
114 // Get current setting for devices
119
120 //------------------------- Main section --------------------
121 // Now construct the GUI itself.
122 // Use 'eIsCreatingFromPrefs' so that the GUI is
123 // initialised with values from gPrefs.
126 // ----------------------- End of main section --------------
127
128 wxCommandEvent e;
129 OnHost(e);
132}
133
134
135/*
136 * Get names of device hosts.
137 */
139{
140 // Gather list of hosts. Only added hosts that have devices attached.
141 // FIXME: TRAP_ERR PaErrorCode not handled in DevicePrefs GetNamesAndLabels()
142 // With an error code won't add hosts, but won't report a problem either.
143 int nDevices = Pa_GetDeviceCount();
144 for (int i = 0; i < nDevices; i++) {
145 const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
146 if ((info!=NULL)&&(info->maxOutputChannels > 0 || info->maxInputChannels > 0)) {
147 wxString name = wxSafeConvertMB2WX(Pa_GetHostApiInfo(info->hostApi)->name);
149 .contains( Verbatim( name ) )) {
150 mHostNames.push_back( Verbatim( name ) );
151 mHostLabels.push_back(name);
152 }
153 }
154 }
155
156 //------------ Sample Rate Names
157 // JKC: I don't understand the following comment.
158 // Can someone please explain or correct it?
159 // XXX: This should use a previously changed, but not yet saved
160 // sound card setting from the "I/O" preferences tab.
161 // LLL: It means that until the user clicks "Ok" in preferences, the
162 // GetSupportedSampleRates() call should use the devices they
163 // may have changed on the Audio I/O page. As coded, the sample
164 // rates it will return could be completely invalid as they will
165 // be what's supported by the devices that were selected BEFORE
166 // coming into preferences.
167 //
168 // GetSupportedSampleRates() allows passing in device names, but
169 // how do you get at them as they are on the Audio I/O page????
170 for (int i = 0; i < AudioIOBase::NumStandardRates; i++)
171 {
172 int iRate = AudioIOBase::StandardRates[i];
173 mSampleRateValues.push_back(iRate);
174 mSampleRateNames.push_back(XO("%i Hz").Format(iRate));
175 }
176
177 mSampleRateNames.push_back(XO("Other..."));
178
179 // The label for the 'Other...' case can be any value at all.
180 mSampleRateValues.push_back(
181 44100); // If chosen, this value will be overwritten
182}
183
185{
186 ChoiceSetting HostSetting{
189 };
190 S.SetBorder(2);
191 S.StartScroller();
192
193 /* i18n-hint Software interface to audio devices */
194 S.StartStatic(XC("Interface", "device"));
195 {
196 S.StartMultiColumn(2);
197 {
198 S.Id(HostID);
199 mHost = S.TieChoice( XXO("&Host:"), HostSetting);
200
201 S.AddPrompt(XXO("Using:"));
202 S.AddFixedText( Verbatim(wxSafeConvertMB2WX(Pa_GetVersionText() ) ) );
203 }
204 S.EndMultiColumn();
205 }
206 S.EndStatic();
207
208 S.StartStatic(XO("Playback"));
209 {
210 S.StartMultiColumn(2);
211 {
212 S.Id(PlayID);
213 mPlay = S.AddChoice(XXO("&Device:"),
214 {} );
215 }
216 S.EndMultiColumn();
217 }
218 S.EndStatic();
219
220 // i18n-hint: modifier as in "Recording preferences", not progressive verb
221 S.StartStatic(XC("Recording", "preference"));
222 {
223 S.StartMultiColumn(2);
224 {
225 S.Id(RecordID);
226 mRecord = S.AddChoice(XXO("De&vice:"),
227 {} );
228
229 S.Id(ChannelsID);
230 mChannels = S.AddChoice(XXO("Cha&nnels:"),
231 {} );
232 }
233 S.EndMultiColumn();
234 }
235 S.EndStatic();
236
237
238 S.StartStatic(XO("Quality"));
239 {
240 S.StartMultiColumn(2);
241 {
242 if (mProject)
243 {
244 S.AddPrompt(XXO("&Project Sample Rate:"));
245
246 S.StartMultiColumn(3);
247 {
251
252 // Now do the edit box...
254 S.TieNumericTextBox({}, mOtherProjectSampleRateValue, 15);
255
256 auto helpBtn = S.AddBitmapButton(theTheme.Bitmap(bmpHelpIcon));
257
258 const auto helpText =
259 XO("Sample Rate used when recording new tracks, mixing down tracks and for playback in this project.")
260 .Translation();
261
262 helpBtn->SetToolTip(helpText);
263 helpBtn->SetLabel(helpText); // for screen readers
264 helpBtn->SetName(helpText);
265 }
266 S.EndMultiColumn();
267 }
268 }
269
270 S.AddPrompt(XXO("D&efault Sample Rate:"));
271
272 S.StartMultiColumn(2);
273 {
276 .TieNumberAsChoice(
279
281 S.TieNumericTextBox({}, mOtherDefaultSampleRateValue, 15);
282 }
283 S.EndMultiColumn();
284
285 S.TieChoice(
286 XXO("Default Sample &Format:"), QualitySettings::SampleFormatSetting);
287
288 S.EndMultiColumn();
289 }
290 S.EndStatic();
291
292 // These previously lived in recording preferences.
293 // However they are liable to become device specific.
294 // Buffering also affects playback, not just recording, so is a device characteristic.
295 S.StartStatic( XO("Latency"));
296 {
297 S.StartThreeColumn();
298 {
299 wxTextCtrl *w;
300 // only show the following controls if we use Portaudio v19, because
301 // for Portaudio v18 we always use default buffer sizes
302 w = S
303 .NameSuffix(XO("milliseconds"))
304 .TieNumericTextBox(XXO("&Buffer length:"),
306 25);
307 S.AddUnits(XO("milliseconds"));
308
309 w = S
310 .NameSuffix(XO("milliseconds"))
311 .TieNumericTextBox(XXO("&Latency compensation:"),
313 S.AddUnits(XO("milliseconds"));
314 }
315 S.EndThreeColumn();
316 }
317 S.EndStatic();
318
319 S.EndScroller();
320
321}
322
323void DevicePrefs::OnHost(wxCommandEvent & e)
324{
325 // Bail if we have no hosts
326 if (mHostNames.size() < 1)
327 return;
328
329 // Find the index for the host API selected
330 int index = -1;
331 auto apiName = mHostLabels[mHost->GetCurrentSelection()];
332 int nHosts = Pa_GetHostApiCount();
333 for (int i = 0; i < nHosts; ++i) {
334 wxString name = wxSafeConvertMB2WX(Pa_GetHostApiInfo(i)->name);
335 if (name == apiName) {
336 index = i;
337 break;
338 }
339 }
340 // We should always find the host!
341 if (index < 0) {
342 wxLogDebug(wxT("DevicePrefs::OnHost(): API index not found"));
343 return;
344 }
345
346 int nDevices = Pa_GetDeviceCount();
347
348 // FIXME: TRAP_ERR PaErrorCode not handled. nDevices can be negative number.
349 if (nDevices == 0) {
350 mHost->Clear();
351 mHost->Append(_("No audio interfaces"), (void *) NULL);
352 mHost->SetSelection(0);
353 }
354
355 const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
356 const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();
357
358 wxArrayString playnames;
359 wxArrayString recordnames;
360 size_t i;
361 int devindex; /* temp variable to hold the numeric ID of each device in turn */
362 wxString device;
363 wxString recDevice;
364
365 recDevice = mRecordDevice;
366 if (!this->mRecordSource.empty())
367 recDevice += wxT(": ") + mRecordSource;
368
369 mRecord->Clear();
370 for (i = 0; i < inMaps.size(); i++) {
371 if (index == inMaps[i].hostIndex) {
372 device = MakeDeviceSourceString(&inMaps[i]);
373 devindex = mRecord->Append(device);
374 // We need to const cast here because SetClientData is a wx function
375 // It is okay because the original variable is non-const.
376 mRecord->SetClientData(devindex, const_cast<DeviceSourceMap *>(&inMaps[i]));
377 if (device == recDevice) { /* if this is the default device, select it */
378 mRecord->SetSelection(devindex);
379 }
380 }
381 }
382
383 mPlay->Clear();
384 for (i = 0; i < outMaps.size(); i++) {
385 if (index == outMaps[i].hostIndex) {
386 device = MakeDeviceSourceString(&outMaps[i]);
387 devindex = mPlay->Append(device);
388 mPlay->SetClientData(devindex, const_cast<DeviceSourceMap *>(&outMaps[i]));
389 if (device == mPlayDevice) { /* if this is the default device, select it */
390 mPlay->SetSelection(devindex);
391 }
392 }
393 }
394
395 /* deal with not having any devices at all */
396 if (mPlay->GetCount() == 0) {
397 playnames.push_back(_("No devices found"));
398 mPlay->Append(playnames[0], (void *) NULL);
399 mPlay->SetSelection(0);
400 }
401 if (mRecord->GetCount() == 0) {
402 recordnames.push_back(_("No devices found"));
403 mRecord->Append(recordnames[0], (void *) NULL);
404 mRecord->SetSelection(0);
405 }
406
407 /* what if we have no device selected? we should choose the default on
408 * this API, as defined by PortAudio. We then fall back to using 0 only if
409 * that fails */
410 if (mPlay->GetCount() && mPlay->GetSelection() == wxNOT_FOUND) {
412 if (defaultMap)
413 mPlay->SetStringSelection(MakeDeviceSourceString(defaultMap));
414
415 if (mPlay->GetSelection() == wxNOT_FOUND) {
416 mPlay->SetSelection(0);
417 }
418 }
419
420 if (mRecord->GetCount() && mRecord->GetSelection() == wxNOT_FOUND) {
422 if (defaultMap)
423 mRecord->SetStringSelection(MakeDeviceSourceString(defaultMap));
424
425 if (mPlay->GetSelection() == wxNOT_FOUND) {
426 mPlay->SetSelection(0);
427 }
428 }
429
430 ShuttleGui::SetMinSize(mPlay, mPlay->GetStrings());
431 ShuttleGui::SetMinSize(mRecord, mRecord->GetStrings());
432 OnDevice(e);
433}
434
435void DevicePrefs::OnDevice(wxCommandEvent & WXUNUSED(event))
436{
437 int ndx = mRecord->GetCurrentSelection();
438 if (ndx == wxNOT_FOUND) {
439 ndx = 0;
440 }
441
442 int sel = mChannels->GetSelection();
443 int cnt = 0;
444
445 DeviceSourceMap *inMap = (DeviceSourceMap *) mRecord->GetClientData(ndx);
446 if (inMap != NULL) {
447 cnt = inMap->numChannels;
448 }
449
450 if (sel != wxNOT_FOUND) {
451 mRecordChannels = sel + 1;
452 }
453
454 mChannels->Clear();
455
456 // Mimic old behavior
457 if (cnt <= 0) {
458 cnt = 16;
459 }
460
461 // Place an artificial limit on the number of channels to prevent an
462 // outrageous number. I don't know if this is really necessary, but
463 // it doesn't hurt.
464 if (cnt > 256) {
465 cnt = 256;
466 }
467
468 wxArrayStringEx channelnames;
469
470 // Channel counts, mono, stereo etc...
471 for (int i = 0; i < cnt; i++) {
472 wxString name;
473
474 if (i == 0) {
475 name = _("1 (Mono)");
476 }
477 else if (i == 1) {
478 name = _("2 (Stereo)");
479 }
480 else {
481 name = wxString::Format(wxT("%d"), i + 1);
482 }
483
484 channelnames.push_back(name);
485 int index = mChannels->Append(name);
486 if (i == mRecordChannels - 1) {
487 mChannels->SetSelection(index);
488 }
489 }
490
491 if (mChannels->GetCount() && mChannels->GetCurrentSelection() == wxNOT_FOUND) {
492 mChannels->SetSelection(0);
493 }
494
495 ShuttleGui::SetMinSize(mChannels, channelnames);
496 Layout();
497}
498
500{
501 const int sel = mDefaultSampleRates->GetSelection();
502 mOtherDefaultSampleRate->Enable(sel == (int)mDefaultSampleRates->GetCount() - 1);
503}
504
506{
507 if (mProjectSampleRates == nullptr)
508 return;
509
510 const int sel = mProjectSampleRates->GetSelection();
512 sel == (int)mProjectSampleRates->GetCount() - 1);
513}
514
516{
519 DeviceSourceMap *map = NULL;
520
521 if (mPlay->GetCount() > 0) {
522 map = (DeviceSourceMap *) mPlay->GetClientData(
523 mPlay->GetSelection());
524 }
525 if (map)
527
528 map = NULL;
529 if (mRecord->GetCount() > 0) {
530 map = (DeviceSourceMap *) mRecord->GetClientData(mRecord->GetSelection());
531 }
532 if (map) {
535 if (map->totalSources >= 1)
537 else
539 AudioIORecordChannels.Write(mChannels->GetSelection() + 1);
540 }
541
544
546
547 // The complex compound control may have value 'other' in which case the
548 // value in prefs comes from the second field.
549 if (mOtherDefaultSampleRate->IsEnabled())
550 {
552 gPrefs->Flush();
553 }
554
555 if (mProject)
556 {
557 auto& projectRate = ProjectRate::Get(*mProject);
558 if (mOtherProjectSampleRate->IsEnabled())
559 projectRate.SetRate(mOtherProjectSampleRateValue);
560 else
561 projectRate.SetRate(mSampleRateValues[mProjectSampleRates->GetSelection()]);
562 }
563
564 return true;
565}
566
567PrefsPanel *DevicePrefsFactory(wxWindow *parent, wxWindowID winid, AudacityProject *project)
568{
569 wxASSERT(parent); // to justify safenew
570 return safenew DevicePrefs(parent, winid, project);
571}
572
573namespace{
576 };
577}
wxT("CloseDown"))
DoubleSetting AudioIOLatencyCorrection
StringSetting AudioIORecordingSource
StringSetting AudioIOPlaybackDevice
DoubleSetting AudioIOLatencyDuration
StringSetting AudioIORecordingDevice
StringSetting AudioIOHost
IntSetting AudioIORecordingSourceIndex
IntSetting AudioIORecordChannels
END_EVENT_TABLE()
wxString MakeDeviceSourceString(const DeviceSourceMap *map)
@ ChannelsID
Definition: DevicePrefs.cpp:57
@ DefaultSampleRateChoice
Definition: DevicePrefs.cpp:58
@ PlayID
Definition: DevicePrefs.cpp:55
@ HostID
Definition: DevicePrefs.cpp:54
@ RecordID
Definition: DevicePrefs.cpp:56
@ ProjectSampleRateChoice
Definition: DevicePrefs.cpp:59
PrefsPanel * DevicePrefsFactory(wxWindow *parent, wxWindowID winid, AudacityProject *project)
#define DEVICE_PREFS_PLUGIN_SYMBOL
Definition: DevicePrefs.h:23
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define XC(s, c)
Definition: Internat.h:37
#define _(s)
Definition: Internat.h:73
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: IteratorX.h:210
#define safenew
Definition: MemoryX.h:10
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
ByColumns_t ByColumns
Definition: Prefs.cpp:515
an object holding per-project preferred sample rate
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
@ eIsSavingToPrefs
Definition: ShuttleGui.h:47
wxString name
Definition: TagsEditor.cpp:166
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static const int StandardRates[]
Array of common audio sample rates.
Definition: AudioIOBase.h:248
static const int NumStandardRates
How many standard sample rates there are.
Definition: AudioIOBase.h:250
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
DeviceSourceMap * GetDefaultInputDevice(int hostIndex)
const std::vector< DeviceSourceMap > & GetInputDeviceMaps()
const std::vector< DeviceSourceMap > & GetOutputDeviceMaps()
static DeviceManager * Instance()
Gets the singleton instance.
DeviceSourceMap * GetDefaultOutputDevice(int hostIndex)
A PrefsPanel used to select recording and playback devices and other settings.
Definition: DevicePrefs.h:26
wxTextCtrl * mOtherProjectSampleRate
Definition: DevicePrefs.h:63
void OnDevice(wxCommandEvent &e)
wxChoice * mDefaultSampleRates
Definition: DevicePrefs.h:68
wxArrayStringEx mHostLabels
Definition: DevicePrefs.h:50
void OnProjectSampleRateChoice(wxCommandEvent &e)
wxChoice * mHost
Definition: DevicePrefs.h:57
wxTextCtrl * mOtherDefaultSampleRate
Definition: DevicePrefs.h:69
ComponentInterfaceSymbol GetSymbol() const override
Definition: DevicePrefs.cpp:81
void PopulateOrExchange(ShuttleGui &S) override
wxChoice * mRecord
Definition: DevicePrefs.h:59
int mOtherDefaultSampleRateValue
Definition: DevicePrefs.h:70
wxString mRecordDevice
Definition: DevicePrefs.h:53
wxString mPlayDevice
Definition: DevicePrefs.h:52
TranslatableStrings mSampleRateNames
Definition: DevicePrefs.h:72
void OnDefaultSampleRateChoice(wxCommandEvent &e)
wxString mRecordSource
Definition: DevicePrefs.h:54
void OnHost(wxCommandEvent &e)
TranslatableString GetDescription() const override
Definition: DevicePrefs.cpp:86
virtual ~DevicePrefs()
Definition: DevicePrefs.cpp:76
wxChoice * mPlay
Definition: DevicePrefs.h:58
void GetNamesAndLabels()
wxChoice * mProjectSampleRates
Definition: DevicePrefs.h:62
ManualPageID HelpPageName() override
If not empty string, the Help button is added below the panel.
Definition: DevicePrefs.cpp:91
wxChoice * mChannels
Definition: DevicePrefs.h:60
std::vector< int > mSampleRateValues
Definition: DevicePrefs.h:73
int mProjectSampleRateIndex
Definition: DevicePrefs.h:65
long mRecordChannels
Definition: DevicePrefs.h:55
AudacityProject * mProject
Definition: DevicePrefs.h:47
void Populate()
Definition: DevicePrefs.cpp:96
int mOtherProjectSampleRateValue
Definition: DevicePrefs.h:66
bool Commit() override
TranslatableStrings mHostNames
Definition: DevicePrefs.h:49
Abstract base class used in importing a file.
Base class for a panel in the PrefsDialog. Classes derived from this class include BatchPrefs,...
Definition: PrefsPanel.h:51
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:259
void Invalidate() override
Definition: Prefs.h:289
bool Reset()
Reset to the default value.
Definition: Prefs.h:284
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
static void SetMinSize(wxWindow *window, const TranslatableStrings &items)
wxBitmap & Bitmap(int iIndex)
Holds a msgid for the translation catalog; may also bind format arguments.
virtual bool Flush() noexcept=0
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
PROJECT_RATE_API IntSetting DefaultSampleRate
PROJECT_RATE_API EnumSetting< sampleFormat > SampleFormatSetting
PrefsPanel::Registration sAttachment
wxString sourceString
Definition: DeviceManager.h:34
wxString deviceString
Definition: DeviceManager.h:35