Merge pull request #12769 from sepalani/wii-speak

IOS/USB: Emulate Wii Speak using cubeb
This commit is contained in:
JMC47
2025-05-21 13:54:56 -04:00
committed by GitHub
36 changed files with 1592 additions and 11 deletions

View File

@ -247,6 +247,8 @@ add_executable(dolphin-emu
DiscordHandler.h
DiscordJoinRequestDialog.cpp
DiscordJoinRequestDialog.h
EmulatedUSB/WiiSpeakWindow.cpp
EmulatedUSB/WiiSpeakWindow.h
FIFO/FIFOAnalyzer.cpp
FIFO/FIFOAnalyzer.h
FIFO/FIFOPlayerWindow.cpp

View File

@ -157,6 +157,7 @@
<ClCompile Include="Debugger\WatchWidget.cpp" />
<ClCompile Include="DiscordHandler.cpp" />
<ClCompile Include="DiscordJoinRequestDialog.cpp" />
<ClCompile Include="EmulatedUSB\WiiSpeakWindow.cpp" />
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
<ClCompile Include="GameList\GameList.cpp" />
@ -375,6 +376,7 @@
<QtMoc Include="Debugger\WatchWidget.h" />
<QtMoc Include="DiscordHandler.h" />
<QtMoc Include="DiscordJoinRequestDialog.h" />
<QtMoc Include="EmulatedUSB\WiiSpeakWindow.h" />
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
<QtMoc Include="FIFO\FIFOPlayerWindow.h" />
<QtMoc Include="GameList\GameList.h" />

View File

@ -0,0 +1,144 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h"
#include <algorithm>
#include <QCheckBox>
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QString>
#include <QVBoxLayout>
#ifdef HAVE_CUBEB
#include "AudioCommon/CubebUtils.h"
#endif
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/System.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));
CreateMainWindow();
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&WiiSpeakWindow::OnEmulationStateChanged);
OnEmulationStateChanged(Core::GetState(Core::System::GetInstance()));
}
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::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_muted = new QCheckBox(tr("Mute"), this);
checkbox_mic_muted->setChecked(Settings::Instance().IsWiiSpeakMuted());
connect(&Settings::Instance(), &Settings::WiiSpeakMuteChanged, checkbox_mic_muted,
&QCheckBox::setChecked);
connect(checkbox_mic_muted, &QCheckBox::toggled, &Settings::Instance(),
&Settings::SetWiiSpeakMuted);
checkbox_mic_muted->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
config_layout->addWidget(checkbox_mic_muted);
auto* volume_layout = new QGridLayout();
static constexpr int FILTER_MIN = -50;
static constexpr int FILTER_MAX = 50;
const int volume_modifier =
std::clamp<int>(Config::Get(Config::MAIN_WII_SPEAK_VOLUME_MODIFIER), FILTER_MIN, FILTER_MAX);
auto filter_slider = new QSlider(Qt::Horizontal, this);
auto slider_label = new QLabel(tr("Volume modifier (value: %1dB)").arg(volume_modifier));
connect(filter_slider, &QSlider::valueChanged, this, [slider_label](int value) {
Config::SetBaseOrCurrent(Config::MAIN_WII_SPEAK_VOLUME_MODIFIER, 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_combobox_microphones = new QComboBox();
#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())
{
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);
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);
setLayout(main_layout);
}
void WiiSpeakWindow::EmulateWiiSpeak(bool emulate)
{
Config::SetBaseOrCurrent(Config::MAIN_EMULATE_WII_SPEAK, emulate);
}
void WiiSpeakWindow::OnEmulationStateChanged(Core::State state)
{
const bool running = state != Core::State::Uninitialized;
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);
}

View File

@ -0,0 +1,28 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
#include "Core/Core.h"
class QCheckBox;
class QComboBox;
class WiiSpeakWindow : public QWidget
{
Q_OBJECT
public:
explicit WiiSpeakWindow(QWidget* parent = nullptr);
~WiiSpeakWindow() override;
private:
void CreateMainWindow();
void OnEmulationStateChanged(Core::State state);
void EmulateWiiSpeak(bool emulate);
void OnInputDeviceChange();
QCheckBox* m_checkbox_enabled;
QComboBox* m_combobox_microphones;
};

View File

