summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSepalani <sepalani@hotmail.fr>2024-05-09 14:51:30 +0400
committerSepalani <sepalani@hotmail.fr>2025-05-07 20:33:22 +0400
commit1ac40f25a2ced19f8a7729f1974cefacf207c845 (patch)
treeea3e02a675c7e940164494b7f17fe1d48d4558ba
parent451e36defcb59f3f0ce72f67a1b49c59f7ab13c3 (diff)
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.
-rw-r--r--Source/Core/AudioCommon/CubebUtils.cpp98
-rw-r--r--Source/Core/AudioCommon/CubebUtils.h6
-rw-r--r--Source/Core/Core/Config/MainSettings.cpp7
-rw-r--r--Source/Core/Core/Config/MainSettings.h1
-rw-r--r--Source/Core/Core/IOS/USB/Emulated/Microphone.cpp215
-rw-r--r--Source/Core/Core/IOS/USB/Emulated/Microphone.h136
-rw-r--r--Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp137
-rw-r--r--Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h34
-rw-r--r--Source/Core/Core/IOS/USB/USBScanner.cpp8
-rw-r--r--Source/Core/DolphinLib.props16
-rw-r--r--Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp84
-rw-r--r--Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h4
-rw-r--r--Source/VSProps/Base.Dolphin.props3
13 files changed, 470 insertions, 279 deletions
diff --git a/Source/Core/AudioCommon/CubebUtils.cpp b/Source/Core/AudioCommon/CubebUtils.cpp
index 6ad94f612c..e63f75c26e 100644
--- a/Source/Core/AudioCommon/CubebUtils.cpp
+++ b/Source/Core/AudioCommon/CubebUtils.cpp
@@ -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;
+}
diff --git a/Source/Core/AudioCommon/CubebUtils.h b/Source/Core/AudioCommon/CubebUtils.h
index 718c5f39c8..11ecdf5a3f 100644
--- a/Source/Core/AudioCommon/CubebUtils.h
+++ b/Source/Core/AudioCommon/CubebUtils.h
@@ -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
diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp
index fd6fecd5d5..6555f20f17 100644
--- a/Source/Core/Core/Config/MainSettings.cpp
+++ b/Source/Core/Core/Config/MainSettings.cpp
@@ -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.
diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h
index 7a0085e8e4..057de249b1 100644
--- a/Source/Core/Core/Config/MainSettings.h
+++ b/Source/Core/Core/Config/MainSettings.h
@@ -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
diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp
index d5bd6f89cd..49bd7cfcaa 100644
--- a/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp
+++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.cpp
@@ -3,89 +3,222 @@
#include "Core/IOS/USB/Emulated/Microphone.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"
-#include <algorithm>
+#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();
+ });
}
+ m_coinit_success = false;
+#endif
+}
- return devices;
+#ifndef HAVE_CUBEB
+void Microphone::StreamInit()
+{
}
-int Microphone::OpenMicrophone()
+void Microphone::StreamStart()
{
- 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::StreamStop()
{
- alcCaptureStart(m_device);
- return static_cast<int>(alcGetError(m_device));
}
-void Microphone::StopCapture()
+void Microphone::StreamTerminate()
{
- alcCaptureStop(m_device);
+}
+#else
+void Microphone::StreamInit()
+{
+#ifdef _WIN32
+ if (!m_coinit_success)
+ {
+ 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::PerformAudioCapture()
+void Microphone::StreamTerminate()
{
- m_num_of_samples = BUFFER_SIZE / 2;
+ 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
+ }
+}
- 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));
+static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state)
+{
+}
- if (m_num_of_samples == 0)
+void Microphone::StreamStart()
+{
+ if (!m_cubeb_ctx)
return;
- alcCaptureSamples(m_device, m_dsp_data.data(), m_num_of_samples);
+#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(), &params, &minimum_latency) != CUBEB_OK)
+ {
+ 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, &params, 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::ByteSwap(const void* src, void* dst) const
+void Microphone::StreamStop()
{
- *static_cast<u16*>(dst) = Common::swap16(*static_cast<const u16*>(src));
+ 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
+ }
}
-void Microphone::GetSoundData()
+long Microphone::CubebDataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
+ void* /*output_buffer*/, long nframes)
{
- if (m_num_of_samples == 0)
- return;
+ auto* mic = static_cast<Microphone*>(user_data);
+ return mic->DataCallback(static_cast<const s16*>(input_buffer), nframes);
+}
- u8* ptr = const_cast<u8*>(m_temp_buffer.data());
- // Convert LE to BE
- for (u32 i = 0; i < m_num_of_samples; i++)
+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++)
{
- for (u32 indchan = 0; indchan < 1; indchan++)
- {
- const u32 curindex = (i * 2) + indchan * (16 / 8);
- ByteSwap(m_dsp_data.data() + curindex, ptr + curindex);
- }
+ m_stream_buffer[m_stream_wpos] = Common::swap16(buff_in[i]);
+ m_stream_wpos = (m_stream_wpos + 1) % STREAM_SIZE;
+ }
+
+ m_samples_avail += nframes;
+ if (m_samples_avail > STREAM_SIZE)
+ {
+ m_samples_avail = STREAM_SIZE;
}
- m_rbuf_dsp.write_bytes(ptr, m_num_of_samples * 2);
+ 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
diff --git a/Source/Core/Core/IOS/USB/Emulated/Microphone.h b/Source/Core/Core/IOS/USB/Emulated/Microphone.h
index 41d21452f0..3d5ae29e1a 100644
--- a/Source/Core/Core/IOS/USB/Emulated/Microphone.h
+++ b/Source/Core/Core/IOS/USB/Emulated/Microphone.h
@@ -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"
-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;
+#ifdef HAVE_CUBEB
+#include "AudioCommon/CubebUtils.h"
- 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;
-};
+struct cubeb;
+struct cubeb_stream;
+#endif
+namespace IOS::HLE::USB
+{
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;
-
- 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;
+ static constexpr u32 BUFF_SIZE_SAMPLES = 16;
+ static constexpr u32 STREAM_SIZE = BUFF_SIZE_SAMPLES * 500;
+
+ 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
diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp
index 6dc47019bf..55896e1c38 100644
--- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp
+++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.cpp
@@ -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
diff --git a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h
index 10b9164ad8..492bf59632 100644
--- a/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h
+++ b/Source/Core/Core/IOS/USB/Emulated/WiiSpeak.h
@@ -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
diff --git a/Source/Core/Core/IOS/USB/USBScanner.cpp b/Source/Core/Core/IOS/USB/USBScanner.cpp
index 783cf9749c..128e59f13c 100644
--- a/Source/Core/Core/IOS/USB/USBScanner.cpp
+++ b/Source/Core/Core/IOS/USB/USBScanner.cpp
@@ -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)
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 97d0dab5e2..d7bb245d29 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -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" />
diff --git a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp
index 26d7593d12..ac01503d04 100644
--- a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.cpp
+++ b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.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);
}
diff --git a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h
index dd7fa758a3..2da4887cc9 100644
--- a/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h
+++ b/Source/Core/DolphinQt/EmulatedUSB/WiiSpeakWindow.h
@@ -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;
};
diff --git a/Source/VSProps/Base.Dolphin.props b/Source/VSProps/Base.Dolphin.props
index 3410b9fef6..f2f213b009 100644
--- a/Source/VSProps/Base.Dolphin.props
+++ b/Source/VSProps/Base.Dolphin.props
@@ -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