diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 68484830..a0a3094a 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -5,6 +5,7 @@ #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "audio/IAudioAPI.h" +#include "audio/IAudioInputAPI.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "config/ActiveSettings.h" @@ -402,6 +403,7 @@ void cemu_initForGame() GraphicPack2::ActivateForCurrentTitle(); // print audio log IAudioAPI::PrintLogging(); + IAudioInputAPI::PrintLogging(); // everything initialized forceLog_printf("------- Run title -------"); // wait till GPU thread is initialized diff --git a/src/Cafe/OS/libs/mic/mic.cpp b/src/Cafe/OS/libs/mic/mic.cpp index 4bc6a969..44373e3f 100644 --- a/src/Cafe/OS/libs/mic/mic.cpp +++ b/src/Cafe/OS/libs/mic/mic.cpp @@ -1,5 +1,7 @@ #include "Cafe/OS/common/OSCommon.h" #include "input/InputManager.h" +#include "audio/IAudioInputAPI.h" +#include "config/CemuConfig.h" enum class MIC_RESULT { @@ -13,6 +15,7 @@ enum class MIC_RESULT #define MIC_SAMPLERATE 32000 +const int MIC_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000 enum class MIC_STATUS_FLAGS : uint32 { @@ -22,8 +25,8 @@ enum class MIC_STATUS_FLAGS : uint32 }; DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS); -#define MIC_HANDLE_DRC0 100 -#define MIC_HANDLE_DRC1 101 +#define MIC_HANDLE_DRC0 0 +#define MIC_HANDLE_DRC1 1 enum class MIC_STATEID { @@ -139,6 +142,36 @@ void micExport_MICInit(PPCInterpreter_t* hCPU) // return status memory_writeU32(hCPU->gpr[6], 0); // no error osLib_returnFromFunction(hCPU, (drcIndex==0)?MIC_HANDLE_DRC0:MIC_HANDLE_DRC1); // success + + auto& config = GetConfig(); + const auto audio_api = IAudioInputAPI::Cubeb; // change this if more input apis get implemented + + std::unique_lock lock(g_audioInputMutex); + if (!g_inputAudio) + { + IAudioInputAPI::DeviceDescriptionPtr device_description; + if (IAudioInputAPI::IsAudioInputAPIAvailable(audio_api)) + { + auto devices = IAudioInputAPI::GetDevices(audio_api); + const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.input_device; }); + if (it != devices.end()) + device_description = *it; + } + + if (device_description) + { + try + { + g_inputAudio = IAudioInputAPI::CreateDevice(audio_api, device_description, MIC_SAMPLERATE, 1, MIC_SAMPLES_PER_3MS_32KHZ, 16); + g_inputAudio->SetVolume(config.input_volume); + } + catch (std::runtime_error& ex) + { + forceLog_printf("can't initialize audio input: %s", ex.what()); + exit(0); + } + } + } } void micExport_MICOpen(PPCInterpreter_t* hCPU) @@ -172,6 +205,10 @@ void micExport_MICOpen(PPCInterpreter_t* hCPU) // success MICStatus.drc[drcIndex].isOpen = true; osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); + + std::shared_lock lock(g_audioInputMutex); + if(g_inputAudio) + g_inputAudio->Play(); } void micExport_MICClose(PPCInterpreter_t* hCPU) @@ -198,6 +235,10 @@ void micExport_MICClose(PPCInterpreter_t* hCPU) // success MICStatus.drc[drcIndex].isOpen = false; osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); + + std::shared_lock lock(g_audioInputMutex); + if (g_inputAudio) + g_inputAudio->Stop(); } void micExport_MICGetStatus(PPCInterpreter_t* hCPU) @@ -360,6 +401,17 @@ void micExport_MICSetDataConsumed(PPCInterpreter_t* hCPU) return; } +void mic_updateDevicePlayState(bool isPlaying) +{ + if (g_inputAudio) + { + if (isPlaying) + g_inputAudio->Play(); + else + g_inputAudio->Stop(); + } +} + void mic_updateOnAXFrame() { sint32 drcIndex = 0; @@ -368,26 +420,39 @@ void mic_updateOnAXFrame() drcIndex = 1; if (mic_isActive(1) == false) { + std::shared_lock lock(g_audioInputMutex); + mic_updateDevicePlayState(false); return; } } - const sint32 micSampleCount = 32000/32; - sint16 micSampleData[micSampleCount]; - - auto controller = InputManager::instance().get_vpad_controller(drcIndex); - if( controller && controller->is_mic_active() ) + std::shared_lock lock(g_audioInputMutex); + mic_updateDevicePlayState(true); + if (g_inputAudio) { - for(sint32 i=0; iConsumeBlock(micSampleData); + mic_feedSamples(0, micSampleData, MIC_SAMPLES_PER_3MS_32KHZ); } else { - memset(micSampleData, 0x00, sizeof(micSampleData)); + const sint32 micSampleCount = 32000 / 32; + sint16 micSampleData[micSampleCount]; + + auto controller = InputManager::instance().get_vpad_controller(drcIndex); + if( controller && controller->is_mic_active() ) + { + for(sint32 i=0; i:Debug>") @@ -26,6 +28,8 @@ if(ENABLE_CUBEB) target_sources(CemuAudio PRIVATE CubebAPI.cpp CubebAPI.h + CubebInputAPI.cpp + CubebInputAPI.h ) #add_compile_definitions(HAS_CUBEB) endif() diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp index 3c1b8ab1..4cfee666 100644 --- a/src/audio/CubebAPI.cpp +++ b/src/audio/CubebAPI.cpp @@ -9,7 +9,7 @@ #endif -void state_cb(cubeb_stream* stream, void* user, cubeb_state state) +static void state_cb(cubeb_stream* stream, void* user, cubeb_state state) { if (!stream) return; diff --git a/src/audio/CubebInputAPI.cpp b/src/audio/CubebInputAPI.cpp new file mode 100644 index 00000000..f22a541a --- /dev/null +++ b/src/audio/CubebInputAPI.cpp @@ -0,0 +1,216 @@ +#include "CubebInputAPI.h" + +#if BOOST_OS_WINDOWS +#include +#include +#include +#pragma comment(lib, "Avrt.lib") +#pragma comment(lib, "ksuser.lib") +#endif + +static void state_cb(cubeb_stream* stream, void* user, cubeb_state state) +{ + if (!stream) + return; + + /*switch (state) + { + case CUBEB_STATE_STARTED: + fprintf(stderr, "stream started\n"); + break; + case CUBEB_STATE_STOPPED: + fprintf(stderr, "stream stopped\n"); + break; + case CUBEB_STATE_DRAINED: + fprintf(stderr, "stream drained\n"); + break; + default: + fprintf(stderr, "unknown stream state %d\n", state); + }*/ +} + +long CubebInputAPI::data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes) +{ + auto* thisptr = (CubebInputAPI*)user; + + const auto size = (size_t)nframes * thisptr->m_channels * (thisptr->m_bitsPerSample / 8); + + std::unique_lock lock(thisptr->m_mutex); + if (thisptr->m_buffer.capacity() <= thisptr->m_buffer.size() + size) + { + forceLogDebug_printf("dropped input sound block since too many buffers are queued"); + return nframes; + } + + thisptr->m_buffer.insert(thisptr->m_buffer.end(), (uint8*)inputbuffer, (uint8*)inputbuffer + size); + + return nframes; +} + +CubebInputAPI::CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, + uint32 bits_per_sample) + : IAudioInputAPI(samplerate, channels, samples_per_block, bits_per_sample) +{ + cubeb_stream_params input_params; + + input_params.format = CUBEB_SAMPLE_S16LE; + input_params.rate = samplerate; + input_params.channels = channels; + input_params.prefs = CUBEB_STREAM_PREF_NONE; + + switch (channels) + { + case 8: + input_params.layout = CUBEB_LAYOUT_3F4_LFE; + break; + case 6: + input_params.layout = CUBEB_LAYOUT_QUAD_LFE | CHANNEL_FRONT_CENTER; + break; + case 4: + input_params.layout = CUBEB_LAYOUT_QUAD; + break; + case 2: + input_params.layout = CUBEB_LAYOUT_STEREO; + break; + default: + input_params.layout = CUBEB_LAYOUT_MONO; + break; + } + + uint32 latency = 1; + cubeb_get_min_latency(s_context, &input_params, &latency); + + m_buffer.reserve((size_t)m_bytesPerBlock * kBlockCount); + + if (cubeb_stream_init(s_context, &m_stream, "Cemu Cubeb input", + devid, &input_params, + nullptr, nullptr, + latency, data_cb, state_cb, this) != CUBEB_OK) + { + throw std::runtime_error("can't initialize cubeb device"); + } +} + +CubebInputAPI::~CubebInputAPI() +{ + if (m_stream) + { + Stop(); + cubeb_stream_destroy(m_stream); + } +} + +bool CubebInputAPI::ConsumeBlock(sint16* data) +{ + std::unique_lock lock(m_mutex); + if (m_buffer.empty()) + { + // we got no data, just write silence + memset(data, 0x00, m_bytesPerBlock); + } + else + { + const auto copied = std::min(m_buffer.size(), (size_t)m_bytesPerBlock); + memcpy(data, m_buffer.data(), copied); + m_buffer.erase(m_buffer.begin(), std::next(m_buffer.begin(), copied)); + lock.unlock(); + // fill rest with silence + if (copied != m_bytesPerBlock) + memset((uint8*)data + copied, 0x00, m_bytesPerBlock - copied); + } + + return true; +} + +bool CubebInputAPI::Play() +{ + if (m_is_playing) + return true; + + if (cubeb_stream_start(m_stream) == CUBEB_OK) + { + m_is_playing = true; + return true; + } + + return false; +} + +bool CubebInputAPI::Stop() +{ + if (!m_is_playing) + return true; + + if (cubeb_stream_stop(m_stream) == CUBEB_OK) + { + m_is_playing = false; + return true; + } + + return false; +} + +void CubebInputAPI::SetVolume(sint32 volume) +{ + IAudioInputAPI::SetVolume(volume); + cubeb_stream_set_volume(m_stream, (float)volume / 100.0f); +} + +bool CubebInputAPI::InitializeStatic() +{ +#if BOOST_OS_WINDOWS + s_com_initialized = (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))); +#endif + + if (cubeb_init(&s_context, "Cemu Input Cubeb", nullptr)) + { + cemuLog_force("can't create cubeb audio api"); + +#if BOOST_OS_WINDOWS + if (s_com_initialized) + { + CoUninitialize(); + s_com_initialized = false; + } +#endif + + return false; + } + + return true; +} + +void CubebInputAPI::Destroy() +{ + if (s_context) + cubeb_destroy(s_context); +#if BOOST_OS_WINDOWS + if (s_com_initialized) + CoUninitialize(); +#endif +} + +std::vector CubebInputAPI::GetDevices() +{ + cubeb_device_collection devices; + if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK) + return {}; + + std::vector result; + result.reserve(devices.count); + for (size_t i = 0; i < devices.count; ++i) + { + //const auto& device = devices.device[i]; + if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) + { + auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, + boost::nowide::widen( + devices.device[i].friendly_name)); + result.emplace_back(device); + } + } + + cubeb_device_collection_destroy(s_context, &devices); + + return result; +} diff --git a/src/audio/CubebInputAPI.h b/src/audio/CubebInputAPI.h new file mode 100644 index 00000000..a794c9ba --- /dev/null +++ b/src/audio/CubebInputAPI.h @@ -0,0 +1,52 @@ +#pragma once + +#include "IAudioInputAPI.h" + +#include + +class CubebInputAPI : public IAudioInputAPI +{ +public: + class CubebDeviceDescription : public DeviceDescription + { + public: + CubebDeviceDescription(cubeb_devid devid, std::string device_id, const std::wstring& name) + : DeviceDescription(name), m_devid(devid), m_device_id(std::move(device_id)) { } + + std::wstring GetIdentifier() const override { return boost::nowide::widen(m_device_id); } + cubeb_devid GetDeviceId() const { return m_devid; } + + private: + cubeb_devid m_devid; + std::string m_device_id; + }; + + using CubebDeviceDescriptionPtr = std::shared_ptr; + + CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); + ~CubebInputAPI(); + + AudioInputAPI GetType() const override { return Cubeb; } + + bool ConsumeBlock(sint16* data) override; + bool Play() override; + bool Stop() override; + bool IsPlaying() const override { return m_is_playing; }; + void SetVolume(sint32 volume) override; + + static std::vector GetDevices(); + + static bool InitializeStatic(); + static void Destroy(); + +private: + inline static bool s_com_initialized = false; + inline static cubeb* s_context = nullptr; + + cubeb_stream* m_stream = nullptr; + bool m_is_playing = false; + + mutable std::shared_mutex m_mutex; + std::vector m_buffer; + static long data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes); +}; diff --git a/src/audio/IAudioInputAPI.cpp b/src/audio/IAudioInputAPI.cpp new file mode 100644 index 00000000..d20dd505 --- /dev/null +++ b/src/audio/IAudioInputAPI.cpp @@ -0,0 +1,66 @@ +#include "IAudioInputAPI.h" +#include "CubebInputAPI.h" + +std::shared_mutex g_audioInputMutex; +AudioInputAPIPtr g_inputAudio; + +std::array IAudioInputAPI::s_availableApis{}; + +IAudioInputAPI::IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) + : m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample) +{ + m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); +} + +void IAudioInputAPI::PrintLogging() +{ + forceLog_printf("------- Init Audio Input backend -------"); + forceLog_printf("Cubeb: %s", s_availableApis[Cubeb] ? "available" : "not supported"); +} + +void IAudioInputAPI::InitializeStatic() +{ + s_availableApis[Cubeb] = CubebInputAPI::InitializeStatic(); +} + +bool IAudioInputAPI::IsAudioInputAPIAvailable(AudioInputAPI api) +{ + if ((size_t)api < s_availableApis.size()) + return s_availableApis[api]; + + cemu_assert_debug(false); + return false; +} + +AudioInputAPIPtr IAudioInputAPI::CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) +{ + if (!IsAudioInputAPIAvailable(api)) + return {}; + + switch(api) + { + case Cubeb: + { + const auto tmp = std::dynamic_pointer_cast(device); + return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); + } + default: + throw std::runtime_error(fmt::format("invalid audio api: {}", api)); + } +} + +std::vector IAudioInputAPI::GetDevices(AudioInputAPI api) +{ + if (!IsAudioInputAPIAvailable(api)) + return {}; + + switch(api) + { + case Cubeb: + { + return CubebInputAPI::GetDevices(); + } + default: + throw std::runtime_error(fmt::format("invalid audio api: {}", api)); + } +} diff --git a/src/audio/IAudioInputAPI.h b/src/audio/IAudioInputAPI.h new file mode 100644 index 00000000..66c43165 --- /dev/null +++ b/src/audio/IAudioInputAPI.h @@ -0,0 +1,71 @@ +#pragma once + +class IAudioInputAPI +{ + friend class GeneralSettings2; + +public: + class DeviceDescription + { + public: + explicit DeviceDescription(std::wstring name) + : m_name(std::move(name)) { } + + virtual ~DeviceDescription() = default; + virtual std::wstring GetIdentifier() const = 0; + const std::wstring& GetName() const { return m_name; } + + bool operator==(const DeviceDescription& o) const + { + return GetIdentifier() == o.GetIdentifier(); + } + + private: + std::wstring m_name; + }; + + using DeviceDescriptionPtr = std::shared_ptr; + + enum AudioInputAPI + { + Cubeb, + + AudioInputAPIEnd, + }; + static constexpr uint32 kBlockCount = 24; + + IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); + virtual ~IAudioInputAPI() = default; + virtual AudioInputAPI GetType() const = 0; + + sint32 GetChannels() const { return m_channels; } + + virtual sint32 GetVolume() const { return m_volume; } + virtual void SetVolume(sint32 volume) { m_volume = volume; } + + virtual bool ConsumeBlock(sint16* data) = 0; + virtual bool Play() = 0; + virtual bool Stop() = 0; + virtual bool IsPlaying() const = 0; + + static void PrintLogging(); + static void InitializeStatic(); + static bool IsAudioInputAPIAvailable(AudioInputAPI api); + + static std::unique_ptr CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); + static std::vector GetDevices(AudioInputAPI api); + +protected: + uint32 m_samplerate, m_channels, m_samplesPerBlock, m_bitsPerSample; + uint32 m_bytesPerBlock; + + sint32 m_volume = 0; + + static std::array s_availableApis; + +private: +}; + +using AudioInputAPIPtr = std::unique_ptr; +extern std::shared_mutex g_audioInputMutex; +extern AudioInputAPIPtr g_inputAudio; diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 84324418..3774c1fa 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -295,8 +295,10 @@ void CemuConfig::Load(XMLConfigParser& parser) audio_delay = audio.get("delay", 2); tv_channels = audio.get("TVChannels", kStereo); pad_channels = audio.get("PadChannels", kStereo); + input_channels = audio.get("InputChannels", kMono); tv_volume = audio.get("TVVolume", 20); pad_volume = audio.get("PadVolume", 0); + input_volume = audio.get("InputVolume", 20); const auto tv = audio.get("TVDevice", ""); try @@ -318,6 +320,16 @@ void CemuConfig::Load(XMLConfigParser& parser) forceLog_printf("config load error: can't load pad device: %s", pad); } + const auto input_device_name = audio.get("InputDevice", ""); + try + { + input_device = boost::nowide::widen(input_device_name); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load input device: %s", input_device_name); + } + // account auto acc = parser.get("Account"); account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); @@ -488,10 +500,13 @@ void CemuConfig::Save(XMLConfigParser& parser) audio.set("delay", audio_delay); audio.set("TVChannels", tv_channels); audio.set("PadChannels", pad_channels); + audio.set("InputChannels", input_channels); audio.set("TVVolume", tv_volume); audio.set("PadVolume", pad_volume); + audio.set("InputVolume", input_volume); audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str()); audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str()); + audio.set("InputDevice", boost::nowide::narrow(input_device).c_str()); // account auto acc = config.set("Account"); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 0dc0843b..4de2001b 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -462,9 +462,9 @@ struct CemuConfig // audio sint32 audio_api = 0; sint32 audio_delay = 2; - AudioChannels tv_channels = kStereo, pad_channels = kStereo; - sint32 tv_volume = 50, pad_volume = 0; - std::wstring tv_device{ L"default" }, pad_device; + AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono; + sint32 tv_volume = 50, pad_volume = 0, input_volume = 50; + std::wstring tv_device{ L"default" }, pad_device, input_device; // account struct diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index fb993b57..0216a080 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -23,6 +23,8 @@ #endif #include "audio/CubebAPI.h" +#include "audio/IAudioInputAPI.h" + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/Account/Account.h" @@ -70,6 +72,15 @@ private: IAudioAPI::DeviceDescriptionPtr m_description; }; +class wxInputDeviceDescription : public wxClientData +{ +public: + wxInputDeviceDescription(const IAudioInputAPI::DeviceDescriptionPtr& description) : m_description(description) {} + const IAudioInputAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; } +private: + IAudioInputAPI::DeviceDescriptionPtr m_description; +}; + class wxVulkanUUID : public wxClientData { public: @@ -424,6 +435,47 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } + { + auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Microphone")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto audio_input_row = new wxFlexGridSizer(0, 3, 0, 0); + audio_input_row->SetFlexibleDirection(wxBOTH); + audio_input_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_input_device = new wxChoice(box, wxID_ANY, wxDefaultPosition); + m_input_device->SetMinSize(wxSize(300, -1)); + m_input_device->SetToolTip(_("Select the active audio input device for Wii U GamePad")); + audio_input_row->Add(m_input_device, 0, wxEXPAND | wxALL, 5); + audio_input_row->AddSpacer(0); + + m_input_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); + + const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only + + audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_input_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices); + + m_input_channels->SetSelection(0); // set default to stereo + + m_input_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); + audio_input_row->Add(m_input_channels, 0, wxEXPAND | wxALL, 5); + audio_input_row->AddSpacer(0); + + audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); + audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5); + auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%")); + audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); + + m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text)); + m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); + + box_sizer->Add(audio_input_row, 1, wxEXPAND, 5); + audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + audio_panel->SetSizerAndFit(audio_panel_sizer); return audio_panel; } @@ -857,9 +909,12 @@ void GeneralSettings2::StoreConfig() config.tv_channels = (AudioChannels)m_tv_channels->GetSelection(); //config.pad_channels = (AudioChannels)m_pad_channels->GetSelection(); config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection(); + //config.input_channels = (AudioChannels)m_input_channels->GetSelection(); + config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection(); config.tv_volume = m_tv_volume->GetValue(); config.pad_volume = m_pad_volume->GetValue(); + config.input_volume = m_input_volume->GetValue(); config.tv_device.clear(); const auto tv_device = m_tv_device->GetSelection(); @@ -879,6 +934,15 @@ void GeneralSettings2::StoreConfig() config.pad_device = device_description->GetDescription()->GetIdentifier(); } + config.input_device = L""; + const auto input_device = m_input_device->GetSelection(); + if (input_device != wxNOT_FOUND && input_device != 0 && m_input_device->HasClientObjectData()) + { + const auto* device_description = (wxDeviceDescription*)m_input_device->GetClientObject(input_device); + if (device_description) + config.input_device = device_description->GetDescription()->GetIdentifier(); + } + // graphics config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection(); @@ -977,19 +1041,29 @@ void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event) void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) { - std::shared_lock lock(g_audioMutex); - if(event.GetEventObject() == m_pad_volume) + + if(event.GetEventObject() == m_input_volume) { - if (g_padAudio) - { - g_padAudio->SetVolume(event.GetInt()); - g_padVolume = event.GetInt(); - } + std::shared_lock lock(g_audioInputMutex); + if (g_inputAudio) + g_inputAudio->SetVolume(event.GetInt()); } else { - if (g_tvAudio) - g_tvAudio->SetVolume(event.GetInt()); + std::shared_lock lock(g_audioMutex); + if(event.GetEventObject() == m_pad_volume) + { + if (g_padAudio) + { + g_padAudio->SetVolume(event.GetInt()); + g_padVolume = event.GetInt(); + } + } + else + { + if (g_tvAudio) + g_tvAudio->SetVolume(event.GetInt()); + } } @@ -1048,9 +1122,11 @@ void GeneralSettings2::UpdateAudioDeviceList() { m_tv_device->Clear(); m_pad_device->Clear(); + m_input_device->Clear(); m_tv_device->Append(_("Disabled")); m_pad_device->Append(_("Disabled")); + m_input_device->Append(_("Disabled")); const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api; const auto devices = IAudioAPI::GetDevices(audio_api); @@ -1060,6 +1136,14 @@ void GeneralSettings2::UpdateAudioDeviceList() m_pad_device->Append(device->GetName(), new wxDeviceDescription(device)); } + const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api; + const auto input_devices = IAudioInputAPI::GetDevices(input_audio_api); + + for (auto& device : input_devices) + { + m_input_device->Append(device->GetName(), new wxInputDeviceDescription(device)); + } + if(m_tv_device->GetCount() > 1) m_tv_device->SetSelection(1); else @@ -1067,6 +1151,8 @@ void GeneralSettings2::UpdateAudioDeviceList() m_pad_device->SetSelection(0); + m_input_device->SetSelection(0); + // todo reset global instance of audio device } @@ -1453,6 +1539,8 @@ void GeneralSettings2::ApplyConfig() m_tv_channels->SetSelection(config.tv_channels); //m_pad_channels->SetSelection(config.pad_channels); m_pad_channels->SetSelection(0); + //m_input_channels->SetSelection(config.pad_channels); + m_input_channels->SetSelection(0); SendSliderEvent(m_tv_volume, config.tv_volume); @@ -1487,6 +1575,22 @@ void GeneralSettings2::ApplyConfig() else m_pad_device->SetSelection(0); + SendSliderEvent(m_input_volume, config.input_volume); + if (!config.input_device.empty() && m_input_device->HasClientObjectData()) + { + for (uint32 i = 0; i < m_input_device->GetCount(); ++i) + { + const auto device_description = (wxInputDeviceDescription*)m_input_device->GetClientObject(i); + if (device_description && config.input_device == device_description->GetDescription()->GetIdentifier()) + { + m_input_device->SetSelection(i); + break; + } + } + } + else + m_input_device->SetSelection(0); + // account UpdateOnlineAccounts(); m_active_account->SetSelection(0); @@ -1551,6 +1655,9 @@ void GeneralSettings2::UpdateAudioDevice() { auto& config = GetConfig(); + std::unique_lock lock(g_audioMutex); + std::unique_lock inputLock(g_audioInputMutex); + // tv audio device { const auto selection = m_tv_device->GetSelection(); @@ -1560,13 +1667,13 @@ void GeneralSettings2::UpdateAudioDevice() return; } + g_tvAudio.reset(); + if (m_tv_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection); if (description) { - std::unique_lock lock(g_audioMutex); - sint32 channels; if (m_game_launched && g_tvAudio) channels = g_tvAudio->GetChannels(); @@ -1588,7 +1695,6 @@ void GeneralSettings2::UpdateAudioDevice() try { - g_tvAudio.reset(); g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_tvAudio->SetVolume(m_tv_volume->GetValue()); } @@ -1609,13 +1715,13 @@ void GeneralSettings2::UpdateAudioDevice() return; } + g_padAudio.reset(); + if (m_pad_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection); if (description) { - std::unique_lock lock(g_audioMutex); - sint32 channels; if (m_game_launched && g_padAudio) channels = g_padAudio->GetChannels(); @@ -1637,7 +1743,6 @@ void GeneralSettings2::UpdateAudioDevice() try { - g_padAudio.reset(); g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_padAudio->SetVolume(m_pad_volume->GetValue()); g_padVolume = m_pad_volume->GetValue(); @@ -1649,6 +1754,54 @@ void GeneralSettings2::UpdateAudioDevice() } } } + + // input audio device + { + const auto selection = m_input_device->GetSelection(); + if (selection == wxNOT_FOUND) + { + cemu_assert_debug(false); + return; + } + + g_inputAudio.reset(); + + if (m_input_device->HasClientObjectData()) + { + const auto description = (wxInputDeviceDescription*)m_input_device->GetClientObject(selection); + if (description) + { + sint32 channels; + if (m_game_launched && g_inputAudio) + channels = g_inputAudio->GetChannels(); + else + { + switch (config.input_channels) + { + case 0: + channels = 1; + break; + case 2: + channels = 6; + break; + default: // stereo + channels = 2; + break; + } + } + + try + { + g_inputAudio = IAudioInputAPI::CreateDevice(IAudioInputAPI::AudioInputAPI::Cubeb, description->GetDescription(), 32000, channels, snd_core::AX_SAMPLES_PER_3MS_32KHZ, 16); + g_inputAudio->SetVolume(m_input_volume->GetValue()); + } + catch (std::runtime_error& ex) + { + forceLog_printf("can't initialize pad audio: %s", ex.what()); + } + } + } + } } void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event) diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index fb3e8092..0ae732eb 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -58,9 +58,9 @@ private: // Audio wxChoice* m_audio_api; wxSlider *m_audio_latency; - wxSlider *m_tv_volume, *m_pad_volume; - wxChoice *m_tv_channels, *m_pad_channels; - wxChoice *m_tv_device, *m_pad_device; + wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume; + wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels; + wxChoice *m_tv_device, *m_pad_device, *m_input_device; // Account wxButton* m_create_account, * m_delete_account; diff --git a/src/main.cpp b/src/main.cpp index 801683d1..1566a83f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "gui/guiWrapper.h" +#include "gui/guiWrapper.h" #include "gui/wxgui.h" #include "util/crypto/aes128.h" #include "gui/MainWindow.h" @@ -29,6 +29,7 @@ #include "Cafe/OS/libs/vpad/vpad.h" #include "audio/IAudioAPI.h" +#include "audio/IAudioInputAPI.h" #if BOOST_OS_WINDOWS #pragma comment(lib,"Dbghelp.lib") #endif @@ -223,6 +224,7 @@ void mainEmulatorCommonInit() rplSymbolStorage_init(); // static initialization IAudioAPI::InitializeStatic(); + IAudioInputAPI::InitializeStatic(); // load graphic packs (must happen before config is loaded) GraphicPack2::LoadAll(); // initialize file system