rpcs3/rpcs3/Emu/Cell/Modules/cellMic.cpp
Daniel López Guimaraes ba3d16b48b cellMic: Fix CELLMIC_DEVATTR_CHANVOL handling
The microphone index given in `arg1` is 1-indexed, so we have to
subtract 1 from the index to access the proper value.

This wasn't caught up before since we were doing direct access of the
array instead of using `::at32` which wouldn't check that the value is
within bounds.

This allows the original SingStar game to boot up again, as it would
otherwise crash then trying to set the volume to the second channel due
to the index being out of bounds.
2024-08-02 19:29:42 +02:00

1407 lines
36 KiB
C++

#include "stdafx.h"
#include "Emu/System.h"
#include "Emu/system_config.h"
#include "Emu/Cell/PPUModule.h"
#include "Utilities/StrUtil.h"
#include "cellMic.h"
#include <Emu/IdManager.h>
#include <Emu/Cell/lv2/sys_event.h>
LOG_CHANNEL(cellMic);
template<>
void fmt_class_string<CellMicInError>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto error)
{
switch (error)
{
STR_CASE(CELL_MICIN_ERROR_ALREADY_INIT);
STR_CASE(CELL_MICIN_ERROR_DEVICE);
STR_CASE(CELL_MICIN_ERROR_NOT_INIT);
STR_CASE(CELL_MICIN_ERROR_PARAM);
STR_CASE(CELL_MICIN_ERROR_PORT_FULL);
STR_CASE(CELL_MICIN_ERROR_ALREADY_OPEN);
STR_CASE(CELL_MICIN_ERROR_NOT_OPEN);
STR_CASE(CELL_MICIN_ERROR_NOT_RUN);
STR_CASE(CELL_MICIN_ERROR_TRANS_EVENT);
STR_CASE(CELL_MICIN_ERROR_OPEN);
STR_CASE(CELL_MICIN_ERROR_SHAREDMEMORY);
STR_CASE(CELL_MICIN_ERROR_MUTEX);
STR_CASE(CELL_MICIN_ERROR_EVENT_QUEUE);
STR_CASE(CELL_MICIN_ERROR_DEVICE_NOT_FOUND);
STR_CASE(CELL_MICIN_ERROR_FATAL);
STR_CASE(CELL_MICIN_ERROR_DEVICE_NOT_SUPPORT);
}
return unknown;
});
}
template<>
void fmt_class_string<CellMicInErrorDsp>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto error)
{
switch (error)
{
STR_CASE(CELL_MICIN_ERROR_DSP);
STR_CASE(CELL_MICIN_ERROR_DSP_ASSERT);
STR_CASE(CELL_MICIN_ERROR_DSP_PATH);
STR_CASE(CELL_MICIN_ERROR_DSP_FILE);
STR_CASE(CELL_MICIN_ERROR_DSP_PARAM);
STR_CASE(CELL_MICIN_ERROR_DSP_MEMALLOC);
STR_CASE(CELL_MICIN_ERROR_DSP_POINTER);
STR_CASE(CELL_MICIN_ERROR_DSP_FUNC);
STR_CASE(CELL_MICIN_ERROR_DSP_MEM);
STR_CASE(CELL_MICIN_ERROR_DSP_ALIGN16);
STR_CASE(CELL_MICIN_ERROR_DSP_ALIGN128);
STR_CASE(CELL_MICIN_ERROR_DSP_EAALIGN128);
STR_CASE(CELL_MICIN_ERROR_DSP_LIB_HANDLER);
STR_CASE(CELL_MICIN_ERROR_DSP_LIB_INPARAM);
STR_CASE(CELL_MICIN_ERROR_DSP_LIB_NOSPU);
STR_CASE(CELL_MICIN_ERROR_DSP_LIB_SAMPRATE);
}
return unknown;
});
}
void mic_context::operator()()
{
// Timestep in microseconds
constexpr u64 TIMESTEP = 256ull * 1'000'000ull / 48000ull;
u64 timeout = 0;
while (thread_ctrl::state() != thread_state::aborting)
{
if (timeout != 0)
{
thread_ctrl::wait_on(wakey, 0, timeout);
wakey.store(0);
}
std::lock_guard lock(mutex);
if (std::none_of(mic_list.begin(), mic_list.end(), [](const microphone_device& dev) { return dev.is_registered(); }))
{
timeout = umax;
continue;
}
else
{
timeout = TIMESTEP - (std::chrono::duration_cast<std::chrono::microseconds>(steady_clock::now().time_since_epoch()).count() % TIMESTEP);
}
for (auto& mic_entry : mic_list)
{
mic_entry.update_audio();
}
auto mic_queue = lv2_event_queue::find(event_queue_key);
if (!mic_queue)
continue;
for (usz dev_num = 0; dev_num < mic_list.size(); dev_num++)
{
microphone_device& device = ::at32(mic_list, dev_num);
if (device.has_data())
{
mic_queue->send(event_queue_source, CELLMIC_DATA, dev_num, 0);
}
}
}
// Cleanup
std::lock_guard lock(mutex);
for (auto& mic_entry : mic_list)
{
mic_entry.close_microphone();
}
}
void mic_context::wake_up()
{
wakey.store(1);
wakey.notify_one();
}
void mic_context::load_config_and_init()
{
mic_list = {};
const std::vector<std::string> device_list = fmt::split(g_cfg.audio.microphone_devices.to_string(), {"@@@"});
if (!device_list.empty())
{
// We only register the first device. The rest is registered with cellAudioInRegisterDevice.
if (g_cfg.audio.microphone_type == microphone_handler::singstar)
{
microphone_device& device = ::at32(mic_list, 0);
device = microphone_device(microphone_handler::singstar);
device.set_registered(true);
device.add_device(device_list[0]);
// Singstar uses the same device for 2 players
if (device_list.size() >= 2)
{
device.add_device(device_list[1]);
}
wake_up();
}
else
{
[[maybe_unused]] const u32 index = register_device(device_list[0]);
}
}
}
u32 mic_context::register_device(const std::string& device_name)
{
usz index = mic_list.size();
for (usz i = 0; i < mic_list.size(); i++)
{
microphone_device& device = ::at32(mic_list, i);
if (!device.is_registered())
{
if (index == mic_list.size())
{
index = i;
}
}
else if (device_name == device.get_device_name())
{
// TODO: what happens if the device is registered twice?
return ::narrow<u32>(i);
}
}
// TODO: Check max mics properly
ensure(index < mic_list.size(), "cellMic max mics exceeded during registration");
switch (g_cfg.audio.microphone_type)
{
case microphone_handler::standard:
case microphone_handler::real_singstar:
case microphone_handler::rocksmith:
{
microphone_device& device = ::at32(mic_list, index);
device = microphone_device(g_cfg.audio.microphone_type.get());
device.set_registered(true);
device.add_device(device_name);
if (auto mic_queue = lv2_event_queue::find(event_queue_key))
{
mic_queue->send(event_queue_source, CELLMIC_ATTACH, index, 0);
}
break;
}
case microphone_handler::singstar:
case microphone_handler::null:
default:
break;
}
wake_up();
return ::narrow<u32>(index);
}
void mic_context::unregister_device(u32 dev_num)
{
// Don't allow to unregister the first device for now.
if (dev_num == 0 || dev_num >= mic_list.size())
{
return;
}
microphone_device& device = ::at32(mic_list, dev_num);
device = microphone_device();
if (auto mic_queue = lv2_event_queue::find(event_queue_key))
{
mic_queue->send(event_queue_source, CELLMIC_DETACH, dev_num, 0);
}
}
bool mic_context::check_device(u32 dev_num)
{
if (dev_num >= mic_list.size())
return false;
microphone_device& device = ::at32(mic_list, dev_num);
return device.is_registered();
}
// Static functions
void microphone_device::variable_byteswap(const void* src, void* dst, const u32 bytesize)
{
switch (bytesize)
{
case 4: *static_cast<u32*>(dst) = *static_cast<const be_t<u32>*>(src); break;
case 2: *static_cast<u16*>(dst) = *static_cast<const be_t<u16>*>(src); break;
default: break;
}
}
// Public functions
microphone_device::microphone_device(microphone_handler type)
{
device_type = type;
}
void microphone_device::add_device(const std::string& name)
{
device_name.push_back(name);
}
error_code microphone_device::open_microphone(const u8 type, const u32 dsp_r, const u32 raw_r, const u8 channels)
{
signal_types = type;
dsp_samplingrate = dsp_r;
raw_samplingrate = raw_r;
num_channels = channels;
// Adjust number of channels depending on microphone type
switch (device_type)
{
case microphone_handler::standard:
break;
case microphone_handler::singstar:
case microphone_handler::real_singstar:
// SingStar mic has always 2 channels, each channel represent a physical microphone
ensure(num_channels >= 2);
if (num_channels > 2)
{
cellMic.error("Tried to open a SingStar-type device with num_channels = %d", num_channels);
num_channels = 2;
}
break;
case microphone_handler::rocksmith:
num_channels = 1;
break;
case microphone_handler::null:
default:
ensure(false);
break;
}
ALCenum num_al_channels;
switch (num_channels)
{
case 1:
num_al_channels = AL_FORMAT_MONO16;
break;
case 2:
// If we're using SingStar each device needs to be mono
if (device_type == microphone_handler::singstar)
num_al_channels = AL_FORMAT_MONO16;
else
num_al_channels = AL_FORMAT_STEREO16;
break;
case 4:
num_al_channels = AL_FORMAT_QUAD16;
break;
default:
cellMic.warning("Requested an invalid number of %d channels. Defaulting to 4 channels instead.", num_channels);
num_al_channels = AL_FORMAT_QUAD16;
num_channels = 4;
break;
}
// Real firmware tries 4, 2 and then 1 channels if the channel count is not supported
// TODO: The used channel count may vary for Sony's camera devices
for (bool found_valid_channels = false; !found_valid_channels;)
{
switch (num_al_channels)
{
case AL_FORMAT_QUAD16:
if (alcIsExtensionPresent(nullptr, "AL_EXT_MCFORMATS") == AL_TRUE)
{
found_valid_channels = true;
break;
}
cellMic.warning("Requested 4 channels but AL_EXT_MCFORMATS not available, trying 2 channels next");
num_al_channels = AL_FORMAT_STEREO16;
num_channels = 2;
break;
case AL_FORMAT_STEREO16:
if (true) // TODO: check stereo capability
{
found_valid_channels = true;
break;
}
cellMic.warning("Requested 2 channels but extension is not available, trying 1 channel next");
num_al_channels = AL_FORMAT_MONO16;
num_channels = 1;
break;
case AL_FORMAT_MONO16:
found_valid_channels = true;
break;
default:
ensure(false);
break;
}
}
// Ensure that the code above delivers what it should
switch (num_al_channels)
{
case AL_FORMAT_QUAD16:
ensure(num_channels == 4 && device_type != microphone_handler::singstar && device_type != microphone_handler::real_singstar);
break;
case AL_FORMAT_STEREO16:
ensure(num_channels == 2 && device_type != microphone_handler::singstar);
break;
case AL_FORMAT_MONO16:
ensure(num_channels == 1 || (num_channels == 2 && device_type == microphone_handler::singstar));
break;
default:
ensure(false);
break;
}
// Make sure we use a proper sampling rate
const auto fixup_samplingrate = [this](u32& rate) -> bool
{
// TODO: The used sample rate may vary for Sony's camera devices
const std::array<u32, 7> samplingrates = { rate, 48000u, 32000u, 24000u, 16000u, 12000u, 8000u };
const auto test_samplingrate = [&samplingrates](const u32& rate)
{
// TODO: actually check if device supports sampling rates
return std::any_of(samplingrates.cbegin() + 1, samplingrates.cend(), [&rate](const u32& r){ return r == rate; });
};
for (u32 samplingrate : samplingrates)
{
if (test_samplingrate(samplingrate))
{
// Use this sampling rate
raw_samplingrate = samplingrate;
cellMic.notice("Using sampling rate %d.", samplingrate);
return true;
}
cellMic.warning("Requested sampling rate %d, but we do not support it. Trying next sampling rate...", samplingrate);
}
return false;
};
if (!fixup_samplingrate(raw_samplingrate))
{
return CELL_MICIN_ERROR_DEVICE_NOT_SUPPORT;
}
aux_samplingrate = dsp_samplingrate = raw_samplingrate; // Same rate for now
ensure(!device_name.empty());
ALCdevice* device = alcCaptureOpenDevice(device_name[0].c_str(), raw_samplingrate, num_al_channels, inbuf_size);
if (alcGetError(device) != ALC_NO_ERROR)
{
cellMic.error("Error opening capture device %s", device_name[0]);
#ifdef _WIN32
cellMic.error("Make sure microphone use is authorized under \"Microphone privacy settings\" in windows configuration");
#endif
return CELL_MICIN_ERROR_DEVICE_NOT_SUPPORT;
}
input_devices.push_back(device);
internal_bufs.emplace_back();
internal_bufs[0].resize(inbuf_size, 0);
temp_buf.resize(inbuf_size, 0);
if (device_type == microphone_handler::singstar && device_name.size() >= 2)
{
// Open a 2nd microphone into the same device
device = alcCaptureOpenDevice(device_name[1].c_str(), raw_samplingrate, AL_FORMAT_MONO16, inbuf_size);
if (alcGetError(device) != ALC_NO_ERROR)
{
// Ignore it and move on
cellMic.error("Error opening 2nd SingStar capture device %s", device_name[1]);
}
else
{
input_devices.push_back(device);
internal_bufs.emplace_back();
internal_bufs[1].resize(inbuf_size, 0);
}
}
sample_size = (bit_resolution / 8) * num_channels;
mic_opened = true;
return CELL_OK;
}
error_code microphone_device::close_microphone()
{
if (mic_started)
{
stop_microphone();
}
for (const auto& micdevice : input_devices)
{
if (alcCaptureCloseDevice(micdevice) != ALC_TRUE)
{
cellMic.error("Error closing capture device");
}
}
input_devices.clear();
internal_bufs.clear();
mic_opened = false;
return CELL_OK;
}
error_code microphone_device::start_microphone()
{
for (const auto& micdevice : input_devices)
{
alcCaptureStart(micdevice);
if (alcGetError(micdevice) != ALC_NO_ERROR)
{
cellMic.error("Error starting capture");
stop_microphone();
return CELL_MICIN_ERROR_FATAL;
}
}
mic_started = true;
return CELL_OK;
}
error_code microphone_device::stop_microphone()
{
for (const auto& micdevice : input_devices)
{
alcCaptureStop(micdevice);
if (alcGetError(micdevice) != ALC_NO_ERROR)
{
cellMic.error("Error stopping capture");
}
}
mic_started = false;
return CELL_OK;
}
void microphone_device::update_audio()
{
if (mic_registered && mic_opened && mic_started)
{
if (signal_types == CELLMIC_SIGTYPE_NULL)
return;
const u32 num_samples = capture_audio();
if (signal_types & CELLMIC_SIGTYPE_RAW)
get_raw(num_samples);
if (signal_types & CELLMIC_SIGTYPE_DSP)
get_dsp(num_samples);
// TODO: aux?
}
}
bool microphone_device::has_data() const
{
return mic_registered && mic_opened && mic_started && (rbuf_raw.has_data() || rbuf_dsp.has_data());
}
u32 microphone_device::capture_audio()
{
ensure(sample_size > 0);
u32 num_samples = inbuf_size / sample_size;
for (const auto micdevice : input_devices)
{
ALCint samples_in = 0;
alcGetIntegerv(micdevice, ALC_CAPTURE_SAMPLES, 1, &samples_in);
if (alcGetError(micdevice) != ALC_NO_ERROR)
{
cellMic.error("Error getting number of captured samples");
return CELL_MICIN_ERROR_FATAL;
}
num_samples = std::min<u32>(num_samples, samples_in);
}
for (u32 index = 0; index < input_devices.size(); index++)
{
alcCaptureSamples(input_devices[index], internal_bufs[index].data(), num_samples);
}
return num_samples;
}
// Private functions
void microphone_device::get_raw(const u32 num_samples)
{
u8* tmp_ptr = temp_buf.data();
switch (device_type)
{
case microphone_handler::real_singstar:
// Straight copy from device
memcpy(tmp_ptr, internal_bufs[0].data(), num_samples * (bit_resolution / 8) * num_channels);
break;
case microphone_handler::standard:
case microphone_handler::rocksmith:
// BE Translation
for (u32 index = 0; index < num_samples; index++)
{
for (u32 indchan = 0; indchan < num_channels; indchan++)
{
const u32 curindex = (index * sample_size) + indchan * (bit_resolution / 8);
microphone_device::variable_byteswap(internal_bufs[0].data() + curindex, tmp_ptr + curindex, bit_resolution / 8);
}
}
break;
case microphone_handler::singstar:
ensure(sample_size == 4);
// Mixing the 2 mics as if channels
if (input_devices.size() == 2)
{
for (u32 index = 0; index < (num_samples * 4); index += 4)
{
tmp_ptr[index] = internal_bufs[0][(index / 2)];
tmp_ptr[index + 1] = internal_bufs[0][(index / 2) + 1];
tmp_ptr[index + 2] = internal_bufs[1][(index / 2)];
tmp_ptr[index + 3] = internal_bufs[1][(index / 2) + 1];
}
}
else
{
for (u32 index = 0; index < (num_samples * 4); index += 4)
{
tmp_ptr[index] = internal_bufs[0][(index / 2)];
tmp_ptr[index + 1] = internal_bufs[0][(index / 2) + 1];
tmp_ptr[index + 2] = 0;
tmp_ptr[index + 3] = 0;
}
}
break;
case microphone_handler::null:
default: ensure(false); break;
}
rbuf_raw.write_bytes(tmp_ptr, num_samples * sample_size);
};
void microphone_device::get_dsp(const u32 num_samples)
{
u8* tmp_ptr = temp_buf.data();
switch (device_type)
{
case microphone_handler::real_singstar:
// Straight copy from device
memcpy(tmp_ptr, internal_bufs[0].data(), num_samples * (bit_resolution / 8) * num_channels);
break;
case microphone_handler::standard:
case microphone_handler::rocksmith:
// BE Translation
for (u32 index = 0; index < num_samples; index++)
{
for (u32 indchan = 0; indchan < num_channels; indchan++)
{
const u32 curindex = (index * sample_size) + indchan * (bit_resolution / 8);
microphone_device::variable_byteswap(internal_bufs[0].data() + curindex, tmp_ptr + curindex, bit_resolution / 8);
}
}
break;
case microphone_handler::singstar:
ensure(sample_size == 4);
// Mixing the 2 mics as if channels
if (input_devices.size() == 2)
{
for (u32 index = 0; index < (num_samples * 4); index += 4)
{
tmp_ptr[index] = internal_bufs[0][(index / 2)];
tmp_ptr[index + 1] = internal_bufs[0][(index / 2) + 1];
tmp_ptr[index + 2] = internal_bufs[1][(index / 2)];
tmp_ptr[index + 3] = internal_bufs[1][(index / 2) + 1];
}
}
else
{
for (u32 index = 0; index < (num_samples * 4); index += 4)
{
tmp_ptr[index] = internal_bufs[0][(index / 2)];
tmp_ptr[index + 1] = internal_bufs[0][(index / 2) + 1];
tmp_ptr[index + 2] = 0;
tmp_ptr[index + 3] = 0;
}
}
break;
case microphone_handler::null:
default: ensure(false); break;
}
rbuf_dsp.write_bytes(tmp_ptr, num_samples * sample_size);
};
/// Initialization/Shutdown Functions
error_code cellMicInit()
{
cellMic.notice("cellMicInit()");
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (mic_thr.init)
return CELL_MICIN_ERROR_ALREADY_INIT;
mic_thr.load_config_and_init();
mic_thr.init = 1;
return CELL_OK;
}
error_code cellMicEnd()
{
cellMic.notice("cellMicEnd()");
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
// TODO
mic_thr.init = 0;
mic_thr.event_queue_key = 0;
mic_thr.event_queue_source = 0;
return CELL_OK;
}
/// Open/Close Microphone Functions
error_code cellMicOpenEx(s32 dev_num, s32 rawSampleRate, s32 rawChannel, s32 DSPSampleRate, s32 bufferSizeMS, u8 signalType)
{
cellMic.notice("cellMicOpenEx(dev_num=%d, rawSampleRate=%d, rawChannel=%d, DSPSampleRate=%d, bufferSizeMS=%d, signalType=0x%x)",
dev_num, rawSampleRate, rawChannel, DSPSampleRate, bufferSizeMS, signalType);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (device.is_opened())
return CELL_MICIN_ERROR_ALREADY_OPEN;
// TODO: bufferSizeMS
return device.open_microphone(signalType, DSPSampleRate, rawSampleRate, rawChannel);
}
error_code cellMicOpen(s32 dev_num, s32 sampleRate)
{
cellMic.trace("cellMicOpen(dev_num=%d sampleRate=%d)", dev_num, sampleRate);
return cellMicOpenEx(dev_num, sampleRate, 1, sampleRate, 0x80, CELLMIC_SIGTYPE_DSP);
}
error_code cellMicOpenRaw(s32 dev_num, s32 sampleRate, s32 maxChannels)
{
cellMic.trace("cellMicOpenRaw(dev_num=%d, sampleRate=%d, maxChannels=%d)", dev_num, sampleRate, maxChannels);
return cellMicOpenEx(dev_num, sampleRate, maxChannels, sampleRate, 0x80, CELLMIC_SIGTYPE_RAW);
}
s32 cellMicIsOpen(s32 dev_num)
{
cellMic.trace("cellMicIsOpen(dev_num=%d)", dev_num);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return false;
if (!mic_thr.check_device(dev_num))
return false;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
return device.is_opened();
}
s32 cellMicIsAttached(s32 dev_num)
{
cellMic.trace("cellMicIsAttached(dev_num=%d)", dev_num);
// TODO
return 1;
}
error_code cellMicClose(s32 dev_num)
{
cellMic.trace("cellMicClose(dev_num=%d)", dev_num);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
return device.close_microphone();
}
/// Starting/Stopping Microphone Functions
error_code cellMicStartEx(s32 dev_num, u32 iflags)
{
cellMic.todo("cellMicStartEx(dev_num=%d, iflags=%d)", dev_num, iflags);
if ((iflags & 0xfffffffc) != 0) // iflags > 3
return CELL_MICIN_ERROR_PARAM;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
// TODO: flags
cellMic.success("We're getting started mate!");
return device.start_microphone();
}
error_code cellMicStart(s32 dev_num)
{
cellMic.trace("cellMicStart(dev_num=%d)", dev_num);
return cellMicStartEx(dev_num, 0);
}
error_code cellMicStop(s32 dev_num)
{
cellMic.trace("cellMicStop(dev_num=%d)", dev_num);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
if (device.is_started())
{
return device.stop_microphone();
}
return CELL_OK;
}
/// Microphone Attributes/States Functions
error_code cellMicGetDeviceAttr(s32 dev_num, CellMicDeviceAttr deviceAttributes, vm::ptr<s32> arg1, vm::ptr<s32> arg2)
{
cellMic.trace("cellMicGetDeviceAttr(dev_num=%d, deviceAttribute=%d, arg1=*0x%x, arg2=*0x%x)", dev_num, +deviceAttributes, arg1, arg2);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (dev_num < 0)
return CELL_MICIN_ERROR_PARAM;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (arg1)
{
switch (deviceAttributes)
{
case CELLMIC_DEVATTR_CHANVOL:
if (*arg1 == 0 || *arg1 > 2 || !arg2)
return CELL_MICIN_ERROR_PARAM;
*arg2 = ::at32(device.attr_chanvol, *arg1 - 1);
break;
case CELLMIC_DEVATTR_LED: *arg1 = device.attr_led; break;
case CELLMIC_DEVATTR_GAIN: *arg1 = device.attr_gain; break;
case CELLMIC_DEVATTR_VOLUME: *arg1 = device.attr_volume; break;
case CELLMIC_DEVATTR_AGC: *arg1 = device.attr_agc; break;
case CELLMIC_DEVATTR_DSPTYPE: *arg1 = device.attr_dsptype; break;
default: return CELL_MICIN_ERROR_PARAM;
}
}
return CELL_OK;
}
error_code cellMicSetDeviceAttr(s32 dev_num, CellMicDeviceAttr deviceAttributes, u32 arg1, u32 arg2)
{
cellMic.trace("cellMicSetDeviceAttr(dev_num=%d, deviceAttributes=%d, arg1=%d, arg2=%d)", dev_num, +deviceAttributes, arg1, arg2);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
switch (deviceAttributes)
{
case CELLMIC_DEVATTR_CHANVOL:
// Used by SingStar to set the volume of each mic
if (arg1 == 0 || arg1 > 2)
return CELL_MICIN_ERROR_PARAM;
::at32(device.attr_chanvol, arg1 - 1) = arg2;
break;
case CELLMIC_DEVATTR_LED: device.attr_led = arg1; break;
case CELLMIC_DEVATTR_GAIN: device.attr_gain = arg1; break;
case CELLMIC_DEVATTR_VOLUME: device.attr_volume = arg1; break;
case CELLMIC_DEVATTR_AGC: device.attr_agc = arg1; break;
case CELLMIC_DEVATTR_DSPTYPE: device.attr_dsptype = arg1; break;
default: return CELL_MICIN_ERROR_PARAM;
}
return CELL_OK;
}
error_code cellMicGetSignalAttr(s32 dev_num, CellMicSignalAttr sig_attrib, vm::ptr<void> value)
{
cellMic.todo("cellMicGetSignalAttr(dev_num=%d, sig_attrib=%d, value=*0x%x)", dev_num, +sig_attrib, value);
if (!value)
return CELL_MICIN_ERROR_PARAM;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
// TODO
return CELL_OK;
}
error_code cellMicSetSignalAttr(s32 dev_num, CellMicSignalAttr sig_attrib, vm::ptr<void> value)
{
cellMic.todo("cellMicSetSignalAttr(dev_num=%d, sig_attrib=%d, value=*0x%x)", dev_num, +sig_attrib, value);
if (!value)
return CELL_MICIN_ERROR_PARAM;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
// TODO
return CELL_OK;
}
error_code cellMicGetSignalState(s32 dev_num, CellMicSignalState sig_state, vm::ptr<void> value)
{
cellMic.trace("cellMicGetSignalState(dev_num=%d, sig_state=%d, value=*0x%x)", dev_num, +sig_state, value);
if (!value)
return CELL_MICIN_ERROR_PARAM;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
be_t<u32>* ival = vm::_ptr<u32>(value.addr());
be_t<f32>* fval = vm::_ptr<f32>(value.addr());
// TODO
switch (sig_state)
{
case CELLMIC_SIGSTATE_LOCTALK:
*ival = 9; // Someone is probably talking (0 to 10)
break;
case CELLMIC_SIGSTATE_FARTALK:
*ival = 1; // The speakers are probably off (0 to 10)
break;
case CELLMIC_SIGSTATE_NSR:
*fval = 0.0f; // No noise reduction
break;
case CELLMIC_SIGSTATE_AGC:
*fval = 1.0f; // No gain applied
break;
case CELLMIC_SIGSTATE_MICENG:
*fval = 40.0f; // 40 decibels
break;
case CELLMIC_SIGSTATE_SPKENG:
*fval = 10.0f; // 10 decibels
break;
default:
return CELL_MICIN_ERROR_PARAM;
}
return CELL_OK;
}
error_code cellMicGetFormatEx(s32 dev_num, vm::ptr<CellMicInputFormatI> format, /*CellMicSignalType*/u32 type)
{
cellMic.trace("cellMicGetFormatEx(dev_num=%d, format=*0x%x, type=0x%x)", dev_num, format, type);
if (!format)
return CELL_MICIN_ERROR_PARAM;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
// TODO: type
format->subframeSize = device.get_bit_resolution() / 8; // Probably?
format->bitResolution = device.get_bit_resolution();
format->sampleRate = device.get_raw_samplingrate();
format->channelNum = device.get_num_channels();
format->dataType = device.get_datatype();
return CELL_OK;
}
error_code cellMicGetFormat(s32 dev_num, vm::ptr<CellMicInputFormatI> format)
{
cellMic.todo("cellMicGetFormat(dev_num=%d, format=*0x%x)", dev_num, format);
return cellMicGetFormatEx(dev_num, format, CELLMIC_SIGTYPE_DSP);
}
error_code cellMicGetFormatRaw(s32 dev_num, vm::ptr<CellMicInputFormatI> format)
{
cellMic.trace("cellMicGetFormatRaw(dev_num=%d, format=0x%x)", dev_num, format);
return cellMicGetFormatEx(dev_num, format, CELLMIC_SIGTYPE_RAW);
}
error_code cellMicGetFormatAux(s32 dev_num, vm::ptr<CellMicInputFormatI> format)
{
cellMic.todo("cellMicGetFormatAux(dev_num=%d, format=0x%x)", dev_num, format);
return cellMicGetFormatEx(dev_num, format, CELLMIC_SIGTYPE_AUX);
}
error_code cellMicGetFormatDsp(s32 dev_num, vm::ptr<CellMicInputFormatI> format)
{
cellMic.todo("cellMicGetFormatDsp(dev_num=%d, format=0x%x)", dev_num, format);
return cellMicGetFormatEx(dev_num, format, CELLMIC_SIGTYPE_DSP);
}
/// Event Queue Functions
error_code cellMicSetNotifyEventQueue(u64 key)
{
cellMic.todo("cellMicSetNotifyEventQueue(key=0x%llx)", key);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
// default mic queue size = 4
auto mic_queue = lv2_event_queue::find(key);
if (!mic_queue)
return CELL_MICIN_ERROR_EVENT_QUEUE;
mic_thr.event_queue_key = key;
// TODO: Properly generate/handle mic events
for (usz i = 0; i < mic_thr.mic_list.size(); i++)
{
microphone_device& device = ::at32(mic_thr.mic_list, i);
if (device.is_registered())
{
mic_queue->send(0, CELLMIC_ATTACH, i, 0);
}
}
return CELL_OK;
}
error_code cellMicSetNotifyEventQueue2(u64 key, u64 source, u64 flag)
{
cellMic.todo("cellMicSetNotifyEventQueue2(key=0x%llx, source=0x%llx, flag=0x%llx", key, source, flag);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
// default mic queue size = 4
auto mic_queue = lv2_event_queue::find(key);
if (!mic_queue)
return CELL_MICIN_ERROR_EVENT_QUEUE;
mic_thr.event_queue_key = key;
mic_thr.event_queue_source = source;
// TODO: Properly generate/handle mic events
for (usz i = 0; i < mic_thr.mic_list.size(); i++)
{
microphone_device& device = ::at32(mic_thr.mic_list, i);
if (device.is_registered())
{
mic_queue->send(source, CELLMIC_ATTACH, i, 0);
}
}
return CELL_OK;
}
error_code cellMicRemoveNotifyEventQueue(u64 key)
{
cellMic.warning("cellMicRemoveNotifyEventQueue(key=0x%llx)", key);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
mic_thr.event_queue_key = 0;
mic_thr.event_queue_source = 0;
return CELL_OK;
}
/// Reading Functions
error_code cell_mic_read(s32 dev_num, vm::ptr<void> data, s32 max_bytes, /*CellMicSignalType*/u32 type)
{
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened() || !(device.get_signal_types() & type))
return CELL_MICIN_ERROR_NOT_OPEN;
if (!data)
return not_an_error(0);
switch (type)
{
case CELLMIC_SIGTYPE_DSP: return not_an_error(device.read_dsp(vm::_ptr<u8>(data.addr()), max_bytes));
case CELLMIC_SIGTYPE_AUX: return not_an_error(0); // TODO
case CELLMIC_SIGTYPE_RAW: return not_an_error(device.read_raw(vm::_ptr<u8>(data.addr()), max_bytes));
default:
fmt::throw_exception("Invalid CELLMIC_SIGTYPE %d", type);
}
return not_an_error(0);
}
error_code cellMicReadRaw(s32 dev_num, vm::ptr<void> data, s32 max_bytes)
{
cellMic.trace("cellMicReadRaw(dev_num=%d, data=0x%x, maxBytes=%d)", dev_num, data, max_bytes);
return cell_mic_read(dev_num, data, max_bytes, CELLMIC_SIGTYPE_RAW);
}
error_code cellMicRead(s32 dev_num, vm::ptr<void> data, u32 max_bytes)
{
cellMic.warning("cellMicRead(dev_num=%d, data=0x%x, maxBytes=0x%x)", dev_num, data, max_bytes);
return cell_mic_read(dev_num, data, max_bytes, CELLMIC_SIGTYPE_DSP);
}
error_code cellMicReadAux(s32 dev_num, vm::ptr<void> data, s32 max_bytes)
{
cellMic.todo("cellMicReadAux(dev_num=%d, data=0x%x, max_bytes=0x%x)", dev_num, data, max_bytes);
return cell_mic_read(dev_num, data, max_bytes, CELLMIC_SIGTYPE_AUX);
}
error_code cellMicReadDsp(s32 dev_num, vm::ptr<void> data, s32 max_bytes)
{
cellMic.warning("cellMicReadDsp(dev_num=%d, data=0x%x, max_bytes=0x%x)", dev_num, data, max_bytes);
return cell_mic_read(dev_num, data, max_bytes, CELLMIC_SIGTYPE_DSP);
}
/// Unimplemented Functions
error_code cellMicReset(s32 dev_num)
{
cellMic.todo("cellMicReset(dev_num=%d)", dev_num);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (!mic_thr.check_device(dev_num))
return CELL_MICIN_ERROR_DEVICE_NOT_FOUND;
microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
if (!device.is_opened())
return CELL_MICIN_ERROR_NOT_OPEN;
// TODO
return CELL_OK;
}
error_code cellMicGetDeviceGUID(s32 dev_num, vm::ptr<u32> ptr_guid)
{
cellMic.todo("cellMicGetDeviceGUID(dev_num=%d ptr_guid=*0x%x)", dev_num, ptr_guid);
if (!ptr_guid)
return CELL_MICIN_ERROR_PARAM;
*ptr_guid = 0xffffffff;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
// TODO
return CELL_OK;
}
error_code cellMicGetDeviceIdentifier(s32 dev_num, vm::ptr<u32> ptr_id)
{
cellMic.todo("cellMicGetDeviceIdentifier(dev_num=%d, ptr_id=*0x%x)", dev_num, ptr_id);
if (ptr_id)
*ptr_id = 0x0;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
// TODO
return CELL_OK;
}
error_code cellMicGetType(s32 dev_num, vm::ptr<s32> ptr_type)
{
cellMic.trace("cellMicGetType(dev_num=%d, ptr_type=*0x%x)", dev_num, ptr_type);
if (!ptr_type)
return CELL_MICIN_ERROR_PARAM;
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
// TODO: get proper type (log message is trace because of massive spam)
*ptr_type = CELLMIC_TYPE_USBAUDIO; // Needed for Guitar Hero: Warriors of Rock (BLUS30487)
return CELL_OK;
}
error_code cellMicGetStatus(s32 dev_num, vm::ptr<CellMicStatus> status)
{
cellMic.todo("cellMicGetStatus(dev_num=%d, status=*0x%x)", dev_num, status);
auto& mic_thr = g_fxo->get<mic_thread>();
const std::lock_guard lock(mic_thr.mutex);
if (!mic_thr.init)
return CELL_MICIN_ERROR_NOT_INIT;
if (dev_num < 0 || !status)
return CELL_MICIN_ERROR_PARAM;
// TODO
if (dev_num < static_cast<s32>(mic_thr.mic_list.size()))
{
const microphone_device& device = ::at32(mic_thr.mic_list, dev_num);
status->raw_samprate = device.get_raw_samplingrate();
status->dsp_samprate = device.get_raw_samplingrate();
status->isStart = device.is_started();
status->isOpen = device.is_opened();
status->dsp_volume = 5; // TODO: 0 - 5 volume
status->local_voice = 10; // TODO: 0 - 10 confidence
status->remote_voice = 0; // TODO: 0 - 10 confidence
status->mic_energy = 60; // TODO: Db
status->spk_energy = 60; // TODO: Db
}
return CELL_OK;
}
error_code cellMicStopEx()
{
cellMic.fatal("cellMicStopEx: unexpected function");
return CELL_OK;
}
error_code cellMicSysShareClose()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicSetMultiMicNotifyEventQueue()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicSysShareStop()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicSysShareOpen()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicCommand()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicSysShareStart()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicSysShareInit()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
error_code cellMicSysShareEnd()
{
UNIMPLEMENTED_FUNC(cellMic);
return CELL_OK;
}
DECLARE(ppu_module_manager::cellMic)("cellMic", []()
{
REG_FUNC(cellMic, cellMicInit);
REG_FUNC(cellMic, cellMicEnd);
REG_FUNC(cellMic, cellMicOpen);
REG_FUNC(cellMic, cellMicClose);
REG_FUNC(cellMic, cellMicGetDeviceGUID);
REG_FUNC(cellMic, cellMicGetType);
REG_FUNC(cellMic, cellMicIsAttached);
REG_FUNC(cellMic, cellMicIsOpen);
REG_FUNC(cellMic, cellMicGetDeviceAttr);
REG_FUNC(cellMic, cellMicSetDeviceAttr);
REG_FUNC(cellMic, cellMicGetSignalAttr);
REG_FUNC(cellMic, cellMicSetSignalAttr);
REG_FUNC(cellMic, cellMicGetSignalState);
REG_FUNC(cellMic, cellMicStart);
REG_FUNC(cellMic, cellMicRead);
REG_FUNC(cellMic, cellMicStop);
REG_FUNC(cellMic, cellMicReset);
REG_FUNC(cellMic, cellMicSetNotifyEventQueue);
REG_FUNC(cellMic, cellMicSetNotifyEventQueue2);
REG_FUNC(cellMic, cellMicRemoveNotifyEventQueue);
REG_FUNC(cellMic, cellMicOpenEx);
REG_FUNC(cellMic, cellMicStartEx);
REG_FUNC(cellMic, cellMicGetFormatRaw);
REG_FUNC(cellMic, cellMicGetFormatAux);
REG_FUNC(cellMic, cellMicGetFormatDsp);
REG_FUNC(cellMic, cellMicOpenRaw);
REG_FUNC(cellMic, cellMicReadRaw);
REG_FUNC(cellMic, cellMicReadAux);
REG_FUNC(cellMic, cellMicReadDsp);
REG_FUNC(cellMic, cellMicGetStatus);
REG_FUNC(cellMic, cellMicStopEx); // this function shouldn't exist
REG_FUNC(cellMic, cellMicSysShareClose);
REG_FUNC(cellMic, cellMicGetFormat);
REG_FUNC(cellMic, cellMicSetMultiMicNotifyEventQueue);
REG_FUNC(cellMic, cellMicGetFormatEx);
REG_FUNC(cellMic, cellMicSysShareStop);
REG_FUNC(cellMic, cellMicSysShareOpen);
REG_FUNC(cellMic, cellMicCommand);
REG_FUNC(cellMic, cellMicSysShareStart);
REG_FUNC(cellMic, cellMicSysShareInit);
REG_FUNC(cellMic, cellMicSysShareEnd);
REG_FUNC(cellMic, cellMicGetDeviceIdentifier);
});