Skip to content

Commit

Permalink
IOS/USB: Emulate Wii Speak (OpenAL)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahpistilli authored and sepalani committed Aug 24, 2024
1 parent 1f5e100 commit 9f76b23
Show file tree
Hide file tree
Showing 17 changed files with 828 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Source/Core/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -429,12 +429,16 @@ add_library(core
IOS/USB/Common.h
IOS/USB/Emulated/Infinity.cpp
IOS/USB/Emulated/Infinity.h
IOS/USB/Emulated/Microphone.cpp
IOS/USB/Emulated/Microphone.h
IOS/USB/Emulated/Skylanders/Skylander.cpp
IOS/USB/Emulated/Skylanders/Skylander.h
IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp
IOS/USB/Emulated/Skylanders/SkylanderCrypto.h
IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp
IOS/USB/Emulated/Skylanders/SkylanderFigure.h
IOS/USB/Emulated/WiiSpeak.cpp
IOS/USB/Emulated/WiiSpeak.h
IOS/USB/Host.cpp
IOS/USB/Host.h
IOS/USB/OH0/OH0.cpp
Expand Down
6 changes: 6 additions & 0 deletions Source/Core/Core/Config/MainSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,12 @@ const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
const Info<bool> MAIN_EMULATE_INFINITY_BASE{
{System::Main, "EmulatedUSBDevices", "EmulateInfinityBase"}, false};

const Info<bool> MAIN_EMULATE_WII_SPEAK{{System::Main, "EmulatedUSBDevices", "EmulateWiiSpeak"},
false};

const Info<std::string> MAIN_WII_SPEAK_MICROPHONE{{System::Main, "General", "WiiSpeakMicrophone"},
""};

// 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)
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/Core/Config/MainSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);

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;

// GameCube path utility functions

Expand Down
91 changes: 91 additions & 0 deletions Source/Core/Core/IOS/USB/Emulated/Microphone.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Core/IOS/USB/Emulated/Microphone.h"

#include "Common/Swap.h"

#include <algorithm>

namespace IOS::HLE::USB
{
std::vector<std::string> Microphone::ListDevices()
{
std::vector<std::string> devices{};
const ALchar* pDeviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
while (*pDeviceList)
{
devices.emplace_back(pDeviceList);
pDeviceList += strlen(pDeviceList) + 1;
}

return devices;
}

int Microphone::OpenMicrophone()
{
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()
{
alcCaptureStart(m_device);
return static_cast<int>(alcGetError(m_device));
}

void Microphone::StopCapture()
{
alcCaptureStop(m_device);
}

void Microphone::PerformAudioCapture()
{
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
{
*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++)
{
for (u32 indchan = 0; indchan < 1; indchan++)
{
const u32 curindex = (i * 2) + indchan * (16 / 8);
ByteSwap(m_dsp_data.data() + curindex, ptr + curindex);
}
}

m_rbuf_dsp.write_bytes(ptr, m_num_of_samples * 2);
}

void Microphone::ReadIntoBuffer(u8* dst, u32 size)
{
m_rbuf_dsp.read_bytes(dst, size);
}

bool Microphone::HasData() const
{
return m_num_of_samples != 0;
}
} // namespace IOS::HLE::USB
113 changes: 113 additions & 0 deletions Source/Core/Core/IOS/USB/Emulated/Microphone.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <OpenAL/al.h>
#include <OpenAL/alc.h>

#include <string>
#include <vector>

#include "Common/CommonTypes.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;

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();

int OpenMicrophone();
int StartCapture();
void StopCapture();
void PerformAudioCapture();
void GetSoundData();
void ReadIntoBuffer(u8* dst, u32 size);
bool HasData() const;

private:
void ByteSwap(const void* src, void* dst) const;

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;
};
} // namespace IOS::HLE::USB
Loading

0 comments on commit 9f76b23

Please sign in to comment.