From 3832ec3607b3f00df63d23effd939a68d6e5b1ae Mon Sep 17 00:00:00 2001 From: OV2 Date: Sat, 15 Dec 2018 14:19:16 +0100 Subject: [PATCH] win32: add audio device selection (xaudio2 only for the moment) --- win32/CXAudio2.cpp | 59 ++++++++- win32/CXAudio2.h | 3 + win32/IS9xSoundOutput.h | 15 +++ win32/rsrc/resource.h | 3 +- win32/rsrc/snes9x.rc | 61 +++++---- win32/wconfig.cpp | 1 + win32/win32_sound.cpp | 16 +++ win32/win32_sound.h | 5 + win32/wsnes9x.cpp | 285 +++++++++++++++++++++++----------------- win32/wsnes9x.h | 3 +- 10 files changed, 300 insertions(+), 151 deletions(-) diff --git a/win32/CXAudio2.cpp b/win32/CXAudio2.cpp index 57dcc1f4..f5555631 100644 --- a/win32/CXAudio2.cpp +++ b/win32/CXAudio2.cpp @@ -125,8 +125,13 @@ returns true if successful, false otherwise bool CXAudio2::InitVoices(void) { HRESULT hr; + // subtract -1, we added "Default" as first index + int device_index = FindDeviceIndex(GUI.AudioDevice) - 1; + if (device_index < 0) + device_index = 0; + if ( FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, (Settings.Stereo?2:1), - Settings.SoundPlaybackRate, 0, 0 , NULL ) ) ) { + Settings.SoundPlaybackRate, 0, device_index, NULL ) ) ) { DXTRACE_ERR_MSGBOX(TEXT("Unable to create mastering voice."),hr); return false; } @@ -308,3 +313,55 @@ void CXAudio2::ProcessSound() availableSamples -= singleBufferSamples; } } + +/* CXAudio2::GetDeviceList +get a list of the available output devices +----- +returns a vector of display names +*/ +std::vector CXAudio2::GetDeviceList() +{ + std::vector device_list; + + if (pXAudio2) + { + UINT32 num_devices; + pXAudio2->GetDeviceCount(&num_devices); + + device_list.push_back(_T("Default")); + + for (unsigned int i = 0; i < num_devices; i++) + { + XAUDIO2_DEVICE_DETAILS device_details; + if (SUCCEEDED(pXAudio2->GetDeviceDetails(i, &device_details))) + { + device_list.push_back(device_details.DisplayName); + } + } + } + + return device_list; +} + +/* CXAudio2::FindDeviceIndex +find a device name in the list of possible output devices +----- +returns the index in the device list returned by GetDeviceList +*/ +int CXAudio2::FindDeviceIndex(TCHAR *audio_device) +{ + std::vector device_list = GetDeviceList(); + + int index = 0; + + for (int i = 0; i < device_list.size(); i++) + { + if (_tcsstr(device_list[i].c_str(), audio_device) != NULL) + { + index = i; + break; + } + } + + return index; +} diff --git a/win32/CXAudio2.h b/win32/CXAudio2.h index 79d0b1e3..ba32647c 100644 --- a/win32/CXAudio2.h +++ b/win32/CXAudio2.h @@ -41,6 +41,9 @@ private: bool InitXAudio2(void); void DeInitXAudio2(void); + std::vector GetDeviceList(); + int FindDeviceIndex(TCHAR *audio_device); + public: CXAudio2(void); ~CXAudio2(void); diff --git a/win32/IS9xSoundOutput.h b/win32/IS9xSoundOutput.h index 8954fe49..d108a5fa 100644 --- a/win32/IS9xSoundOutput.h +++ b/win32/IS9xSoundOutput.h @@ -7,6 +7,8 @@ #ifndef IS9XSOUNDOUTPUT_H #define IS9XSOUNDOUTPUT_H #include "../port.h" +#include +#include /* IS9xSoundOutput Interface for the sound output. @@ -31,6 +33,19 @@ public: // Host sound system. If the sound system is callback based, ProcessSound should do a syncronized // S9xFinalizeSamples and return. virtual void ProcessSound()=0; + + // GetDeviceList should return a list of device strings that can be displayed in a dropdown + virtual std::vector GetDeviceList() + { + return std::vector(); + } + + // FindDeviceIndex should try to find a matching index in the device list for a particular device string + virtual int FindDeviceIndex(TCHAR *audio_device) + { + return 0; + } + }; #endif diff --git a/win32/rsrc/resource.h b/win32/rsrc/resource.h index 64b54a78..25c22a42 100644 --- a/win32/rsrc/resource.h +++ b/win32/rsrc/resource.h @@ -33,7 +33,6 @@ #define IDD_CREATEMOVIE 135 #define IDD_KEYCUSTOM 136 #define IDI_ICON1 144 -#define IDI_ICON2 160 #define IDB_REMOVABLE 145 #define IDB_RAMDISK 146 #define IDB_UNKNOWN 147 @@ -42,10 +41,12 @@ #define IDD_MULTICART 150 #define IDD_DIALOG_SHADER_PARAMS 155 #define IDD_DIALOG_XAUDIO2_INIT_ERROR 159 +#define IDI_ICON2 160 #define IDC_DRIVER 1001 #define IDC_BUFLEN 1002 #define IDC_RATE 1003 #define IDC_MIX 1004 +#define IDC_OUTPUT_DEVICE 1004 #define IDC_DYNRATECONTROL 1005 #define IDC_STEREO 1006 #define IDC_REV_STEREO 1007 diff --git a/win32/rsrc/snes9x.rc b/win32/rsrc/snes9x.rc index e8b268dd..219b17a4 100644 --- a/win32/rsrc/snes9x.rc +++ b/win32/rsrc/snes9x.rc @@ -26,40 +26,42 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // Dialog // -IDD_SOUND_OPTS DIALOGEX 0, 0, 413, 144 -STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU +IDD_SOUND_OPTS DIALOGEX 0, 0, 413, 157 +STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU CAPTION "Sound Settings" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - GROUPBOX "Sound Quality",IDC_STATIC,119,7,286,112,0,WS_EX_TRANSPARENT - DEFPUSHBUTTON "&OK",IDOK,288,122,56,16 - COMBOBOX IDC_DRIVER,177,24,106,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "Sound Driver:",IDC_STATIC,125,25,49,11 - COMBOBOX IDC_BUFLEN,177,56,106,101,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "Buffer Length:",IDC_STATIC,125,58,49,11 - COMBOBOX IDC_RATE,177,40,106,171,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "Playback Rate:",IDC_STATIC,125,41,49,11 - CONTROL "&Dynamic Rate Control",IDC_DYNRATECONTROL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,90,96,10 - CONTROL "&Automatic Input Rate",IDC_AUTOMATICINPUTRATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,105,96,10 - CONTROL "&Stereo",IDC_STEREO,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,295,15,93,10 - CONTROL "&Reverse Stereo",IDC_REV_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,30,94,10 + GROUPBOX "Sound Quality",IDC_STATIC,119,7,286,124,0,WS_EX_TRANSPARENT + DEFPUSHBUTTON "&OK",IDOK,288,134,56,16 + COMBOBOX IDC_DRIVER,177,34,106,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Sound Driver:",IDC_STATIC,125,36,49,11 + COMBOBOX IDC_BUFLEN,177,68,106,101,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Buffer Length:",IDC_STATIC,125,70,49,11 + COMBOBOX IDC_RATE,177,51,106,171,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Playback Rate:",IDC_STATIC,125,53,49,11 + CONTROL "&Dynamic Rate Control",IDC_DYNRATECONTROL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,97,102,10 + CONTROL "&Automatic Input Rate",IDC_AUTOMATICINPUTRATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,109,102,10 + CONTROL "&Stereo",IDC_STEREO,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,295,37,102,10 + CONTROL "&Reverse Stereo",IDC_REV_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,49,102,10 CONTROL "&Synchronize with sound core",IDC_SYNC_TO_SOUND_CPU, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,75,102,10 - PUSHBUTTON "&Cancel",IDCANCEL,350,122,56,16 - CONTROL "&Mute sound",IDC_MUTE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,45,93,12 - CONTROL "Frame Advance mu&te",IDC_FAMT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,60,90,12 - CONTROL "",IDC_INRATE,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | WS_TABSTOP,124,88,157,26 - EDITTEXT IDC_INRATEEDIT,177,74,105,12,ES_AUTOHSCROLL | ES_NUMBER - LTEXT "Input Rate:",IDC_INRATETEXT,126,74,46,11 - CONTROL "",IDC_SLIDER_VOLUME_REGULAR,"msctls_trackbar32",TBS_AUTOTICKS | TBS_VERT | TBS_BOTH | WS_TABSTOP | 0x400,22,25,23,73 - EDITTEXT IDC_EDIT_VOLUME_REGULAR,22,102,22,13,ES_AUTOHSCROLL + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,85,102,10 + PUSHBUTTON "&Cancel",IDCANCEL,350,134,56,16 + CONTROL "&Mute sound",IDC_MUTE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,61,102,10 + CONTROL "Frame Advance mu&te",IDC_FAMT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,295,73,102,10 + CONTROL "",IDC_INRATE,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | WS_TABSTOP,124,100,157,26 + EDITTEXT IDC_INRATEEDIT,177,86,105,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "Input Rate:",IDC_INRATETEXT,126,86,46,11 + CONTROL "",IDC_SLIDER_VOLUME_REGULAR,"msctls_trackbar32",TBS_AUTOTICKS | TBS_VERT | TBS_BOTH | WS_TABSTOP | 0x400,22,25,23,80 + EDITTEXT IDC_EDIT_VOLUME_REGULAR,22,111,22,13,ES_AUTOHSCROLL LTEXT "Regular",IDC_STATIC,22,17,49,10 - GROUPBOX "Volume",IDC_STATIC,7,7,105,112,0,WS_EX_TRANSPARENT - CONTROL "",IDC_SLIDER_VOLUME_TURBO,"msctls_trackbar32",TBS_AUTOTICKS | TBS_VERT | TBS_BOTH | WS_TABSTOP | 0x400,70,25,23,73 - EDITTEXT IDC_EDIT_VOLUME_TURBO,70,102,22,13,ES_AUTOHSCROLL + GROUPBOX "Volume",IDC_STATIC,7,7,105,124,0,WS_EX_TRANSPARENT + CONTROL "",IDC_SLIDER_VOLUME_TURBO,"msctls_trackbar32",TBS_AUTOTICKS | TBS_VERT | TBS_BOTH | WS_TABSTOP | 0x400,70,25,23,80 + EDITTEXT IDC_EDIT_VOLUME_TURBO,70,111,22,13,ES_AUTOHSCROLL LTEXT "Fast-Forward",IDC_STATIC,62,17,49,10 - LTEXT "%",IDC_STATIC,47,104,8,8 - LTEXT "%",IDC_STATIC,95,104,8,8 + LTEXT "%",IDC_STATIC,47,113,8,8 + LTEXT "%",IDC_STATIC,95,113,8,8 + COMBOBOX IDC_OUTPUT_DEVICE,177,17,220,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Output Device:",IDC_STATIC,125,19,49,11 END IDD_ROM_INFO DIALOGEX 0, 0, 233, 185 @@ -575,7 +577,7 @@ BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 406 TOPMARGIN, 7 - BOTTOMMARGIN, 138 + BOTTOMMARGIN, 150 END IDD_ROM_INFO, DIALOG @@ -823,6 +825,7 @@ IDB_HIDDENFOLDER BITMAP "hiddir.bmp" // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "icon1.ico" + IDI_ICON2 ICON "icon2.ico" diff --git a/win32/wconfig.cpp b/win32/wconfig.cpp index 415dc25d..c4c3a707 100644 --- a/win32/wconfig.cpp +++ b/win32/wconfig.cpp @@ -828,6 +828,7 @@ void WinRegisterConfigItems() AddBoolC("MuteFrameAdvance", GUI.FAMute, false, "true to prevent Snes9x from outputting sound when the Frame Advance command is in use"); AddUIntC("VolumeRegular", GUI.VolumeRegular, 100, "volume during regular play (percentage between 0 and 100)"); AddUIntC("VolumeTurbo", GUI.VolumeTurbo, 100, "volume during turbo mode (percentage between 0 and 100)"); + AddStringC("OutputDevice", GUI.AudioDevice, MAX_AUDIO_NAME_LENGTH, "Default", "Name of the output audio device (substring matching, XAudio2 only atm), set to 'Default' for default audio device"); #undef CATEGORY #define CATEGORY "Controls" AddBoolC("AllowLeftRight", Settings.UpAndDown, false, "true to allow left+right and up+down"); diff --git a/win32/win32_sound.cpp b/win32/win32_sound.cpp index 1c233844..bbb63ff8 100644 --- a/win32/win32_sound.cpp +++ b/win32/win32_sound.cpp @@ -107,3 +107,19 @@ void S9xSoundCallback(void *data) S9xSoundOutput->ProcessSound(); } + +/* GetAvailableSoundDevices +returns a list of output devices available for the current output driver +*/ +std::vector GetAvailableSoundDevices() +{ + return S9xSoundOutput->GetDeviceList(); +} + +/* FindAudioDeviceIndex +find an audio device that matches the currently configured audio device string +*/ +int FindAudioDeviceIndex(TCHAR *audio_device) +{ + return S9xSoundOutput->FindDeviceIndex(audio_device); +} diff --git a/win32/win32_sound.h b/win32/win32_sound.h index e1c57de5..0728e2fe 100644 --- a/win32/win32_sound.h +++ b/win32/win32_sound.h @@ -7,8 +7,13 @@ #ifndef WIN32_SOUND_H #define WIN32_SOUND_H +#include +#include + bool ReInitSound(); void S9xSoundCallback(void *data); void CloseSoundDevice(); +std::vector GetAvailableSoundDevices(); +int FindAudioDeviceIndex(TCHAR *audio_device); #endif diff --git a/win32/wsnes9x.cpp b/win32/wsnes9x.cpp index ec66b19d..19e73b03 100644 --- a/win32/wsnes9x.cpp +++ b/win32/wsnes9x.cpp @@ -4317,150 +4317,183 @@ BOOL CreateToolTip(int toolID, HWND hDlg, TCHAR* pText) return TRUE; } +void UpdateAudioDeviceDropdown(HWND hCtl) +{ + std::vector device_list = GetAvailableSoundDevices(); + + ComboBox_ResetContent(hCtl); + + int num_devices = device_list.size(); + + if (!num_devices) + { + ComboBox_AddString(hCtl, _T("Default")); + } + else + { + for (int i = 0; i < num_devices; i++) + { + ComboBox_AddString(hCtl, device_list[i].c_str()); + } + } +} + INT_PTR CALLBACK DlgSoundConf(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { HWND hTrackbar; TCHAR valTxt[10]; + + // temporary GUI state for restoring after switching devices (need to actually switch devices to get output devices) + static int prevDriver; + switch(msg) { - case WM_INITDIALOG: - WinRefreshDisplay(); + case WM_INITDIALOG: + { + WinRefreshDisplay(); - // FIXME: these strings should come from wlanguage.h + prevDriver = GUI.SoundDriver; - CreateToolTip(IDC_INRATEEDIT,hDlg,TEXT("For each 'Input rate' samples generated by the SNES, 'Playback rate' samples will produced. If you experience crackling you can try to lower this setting.")); - CreateToolTip(IDC_INRATE,hDlg,TEXT("For each 'Input rate' samples generated by the SNES, 'Playback rate' samples will produced. If you experience crackling you can try to lower this setting.")); - CreateToolTip(IDC_DYNRATECONTROL, hDlg, TEXT("Try to dynamically adjust the input rate to never overflow or underflow the sound buffer. Only works with XAudio2.")); + // FIXME: these strings should come from wlanguage.h - int pos; - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("Snes9x DirectSound")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_SNES9X_DIRECT_SOUND_DRIVER); - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("XAudio2")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_XAUDIO2_SOUND_DRIVER); -#ifdef FMOD_SUPPORT - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("FMOD DirectSound")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_FMOD_DIRECT_SOUND_DRIVER); - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("FMOD Windows Multimedia")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_FMOD_WAVE_SOUND_DRIVER); - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("FMOD A3D")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_FMOD_A3D_SOUND_DRIVER); -#elif defined FMODEX_SUPPORT - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("FMOD Ex Default")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_FMODEX_DEFAULT_DRIVER); - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("FMOD Ex ASIO")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_FMODEX_ASIO_DRIVER); - pos = SendDlgItemMessage(hDlg, IDC_DRIVER, CB_INSERTSTRING,-1,(LPARAM)TEXT("FMOD Ex OpenAL")); - SendDlgItemMessage(hDlg, IDC_DRIVER, CB_SETITEMDATA,pos,WIN_FMODEX_OPENAL_DRIVER); -#endif - SendDlgItemMessage(hDlg, IDC_DRIVER,CB_SETCURSEL,0,0); - for(pos = 0;pos= 0 && sliderVal <= 100) ? sliderVal : 100; + // output device + Edit_GetText(GetDlgItem(hDlg, IDC_OUTPUT_DEVICE), GUI.AudioDevice, MAX_AUDIO_NAME_LENGTH); + WinSaveConfigFile(); // already done in WinProc on return // ReInitSound(); - } /* FALL THROUGH */ + EndDialog(hDlg, 1); + + } case IDCANCEL: + GUI.SoundDriver = prevDriver; EndDialog(hDlg, 1); return true; @@ -4590,6 +4629,15 @@ INT_PTR CALLBACK DlgSoundConf(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) SendDlgItemMessage(hDlg,IDC_BUFLEN,CB_SETCURSEL,7,0); break; } + + // get current selected device name, switch driver, try to select the same + HWND output_dropdown = GetDlgItem(hDlg, IDC_OUTPUT_DEVICE); + TCHAR selected_device[MAX_AUDIO_NAME_LENGTH]; + Edit_GetText(output_dropdown, selected_device, MAX_AUDIO_NAME_LENGTH); + GUI.SoundDriver = driver; + ReInitSound(); + UpdateAudioDeviceDropdown(output_dropdown); + ComboBox_SetCurSel(output_dropdown, FindAudioDeviceIndex(selected_device)); return true; } else return false; @@ -7349,7 +7397,6 @@ INT_PTR CALLBACK DlgFunky(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) lstrcpy(prevD3DShaderFile, GUI.D3DshaderFileName); lstrcpy(prevOGLShaderFile, GUI.OGLshaderFileName); - _stprintf(s, TEXT("Current: %dx%d %dbit %dHz"), GUI.FullscreenMode.width, GUI.FullscreenMode.height, GUI.FullscreenMode.depth, GUI.FullscreenMode.rate); SendDlgItemMessage(hDlg, IDC_CURRMODE, WM_SETTEXT, 0, (LPARAM)s); diff --git a/win32/wsnes9x.h b/win32/wsnes9x.h index c5b89cf7..abc8ad61 100644 --- a/win32/wsnes9x.h +++ b/win32/wsnes9x.h @@ -28,7 +28,7 @@ #include "rsrc/resource.h" #define COUNT(a) (sizeof (a) / sizeof (a[0])) -#define GUI_VERSION 1008 +#define MAX_AUDIO_NAME_LENGTH 1024 #define MAX_RECENT_GAMES_LIST_SIZE 32 #define MAX_RECENT_HOSTS_LIST_SIZE 16 @@ -216,6 +216,7 @@ struct sGUI { // used for sync sound synchronization CRITICAL_SECTION SoundCritSect; HANDLE SoundSyncEvent; + TCHAR AudioDevice[MAX_AUDIO_NAME_LENGTH]; TCHAR RomDir [_MAX_PATH]; TCHAR ScreensDir [_MAX_PATH];