mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #14066 from Biendeo/master
IOS: Logitech USB Microphone Basic Support
This commit is contained in:
@@ -439,6 +439,8 @@ add_library(core
|
||||
IOS/USB/Emulated/Skylanders/SkylanderFigure.h
|
||||
IOS/USB/Emulated/WiiSpeak.cpp
|
||||
IOS/USB/Emulated/WiiSpeak.h
|
||||
IOS/USB/Emulated/LogitechMic.cpp
|
||||
IOS/USB/Emulated/LogitechMic.h
|
||||
IOS/USB/Host.cpp
|
||||
IOS/USB/Host.h
|
||||
IOS/USB/OH0/OH0.cpp
|
||||
|
||||
@@ -615,6 +615,27 @@ const Info<bool> MAIN_WII_SPEAK_MUTED{{System::Main, "EmulatedUSBDevices", "WiiS
|
||||
const Info<s16> MAIN_WII_SPEAK_VOLUME_MODIFIER{
|
||||
{System::Main, "EmulatedUSBDevices", "WiiSpeakVolumeModifier"}, 0};
|
||||
|
||||
const std::array<Info<bool>, EMULATED_LOGITECH_MIC_COUNT> MAIN_EMULATE_LOGITECH_MIC{
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic1"}, false},
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic2"}, false},
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic3"}, false},
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic4"}, false}};
|
||||
const std::array<Info<std::string>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MICROPHONE{
|
||||
Info<std::string>{{System::Main, "EmulatedUSBDevices", "LogitechMic1Microphone"}, ""},
|
||||
Info<std::string>{{System::Main, "EmulatedUSBDevices", "LogitechMic2Microphone"}, ""},
|
||||
Info<std::string>{{System::Main, "EmulatedUSBDevices", "LogitechMic3Microphone"}, ""},
|
||||
Info<std::string>{{System::Main, "EmulatedUSBDevices", "LogitechMic4Microphone"}, ""}};
|
||||
const std::array<Info<bool>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED{
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "LogitechMic1Muted"}, false},
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "LogitechMic2Muted"}, false},
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "LogitechMic3Muted"}, false},
|
||||
Info<bool>{{System::Main, "EmulatedUSBDevices", "LogitechMic4Muted"}, false}};
|
||||
const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER{
|
||||
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic1VolumeModifier"}, 0},
|
||||
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic2VolumeModifier"}, 0},
|
||||
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0},
|
||||
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}};
|
||||
|
||||
// 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.
|
||||
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
|
||||
|
||||
@@ -375,6 +375,14 @@ extern const Info<std::string> MAIN_WII_SPEAK_MICROPHONE;
|
||||
extern const Info<bool> MAIN_WII_SPEAK_MUTED;
|
||||
extern const Info<s16> MAIN_WII_SPEAK_VOLUME_MODIFIER;
|
||||
|
||||
static constexpr std::size_t EMULATED_LOGITECH_MIC_COUNT = 4;
|
||||
|
||||
extern const std::array<Info<bool>, EMULATED_LOGITECH_MIC_COUNT> MAIN_EMULATE_LOGITECH_MIC;
|
||||
extern const std::array<Info<std::string>, EMULATED_LOGITECH_MIC_COUNT>
|
||||
MAIN_LOGITECH_MIC_MICROPHONE;
|
||||
extern const std::array<Info<bool>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED;
|
||||
extern const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER;
|
||||
|
||||
// GameCube path utility functions
|
||||
|
||||
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
|
||||
|
||||
@@ -25,6 +25,7 @@ enum ReturnCode : s32
|
||||
IPC_SUCCESS = 0, // Success
|
||||
IPC_EACCES = -1, // Permission denied
|
||||
IPC_EEXIST = -2, // File exists
|
||||
IPC_STALL = -3, // Fail stall
|
||||
IPC_EINVAL = -4, // Invalid argument or fd
|
||||
IPC_EMAX = -5, // Too many file descriptors open
|
||||
IPC_ENOENT = -6, // File not found
|
||||
|
||||
@@ -29,9 +29,13 @@ enum ControlRequestTypes
|
||||
DIR_HOST2DEVICE = 0,
|
||||
DIR_DEVICE2HOST = 1,
|
||||
TYPE_STANDARD = 0,
|
||||
TYPE_CLASS = 1,
|
||||
TYPE_VENDOR = 2,
|
||||
TYPE_RESERVED = 3,
|
||||
REC_DEVICE = 0,
|
||||
REC_INTERFACE = 1,
|
||||
REC_ENDPOINT = 2,
|
||||
REC_OTHER = 3,
|
||||
};
|
||||
|
||||
constexpr u16 USBHDR(u8 dir, u8 type, u8 recipient, u8 request)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/Microphone.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
class LogitechMicState final : public MicrophoneState
|
||||
{
|
||||
public:
|
||||
// Use atomic for members concurrently used by the data callback
|
||||
std::atomic<bool> mute;
|
||||
std::atomic<u8> volume;
|
||||
std::atomic<u32> sample_rate = DEFAULT_SAMPLING_RATE;
|
||||
|
||||
static constexpr u32 DEFAULT_SAMPLING_RATE = 48000;
|
||||
|
||||
bool IsSampleOn() const override;
|
||||
bool IsMuted() const override;
|
||||
u32 GetDefaultSamplingRate() const override;
|
||||
};
|
||||
|
||||
class LogitechMic final : public Device
|
||||
{
|
||||
public:
|
||||
explicit LogitechMic(u8 index);
|
||||
|
||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
|
||||
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
|
||||
bool Attach() override;
|
||||
bool AttachAndChangeInterface(u8 interface) override;
|
||||
int CancelTransfer(u8 endpoint) override;
|
||||
int ChangeInterface(u8 interface) override;
|
||||
int GetNumberOfAltSettings(u8 interface) override;
|
||||
int SetAltSetting(u8 alt_setting) override;
|
||||
int SubmitTransfer(std::unique_ptr<CtrlMessage> cmd) override;
|
||||
int SubmitTransfer(std::unique_ptr<BulkMessage> cmd) override;
|
||||
int SubmitTransfer(std::unique_ptr<IntrMessage> cmd) override;
|
||||
int SubmitTransfer(std::unique_ptr<IsoMessage> cmd) override;
|
||||
|
||||
private:
|
||||
LogitechMicState m_sampler{};
|
||||
|
||||
int GetAudioControl(std::unique_ptr<CtrlMessage>& cmd);
|
||||
int SetAudioControl(std::unique_ptr<CtrlMessage>& cmd);
|
||||
int EndpointAudioControl(std::unique_ptr<CtrlMessage>& cmd);
|
||||
|
||||
const u16 m_vid = 0x046d;
|
||||
const u16 m_pid = 0x0a03;
|
||||
u8 m_index = 0;
|
||||
u8 m_active_interface = 0;
|
||||
bool m_device_attached = false;
|
||||
std::unique_ptr<Microphone> m_microphone{};
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
||||
@@ -17,9 +17,7 @@
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Emulated/WiiSpeak.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -32,16 +30,28 @@
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
Microphone::Microphone(const WiiSpeakState& sampler) : m_sampler(sampler)
|
||||
#ifdef HAVE_CUBEB
|
||||
Microphone::Microphone(const MicrophoneState& sampler, const std::string& worker_name)
|
||||
: m_sampler(sampler), m_worker(worker_name)
|
||||
{
|
||||
StreamInit();
|
||||
}
|
||||
#else
|
||||
Microphone::Microphone(const MicrophoneState& sampler, const std::string& worker_name)
|
||||
: m_sampler(sampler)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
Microphone::~Microphone()
|
||||
{
|
||||
StreamTerminate();
|
||||
}
|
||||
|
||||
void Microphone::Initialize()
|
||||
{
|
||||
StreamInit();
|
||||
}
|
||||
|
||||
#ifndef HAVE_CUBEB
|
||||
void Microphone::StreamInit()
|
||||
{
|
||||
@@ -63,12 +73,12 @@ void Microphone::StreamInit()
|
||||
{
|
||||
if (!m_worker.Execute([this] { m_cubeb_ctx = CubebUtils::GetContext(); }))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Failed to init Wii Speak stream");
|
||||
ERROR_LOG_FMT(IOS_USB, "Failed to init microphone stream");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Not here but rather inside the WiiSpeak device if possible?
|
||||
StreamStart(m_sampler.DEFAULT_SAMPLING_RATE);
|
||||
StreamStart(m_sampler.GetDefaultSamplingRate());
|
||||
}
|
||||
|
||||
void Microphone::StreamTerminate()
|
||||
@@ -108,6 +118,7 @@ void Microphone::StreamStart(u32 sampling_rate)
|
||||
params.channels = 1;
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
|
||||
std::lock_guard lock(m_ring_lock);
|
||||
u32 minimum_latency;
|
||||
if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK)
|
||||
{
|
||||
@@ -115,9 +126,8 @@ void Microphone::StreamStart(u32 sampling_rate)
|
||||
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",
|
||||
cubeb_devid input_device = CubebUtils::GetInputDeviceById(GetInputDeviceId());
|
||||
if (cubeb_stream_init(m_cubeb_ctx.get(), &m_cubeb_stream, GetCubebStreamName().c_str(),
|
||||
input_device, ¶ms, nullptr, nullptr,
|
||||
std::max<u32>(16, minimum_latency), CubebDataCallback, StateCallback,
|
||||
this) != CUBEB_OK)
|
||||
@@ -132,6 +142,8 @@ void Microphone::StreamStart(u32 sampling_rate)
|
||||
return;
|
||||
}
|
||||
|
||||
m_stream_buffer.resize(GetStreamSize());
|
||||
m_stream_wpos = 0;
|
||||
INFO_LOG_FMT(IOS_USB, "started cubeb stream");
|
||||
});
|
||||
}
|
||||
@@ -156,12 +168,13 @@ long Microphone::CubebDataCallback(cubeb_stream* stream, void* user_data, const
|
||||
if (Core::GetState(Core::System::GetInstance()) != Core::State::Running)
|
||||
return nframes;
|
||||
|
||||
auto* mic = static_cast<Microphone*>(user_data);
|
||||
|
||||
// Skip data when HLE Wii Speak is muted
|
||||
// TODO: Update cubeb and use cubeb_stream_set_input_mute
|
||||
if (Config::Get(Config::MAIN_WII_SPEAK_MUTED))
|
||||
if (mic->IsMicrophoneMuted())
|
||||
return nframes;
|
||||
|
||||
auto* mic = static_cast<Microphone*>(user_data);
|
||||
return mic->DataCallback(static_cast<const SampleType*>(input_buffer), nframes);
|
||||
}
|
||||
|
||||
@@ -170,27 +183,29 @@ long Microphone::DataCallback(const SampleType* input_buffer, long nframes)
|
||||
std::lock_guard lock(m_ring_lock);
|
||||
|
||||
// Skip data if sampling is off or mute is on
|
||||
if (!m_sampler.sample_on || m_sampler.mute)
|
||||
if (!m_sampler.IsSampleOn() || m_sampler.IsMuted())
|
||||
return nframes;
|
||||
|
||||
std::span<const SampleType> buffer(input_buffer, nframes);
|
||||
const auto gain = ComputeGain(Config::Get(Config::MAIN_WII_SPEAK_VOLUME_MODIFIER));
|
||||
const auto gain = ComputeGain(GetVolumeModifier());
|
||||
const auto apply_gain = [gain](SampleType sample) {
|
||||
return MathUtil::SaturatingCast<SampleType>(sample * gain);
|
||||
};
|
||||
|
||||
const u32 stream_size = GetStreamSize();
|
||||
const bool are_samples_byte_swapped = AreSamplesByteSwapped();
|
||||
for (const SampleType le_sample : std::ranges::transform_view(buffer, apply_gain))
|
||||
{
|
||||
UpdateLoudness(le_sample);
|
||||
m_stream_buffer[m_stream_wpos] = Common::swap16(le_sample);
|
||||
m_stream_wpos = (m_stream_wpos + 1) % STREAM_SIZE;
|
||||
m_stream_buffer[m_stream_wpos] =
|
||||
are_samples_byte_swapped ? Common::swap16(le_sample) : le_sample;
|
||||
m_stream_wpos = (m_stream_wpos + 1) % stream_size;
|
||||
}
|
||||
|
||||
m_samples_avail += nframes;
|
||||
if (m_samples_avail > STREAM_SIZE)
|
||||
if (m_samples_avail > stream_size)
|
||||
{
|
||||
WARN_LOG_FMT(IOS_USB, "Wii Speak ring buffer is full, data will be lost!");
|
||||
m_samples_avail = STREAM_SIZE;
|
||||
m_samples_avail = stream_size;
|
||||
}
|
||||
|
||||
return nframes;
|
||||
@@ -201,12 +216,9 @@ u16 Microphone::ReadIntoBuffer(u8* ptr, u32 size)
|
||||
{
|
||||
static constexpr u32 SINGLE_READ_SIZE = BUFF_SIZE_SAMPLES * sizeof(SampleType);
|
||||
|
||||
// Avoid buffer overflow during memcpy
|
||||
static_assert((STREAM_SIZE % BUFF_SIZE_SAMPLES) == 0,
|
||||
"The STREAM_SIZE isn't a multiple of BUFF_SIZE_SAMPLES");
|
||||
|
||||
std::lock_guard lock(m_ring_lock);
|
||||
|
||||
const u32 stream_size = GetStreamSize();
|
||||
u8* begin = ptr;
|
||||
for (u8* end = begin + size; ptr < end; ptr += SINGLE_READ_SIZE, size -= SINGLE_READ_SIZE)
|
||||
{
|
||||
@@ -218,14 +230,14 @@ u16 Microphone::ReadIntoBuffer(u8* ptr, u32 size)
|
||||
|
||||
m_samples_avail -= BUFF_SIZE_SAMPLES;
|
||||
m_stream_rpos += BUFF_SIZE_SAMPLES;
|
||||
m_stream_rpos %= STREAM_SIZE;
|
||||
m_stream_rpos %= stream_size;
|
||||
}
|
||||
return static_cast<u16>(ptr - begin);
|
||||
}
|
||||
|
||||
u16 Microphone::GetLoudnessLevel() const
|
||||
{
|
||||
if (m_sampler.mute || Config::Get(Config::MAIN_WII_SPEAK_MUTED))
|
||||
if (m_sampler.IsMuted() || IsMicrophoneMuted())
|
||||
return 0;
|
||||
return m_loudness_level;
|
||||
}
|
||||
@@ -365,7 +377,7 @@ void Microphone::Loudness::LogStats()
|
||||
const auto crest_factor_db = GetDecibel(crest_factor);
|
||||
|
||||
INFO_LOG_FMT(IOS_USB,
|
||||
"Wii Speak loudness stats (sample count: {}/{}):\n"
|
||||
"Microphone loudness stats (sample count: {}/{}):\n"
|
||||
" - min={} max={} amplitude={} ({} dB)\n"
|
||||
" - rms={} ({} dB) \n"
|
||||
" - abs_mean={} ({} dB)\n"
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
#include "AudioCommon/CubebUtils.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
@@ -22,31 +20,48 @@ struct cubeb_stream;
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
struct WiiSpeakState;
|
||||
class MicrophoneState
|
||||
{
|
||||
public:
|
||||
virtual ~MicrophoneState() = default;
|
||||
|
||||
class Microphone final
|
||||
virtual bool IsSampleOn() const = 0;
|
||||
virtual bool IsMuted() const = 0;
|
||||
virtual u32 GetDefaultSamplingRate() const = 0;
|
||||
};
|
||||
|
||||
class Microphone
|
||||
{
|
||||
public:
|
||||
using FloatType = float;
|
||||
using SampleType = s16;
|
||||
using UnsignedSampleType = std::make_unsigned_t<SampleType>;
|
||||
|
||||
Microphone(const WiiSpeakState& sampler);
|
||||
~Microphone();
|
||||
Microphone(const MicrophoneState& sampler, const std::string& worker_name);
|
||||
virtual ~Microphone();
|
||||
|
||||
void Initialize();
|
||||
bool HasData(u32 sample_count) const;
|
||||
u16 ReadIntoBuffer(u8* ptr, u32 size);
|
||||
u16 GetLoudnessLevel() const;
|
||||
FloatType ComputeGain(FloatType relative_db) const;
|
||||
void SetSamplingRate(u32 sampling_rate);
|
||||
|
||||
protected:
|
||||
static constexpr u32 BUFF_SIZE_SAMPLES = 32;
|
||||
|
||||
private:
|
||||
#ifdef HAVE_CUBEB
|
||||
static long CubebDataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long nframes);
|
||||
virtual std::string GetInputDeviceId() const = 0;
|
||||
virtual std::string GetCubebStreamName() const = 0;
|
||||
virtual s16 GetVolumeModifier() const = 0;
|
||||
virtual bool AreSamplesByteSwapped() const = 0;
|
||||
#endif
|
||||
|
||||
long DataCallback(const SampleType* input_buffer, long nframes);
|
||||
virtual bool IsMicrophoneMuted() const = 0;
|
||||
void UpdateLoudness(SampleType sample);
|
||||
|
||||
void StreamInit();
|
||||
@@ -54,10 +69,8 @@ private:
|
||||
void StreamStart(u32 sampling_rate);
|
||||
void StreamStop();
|
||||
|
||||
static constexpr u32 BUFF_SIZE_SAMPLES = 32;
|
||||
static constexpr u32 STREAM_SIZE = BUFF_SIZE_SAMPLES * 500;
|
||||
|
||||
std::array<SampleType, STREAM_SIZE> m_stream_buffer{};
|
||||
virtual u32 GetStreamSize() const = 0;
|
||||
std::vector<SampleType> m_stream_buffer{};
|
||||
u32 m_stream_wpos = 0;
|
||||
u32 m_stream_rpos = 0;
|
||||
u32 m_samples_avail = 0;
|
||||
@@ -102,12 +115,12 @@ private:
|
||||
|
||||
mutable std::mutex m_ring_lock;
|
||||
|
||||
const WiiSpeakState& m_sampler;
|
||||
const MicrophoneState& m_sampler;
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
std::shared_ptr<cubeb> m_cubeb_ctx = nullptr;
|
||||
cubeb_stream* m_cubeb_stream = nullptr;
|
||||
CubebUtils::CoInitSyncWorker m_worker{"Wii Speak Worker"};
|
||||
CubebUtils::CoInitSyncWorker m_worker;
|
||||
#endif
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
||||
|
||||
@@ -5,11 +5,56 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
bool WiiSpeakState::IsSampleOn() const
|
||||
{
|
||||
return sample_on;
|
||||
}
|
||||
|
||||
bool WiiSpeakState::IsMuted() const
|
||||
{
|
||||
return mute;
|
||||
}
|
||||
|
||||
u32 WiiSpeakState::GetDefaultSamplingRate() const
|
||||
{
|
||||
return DEFAULT_SAMPLING_RATE;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class MicrophoneWiiSpeak final : public Microphone
|
||||
{
|
||||
public:
|
||||
explicit MicrophoneWiiSpeak(const WiiSpeakState& sampler)
|
||||
: Microphone(sampler, "Wii Speak Worker")
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef HAVE_CUBEB
|
||||
std::string GetInputDeviceId() const override
|
||||
{
|
||||
return Config::Get(Config::MAIN_WII_SPEAK_MICROPHONE);
|
||||
}
|
||||
std::string GetCubebStreamName() const override { return "Dolphin Emulated Wii Speak"; }
|
||||
s16 GetVolumeModifier() const override
|
||||
{
|
||||
return Config::Get(Config::MAIN_WII_SPEAK_VOLUME_MODIFIER);
|
||||
}
|
||||
bool AreSamplesByteSwapped() const override { return true; }
|
||||
#endif
|
||||
|
||||
bool IsMicrophoneMuted() const override { return Config::Get(Config::MAIN_WII_SPEAK_MUTED); }
|
||||
u32 GetStreamSize() const override { return BUFF_SIZE_SAMPLES * 500; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
WiiSpeak::WiiSpeak()
|
||||
{
|
||||
m_id = u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1);
|
||||
@@ -44,7 +89,10 @@ bool WiiSpeak::Attach()
|
||||
|
||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid);
|
||||
if (!m_microphone)
|
||||
m_microphone = std::make_unique<Microphone>(m_sampler);
|
||||
{
|
||||
m_microphone = std::make_unique<MicrophoneWiiSpeak>(m_sampler);
|
||||
m_microphone->Initialize();
|
||||
}
|
||||
m_device_attached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
struct WiiSpeakState
|
||||
class WiiSpeakState final : public MicrophoneState
|
||||
{
|
||||
public:
|
||||
// Use atomic for members concurrently used by the data callback
|
||||
std::atomic<bool> sample_on;
|
||||
std::atomic<bool> mute;
|
||||
@@ -24,6 +25,10 @@ struct WiiSpeakState
|
||||
bool sp_on;
|
||||
|
||||
static constexpr u32 DEFAULT_SAMPLING_RATE = 16000;
|
||||
|
||||
bool IsSampleOn() const override;
|
||||
bool IsMuted() const override;
|
||||
u32 GetDefaultSamplingRate() const override;
|
||||
};
|
||||
|
||||
class WiiSpeak final : public Device
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/Infinity.h"
|
||||
#include "Core/IOS/USB/Emulated/LogitechMic.h"
|
||||
#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "Core/IOS/USB/Emulated/WiiSpeak.h"
|
||||
#include "Core/IOS/USB/Host.h"
|
||||
@@ -189,6 +190,14 @@ void USBScanner::AddEmulatedDevices(DeviceMap* new_devices)
|
||||
auto wii_speak = std::make_unique<USB::WiiSpeak>();
|
||||
AddDevice(std::move(wii_speak), new_devices);
|
||||
}
|
||||
for (u8 index = 0; index != Config::EMULATED_LOGITECH_MIC_COUNT; ++index)
|
||||
{
|
||||
if (Config::Get(Config::MAIN_EMULATE_LOGITECH_MIC[index]) && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto logitech_mic = std::make_unique<USB::LogitechMic>(index);
|
||||
AddDevice(std::move(logitech_mic), new_devices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USBScanner::WakeupSantrollerDevice(libusb_device* device)
|
||||
|
||||
@@ -414,6 +414,7 @@
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Common.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Infinity.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\LogitechMic.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Microphone.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\Skylander.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.h" />
|
||||
@@ -1096,6 +1097,7 @@
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Infinity.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\LogitechMic.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Microphone.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\Skylander.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.cpp" />
|
||||
|
||||
@@ -262,6 +262,8 @@ add_executable(dolphin-emu
|
||||
DiscordJoinRequestDialog.h
|
||||
EmulatedUSB/WiiSpeakWindow.cpp
|
||||
EmulatedUSB/WiiSpeakWindow.h
|
||||
EmulatedUSB/LogitechMicWindow.cpp
|
||||
EmulatedUSB/LogitechMicWindow.h
|
||||
FIFO/FIFOAnalyzer.cpp
|
||||
FIFO/FIFOAnalyzer.h
|
||||
FIFO/FIFOPlayerWindow.cpp
|
||||
|
||||
@@ -160,6 +160,7 @@
|
||||
<ClCompile Include="Debugger\WatchWidget.cpp" />
|
||||
<ClCompile Include="DiscordHandler.cpp" />
|
||||
<ClCompile Include="DiscordJoinRequestDialog.cpp" />
|
||||
<ClCompile Include="EmulatedUSB\LogitechMicWindow.cpp" />
|
||||
<ClCompile Include="EmulatedUSB\WiiSpeakWindow.cpp" />
|
||||
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
|
||||
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
|
||||
@@ -387,6 +388,7 @@
|
||||
<QtMoc Include="Debugger\WatchWidget.h" />
|
||||
<QtMoc Include="DiscordHandler.h" />
|
||||
<QtMoc Include="DiscordJoinRequestDialog.h" />
|
||||
<QtMoc Include="EmulatedUSB\LogitechMicWindow.h" />
|
||||
<QtMoc Include="EmulatedUSB\WiiSpeakWindow.h" />
|
||||
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
|
||||
<QtMoc Include="FIFO\FIFOPlayerWindow.h" />
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "DolphinQt/EmulatedUSB/LogitechMicWindow.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QString>
|
||||
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "AudioCommon/CubebUtils.h"
|
||||
#endif
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/System.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
LogitechMicWindow::LogitechMicWindow(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Logitech USB Microphone Manager"));
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
setObjectName(QStringLiteral("logitech_mic_manager"));
|
||||
setMinimumSize(QSize(700, 200));
|
||||
|
||||
CreateMainWindow();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
&LogitechMicWindow::OnEmulationStateChanged);
|
||||
|
||||
OnEmulationStateChanged(Core::GetState(Core::System::GetInstance()));
|
||||
}
|
||||
|
||||
void LogitechMicWindow::CreateMainWindow()
|
||||
{
|
||||
auto* const main_layout = new QVBoxLayout;
|
||||
auto* const 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);
|
||||
|
||||
CreateCheckboxGroup(main_layout);
|
||||
|
||||
CreateMicrophoneConfigurationGroup(main_layout);
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void LogitechMicWindow::CreateCheckboxGroup(QVBoxLayout* main_layout)
|
||||
{
|
||||
auto* checkbox_group = new QGroupBox();
|
||||
auto* checkbox_layout = new QHBoxLayout();
|
||||
checkbox_layout->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
for (std::size_t index = 0; index != Config::EMULATED_LOGITECH_MIC_COUNT; ++index)
|
||||
{
|
||||
m_mic_enabled_checkboxes[index] = new ConfigBool(
|
||||
tr("Emulate Logitech USB Mic %1").arg(index + 1), Config::MAIN_EMULATE_LOGITECH_MIC[index]);
|
||||
checkbox_layout->addWidget(m_mic_enabled_checkboxes[index]);
|
||||
}
|
||||
|
||||
checkbox_group->setLayout(checkbox_layout);
|
||||
main_layout->addWidget(checkbox_group);
|
||||
}
|
||||
|
||||
void LogitechMicWindow::CreateMicrophoneConfigurationGroup(QVBoxLayout* main_layout)
|
||||
{
|
||||
auto* main_config_group = new QGroupBox(tr("Microphone Configuration"));
|
||||
auto* main_config_layout = new QVBoxLayout();
|
||||
|
||||
for (std::size_t index = 0; index != Config::EMULATED_LOGITECH_MIC_COUNT; ++index)
|
||||
{
|
||||
// i18n: %1 is a number from 1 to 4.
|
||||
auto* config_group = new QGroupBox(tr("Microphone %1 Configuration").arg(index + 1));
|
||||
auto* const config_layout = new QHBoxLayout();
|
||||
|
||||
auto* const mic_muted = new ConfigBool(tr("Mute"), Config::MAIN_LOGITECH_MIC_MUTED[index]);
|
||||
mic_muted->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
config_layout->addWidget(mic_muted);
|
||||
|
||||
static constexpr int FILTER_MIN = -50;
|
||||
static constexpr int FILTER_MAX = 50;
|
||||
|
||||
auto* volume_layout = new QGridLayout();
|
||||
const int volume_modifier = std::clamp<int>(
|
||||
Config::Get(Config::MAIN_LOGITECH_MIC_VOLUME_MODIFIER[index]), FILTER_MIN, FILTER_MAX);
|
||||
auto* const filter_slider = new QSlider(Qt::Horizontal, this);
|
||||
auto* const slider_label = new QLabel(tr("Volume modifier (value: %1dB)").arg(volume_modifier));
|
||||
connect(filter_slider, &QSlider::valueChanged, this, [slider_label, index](int value) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_LOGITECH_MIC_VOLUME_MODIFIER[index], value);
|
||||
slider_label->setText(tr("Volume modifier (value: %1dB)").arg(value));
|
||||
});
|
||||
filter_slider->setMinimum(FILTER_MIN);
|
||||
filter_slider->setMaximum(FILTER_MAX);
|
||||
filter_slider->setValue(volume_modifier);
|
||||
filter_slider->setTickPosition(QSlider::TicksBothSides);
|
||||
filter_slider->setTickInterval(10);
|
||||
filter_slider->setSingleStep(1);
|
||||
volume_layout->addWidget(new QLabel(QStringLiteral("%1dB").arg(FILTER_MIN)), 0, 0,
|
||||
Qt::AlignLeft);
|
||||
volume_layout->addWidget(slider_label, 0, 1, Qt::AlignCenter);
|
||||
volume_layout->addWidget(new QLabel(QStringLiteral("%1dB").arg(FILTER_MAX)), 0, 2,
|
||||
Qt::AlignRight);
|
||||
volume_layout->addWidget(filter_slider, 1, 0, 1, 3);
|
||||
config_layout->addLayout(volume_layout);
|
||||
config_layout->setStretch(1, 3);
|
||||
|
||||
m_mic_device_comboboxes[index] = new QComboBox();
|
||||
#ifndef HAVE_CUBEB
|
||||
m_combobox_microphone[index]->addItem(
|
||||
QLatin1String("(%1)").arg(tr("Audio backend unsupported")), QString{});
|
||||
#else
|
||||
m_mic_device_comboboxes[index]->addItem(
|
||||
QLatin1String("(%1)").arg(tr("Autodetect preferred microphone")), QString{});
|
||||
for (auto& [device_id, device_name] : CubebUtils::ListInputDevices())
|
||||
{
|
||||
const auto user_data = QString::fromStdString(device_id);
|
||||
m_mic_device_comboboxes[index]->addItem(QString::fromStdString(device_name), user_data);
|
||||
}
|
||||
#endif
|
||||
auto current_device_id =
|
||||
QString::fromStdString(Config::Get(Config::MAIN_LOGITECH_MIC_MICROPHONE[index]));
|
||||
m_mic_device_comboboxes[index]->setCurrentIndex(
|
||||
m_mic_device_comboboxes[index]->findData(current_device_id));
|
||||
connect(m_mic_device_comboboxes[index], &QComboBox::currentIndexChanged, this,
|
||||
[index, this]() { OnInputDeviceChange(index); });
|
||||
config_layout->addWidget(m_mic_device_comboboxes[index]);
|
||||
|
||||
config_group->setLayout(config_layout);
|
||||
main_config_layout->addWidget(config_group);
|
||||
}
|
||||
|
||||
main_config_group->setLayout(main_config_layout);
|
||||
main_layout->addWidget(main_config_group);
|
||||
}
|
||||
|
||||
void LogitechMicWindow::OnEmulationStateChanged(Core::State state)
|
||||
{
|
||||
const bool running = state != Core::State::Uninitialized;
|
||||
|
||||
for (std::size_t index = 0; index != Config::EMULATED_LOGITECH_MIC_COUNT; ++index)
|
||||
{
|
||||
m_mic_enabled_checkboxes[index]->setEnabled(!running);
|
||||
}
|
||||
}
|
||||
|
||||
void LogitechMicWindow::OnInputDeviceChange(std::size_t index)
|
||||
{
|
||||
auto user_data = m_mic_device_comboboxes[index]->currentData();
|
||||
if (user_data.isValid())
|
||||
{
|
||||
const std::string device_id = user_data.toString().toStdString();
|
||||
Config::SetBaseOrCurrent(Config::MAIN_LOGITECH_MIC_MICROPHONE[index], device_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
|
||||
class ConfigBool;
|
||||
class QComboBox;
|
||||
|
||||
class LogitechMicWindow final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LogitechMicWindow(QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
void CreateMainWindow();
|
||||
void CreateCheckboxGroup(QVBoxLayout* main_layout);
|
||||
void CreateMicrophoneConfigurationGroup(QVBoxLayout* main_layout);
|
||||
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
void OnInputDeviceChange(std::size_t index);
|
||||
|
||||
std::array<ConfigBool*, Config::EMULATED_LOGITECH_MIC_COUNT> m_mic_enabled_checkboxes;
|
||||
std::array<QComboBox*, 4> m_mic_device_comboboxes;
|
||||
};
|
||||
@@ -94,6 +94,7 @@
|
||||
#include "DolphinQt/Debugger/ThreadWidget.h"
|
||||
#include "DolphinQt/Debugger/WatchWidget.h"
|
||||
#include "DolphinQt/DiscordHandler.h"
|
||||
#include "DolphinQt/EmulatedUSB/LogitechMicWindow.h"
|
||||
#include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h"
|
||||
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
|
||||
#include "DolphinQt/GCMemcardManager.h"
|
||||
@@ -572,6 +573,7 @@ void MainWindow::ConnectMenuBar()
|
||||
connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
|
||||
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
|
||||
connect(m_menu_bar, &MenuBar::ShowWiiSpeakWindow, this, &MainWindow::ShowWiiSpeakWindow);
|
||||
connect(m_menu_bar, &MenuBar::ShowLogitechMicWindow, this, &MainWindow::ShowLogitechMicWindow);
|
||||
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
|
||||
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
@@ -1425,6 +1427,18 @@ void MainWindow::ShowWiiSpeakWindow()
|
||||
m_wii_speak_window->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::ShowLogitechMicWindow()
|
||||
{
|
||||
if (!m_logitech_mic_window)
|
||||
{
|
||||
m_logitech_mic_window = new LogitechMicWindow();
|
||||
}
|
||||
|
||||
m_logitech_mic_window->show();
|
||||
m_logitech_mic_window->raise();
|
||||
m_logitech_mic_window->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::StateLoad()
|
||||
{
|
||||
QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ?
|
||||
|
||||
@@ -55,6 +55,7 @@ class ToolBar;
|
||||
class WatchWidget;
|
||||
class WiiTASInputWindow;
|
||||
class WiiSpeakWindow;
|
||||
class LogitechMicWindow;
|
||||
struct WindowSystemInfo;
|
||||
|
||||
namespace Core
|
||||
@@ -177,6 +178,7 @@ private:
|
||||
void ShowSkylanderPortal();
|
||||
void ShowInfinityBase();
|
||||
void ShowWiiSpeakWindow();
|
||||
void ShowLogitechMicWindow();
|
||||
void ShowMemcardManager();
|
||||
void ShowResourcePackManager();
|
||||
void ShowCheatsManager();
|
||||
@@ -252,6 +254,7 @@ private:
|
||||
SkylanderPortalWindow* m_skylander_window = nullptr;
|
||||
InfinityBaseWindow* m_infinity_window = nullptr;
|
||||
WiiSpeakWindow* m_wii_speak_window = nullptr;
|
||||
LogitechMicWindow* m_logitech_mic_window = nullptr;
|
||||
MappingWindow* m_hotkey_window = nullptr;
|
||||
FreeLookWindow* m_freelook_window = nullptr;
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@ void MenuBar::AddToolsMenu()
|
||||
usb_device_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
|
||||
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
|
||||
usb_device_menu->addAction(tr("&Wii Speak"), this, &MenuBar::ShowWiiSpeakWindow);
|
||||
usb_device_menu->addAction(tr("&Logitech USB Microphone"), this, &MenuBar::ShowLogitechMicWindow);
|
||||
tools_menu->addMenu(usb_device_menu);
|
||||
|
||||
tools_menu->addSeparator();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user