1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-19 17:40:51 +02:00
audacity/src/toolbars/DeviceToolBar.cpp
Paul Licameli b256a4fd1a Less code directly inside ShuttleGui code blocks...
... Trying to reduce that just to chained calls on S, or conditional and looping
logic for variations in layout.

Lift some declarations to higher scope; or use expressions that avoid local
variables; or even use lambdas for more complicated computation of arguments
for the member functions of S.
2019-11-29 15:16:51 -05:00

875 lines
28 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
DeviceToolBar.cpp
Dominic Mazzoni
*******************************************************************//*!
\class DeviceToolBar
\brief A toobar to allow easier changing of input and output devices .
*//*******************************************************************/
#include "../Audacity.h" // for USE_* macros
#include "DeviceToolBar.h"
#include "ToolManager.h"
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#include <wx/setup.h> // for wxUSE_* macros
#ifndef WX_PRECOMP
#include <wx/choice.h>
#include <wx/event.h>
#include <wx/intl.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/statbmp.h>
#include <wx/stattext.h>
#include <wx/tooltip.h>
#endif
#include "ToolDock.h"
#include "../TrackPanel.h"
#include "../AColor.h"
#include "../AllThemeResources.h"
#include "../AudioIOBase.h"
#include "../ImageManipulation.h"
#include "../KeyboardCapture.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../ShuttleGui.h"
#include "../widgets/Grabber.h"
#include "../DeviceManager.h"
#include "../widgets/AudacityMessageBox.h"
#include "../widgets/Grabber.h"
#if wxUSE_ACCESSIBILITY
#include "../widgets/WindowAccessible.h"
#endif
IMPLEMENT_CLASS(DeviceToolBar, ToolBar);
////////////////////////////////////////////////////////////
/// Methods for DeviceToolBar
////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(DeviceToolBar, ToolBar)
EVT_CHOICE(wxID_ANY, DeviceToolBar::OnChoice)
EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, DeviceToolBar::OnCaptureKey)
END_EVENT_TABLE()
static int DeviceToolbarPrefsID()
{
static int value = wxNewId();
return value;
}
//Standard contructor
DeviceToolBar::DeviceToolBar( AudacityProject &project )
: ToolBar( project, DeviceBarID, _("Device"), wxT("Device"), true )
{
wxTheApp->Bind( EVT_RESCANNED_DEVICES,
&DeviceToolBar::OnRescannedDevices, this );
}
DeviceToolBar::~DeviceToolBar()
{
}
DeviceToolBar &DeviceToolBar::Get( AudacityProject &project )
{
auto &toolManager = ToolManager::Get( project );
return *static_cast<DeviceToolBar*>( toolManager.GetToolBar(DeviceBarID) );
}
const DeviceToolBar &DeviceToolBar::Get( const AudacityProject &project )
{
return Get( const_cast<AudacityProject&>( project )) ;
}
void DeviceToolBar::Create(wxWindow *parent)
{
ToolBar::Create(parent);
// Simulate a size event to set initial meter placement/size
wxSizeEvent event(GetSize(), GetId());
event.SetEventObject(this);
GetEventHandler()->ProcessEvent(event);
}
void DeviceToolBar::DeinitChildren()
{
mInput = NULL;
mOutput = NULL;
mInputChannels = NULL;
mHost = NULL;
}
void DeviceToolBar::Populate()
{
SetBackgroundColour( theTheme.Colour( clrMedium ) );
DeinitChildren();
// Hosts
mHost = safenew wxChoice(this,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize);
#if wxUSE_ACCESSIBILITY
// so that name can be set on a standard control
mHost->SetAccessible(safenew WindowAccessible(mHost));
#endif
Add(mHost, 0, wxALIGN_CENTER);
// Input device
Add(safenew AStaticBitmap(this,
wxID_ANY,
theTheme.Bitmap(bmpMic)), 0, wxALIGN_CENTER);
mInput = safenew wxChoice(this,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize);
#if wxUSE_ACCESSIBILITY
// so that name can be set on a standard control
mInput->SetAccessible(safenew WindowAccessible(mInput));
#endif
// Input channels
Add(mInput, 0, wxALIGN_CENTER);
mInputChannels = safenew wxChoice(this,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize);
#if wxUSE_ACCESSIBILITY
// so that name can be set on a standard control
mInputChannels->SetAccessible(safenew WindowAccessible(mInputChannels));
#endif
Add(mInputChannels, 0, wxALIGN_CENTER);
// Output device
Add(safenew AStaticBitmap(this,
wxID_ANY,
theTheme.Bitmap(bmpSpeaker)), 0, wxALIGN_CENTER);
mOutput = safenew wxChoice(this,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize);
#if wxUSE_ACCESSIBILITY
// so that name can be set on a standard control
mOutput->SetAccessible(safenew WindowAccessible(mOutput));
#endif
Add(mOutput, 0, wxALIGN_CENTER);
mHost->Bind(wxEVT_SET_FOCUS,
&DeviceToolBar::OnFocus,
this);
mHost->Bind(wxEVT_KILL_FOCUS,
&DeviceToolBar::OnFocus,
this);
mOutput->Bind(wxEVT_SET_FOCUS,
&DeviceToolBar::OnFocus,
this);
mOutput->Bind(wxEVT_KILL_FOCUS,
&DeviceToolBar::OnFocus,
this);
mInput->Bind(wxEVT_SET_FOCUS,
&DeviceToolBar::OnFocus,
this);
mInput->Bind(wxEVT_KILL_FOCUS,
&DeviceToolBar::OnFocus,
this);
mInputChannels->Bind(wxEVT_SET_FOCUS,
&DeviceToolBar::OnFocus,
this);
mInputChannels->Bind(wxEVT_KILL_FOCUS,
&DeviceToolBar::OnFocus,
this);
SetNames();
RefillCombos();
}
void DeviceToolBar::RefillCombos()
{
FillHosts();
FillHostDevices();
FillInputChannels();
// make the device display selection reflect the prefs if they exist
UpdatePrefs();
}
void DeviceToolBar::OnFocus(wxFocusEvent &event)
{
KeyboardCapture::OnFocus( *this, event );
}
void DeviceToolBar::OnCaptureKey(wxCommandEvent &event)
{
wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
int keyCode = kevent->GetKeyCode();
// Pass UP/DOWN/LEFT/RIGHT through for input/output choice
if (FindFocus() == mInput && (keyCode == WXK_LEFT || keyCode == WXK_RIGHT
|| keyCode == WXK_UP || keyCode == WXK_DOWN)) {
return;
}
if (FindFocus() == mOutput && (keyCode == WXK_LEFT || keyCode == WXK_RIGHT
|| keyCode == WXK_UP || keyCode == WXK_DOWN)) {
return;
}
event.Skip();
return;
}
void DeviceToolBar::UpdatePrefs()
{
wxString hostName;
wxString devName;
wxString sourceName;
wxString desc;
const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();
int hostSelectionIndex = mHost->GetSelection();
wxString oldHost = hostSelectionIndex >= 0 ? mHost->GetString(hostSelectionIndex) :
wxT("");
hostName = gPrefs->Read(wxT("/AudioIO/Host"), wxT(""));
// if the prefs host name doesn't match the one displayed, it changed
// in another project's DeviceToolBar, so we need to repopulate everything.
if (oldHost != hostName)
FillHostDevices();
devName = gPrefs->Read(wxT("/AudioIO/RecordingDevice"), wxT(""));
sourceName = gPrefs->Read(wxT("/AudioIO/RecordingSource"), wxT(""));
if (sourceName.empty())
desc = devName;
else
desc = devName + wxT(": ") + sourceName;
if (mInput->GetStringSelection() != desc &&
mInput->FindString(desc) != wxNOT_FOUND) {
mInput->SetStringSelection(desc);
FillInputChannels();
} else if (mInput->GetStringSelection() != desc && mInput->GetCount()) {
for (size_t i = 0; i < inMaps.size(); i++) {
if (inMaps[i].hostString == hostName &&
MakeDeviceSourceString(&inMaps[i]) == mInput->GetString(0)) {
// use the default. It should exist but check just in case, falling back on the 0 index.
DeviceSourceMap *defaultMap = DeviceManager::Instance()->GetDefaultInputDevice(inMaps[i].hostIndex);
if (defaultMap) {
mInput->SetStringSelection(MakeDeviceSourceString(defaultMap));
SetDevices(defaultMap, NULL);
} else {
//use the first item (0th index) if we have no familiar devices
mInput->SetSelection(0);
SetDevices(&inMaps[i], NULL);
}
break;
}
}
}
devName = gPrefs->Read(wxT("/AudioIO/PlaybackDevice"), wxT(""));
sourceName = gPrefs->Read(wxT("/AudioIO/PlaybackSource"), wxT(""));
if (sourceName.empty())
desc = devName;
else
desc = devName + wxT(": ") + sourceName;
if (mOutput->GetStringSelection() != desc &&
mOutput->FindString(desc) != wxNOT_FOUND) {
mOutput->SetStringSelection(desc);
} else if (mOutput->GetStringSelection() != desc &&
mOutput->GetCount()) {
for (size_t i = 0; i < outMaps.size(); i++) {
if (outMaps[i].hostString == hostName &&
MakeDeviceSourceString(&outMaps[i]) == mOutput->GetString(0)) {
// use the default. It should exist but check just in case, falling back on the 0 index.
DeviceSourceMap *defaultMap = DeviceManager::Instance()->GetDefaultOutputDevice(outMaps[i].hostIndex);
if (defaultMap) {
mOutput->SetStringSelection(MakeDeviceSourceString(defaultMap));
SetDevices(NULL, defaultMap);
} else {
//use the first item (0th index) if we have no familiar devices
mOutput->SetSelection(0);
SetDevices(NULL, &outMaps[i]);
}
break;
}
}
}
long oldChannels, newChannels;
oldChannels = mInputChannels->GetSelection() + 1;
gPrefs->Read(wxT("/AudioIO/RecordChannels"), &newChannels, 0);
if (newChannels > 0 && oldChannels != newChannels)
mInputChannels->SetSelection(newChannels - 1);
if (!hostName.empty() && mHost->GetStringSelection() != hostName)
mHost->SetStringSelection(hostName);
RegenerateTooltips();
// Set label to pull in language change
SetLabel(_("Device"));
// Give base class a chance
ToolBar::UpdatePrefs();
Layout();
Refresh();
}
void DeviceToolBar::UpdateSelectedPrefs( int id )
{
if (id == DeviceToolbarPrefsID())
UpdatePrefs();
ToolBar::UpdateSelectedPrefs( id );
}
void DeviceToolBar::EnableDisableButtons()
{
auto gAudioIO = AudioIOBase::Get();
if (gAudioIO) {
// we allow changes when monitoring, but not when recording
bool audioStreamActive = gAudioIO->IsStreamActive() && !gAudioIO->IsMonitoring();
// Here we should relinquish focus
if (audioStreamActive) {
wxWindow *focus = wxWindow::FindFocus();
if (focus == mHost || focus == mInput || focus == mOutput || focus == mInputChannels)
TrackPanel::Get( mProject ).SetFocus();
}
mHost->Enable(!audioStreamActive);
mInput->Enable(!audioStreamActive);
mOutput->Enable(!audioStreamActive);
mInputChannels->Enable(!audioStreamActive);
}
}
void DeviceToolBar::SetNames()
{
/* i18n-hint: (noun) It's the device used for playback.*/
mOutput->SetName(_("Playback Device"));
/* i18n-hint: (noun) It's the device used for recording.*/
mInput->SetName(_("Recording Device"));
mHost->SetName(_("Audio Host"));
mInputChannels->SetName(_("Recording Channels"));
}
void DeviceToolBar::RegenerateTooltips()
{
#if wxUSE_TOOLTIPS
SetNames();
mOutput->SetToolTip(mOutput->GetName() + wxT(" - ") + mOutput->GetStringSelection());
mInput->SetToolTip(mInput->GetName() + wxT(" - ") + mInput->GetStringSelection());
mHost->SetToolTip(mHost->GetName() + wxT(" - ") + mHost->GetStringSelection());
mInputChannels->SetToolTip(mInputChannels->GetName() + wxT(" - ") + mInputChannels->GetStringSelection());
#endif
}
bool DeviceToolBar::Layout()
{
bool ret;
ret = ToolBar::Layout();
RepositionCombos();
return ret;
}
// returns true if the combo is constrained and false otherwise
// @param toolbarWidth the width of the toolbar in pixels
// @param ratio an in/out for the desired and resultant width ratio.
// @param flex the amount of extra space allowed to have past the available.
// the amount used is subtracted.
static bool RepositionCombo(wxWindow *combo, int toolbarWidth, wxSize desiredSize,
float &ratio, float &flex, int marginPixels, bool changesRatio)
{
float ratioChange;
bool constrained = false;
// push margin pixels
desiredSize.x += marginPixels;
// truncate the window size if necessary
if (desiredSize.x > toolbarWidth * (flex + ratio)) {
constrained = true;
desiredSize.SetWidth(toolbarWidth * (flex + ratio));
if (desiredSize.GetWidth() - marginPixels < 0)
desiredSize.SetWidth(marginPixels);
}
// keep track of how much space gained or lost so it can be used by other combos.
if (changesRatio) {
ratioChange = (desiredSize.x / ((float) toolbarWidth)) - ratio;
ratio += ratioChange;
flex -= ratioChange;
}
// pop the margin pixels
desiredSize.x -= marginPixels;
combo->SetMinSize(desiredSize);
return constrained;
}
//These don't add up to 1 because there is a bit of margin that we allow
//the layout sizer to handle.
#define kHostWidthRatio 0.13f
#define kInputWidthRatio 0.32f
#define kOutputWidthRatio 0.32f
#define kChannelsWidthRatio 0.18f
void DeviceToolBar::RepositionCombos()
{
int w, h, dockw, dockh;
float ratioUnused;
bool constrained = true;
wxWindow *window;
wxSize desiredInput, desiredOutput, desiredHost, desiredChannels;
float hostRatio, outputRatio, inputRatio, channelsRatio;
// if the toolbar is docked then the width we should use is the project width.
// as the toolbar's width can extend past this.
GetClientSize(&w, &h);
// FIXME: Note that there's some bug in here, in that even if the prefs show the toolbar
// docked, on initialization, this call to IsDocked() returns false.
if (IsDocked()) {
// If the toolbar is docked its width can be larger than what is actually viewable
// So take the min. We don't need to worry about having another toolbar to the left off us
// because if we are larger than the dock size we always get our own row.
// and if smaller then we don't use the dock size (because we take the min).
window = GetDock();
window->GetClientSize(&dockw, &dockh);
// The fudge factor here is because the docked toolbar is 2 pixels smaller.
int kFudge1 = -2;
// Grabber is Included, but not resizer if docked.
w-= grabberWidth + kFudge1;
if (dockw < w)
w = dockw;
} else {
// If not docked, we have a holding window.
wxWindow *pParent = GetParent();
if( pParent ){
int w1, h1;
pParent->GetSize( &w1, &h1 );
// This Resizer is considerably bigger than the 4px docked version.
// It's the diagonal striped resizer, not the veertical bars.
int kStripyResizerSize = 15;
// Grabber AND resizer included.
w = w1- (kStripyResizerSize + grabberWidth );
}
}
if (w <= 0)
return;
// set up initial sizes and ratios
// Note that the y values of the desired sizes are not changed, so that the height
// of the toolbar is not changed
hostRatio = kHostWidthRatio;
inputRatio = kInputWidthRatio;
outputRatio = kOutputWidthRatio;
channelsRatio = kChannelsWidthRatio;
desiredHost.x = mHost->GetBestSize().x *4;
desiredHost.y = mHost->GetSize().y;
desiredInput.x = mInput->GetBestSize().x *4;
desiredInput.y = mInput->GetSize().y;
desiredOutput.x = mOutput->GetBestSize().x *4;
desiredOutput.y = mOutput->GetSize().y;
desiredChannels.x = mInputChannels->GetBestSize().x *4;
desiredChannels.y = mInputChannels->GetSize().y;
// wxGtk (Gnome) has larger comboboxes than the other platforms. For DeviceToolBar this prevents
// the toolbar docking on a single height row. So we shrink it to prevent this.
#ifdef __WXGTK__
desiredHost.SetHeight(mHost->GetBestSize().y -4);
desiredInput.SetHeight(desiredHost.GetHeight());
desiredOutput.SetHeight(desiredHost.GetHeight());
desiredChannels.SetHeight(desiredHost.GetHeight());
#endif
ratioUnused = 0.995f - (kHostWidthRatio + kInputWidthRatio + kOutputWidthRatio + kChannelsWidthRatio);
int i = 0;
// limit the amount of times we solve contraints to 5
// As we now ask for more than is available, we only do this iteration once.
while (constrained && ratioUnused > 0.01f && i < 5) {
i++;
constrained = RepositionCombo(mHost, w, desiredHost, hostRatio, ratioUnused, 0, true);
constrained |= RepositionCombo(mInput, w, desiredInput, inputRatio, ratioUnused, theTheme.Bitmap(bmpMic).GetWidth(), true);
constrained |= RepositionCombo(mOutput, w, desiredOutput, outputRatio, ratioUnused, theTheme.Bitmap(bmpSpeaker).GetWidth(), true);
constrained |= RepositionCombo(mInputChannels, w, desiredChannels, channelsRatio, ratioUnused, 0, true);
}
Update();
}
void DeviceToolBar::FillHosts()
{
wxArrayString hosts;
size_t i;
const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();
// go over our lists add the host to the list if it isn't there yet
for (i = 0; i < inMaps.size(); i++)
if ( ! make_iterator_range( hosts ).contains( inMaps[i].hostString ) )
hosts.push_back(inMaps[i].hostString);
for (i = 0; i < outMaps.size(); i++)
if ( ! make_iterator_range( hosts ).contains( outMaps[i].hostString ) )
hosts.push_back(outMaps[i].hostString);
mHost->Clear();
mHost->Append(hosts);
if (hosts.size() == 0)
mHost->Enable(false);
mHost->InvalidateBestSize();
mHost->SetMaxSize(mHost->GetBestSize()*4);
}
void DeviceToolBar::FillHostDevices()
{
const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
const std::vector<DeviceSourceMap> &outMaps = DeviceManager::Instance()->GetOutputDeviceMaps();
//read what is in the prefs
wxString host = gPrefs->Read(wxT("/AudioIO/Host"), wxT(""));
size_t i;
int foundHostIndex = -1;
// if the host is not in the hosts combo then we rescanned.
// set it to blank so we search for another host.
if (mHost->FindString(host) == wxNOT_FOUND)
host = wxT("");
for (i = 0; i < outMaps.size(); i++) {
if (outMaps[i].hostString == host) {
foundHostIndex = outMaps[i].hostIndex;
break;
}
}
if (foundHostIndex == -1) {
for (i = 0; i < inMaps.size(); i++) {
if (inMaps[i].hostString == host) {
foundHostIndex = inMaps[i].hostIndex;
break;
}
}
}
// If no host was found based on the prefs device host, load the first available one
if (foundHostIndex == -1) {
if (outMaps.size())
foundHostIndex = outMaps[0].hostIndex;
else if (inMaps.size())
foundHostIndex = inMaps[0].hostIndex;
}
// Make sure in/out are clear in case no host was found
mInput->Clear();
mOutput->Clear();
// If we still have no host it means no devices, in which case do nothing.
if (foundHostIndex == -1)
return;
// Repopulate the Input/Output device list available to the user
for (i = 0; i < inMaps.size(); i++) {
if (foundHostIndex == inMaps[i].hostIndex) {
mInput->Append(MakeDeviceSourceString(&inMaps[i]));
if (host.empty()) {
host = inMaps[i].hostString;
gPrefs->Write(wxT("/AudioIO/Host"), host);
mHost->SetStringSelection(host);
}
}
}
mInput->Enable(mInput->GetCount() ? true : false);
mInput->InvalidateBestSize();
mInput->SetMaxSize(mInput->GetBestSize()*4);
for (i = 0; i < outMaps.size(); i++) {
if (foundHostIndex == outMaps[i].hostIndex) {
mOutput->Append(MakeDeviceSourceString(&outMaps[i]));
if (host.empty()) {
host = outMaps[i].hostString;
gPrefs->Write(wxT("/AudioIO/Host"), host);
gPrefs->Flush();
mHost->SetStringSelection(host);
}
}
}
mOutput->Enable(mOutput->GetCount() ? true : false);
mOutput->InvalidateBestSize();
mOutput->SetMaxSize(mOutput->GetBestSize()*4);
// The setting of the Device is left up to OnChoice
}
void DeviceToolBar::OnRescannedDevices( wxCommandEvent &event )
{
event.Skip();
// Hosts may have disappeared or appeared so a complete repopulate is needed.
RefillCombos();
}
//return 1 if host changed, 0 otherwise.
int DeviceToolBar::ChangeHost()
{
int hostSelectionIndex;
hostSelectionIndex = mHost->GetSelection();
wxString oldHost = gPrefs->Read(wxT("/AudioIO/Host"), wxT(""));
wxString newHost = hostSelectionIndex >= 0 ? mHost->GetString(hostSelectionIndex) :
oldHost;
if (oldHost == newHost)
return 0;
//change the host and switch to correct devices.
gPrefs->Write(wxT("/AudioIO/Host"), newHost);
gPrefs->Flush();
// populate the devices
FillHostDevices();
return 1;
}
void DeviceToolBar::FillInputChannels()
{
const std::vector<DeviceSourceMap> &inMaps = DeviceManager::Instance()->GetInputDeviceMaps();
wxString host = gPrefs->Read(wxT("/AudioIO/Host"), wxT(""));
wxString device = gPrefs->Read(wxT("/AudioIO/RecordingDevice"), wxT(""));
wxString source = gPrefs->Read(wxT("/AudioIO/RecordingSource"), wxT(""));
long oldChannels = 2, newChannels;
gPrefs->Read(wxT("/AudioIO/RecordChannels"), &oldChannels);
int index = -1;
size_t i, j;
mInputChannels->Clear();
for (i = 0; i < inMaps.size(); i++) {
if (source == inMaps[i].sourceString &&
device == inMaps[i].deviceString &&
host == inMaps[i].hostString) {
// add one selection for each channel of this source
for (j = 0; j < (unsigned int) inMaps[i].numChannels; j++) {
wxString name;
if (j == 0) {
name = _("1 (Mono) Recording Channel");
}
else if (j == 1) {
name = _("2 (Stereo) Recording Channels");
}
else {
name = wxString::Format(wxT("%d"), (int) j + 1);
}
mInputChannels->Append(name);
}
newChannels = inMaps[i].numChannels;
if (oldChannels <= newChannels && oldChannels >= 1)
newChannels = oldChannels;
if (newChannels >= 1)
mInputChannels->SetSelection(newChannels - 1);
gPrefs->Write(wxT("/AudioIO/RecordChannels"), newChannels);
mInputChannels->Enable(mInputChannels->GetCount() ? true : false);
index = i;
break;
}
}
if (index == -1)
mInputChannels->Enable(false);
mInputChannels->InvalidateBestSize();
mInputChannels->SetMaxSize(mInputChannels->GetBestSize()*4);
}
void DeviceToolBar::SetDevices(const DeviceSourceMap *in, const DeviceSourceMap *out)
{
if (in) {
gPrefs->Write(wxT("/AudioIO/RecordingDevice"), in->deviceString);
gPrefs->Write(wxT("/AudioIO/RecordingSourceIndex"), in->sourceIndex);
if (in->totalSources >= 1) {
gPrefs->Write(wxT("/AudioIO/RecordingSource"), in->sourceString);
} else {
gPrefs->Write(wxT("/AudioIO/RecordingSource"), wxT(""));
}
gPrefs->Flush();
FillInputChannels();
}
if (out) {
gPrefs->Write(wxT("/AudioIO/PlaybackDevice"), out->deviceString);
if (out->totalSources >= 1) {
gPrefs->Write(wxT("/AudioIO/PlaybackSource"), out->sourceString);
} else {
gPrefs->Write(wxT("/AudioIO/PlaybackSource"), wxT(""));
}
gPrefs->Flush();
}
}
void DeviceToolBar::ChangeDevice(bool isInput)
{
int newIndex = -1;
wxChoice *combo = isInput ? mInput :mOutput;
size_t i;
int selectionIndex = combo->GetSelection();
wxString host = gPrefs->Read(wxT("/AudioIO/Host"), wxT(""));
const std::vector<DeviceSourceMap> &maps = isInput ? DeviceManager::Instance()->GetInputDeviceMaps()
: DeviceManager::Instance()->GetOutputDeviceMaps();
// Find device indices for input and output
if (selectionIndex >= 0 ) {
wxString newDevice = combo->GetStringSelection();
for (i = 0; i < maps.size(); ++i) {
wxString name;
name = MakeDeviceSourceString(&maps[i]);
if (name == newDevice && maps[i].hostString == host) {
newIndex = i;
}
}
}
if (newIndex < 0) {
wxLogDebug(wxT("DeviceToolBar::OnChoice(): couldn't find device indices"));
return;
}
SetDevices(isInput ? &maps[newIndex] : NULL,
isInput ? NULL : &maps[newIndex]);
}
void DeviceToolBar::OnChoice(wxCommandEvent &event)
{
wxObject *eventObject = event.GetEventObject();
//if we've changed hosts, we've handled the device switching already.
if (eventObject == mHost) {
ChangeHost();
} else if (eventObject == mInputChannels) {
int channelsSelectionIndex = mInputChannels->GetSelection();
if (channelsSelectionIndex >= 0)
gPrefs->Write(wxT("/AudioIO/RecordChannels"),channelsSelectionIndex + 1);
} else if (eventObject == mInput) {
ChangeDevice(true);
}
else if (eventObject == mOutput) {
ChangeDevice(false);
}
auto gAudioIO = AudioIOBase::Get();
if (gAudioIO) {
// We cannot have gotten here if gAudioIO->IsAudioTokenActive(),
// per the setting of AudioIONotBusyFlag and AudioIOBusyFlag in
// AudacityProject::GetUpdateFlags().
// However, we can have an invalid audio token (so IsAudioTokenActive()
// is false), but be monitoring.
// If monitoring, have to stop the stream, so HandleDeviceChange() can work.
// We could disable the Preferences command while monitoring, i.e.,
// set AudioIONotBusyFlag/AudioIOBusyFlag according to monitoring, as well.
// Instead allow it because unlike recording, for example, monitoring
// is not clearly something that should prohibit changing device.
// TODO: We *could* be smarter in this method and call HandleDeviceChange()
// only when the device choices actually changed. True of lots of prefs!
// As is, we always stop monitoring before handling the device change.
if (gAudioIO->IsMonitoring())
{
gAudioIO->StopStream();
while (gAudioIO->IsBusy())
wxMilliSleep(100);
}
gAudioIO->HandleDeviceChange();
}
wxTheApp->AddPendingEvent(wxCommandEvent{
EVT_PREFS_UPDATE, DeviceToolbarPrefsID() });
}
void DeviceToolBar::ShowInputDialog()
{
ShowComboDialog(mInput, wxString(_("Select Recording Device")));
}
void DeviceToolBar::ShowOutputDialog()
{
ShowComboDialog(mOutput, wxString(_("Select Playback Device")));
}
void DeviceToolBar::ShowHostDialog()
{
ShowComboDialog(mHost, wxString(_("Select Audio Host")));
}
void DeviceToolBar::ShowChannelsDialog()
{
ShowComboDialog(mInputChannels, wxString(_("Select Recording Channels")));
}
void DeviceToolBar::ShowComboDialog(wxChoice *combo, const wxString &title)
{
if (!combo || combo->GetCount() == 0) {
AudacityMessageBox(_("Device information is not available."));
return;
}
#if USE_PORTMIXER
wxArrayStringEx inputSources = combo->GetStrings();
wxDialogWrapper dlg(nullptr, wxID_ANY, title);
dlg.SetName(dlg.GetTitle());
ShuttleGui S(&dlg, eIsCreating);
wxChoice *c;
S.StartVerticalLay(true);
{
S.StartHorizontalLay(wxCENTER, false);
{
c = S.AddChoice(combo->GetName(),
inputSources,
combo->GetSelection());
}
S.EndHorizontalLay();
}
S.EndVerticalLay();
S.AddStandardButtons();
dlg.GetSizer()->SetSizeHints(&dlg);
dlg.Center();
if (dlg.ShowModal() == wxID_OK)
{
wxCommandEvent dummyEvent;
dummyEvent.SetEventObject(combo);
// SetSelection() doesn't send an event, so we call OnChoice explicitly
combo->SetSelection(c->GetSelection());
OnChoice(dummyEvent);
}
#endif
}
static RegisteredToolbarFactory factory{ DeviceBarID,
[]( AudacityProject &project ){
return ToolBar::Holder{ safenew DeviceToolBar{ project } }; }
};