@ -296,6 +296,15 @@ void HotkeyScheduler::Run()
Settings::Instance().SetUSBKeyboardConnected(
!Settings::Instance().IsUSBKeyboardConnected());
}
if (IsHotkey(HK_TOGGLE_WII_SPEAK_MUTE))
{
const bool muted = !Settings::Instance().IsWiiSpeakMuted();
Settings::Instance().SetWiiSpeakMuted(muted);
// i18n: Wii Speak (un)muted notification message
const QString msg = tr("Wii Speak %1").arg(muted ? tr("muted") : tr("unmuted"));
OSD::AddMessage(msg.toStdString());
}
}
if (IsHotkey(HK_PREV_WIIMOTE_PROFILE_1))

View File

@ -95,6 +95,7 @@
#include "DolphinQt/Debugger/ThreadWidget.h"
#include "DolphinQt/Debugger/WatchWidget.h"
#include "DolphinQt/DiscordHandler.h"
#include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h"
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
#include "DolphinQt/GCMemcardManager.h"
#include "DolphinQt/GameList/GameList.h"
@ -581,6 +582,7 @@ void MainWindow::ConnectMenuBar()
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
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::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
#ifdef USE_RETRO_ACHIEVEMENTS
@ -1440,6 +1442,18 @@ void MainWindow::ShowInfinityBase()
m_infinity_window->activateWindow();
}
void MainWindow::ShowWiiSpeakWindow()
{
if (!m_wii_speak_window)
{
m_wii_speak_window = new WiiSpeakWindow();
}
m_wii_speak_window->show();
m_wii_speak_window->raise();
m_wii_speak_window->activateWindow();
}
void MainWindow::StateLoad()
{
QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ?

View File

@ -56,6 +56,7 @@ class ThreadWidget;
class ToolBar;
class WatchWidget;
class WiiTASInputWindow;
class WiiSpeakWindow;
struct WindowSystemInfo;
namespace Core
@ -177,6 +178,7 @@ private:
void ShowFIFOPlayer();
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowWiiSpeakWindow();
void ShowMemcardManager();
void ShowResourcePackManager();
void ShowCheatsManager();
@ -250,6 +252,7 @@ private:
FIFOPlayerWindow* m_fifo_window = nullptr;
SkylanderPortalWindow* m_skylander_window = nullptr;
InfinityBaseWindow* m_infinity_window = nullptr;
WiiSpeakWindow* m_wii_speak_window = nullptr;
MappingWindow* m_hotkey_window = nullptr;
FreeLookWindow* m_freelook_window = nullptr;

View File

@ -281,6 +281,7 @@ void MenuBar::AddToolsMenu()
auto* usb_device_menu = new QMenu(tr("Emulated USB Devices"), tools_menu);
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);
tools_menu->addMenu(usb_device_menu);
tools_menu->addSeparator();

View File

@ -94,6 +94,7 @@ signals:
void ShowResourcePackManager();
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowWiiSpeakWindow();
void ConnectWiiRemote(int id);
#ifdef USE_RETRO_ACHIEVEMENTS

View File

@ -795,6 +795,20 @@ void Settings::SetUSBKeyboardConnected(bool connected)
}
}
bool Settings::IsWiiSpeakMuted() const
{
return Config::Get(Config::MAIN_WII_SPEAK_MUTED);
}
void Settings::SetWiiSpeakMuted(bool muted)
{
if (IsWiiSpeakMuted() == muted)
return;
Config::SetBaseOrCurrent(Config::MAIN_WII_SPEAK_MUTED, muted);
emit WiiSpeakMuteChanged(muted);
}
void Settings::SetIsContinuouslyFrameStepping(bool is_stepping)
{
m_continuously_frame_stepping = is_stepping;

View File

@ -119,6 +119,9 @@ public:
bool IsUSBKeyboardConnected() const;
void SetUSBKeyboardConnected(bool connected);
bool IsWiiSpeakMuted() const;
void SetWiiSpeakMuted(bool muted);
void SetIsContinuouslyFrameStepping(bool is_stepping);
bool GetIsContinuouslyFrameStepping() const;
@ -222,6 +225,7 @@ signals:
void DevicesChanged();
void SDCardInsertionChanged(bool inserted);
void USBKeyboardConnectionChanged(bool connected);
void WiiSpeakMuteChanged(bool muted);
void EnableGfxModsChanged(bool enabled);
private: