mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-16 04:07:51 +00:00
IOS/USB: Emulate Wii Speak using cubeb
Based on @noahpistilli (Sketch) PR: https://github.com/dolphin-emu/dolphin/pull/12567 Fixed the Windows support and the heisenbug caused by uninitialized members. Config system integration finalized.
This commit is contained in:
parent
451e36defc
commit
1ac40f25a2
@ -72,3 +72,101 @@ std::shared_ptr<cubeb> CubebUtils::GetContext()
|
||||
weak = shared = {ctx, DestroyContext};
|
||||
return shared;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> CubebUtils::ListInputDevices()
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> devices;
|
||||
|
||||
cubeb_device_collection collection;
|
||||
auto cubeb_ctx = CubebUtils::GetContext();
|
||||
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_INPUT, &collection);
|
||||
|
||||
if (r != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG_FMT(AUDIO, "Error listing cubeb input devices");
|
||||
return devices;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(AUDIO, "Listing cubeb input devices:");
|
||||
for (uint32_t i = 0; i < collection.count; i++)
|
||||
{
|
||||
const auto& info = collection.device[i];
|
||||
const auto device_state = info.state;
|
||||
const char* state_name = [device_state] {
|
||||
switch (device_state)
|
||||
{
|
||||
case CUBEB_DEVICE_STATE_DISABLED:
|
||||
return "disabled";
|
||||
case CUBEB_DEVICE_STATE_UNPLUGGED:
|
||||
return "unplugged";
|
||||
case CUBEB_DEVICE_STATE_ENABLED:
|
||||
return "enabled";
|
||||
default:
|
||||
return "unknown?";
|
||||
}
|
||||
}();
|
||||
|
||||
// According to cubeb_device_info definition in cubeb.h:
|
||||
// > "Optional vendor name, may be NULL."
|
||||
// In practice, it seems some other fields might be NULL as well.
|
||||
static constexpr auto fmt_str = [](const char* ptr) constexpr -> const char* {
|
||||
return (ptr == nullptr) ? "(null)" : ptr;
|
||||
};
|
||||
|
||||
INFO_LOG_FMT(AUDIO,
|
||||
"[{}] Device ID: {}\n"
|
||||
"\tName: {}\n"
|
||||
"\tGroup ID: {}\n"
|
||||
"\tVendor: {}\n"
|
||||
"\tState: {}",
|
||||
i, fmt_str(info.device_id), fmt_str(info.friendly_name), fmt_str(info.group_id),
|
||||
fmt_str(info.vendor_name), state_name);
|
||||
|
||||
if (info.device_id == nullptr)
|
||||
continue; // Shouldn't happen
|
||||
|
||||
if (info.state == CUBEB_DEVICE_STATE_ENABLED)
|
||||
{
|
||||
devices.emplace_back(info.device_id, fmt_str(info.friendly_name));
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
cubeb_devid CubebUtils::GetInputDeviceById(std::string_view id)
|
||||
{
|
||||
if (id.empty())
|
||||
return nullptr;
|
||||
|
||||
cubeb_device_collection collection;
|
||||
auto cubeb_ctx = CubebUtils::GetContext();
|
||||
const int r = cubeb_enumerate_devices(cubeb_ctx.get(), CUBEB_DEVICE_TYPE_INPUT, &collection);
|
||||
|
||||
if (r != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG_FMT(AUDIO, "Error enumerating cubeb input devices");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cubeb_devid device_id = nullptr;
|
||||
for (uint32_t i = 0; i < collection.count; i++)
|
||||
{
|
||||
const auto& info = collection.device[i];
|
||||
if (id.compare(info.device_id) == 0)
|
||||
{
|
||||
device_id = info.devid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (device_id == nullptr)
|
||||
{
|
||||
WARN_LOG_FMT(AUDIO, "Failed to find selected input device, defaulting to system preferences");
|
||||
}
|
||||
|
||||
cubeb_device_collection_destroy(cubeb_ctx.get(), &collection);
|
||||
|
||||
return device_id;
|
||||
}
|
||||
|
@ -4,10 +4,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
struct cubeb;
|
||||
|
||||
namespace CubebUtils
|
||||
{
|
||||
std::shared_ptr<cubeb> GetContext();
|
||||
std::vector<std::pair<std::string, std::string>> ListInputDevices();
|
||||
const void* GetInputDeviceById(std::string_view id);
|
||||
} // namespace CubebUtils
|
||||
|
@ -597,8 +597,11 @@ const Info<bool> MAIN_EMULATE_INFINITY_BASE{
|
||||
const Info<bool> MAIN_EMULATE_WII_SPEAK{{System::Main, "EmulatedUSBDevices", "EmulateWiiSpeak"},
|
||||
false};
|
||||
|
||||
const Info<std::string> MAIN_WII_SPEAK_MICROPHONE{{System::Main, "General", "WiiSpeakMicrophone"},
|
||||
""};
|
||||
const Info<std::string> MAIN_WII_SPEAK_MICROPHONE{
|
||||
{System::Main, "EmulatedUSBDevices", "WiiSpeakMicrophone"}, ""};
|
||||
|
||||
const Info<bool> MAIN_WII_SPEAK_CONNECTED{{System::Main, "EmulatedUSBDevices", "WiiSpeakConnected"},
|
||||
false};
|
||||
|
||||
// The reason we need this function is because some memory card code
|
||||
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
|
||||
|
@ -364,6 +364,7 @@ extern const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL;
|
||||
extern const Info<bool> MAIN_EMULATE_INFINITY_BASE;
|
||||
extern const Info<bool> MAIN_EMULATE_WII_SPEAK;
|
||||
extern const Info<std::string> MAIN_WII_SPEAK_MICROPHONE;
|
||||
extern const Info<bool> MAIN_WII_SPEAK_CONNECTED;
|
||||
|
||||
// GameCube path utility functions
|
||||
|
||||
|
@ -3,89 +3,222 @@
|
||||
|
||||
#include "Core/IOS/USB/Emulated/Microphone.h"
|
||||
|
||||
#include "Common/Swap.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
#include "AudioCommon/CubebUtils.h"
|
||||
#endif
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Objbase.h>
|
||||
#endif
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
std::vector<std::string> Microphone::ListDevices()
|
||||
Microphone::Microphone()
|
||||
{
|
||||
std::vector<std::string> devices{};
|
||||
const ALchar* pDeviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||
while (*pDeviceList)
|
||||
#if defined(_WIN32) && defined(HAVE_CUBEB)
|
||||
m_work_queue.PushBlocking([this] {
|
||||
const auto result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
m_coinit_success = result == S_OK;
|
||||
m_should_couninit = m_coinit_success || result == S_FALSE;
|
||||
});
|
||||
#endif
|
||||
|
||||
StreamInit();
|
||||
}
|
||||
|
||||
Microphone::~Microphone()
|
||||
{
|
||||
StreamTerminate();
|
||||
|
||||
#if defined(_WIN32) && defined(HAVE_CUBEB)
|
||||
if (m_should_couninit)
|
||||
{
|
||||
devices.emplace_back(pDeviceList);
|
||||
pDeviceList += strlen(pDeviceList) + 1;
|
||||
m_work_queue.PushBlocking([this] {
|
||||
m_should_couninit = false;
|
||||
CoUninitialize();
|
||||
});
|
||||
}
|
||||
|
||||
return devices;
|
||||
m_coinit_success = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int Microphone::OpenMicrophone()
|
||||
#ifndef HAVE_CUBEB
|
||||
void Microphone::StreamInit()
|
||||
{
|
||||
m_device = alcCaptureOpenDevice(nullptr, SAMPLING_RATE, AL_FORMAT_MONO16, BUFFER_SIZE);
|
||||
m_dsp_data.resize(BUFFER_SIZE, 0);
|
||||
m_temp_buffer.resize(BUFFER_SIZE, 0);
|
||||
return static_cast<int>(alcGetError(m_device));
|
||||
}
|
||||
|
||||
int Microphone::StartCapture()
|
||||
void Microphone::StreamStart()
|
||||
{
|
||||
alcCaptureStart(m_device);
|
||||
return static_cast<int>(alcGetError(m_device));
|
||||
}
|
||||
|
||||
void Microphone::StopCapture()
|
||||
void Microphone::StreamStop()
|
||||
{
|
||||
alcCaptureStop(m_device);
|
||||
}
|
||||
|
||||
void Microphone::PerformAudioCapture()
|
||||
void Microphone::StreamTerminate()
|
||||
{
|
||||
m_num_of_samples = BUFFER_SIZE / 2;
|
||||
|
||||
ALCint samples_in{};
|
||||
alcGetIntegerv(m_device, ALC_CAPTURE_SAMPLES, 1, &samples_in);
|
||||
m_num_of_samples = std::min(m_num_of_samples, static_cast<u32>(samples_in));
|
||||
|
||||
if (m_num_of_samples == 0)
|
||||
return;
|
||||
|
||||
alcCaptureSamples(m_device, m_dsp_data.data(), m_num_of_samples);
|
||||
}
|
||||
|
||||
void Microphone::ByteSwap(const void* src, void* dst) const
|
||||
#else
|
||||
void Microphone::StreamInit()
|
||||
{
|
||||
*static_cast<u16*>(dst) = Common::swap16(*static_cast<const u16*>(src));
|
||||
}
|
||||
|
||||
void Microphone::GetSoundData()
|
||||
{
|
||||
if (m_num_of_samples == 0)
|
||||
return;
|
||||
|
||||
u8* ptr = const_cast<u8*>(m_temp_buffer.data());
|
||||
// Convert LE to BE
|
||||
for (u32 i = 0; i < m_num_of_samples; i++)
|
||||
#ifdef _WIN32
|
||||
if (!m_coinit_success)
|
||||
{
|
||||
for (u32 indchan = 0; indchan < 1; indchan++)
|
||||
ERROR_LOG_FMT(IOS_USB, "Failed to init Wii Speak stream");
|
||||
return;
|
||||
}
|
||||
m_work_queue.PushBlocking([this] {
|
||||
#endif
|
||||
m_cubeb_ctx = CubebUtils::GetContext();
|
||||
#ifdef _WIN32
|
||||
});
|
||||
#endif
|
||||
|
||||
// TODO: Not here but rather inside the WiiSpeak device if possible?
|
||||
StreamStart();
|
||||
}
|
||||
|
||||
void Microphone::StreamTerminate()
|
||||
{
|
||||
StreamStop();
|
||||
|
||||
if (m_cubeb_ctx)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_coinit_success)
|
||||
return;
|
||||
m_work_queue.PushBlocking([this] {
|
||||
#endif
|
||||
m_cubeb_ctx.reset();
|
||||
#ifdef _WIN32
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state)
|
||||
{
|
||||
}
|
||||
|
||||
void Microphone::StreamStart()
|
||||
{
|
||||
if (!m_cubeb_ctx)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!m_coinit_success)
|
||||
return;
|
||||
m_work_queue.PushBlocking([this] {
|
||||
#endif
|
||||
cubeb_stream_params params{};
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.rate = SAMPLING_RATE;
|
||||
params.channels = 1;
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
|
||||
u32 minimum_latency;
|
||||
if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK)
|
||||
{
|
||||
const u32 curindex = (i * 2) + indchan * (16 / 8);
|
||||
ByteSwap(m_dsp_data.data() + curindex, ptr + curindex);
|
||||
WARN_LOG_FMT(IOS_USB, "Error getting minimum latency");
|
||||
minimum_latency = 16;
|
||||
}
|
||||
|
||||
cubeb_devid input_device =
|
||||
CubebUtils::GetInputDeviceById(Config::Get(Config::MAIN_WII_SPEAK_MICROPHONE));
|
||||
if (cubeb_stream_init(m_cubeb_ctx.get(), &m_cubeb_stream, "Dolphin Emulated Wii Speak",
|
||||
input_device, ¶ms, nullptr, nullptr,
|
||||
std::max<u32>(16, minimum_latency), CubebDataCallback, StateCallback,
|
||||
this) != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Error initializing cubeb stream");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cubeb_stream_start(m_cubeb_stream) != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Error starting cubeb stream");
|
||||
return;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(IOS_USB, "started cubeb stream");
|
||||
#ifdef _WIN32
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void Microphone::StreamStop()
|
||||
{
|
||||
if (m_cubeb_stream)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_work_queue.PushBlocking([this] {
|
||||
#endif
|
||||
if (cubeb_stream_stop(m_cubeb_stream) != CUBEB_OK)
|
||||
ERROR_LOG_FMT(IOS_USB, "Error stopping cubeb stream");
|
||||
cubeb_stream_destroy(m_cubeb_stream);
|
||||
m_cubeb_stream = nullptr;
|
||||
#ifdef _WIN32
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
long Microphone::CubebDataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* /*output_buffer*/, long nframes)
|
||||
{
|
||||
auto* mic = static_cast<Microphone*>(user_data);
|
||||
return mic->DataCallback(static_cast<const s16*>(input_buffer), nframes);
|
||||
}
|
||||
|
||||
long Microphone::DataCallback(const s16* input_buffer, long nframes)
|
||||
{
|
||||
std::lock_guard lock(m_ring_lock);
|
||||
|
||||
const s16* buff_in = static_cast<const s16*>(input_buffer);
|
||||
for (long i = 0; i < nframes; i++)
|
||||
{
|
||||
m_stream_buffer[m_stream_wpos] = Common::swap16(buff_in[i]);
|
||||
m_stream_wpos = (m_stream_wpos + 1) % STREAM_SIZE;
|
||||
}
|
||||
|
||||
m_rbuf_dsp.write_bytes(ptr, m_num_of_samples * 2);
|
||||
m_samples_avail += nframes;
|
||||
if (m_samples_avail > STREAM_SIZE)
|
||||
{
|
||||
m_samples_avail = STREAM_SIZE;
|
||||
}
|
||||
|
||||
return nframes;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Microphone::ReadIntoBuffer(u8* dst, u32 size)
|
||||
{
|
||||
m_rbuf_dsp.read_bytes(dst, size);
|
||||
std::lock_guard lock(m_ring_lock);
|
||||
|
||||
if (m_samples_avail >= BUFF_SIZE_SAMPLES)
|
||||
{
|
||||
u8* last_buffer = reinterpret_cast<u8*>(&m_stream_buffer[m_stream_rpos]);
|
||||
std::memcpy(dst, static_cast<u8*>(last_buffer), size);
|
||||
|
||||
m_samples_avail -= BUFF_SIZE_SAMPLES;
|
||||
|
||||
m_stream_rpos += BUFF_SIZE_SAMPLES;
|
||||
m_stream_rpos %= STREAM_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
bool Microphone::HasData() const
|
||||
{
|
||||
return m_num_of_samples != 0;
|
||||
std::lock_guard lock(m_ring_lock);
|
||||
return m_samples_avail > 0 && Config::Get(Config::MAIN_WII_SPEAK_CONNECTED);
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
||||
|
@ -3,111 +3,65 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "AudioCommon/CubebUtils.h"
|
||||
|
||||
struct cubeb;
|
||||
struct cubeb_stream;
|
||||
#endif
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
template <size_t S>
|
||||
class simple_ringbuf
|
||||
{
|
||||
public:
|
||||
simple_ringbuf() { m_container.resize(S); }
|
||||
|
||||
bool has_data() const { return m_used != 0; }
|
||||
|
||||
u32 read_bytes(u8* buf, const u32 size)
|
||||
{
|
||||
u32 to_read = size > m_used ? m_used : size;
|
||||
if (!to_read)
|
||||
return 0;
|
||||
|
||||
u8* data = m_container.data();
|
||||
u32 new_tail = m_tail + to_read;
|
||||
|
||||
if (new_tail >= S)
|
||||
{
|
||||
u32 first_chunk_size = S - m_tail;
|
||||
std::memcpy(buf, data + m_tail, first_chunk_size);
|
||||
std::memcpy(buf + first_chunk_size, data, to_read - first_chunk_size);
|
||||
m_tail = (new_tail - S);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buf, data + m_tail, to_read);
|
||||
m_tail = new_tail;
|
||||
}
|
||||
|
||||
m_used -= to_read;
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
void write_bytes(const u8* buf, const u32 size)
|
||||
{
|
||||
if (u32 over_size = m_used + size; over_size > S)
|
||||
{
|
||||
m_tail += (over_size - S);
|
||||
if (m_tail > S)
|
||||
m_tail -= S;
|
||||
|
||||
m_used = S;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_used = over_size;
|
||||
}
|
||||
|
||||
u8* data = m_container.data();
|
||||
u32 new_head = m_head + size;
|
||||
|
||||
if (new_head >= S)
|
||||
{
|
||||
u32 first_chunk_size = S - m_head;
|
||||
std::memcpy(data + m_head, buf, first_chunk_size);
|
||||
std::memcpy(data, buf + first_chunk_size, size - first_chunk_size);
|
||||
m_head = (new_head - S);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(data + m_head, buf, size);
|
||||
m_head = new_head;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<u8> m_container;
|
||||
u32 m_head = 0, m_tail = 0, m_used = 0;
|
||||
};
|
||||
|
||||
class Microphone final
|
||||
{
|
||||
public:
|
||||
static std::vector<std::string> ListDevices();
|
||||
Microphone();
|
||||
~Microphone();
|
||||
|
||||
int OpenMicrophone();
|
||||
int StartCapture();
|
||||
void StopCapture();
|
||||
void PerformAudioCapture();
|
||||
void GetSoundData();
|
||||
void ReadIntoBuffer(u8* dst, u32 size);
|
||||
bool HasData() const;
|
||||
void ReadIntoBuffer(u8* dst, u32 size);
|
||||
|
||||
private:
|
||||
void ByteSwap(const void* src, void* dst) const;
|
||||
#ifdef HAVE_CUBEB
|
||||
static long CubebDataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long nframes);
|
||||
#endif
|
||||
|
||||
long DataCallback(const s16* input_buffer, long nframes);
|
||||
|
||||
void StreamInit();
|
||||
void StreamTerminate();
|
||||
void StreamStart();
|
||||
void StreamStop();
|
||||
|
||||
static constexpr u32 SAMPLING_RATE = 8000;
|
||||
static constexpr u32 BUFFER_SIZE = SAMPLING_RATE / 2;
|
||||
static constexpr u32 BUFF_SIZE_SAMPLES = 16;
|
||||
static constexpr u32 STREAM_SIZE = BUFF_SIZE_SAMPLES * 500;
|
||||
|
||||
ALCdevice* m_device;
|
||||
u32 m_num_of_samples{};
|
||||
std::vector<u8> m_dsp_data{};
|
||||
std::vector<u8> m_temp_buffer{};
|
||||
simple_ringbuf<BUFFER_SIZE> m_rbuf_dsp;
|
||||
std::array<s16, STREAM_SIZE> m_stream_buffer{};
|
||||
u32 m_stream_wpos = 0;
|
||||
u32 m_stream_rpos = 0;
|
||||
u32 m_samples_avail = 0;
|
||||
|
||||
mutable std::mutex m_ring_lock;
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
std::shared_ptr<cubeb> m_cubeb_ctx = nullptr;
|
||||
cubeb_stream* m_cubeb_stream = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
Common::AsyncWorkThread m_work_queue{"Wii Speak Worker"};
|
||||
bool m_coinit_success = false;
|
||||
bool m_should_couninit = false;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "Core/IOS/USB/Emulated/WiiSpeak.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
@ -10,67 +11,10 @@ namespace IOS::HLE::USB
|
||||
{
|
||||
WiiSpeak::WiiSpeak()
|
||||
{
|
||||
m_vid = 0x57E;
|
||||
m_pid = 0x0308;
|
||||
m_id = (u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1));
|
||||
m_device_descriptor =
|
||||
DeviceDescriptor{0x12, 0x1, 0x200, 0, 0, 0, 0x10, 0x57E, 0x0308, 0x0214, 0x1, 0x2, 0x0, 0x1};
|
||||
m_config_descriptor.emplace_back(ConfigDescriptor{0x9, 0x2, 0x0030, 0x1, 0x1, 0x0, 0x80, 0x32});
|
||||
m_interface_descriptor.emplace_back(
|
||||
InterfaceDescriptor{0x9, 0x4, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0x0});
|
||||
m_interface_descriptor.emplace_back(
|
||||
InterfaceDescriptor{0x9, 0x4, 0x0, 0x01, 0x03, 0xFF, 0xFF, 0xFF, 0x0});
|
||||
m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x81, 0x1, 0x0020, 0x1});
|
||||
m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x2, 0x2, 0x0020, 0});
|
||||
m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x3, 0x1, 0x0040, 1});
|
||||
|
||||
m_microphone = Microphone();
|
||||
if (m_microphone.OpenMicrophone() != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Error opening the microphone.");
|
||||
b_is_mic_connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_microphone.StartCapture() != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Error starting captures.");
|
||||
b_is_mic_connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_microphone_thread = std::thread([this] {
|
||||
u64 timeout{};
|
||||
constexpr u64 TIMESTEP = 256ull * 1'000'000ull / 48000ull;
|
||||
while (true)
|
||||
{
|
||||
if (m_shutdown_event.WaitFor(std::chrono::microseconds{timeout}))
|
||||
return;
|
||||
|
||||
std::lock_guard lg(m_mutex);
|
||||
timeout = TIMESTEP - (std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count() %
|
||||
TIMESTEP);
|
||||
m_microphone.PerformAudioCapture();
|
||||
m_microphone.GetSoundData();
|
||||
}
|
||||
});
|
||||
m_id = u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1);
|
||||
}
|
||||
|
||||
WiiSpeak::~WiiSpeak()
|
||||
{
|
||||
{
|
||||
std::lock_guard lg(m_mutex);
|
||||
if (!m_microphone_thread.joinable())
|
||||
return;
|
||||
|
||||
m_shutdown_event.Set();
|
||||
}
|
||||
|
||||
m_microphone_thread.join();
|
||||
m_microphone.StopCapture();
|
||||
}
|
||||
WiiSpeak::~WiiSpeak() = default;
|
||||
|
||||
DeviceDescriptor WiiSpeak::GetDeviceDescriptor() const
|
||||
{
|
||||
@ -98,6 +42,8 @@ bool WiiSpeak::Attach()
|
||||
return true;
|
||||
|
||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid);
|
||||
if (!m_microphone)
|
||||
m_microphone = std::make_unique<Microphone>();
|
||||
m_device_attached = true;
|
||||
return true;
|
||||
}
|
||||
@ -147,15 +93,18 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value,
|
||||
cmd->index, cmd->length);
|
||||
|
||||
if (!b_is_mic_connected)
|
||||
return IPC_ENOENT;
|
||||
// Without a proper way to reconnect the emulated Wii Speak,
|
||||
// this error after being raised prevents some games to use the microphone later.
|
||||
//
|
||||
// if (!IsMicrophoneConnected())
|
||||
// return IPC_ENOENT;
|
||||
|
||||
switch (cmd->request_type << 8 | cmd->request)
|
||||
{
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_INTERFACE, REQUEST_GET_INTERFACE):
|
||||
{
|
||||
constexpr std::array<u8, 1> data{1};
|
||||
cmd->FillBuffer(data.data(), 1);
|
||||
constexpr u8 data{1};
|
||||
cmd->FillBuffer(&data, sizeof(data));
|
||||
cmd->ScheduleTransferCompletion(1, 100);
|
||||
break;
|
||||
}
|
||||
@ -169,15 +118,15 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||
{
|
||||
if (!init)
|
||||
{
|
||||
constexpr std::array<u8, 1> data{0};
|
||||
cmd->FillBuffer(data.data(), 1);
|
||||
constexpr u8 data{0};
|
||||
cmd->FillBuffer(&data, sizeof(data));
|
||||
cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
|
||||
init = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr std::array<u8, 1> data{1};
|
||||
cmd->FillBuffer(data.data(), 1);
|
||||
constexpr u8 data{1};
|
||||
cmd->FillBuffer(&data, sizeof(data));
|
||||
cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
|
||||
}
|
||||
break;
|
||||
@ -212,8 +161,8 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||
|
||||
int WiiSpeak::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
||||
{
|
||||
if (!b_is_mic_connected)
|
||||
return IPC_ENOENT;
|
||||
// if (!IsMicrophoneConnected())
|
||||
// return IPC_ENOENT;
|
||||
|
||||
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
@ -224,17 +173,16 @@ int WiiSpeak::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
||||
ERROR_LOG_FMT(IOS_USB, "Wii Speak command invalid");
|
||||
return IPC_EINVAL;
|
||||
}
|
||||
|
||||
if (cmd->endpoint == 0x81 && m_microphone.HasData())
|
||||
m_microphone.ReadIntoBuffer(packets, cmd->length);
|
||||
if (cmd->endpoint == 0x81 && m_microphone && m_microphone->HasData())
|
||||
m_microphone->ReadIntoBuffer(packets, cmd->length);
|
||||
|
||||
// Anything more causes the visual cue to not appear.
|
||||
// Anything less is more choppy audio.
|
||||
cmd->ScheduleTransferCompletion(IPC_SUCCESS, 20000);
|
||||
cmd->ScheduleTransferCompletion(IPC_SUCCESS, 2500);
|
||||
return IPC_SUCCESS;
|
||||
};
|
||||
}
|
||||
|
||||
void WiiSpeak::SetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
void WiiSpeak::SetRegister(const std::unique_ptr<CtrlMessage>& cmd)
|
||||
{
|
||||
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
@ -245,21 +193,21 @@ void WiiSpeak::SetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
switch (reg)
|
||||
{
|
||||
case SAMPLER_STATE:
|
||||
sampler.sample_on = !!arg1;
|
||||
m_sampler.sample_on = !!arg1;
|
||||
break;
|
||||
case SAMPLER_FREQ:
|
||||
switch (arg1)
|
||||
{
|
||||
case FREQ_8KHZ:
|
||||
sampler.freq = 8000;
|
||||
m_sampler.freq = 8000;
|
||||
break;
|
||||
case FREQ_11KHZ:
|
||||
sampler.freq = 11025;
|
||||
m_sampler.freq = 11025;
|
||||
break;
|
||||
case FREQ_RESERVED:
|
||||
case FREQ_16KHZ:
|
||||
default:
|
||||
sampler.freq = 16000;
|
||||
m_sampler.freq = 16000;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -267,28 +215,28 @@ void WiiSpeak::SetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
switch (arg1 & ~0x300)
|
||||
{
|
||||
case GAIN_00dB:
|
||||
sampler.gain = 0;
|
||||
m_sampler.gain = 0;
|
||||
break;
|
||||
case GAIN_15dB:
|
||||
sampler.gain = 15;
|
||||
m_sampler.gain = 15;
|
||||
break;
|
||||
case GAIN_30dB:
|
||||
sampler.gain = 30;
|
||||
m_sampler.gain = 30;
|
||||
break;
|
||||
case GAIN_36dB:
|
||||
default:
|
||||
sampler.gain = 36;
|
||||
m_sampler.gain = 36;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EC_STATE:
|
||||
sampler.ec_reset = !!arg1;
|
||||
m_sampler.ec_reset = !!arg1;
|
||||
break;
|
||||
case SP_STATE:
|
||||
switch (arg1)
|
||||
{
|
||||
case SP_ENABLE:
|
||||
sampler.sp_on = arg2 == 0;
|
||||
m_sampler.sp_on = arg2 == 0;
|
||||
break;
|
||||
case SP_SIN:
|
||||
case SP_SOUT:
|
||||
@ -297,12 +245,12 @@ void WiiSpeak::SetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
}
|
||||
break;
|
||||
case SAMPLER_MUTE:
|
||||
sampler.mute = !!arg1;
|
||||
m_sampler.mute = !!arg1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WiiSpeak::GetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
void WiiSpeak::GetRegister(const std::unique_ptr<CtrlMessage>& cmd) const
|
||||
{
|
||||
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
@ -313,10 +261,10 @@ void WiiSpeak::GetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
switch (reg)
|
||||
{
|
||||
case SAMPLER_STATE:
|
||||
memory.Write_U16(sampler.sample_on ? 1 : 0, arg1);
|
||||
memory.Write_U16(m_sampler.sample_on ? 1 : 0, arg1);
|
||||
break;
|
||||
case SAMPLER_FREQ:
|
||||
switch (sampler.freq)
|
||||
switch (m_sampler.freq)
|
||||
{
|
||||
case 8000:
|
||||
memory.Write_U16(FREQ_8KHZ, arg1);
|
||||
@ -331,7 +279,7 @@ void WiiSpeak::GetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
}
|
||||
break;
|
||||
case SAMPLER_GAIN:
|
||||
switch (sampler.gain)
|
||||
switch (m_sampler.gain)
|
||||
{
|
||||
case 0:
|
||||
memory.Write_U16(0x300 | GAIN_00dB, arg1);
|
||||
@ -349,7 +297,7 @@ void WiiSpeak::GetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
}
|
||||
break;
|
||||
case EC_STATE:
|
||||
memory.Write_U16(sampler.ec_reset ? 1 : 0, arg1);
|
||||
memory.Write_U16(m_sampler.ec_reset ? 1 : 0, arg1);
|
||||
break;
|
||||
case SP_STATE:
|
||||
switch (memory.Read_U16(arg1))
|
||||
@ -367,8 +315,13 @@ void WiiSpeak::GetRegister(std::unique_ptr<CtrlMessage>& cmd)
|
||||
}
|
||||
break;
|
||||
case SAMPLER_MUTE:
|
||||
memory.Write_U16(sampler.mute ? 1 : 0, arg1);
|
||||
memory.Write_U16(m_sampler.mute ? 1 : 0, arg1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool WiiSpeak::IsMicrophoneConnected() const
|
||||
{
|
||||
return Config::Get(Config::MAIN_WII_SPEAK_CONNECTED);
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
||||
|
@ -4,12 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/Microphone.h"
|
||||
|
||||
@ -47,7 +44,7 @@ private:
|
||||
bool sp_on;
|
||||
};
|
||||
|
||||
WSState sampler{};
|
||||
WSState m_sampler{};
|
||||
|
||||
enum Registers
|
||||
{
|
||||
@ -75,22 +72,25 @@ private:
|
||||
SP_RIN = 0x200d
|
||||
};
|
||||
|
||||
void GetRegister(std::unique_ptr<CtrlMessage>& cmd);
|
||||
void SetRegister(std::unique_ptr<CtrlMessage>& cmd);
|
||||
void GetRegister(const std::unique_ptr<CtrlMessage>& cmd) const;
|
||||
void SetRegister(const std::unique_ptr<CtrlMessage>& cmd);
|
||||
bool IsMicrophoneConnected() const;
|
||||
|
||||
u16 m_vid = 0;
|
||||
u16 m_pid = 0;
|
||||
const u16 m_vid = 0x057E;
|
||||
const u16 m_pid = 0x0308;
|
||||
u8 m_active_interface = 0;
|
||||
bool m_device_attached = false;
|
||||
bool init = false;
|
||||
bool b_is_mic_connected = true;
|
||||
Microphone m_microphone;
|
||||
DeviceDescriptor m_device_descriptor{};
|
||||
std::vector<ConfigDescriptor> m_config_descriptor;
|
||||
std::vector<InterfaceDescriptor> m_interface_descriptor;
|
||||
std::vector<EndpointDescriptor> m_endpoint_descriptor;
|
||||
std::thread m_microphone_thread;
|
||||
std::mutex m_mutex;
|
||||
Common::Event m_shutdown_event;
|
||||
std::unique_ptr<Microphone> m_microphone{};
|
||||
const DeviceDescriptor m_device_descriptor{0x12, 0x1, 0x200, 0, 0, 0, 0x10,
|
||||
0x57E, 0x0308, 0x0214, 0x1, 0x2, 0x0, 0x1};
|
||||
const std::vector<ConfigDescriptor> m_config_descriptor{
|
||||
{0x9, 0x2, 0x0030, 0x1, 0x1, 0x0, 0x80, 0x32}};
|
||||
const std::vector<InterfaceDescriptor> m_interface_descriptor{
|
||||
{0x9, 0x4, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0x0},
|
||||
{0x9, 0x4, 0x0, 0x01, 0x03, 0xFF, 0xFF, 0xFF, 0x0}};
|
||||
const std::vector<EndpointDescriptor> m_endpoint_descriptor{{0x7, 0x5, 0x81, 0x1, 0x0020, 0x1},
|
||||
{0x7, 0x5, 0x2, 0x2, 0x0020, 0},
|
||||
{0x7, 0x5, 0x3, 0x1, 0x0040, 1}};
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
||||
|
@ -178,9 +178,11 @@ void USBScanner::AddEmulatedDevices(DeviceMap* new_devices)
|
||||
auto infinity_base = std::make_unique<USB::InfinityUSB>();
|
||||
AddDevice(std::move(infinity_base), new_devices);
|
||||
}
|
||||
|
||||
auto wii_speak = std::make_unique<USB::WiiSpeak>();
|
||||
AddDevice(std::move(wii_speak), new_devices);
|
||||
if (Config::Get(Config::MAIN_EMULATE_WII_SPEAK) && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto wii_speak = std::make_unique<USB::WiiSpeak>();
|
||||
AddDevice(std::move(wii_speak), new_devices);
|
||||
}
|
||||
}
|
||||
|
||||
void USBScanner::AddDevice(std::unique_ptr<USB::Device> device, DeviceMap* new_devices)
|
||||
|
@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AudioCommon\AudioCommon.h" />
|
||||
<ItemGroup Condition="'$(EnableCubeb)'!='false'">
|
||||
<ClInclude Include="AudioCommon\CubebStream.h" />
|
||||
<ClInclude Include="AudioCommon\CubebUtils.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AudioCommon\AudioCommon.h" />
|
||||
<ClInclude Include="AudioCommon\Enums.h" />
|
||||
<ClInclude Include="AudioCommon\Mixer.h" />
|
||||
<ClInclude Include="AudioCommon\NullSoundStream.h" />
|
||||
@ -290,7 +293,6 @@
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceGecko.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceIPL.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceModem.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI.h" />
|
||||
<ClInclude Include="Core\HW\GBACore.h" />
|
||||
@ -775,10 +777,13 @@
|
||||
<ClInclude Include="VideoCommon\XFStateManager.h" />
|
||||
<ClInclude Include="VideoCommon\XFStructs.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AudioCommon\AudioCommon.cpp" />
|
||||
<ItemGroup Condition="'$(EnableCubeb)'!='false'">
|
||||
<ClCompile Include="AudioCommon\CubebStream.cpp" />
|
||||
<ClCompile Include="AudioCommon\CubebUtils.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AudioCommon\AudioCommon.cpp" />
|
||||
<ClCompile Include="AudioCommon\Mixer.cpp" />
|
||||
<ClCompile Include="AudioCommon\NullSoundStream.cpp" />
|
||||
<ClCompile Include="AudioCommon\OpenALStream.cpp" />
|
||||
@ -962,7 +967,6 @@
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceGecko.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceIPL.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMemoryCard.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceModem.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI.cpp" />
|
||||
<ClCompile Include="Core\HW\GBACore.cpp" />
|
||||
|
@ -3,33 +3,25 @@
|
||||
|
||||
#include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QCompleter>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/IOFile.h"
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "AudioCommon/CubebUtils.h"
|
||||
#endif
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Emulated/Microphone.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
WiiSpeakWindow::WiiSpeakWindow(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
// i18n: Window for managing the Wii Speak microphone
|
||||
setWindowTitle(tr("Wii Speak Manager"));
|
||||
setObjectName(QStringLiteral("wii_speak_manager"));
|
||||
setMinimumSize(QSize(700, 200));
|
||||
@ -39,8 +31,6 @@ WiiSpeakWindow::WiiSpeakWindow(QWidget* parent) : QWidget(parent)
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
&WiiSpeakWindow::OnEmulationStateChanged);
|
||||
|
||||
installEventFilter(this);
|
||||
|
||||
OnEmulationStateChanged(Core::GetState(Core::System::GetInstance()));
|
||||
}
|
||||
|
||||
@ -49,25 +39,53 @@ WiiSpeakWindow::~WiiSpeakWindow() = default;
|
||||
void WiiSpeakWindow::CreateMainWindow()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout();
|
||||
auto* label = new QLabel();
|
||||
label->setText(QStringLiteral("<center><i>%1</i></center>")
|
||||
.arg(tr("Some settings cannot be changed when emulation is running.")));
|
||||
main_layout->addWidget(label);
|
||||
|
||||
auto* checkbox_group = new QGroupBox();
|
||||
auto* checkbox_layout = new QHBoxLayout();
|
||||
checkbox_layout->setAlignment(Qt::AlignLeft);
|
||||
m_checkbox = new QCheckBox(tr("Emulate Wii Speak"), this);
|
||||
m_checkbox->setChecked(Config::Get(Config::MAIN_EMULATE_WII_SPEAK));
|
||||
connect(m_checkbox, &QCheckBox::toggled, this, &WiiSpeakWindow::EmulateWiiSpeak);
|
||||
checkbox_layout->addWidget(m_checkbox);
|
||||
checkbox_layout->setAlignment(Qt::AlignHCenter);
|
||||
m_checkbox_enabled = new QCheckBox(tr("Emulate Wii Speak"), this);
|
||||
m_checkbox_enabled->setChecked(Config::Get(Config::MAIN_EMULATE_WII_SPEAK));
|
||||
connect(m_checkbox_enabled, &QCheckBox::toggled, this, &WiiSpeakWindow::EmulateWiiSpeak);
|
||||
checkbox_layout->addWidget(m_checkbox_enabled);
|
||||
checkbox_group->setLayout(checkbox_layout);
|
||||
main_layout->addWidget(checkbox_group);
|
||||
|
||||
auto* config_group = new QGroupBox(tr("Microphone Configuration"));
|
||||
auto* config_layout = new QHBoxLayout();
|
||||
|
||||
auto checkbox_mic_connected = new QCheckBox(tr("Connect"), this);
|
||||
checkbox_mic_connected->setChecked(Config::Get(Config::MAIN_WII_SPEAK_CONNECTED));
|
||||
connect(checkbox_mic_connected, &QCheckBox::toggled, this,
|
||||
&WiiSpeakWindow::SetWiiSpeakConnectionState);
|
||||
config_layout->addWidget(checkbox_mic_connected);
|
||||
|
||||
m_combobox_microphones = new QComboBox();
|
||||
for (const std::string& device : IOS::HLE::USB::Microphone::ListDevices())
|
||||
#ifndef HAVE_CUBEB
|
||||
m_combobox_microphones->addItem(QLatin1String("(%1)").arg(tr("Audio backend unsupported")),
|
||||
QString{});
|
||||
#else
|
||||
m_combobox_microphones->addItem(QLatin1String("(%1)").arg(tr("Autodetect preferred microphone")),
|
||||
QString{});
|
||||
for (auto& [device_id, device_name] : CubebUtils::ListInputDevices())
|
||||
{
|
||||
m_combobox_microphones->addItem(QString::fromStdString(device));
|
||||
const auto user_data = QString::fromStdString(device_id);
|
||||
m_combobox_microphones->addItem(QString::fromStdString(device_name), user_data);
|
||||
}
|
||||
#endif
|
||||
connect(m_combobox_microphones, &QComboBox::currentIndexChanged, this,
|
||||
&WiiSpeakWindow::OnInputDeviceChange);
|
||||
|
||||
checkbox_layout->addWidget(m_combobox_microphones);
|
||||
auto current_device_id = QString::fromStdString(Config::Get(Config::MAIN_WII_SPEAK_MICROPHONE));
|
||||
m_combobox_microphones->setCurrentIndex(m_combobox_microphones->findData(current_device_id));
|
||||
config_layout->addWidget(m_combobox_microphones);
|
||||
|
||||
config_group->setLayout(config_layout);
|
||||
main_layout->addWidget(config_group);
|
||||
|
||||
main_layout->addWidget(checkbox_group);
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
@ -76,9 +94,25 @@ void WiiSpeakWindow::EmulateWiiSpeak(bool emulate)
|
||||
Config::SetBaseOrCurrent(Config::MAIN_EMULATE_WII_SPEAK, emulate);
|
||||
}
|
||||
|
||||
void WiiSpeakWindow::SetWiiSpeakConnectionState(bool connected)
|
||||
{
|
||||
Config::SetBaseOrCurrent(Config::MAIN_WII_SPEAK_CONNECTED, connected);
|
||||
}
|
||||
|
||||
void WiiSpeakWindow::OnEmulationStateChanged(Core::State state)
|
||||
{
|
||||
const bool running = state != Core::State::Uninitialized;
|
||||
|
||||
m_checkbox->setEnabled(!running);
|
||||
m_checkbox_enabled->setEnabled(!running);
|
||||
m_combobox_microphones->setEnabled(!running);
|
||||
}
|
||||
|
||||
void WiiSpeakWindow::OnInputDeviceChange()
|
||||
{
|
||||
auto user_data = m_combobox_microphones->currentData();
|
||||
if (!user_data.isValid())
|
||||
return;
|
||||
|
||||
const std::string device_id = user_data.toString().toStdString();
|
||||
Config::SetBaseOrCurrent(Config::MAIN_WII_SPEAK_MICROPHONE, device_id);
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ private:
|
||||
void CreateMainWindow();
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
void EmulateWiiSpeak(bool emulate);
|
||||
void SetWiiSpeakConnectionState(bool connected);
|
||||
void OnInputDeviceChange();
|
||||
|
||||
QCheckBox* m_checkbox;
|
||||
QCheckBox* m_checkbox_enabled;
|
||||
QComboBox* m_combobox_microphones;
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
<!--This file is included by Dolphin code only-->
|
||||
<PropertyGroup>
|
||||
<TargetName Condition="'$(ConfigurationType)'=='Application'">$(ProjectName)$(TargetSuffix)</TargetName>
|
||||
<EnableCubeb>true</EnableCubeb>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
@ -47,7 +48,7 @@
|
||||
<PreprocessorDefinitions>USE_RETRO_ACHIEVEMENTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_HASH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>HAVE_CUBEB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(EnableCubeb)'!='false'">HAVE_CUBEB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
||||
<!-- Warnings one may want to ignore when using Level4.
|
||||
4201 nonstandard extension used : nameless struct/union
|
||||
|
Loading…
Reference in New Issue
Block a user