diff --git a/rpcs3/Emu/Cell/Modules/cellAvconfExt.cpp b/rpcs3/Emu/Cell/Modules/cellAvconfExt.cpp index f748f87bf8..8418c646c3 100644 --- a/rpcs3/Emu/Cell/Modules/cellAvconfExt.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAvconfExt.cpp @@ -1,8 +1,9 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/System.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" #include "Emu/RSX/rsx_utils.h" +#include "Utilities/StrUtil.h" #include "cellAudioIn.h" #include "cellAudioOut.h" @@ -11,6 +12,115 @@ LOG_CHANNEL(cellAvconfExt); +struct avconf_manager +{ + std::vector devices; + + void copy_device_info(u32 num, vm::ptr info); + avconf_manager(); +}; + +avconf_manager::avconf_manager() +{ + u32 curindex = 0; + + auto mic_list = fmt::split(g_cfg.audio.microphone_devices, {"@@@"}); + if (mic_list.size()) + { + switch (g_cfg.audio.microphone_type) + { + case microphone_handler::standard: + for (u32 index = 0; index < mic_list.size(); index++) + { + devices.emplace_back(); + + devices[curindex].portType = CELL_AUDIO_IN_PORT_USB; + devices[curindex].availableModeCount = 1; + devices[curindex].state = CELL_AUDIO_IN_DEVICE_STATE_AVAILABLE; + devices[curindex].deviceId = 0xE11CC0DE + curindex; + devices[curindex].type = 0xC0DEE11C; + devices[curindex].availableModes[0].type = CELL_AUDIO_IN_CODING_TYPE_LPCM; + devices[curindex].availableModes[0].channel = CELL_AUDIO_IN_CHNUM_2; + devices[curindex].availableModes[0].fs = CELL_AUDIO_IN_FS_8KHZ | CELL_AUDIO_IN_FS_12KHZ | CELL_AUDIO_IN_FS_16KHZ | CELL_AUDIO_IN_FS_24KHZ | CELL_AUDIO_IN_FS_32KHZ | CELL_AUDIO_IN_FS_48KHZ; + devices[curindex].deviceNumber = curindex; + strcpy(devices[curindex].name, mic_list[index].c_str()); + + curindex++; + } + break; + case microphone_handler::real_singstar: + case microphone_handler::singstar: + // Only one device for singstar device + devices.emplace_back(); + + devices[curindex].portType = CELL_AUDIO_IN_PORT_USB; + devices[curindex].availableModeCount = 1; + devices[curindex].state = CELL_AUDIO_IN_DEVICE_STATE_AVAILABLE; + devices[curindex].deviceId = 0x57A3C0DE; + devices[curindex].type = 0xC0DE57A3; + devices[curindex].availableModes[0].type = CELL_AUDIO_IN_CODING_TYPE_LPCM; + devices[curindex].availableModes[0].channel = CELL_AUDIO_IN_CHNUM_2; + devices[curindex].availableModes[0].fs = CELL_AUDIO_IN_FS_8KHZ | CELL_AUDIO_IN_FS_12KHZ | CELL_AUDIO_IN_FS_16KHZ | CELL_AUDIO_IN_FS_24KHZ | CELL_AUDIO_IN_FS_32KHZ | CELL_AUDIO_IN_FS_48KHZ; + devices[curindex].deviceNumber = curindex; + strcpy(devices[curindex].name, mic_list[0].c_str()); + + curindex++; + break; + case microphone_handler::rocksmith: + devices.emplace_back(); + + devices[curindex].portType = CELL_AUDIO_IN_PORT_USB; + devices[curindex].availableModeCount = 1; + devices[curindex].state = CELL_AUDIO_IN_DEVICE_STATE_AVAILABLE; + devices[curindex].deviceId = 0x12BA00FF; // Specific to rocksmith usb input + devices[curindex].type = 0xC0DE73C4; + devices[curindex].availableModes[0].type = CELL_AUDIO_IN_CODING_TYPE_LPCM; + devices[curindex].availableModes[0].channel = CELL_AUDIO_IN_CHNUM_1; + devices[curindex].availableModes[0].fs = CELL_AUDIO_IN_FS_8KHZ | CELL_AUDIO_IN_FS_12KHZ | CELL_AUDIO_IN_FS_16KHZ | CELL_AUDIO_IN_FS_24KHZ | CELL_AUDIO_IN_FS_32KHZ | CELL_AUDIO_IN_FS_48KHZ; + devices[curindex].deviceNumber = curindex; + strcpy(devices[curindex].name, mic_list[0].c_str()); + + curindex++; + break; + default: break; + } + } + + if (g_cfg.io.camera != camera_handler::null) + { + devices.emplace_back(); + + devices[curindex].portType = CELL_AUDIO_IN_PORT_USB; + devices[curindex].availableModeCount = 1; + devices[curindex].state = CELL_AUDIO_IN_DEVICE_STATE_AVAILABLE; + devices[curindex].deviceId = 0xDEADBEEF; + devices[curindex].type = 0xBEEFDEAD; + devices[curindex].availableModes[0].type = CELL_AUDIO_IN_CODING_TYPE_LPCM; + devices[curindex].availableModes[0].channel = CELL_AUDIO_IN_CHNUM_NONE; + devices[curindex].availableModes[0].fs = CELL_AUDIO_IN_FS_8KHZ | CELL_AUDIO_IN_FS_12KHZ | CELL_AUDIO_IN_FS_16KHZ | CELL_AUDIO_IN_FS_24KHZ | CELL_AUDIO_IN_FS_32KHZ | CELL_AUDIO_IN_FS_48KHZ; + devices[curindex].deviceNumber = curindex; + strcpy(devices[curindex].name, "USB Camera"); + + curindex++; + } +} + +void avconf_manager::copy_device_info(u32 num, vm::ptr info) +{ + memset(info.get_ptr(), 0, sizeof(CellAudioInDeviceInfo)); + + info->portType = devices[num].portType; + info->availableModeCount = devices[num].availableModeCount; + info->state = devices[num].state; + info->deviceId = devices[num].deviceId; + info->type = devices[num].type; + info->availableModes[0].type = devices[num].availableModes[0].type; + info->availableModes[0].channel = devices[num].availableModes[0].channel; + info->availableModes[0].fs = devices[num].availableModes[0].fs; + info->deviceNumber = devices[num].deviceNumber; + strcpy(info->name, devices[num].name); +} + s32 cellAudioOutUnregisterDevice(u32 deviceNumber) { cellAvconfExt.todo("cellAudioOutUnregisterDevice(deviceNumber=0x%x)", deviceNumber); @@ -38,23 +148,21 @@ s32 cellVideoOutSetupDisplay() s32 cellAudioInGetDeviceInfo(u32 deviceNumber, u32 deviceIndex, vm::ptr info) { cellAvconfExt.todo("cellAudioInGetDeviceInfo(deviceNumber=0x%x, deviceIndex=0x%x, info=*0x%x)", deviceNumber, deviceIndex, info); - info->portType = CELL_AUDIO_IN_PORT_USB; - info->availableModeCount = 1; - info->state = CELL_AUDIO_IN_DEVICE_STATE_AVAILABLE; - info->deviceNumber = 0; - // Some games check if deviceId and type are the same. - info->deviceId = 0xDEADBEEF; - info->type = 0xBEEFDEAD; - info->availableModes[0].type = CELL_AUDIO_IN_CODING_TYPE_LPCM; - info->availableModes[0].channel = CELL_AUDIO_IN_CHNUM_NONE; - info->availableModes[0].fs = CELL_AUDIO_IN_FS_8KHZ | CELL_AUDIO_IN_FS_12KHZ | CELL_AUDIO_IN_FS_16KHZ | CELL_AUDIO_IN_FS_24KHZ | CELL_AUDIO_IN_FS_32KHZ | CELL_AUDIO_IN_FS_48KHZ; - strcpy(info->name, "USB Camera"); + + auto av_manager = fxm::get_always(); + + if (deviceNumber >= av_manager->devices.size()) + return CELL_AUDIO_OUT_ERROR_DEVICE_NOT_FOUND; + + av_manager->copy_device_info(deviceNumber, info); + return CELL_OK; } s32 cellVideoOutConvertCursorColor(u32 videoOut, s32 displaybuffer_format, f32 gamma, s32 source_buffer_format, vm::ptr src_addr, vm::ptr dest_addr, s32 num) { - cellAvconfExt.todo("cellVideoOutConvertCursorColor(videoOut=%d, displaybuffer_format=0x%x, gamma=0x%x, source_buffer_format=0x%x, src_addr=*0x%x, dest_addr=*0x%x, num=0x%x)", videoOut, displaybuffer_format, gamma, source_buffer_format, src_addr, dest_addr, num); + cellAvconfExt.todo("cellVideoOutConvertCursorColor(videoOut=%d, displaybuffer_format=0x%x, gamma=0x%x, source_buffer_format=0x%x, src_addr=*0x%x, dest_addr=*0x%x, num=0x%x)", videoOut, + displaybuffer_format, gamma, source_buffer_format, src_addr, dest_addr, num); return CELL_OK; } @@ -68,26 +176,30 @@ s32 cellVideoOutGetGamma(u32 videoOut, vm::ptr gamma) } auto conf = fxm::get_always(); - *gamma = conf->gamma; + *gamma = conf->gamma; return CELL_OK; } -s32 cellAudioInGetAvailableDeviceInfo(u32 count, vm::ptr info) +s32 cellAudioInGetAvailableDeviceInfo(u32 count, vm::ptr device_info) { - cellAvconfExt.todo("cellAudioInGetAvailableDeviceInfo(count=0x%x, info=*0x%x)", count, info); - info->portType = CELL_AUDIO_IN_PORT_USB; - info->availableModeCount = 1; - info->state = CELL_AUDIO_IN_DEVICE_STATE_AVAILABLE; - info->deviceNumber = 0; - // Some games check if deviceId and type are the same. - info->deviceId = 0xDEADBEEF; - info->type = 0xBEEFDEAD; - info->availableModes[0].type = CELL_AUDIO_IN_CODING_TYPE_LPCM; - info->availableModes[0].channel = CELL_AUDIO_IN_CHNUM_NONE; - info->availableModes[0].fs = CELL_AUDIO_IN_FS_8KHZ | CELL_AUDIO_IN_FS_12KHZ | CELL_AUDIO_IN_FS_16KHZ | CELL_AUDIO_IN_FS_24KHZ | CELL_AUDIO_IN_FS_32KHZ | CELL_AUDIO_IN_FS_48KHZ; - strcpy(info->name, "USB Camera"); - return 1; // number of available devices + cellAvconfExt.todo("cellAudioInGetAvailableDeviceInfo(count=0x%x, info=*0x%x)", count, device_info); + + if (count > 16 || !device_info.addr()) + { + return CELL_AUDIO_IN_ERROR_ILLEGAL_PARAMETER; + } + + auto av_manager = fxm::get_always(); + + u32 num_devices_returned = std::min(count, (u32)av_manager->devices.size()); + + for (u32 index = 0; index < num_devices_returned; index++) + { + av_manager->copy_device_info(index, device_info + index); + } + + return (s32)num_devices_returned; } s32 cellAudioOutGetAvailableDeviceInfo(u32 count, vm::ptr info) @@ -110,7 +222,7 @@ s32 cellVideoOutSetGamma(u32 videoOut, f32 gamma) return CELL_VIDEO_OUT_ERROR_ILLEGAL_PARAMETER; } - auto conf = fxm::get_always(); + auto conf = fxm::get_always(); conf->gamma = gamma; return CELL_OK; @@ -137,6 +249,7 @@ s32 cellAudioInSetDeviceMode(u32 deviceMode) s32 cellAudioInRegisterDevice(u64 deviceType, vm::cptr name, vm::ptr option, vm::ptr config) { cellAvconfExt.todo("cellAudioInRegisterDevice(deviceType=0x%llx, name=%s, option=*0x%x, config=*0x%x)", deviceType, name, option, config); + return 0; // device number } @@ -155,10 +268,10 @@ s32 cellVideoOutGetScreenSize(u32 videoOut, vm::ptr screenSize) return CELL_VIDEO_OUT_ERROR_UNSUPPORTED_VIDEO_OUT; } - //TODO: Use virtual screen size + // TODO: Use virtual screen size #ifdef _WIN32 - //HDC screen = GetDC(NULL); - //float diagonal = roundf(sqrtf((powf(float(GetDeviceCaps(screen, HORZSIZE)), 2) + powf(float(GetDeviceCaps(screen, VERTSIZE)), 2))) * 0.0393f); + // HDC screen = GetDC(NULL); + // float diagonal = roundf(sqrtf((powf(float(GetDeviceCaps(screen, HORZSIZE)), 2) + powf(float(GetDeviceCaps(screen, VERTSIZE)), 2))) * 0.0393f); #else // TODO: Linux implementation, without using wx // float diagonal = roundf(sqrtf((powf(wxGetDisplaySizeMM().GetWidth(), 2) + powf(wxGetDisplaySizeMM().GetHeight(), 2))) * 0.0393f); @@ -173,9 +286,8 @@ s32 cellVideoOutSetCopyControl(u32 videoOut, u32 control) return CELL_OK; } - -DECLARE(ppu_module_manager::cellAvconfExt)("cellSysutilAvconfExt", []() -{ +DECLARE(ppu_module_manager::cellAvconfExt) +("cellSysutilAvconfExt", []() { REG_FUNC(cellSysutilAvconfExt, cellAudioOutUnregisterDevice); REG_FUNC(cellSysutilAvconfExt, cellAudioOutGetDeviceInfo2); REG_FUNC(cellSysutilAvconfExt, cellVideoOutSetXVColor); diff --git a/rpcs3/Emu/Cell/Modules/cellMic.cpp b/rpcs3/Emu/Cell/Modules/cellMic.cpp index 8fb5a40ab0..cc5effba99 100644 --- a/rpcs3/Emu/Cell/Modules/cellMic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMic.cpp @@ -1,6 +1,7 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/System.h" #include "Emu/Cell/PPUModule.h" +#include "Utilities/StrUtil.h" #include "cellMic.h" #include @@ -8,187 +9,720 @@ LOG_CHANNEL(cellMic); +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) { + switch (value) + { + case microphone_handler::null: return "Null"; + case microphone_handler::standard: return "Standard"; + case microphone_handler::singstar: return "Singstar"; + case microphone_handler::real_singstar: return "Real Singstar"; + case microphone_handler::rocksmith: return "Rocksmith"; + } + + return unknown; + }); +} + void mic_context::operator()() { - while (fxm::check() == this && !Emu.IsStopped()) + while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped()) { - thread_ctrl::wait_for(1000); - + // The time between processing is copied from audio thread + // Might be inaccurate for mic thread if (Emu.IsPaused()) + { + thread_ctrl::wait_for(1000); // hack continue; + } - if (!micOpened || !micStarted) + const u64 stamp0 = get_system_time(); + const u64 time_pos = stamp0 - start_time - Emu.GetPauseTime(); + + const u64 expected_time = m_counter * 256 * 1000000 / 48000; + if (expected_time >= time_pos) + { + thread_ctrl::wait_for(1000); // hack continue; + } + m_counter++; - auto micQueue = lv2_event_queue::find(eventQueueKey); - if (!micQueue) - continue; + // Process signals + { + auto micl = g_idm->lock(0); - micQueue->send(0, CELL_MIC_DATA, 0, 0); + for (auto& mic_entry : mic_list) + { + auto& mic = mic_entry.second; + mic.update_audio(); + } + + auto mic_queue = lv2_event_queue::find(event_queue_key); + if (!mic_queue) + continue; + + for (auto& mic_entry : mic_list) + { + auto& mic = mic_entry.second; + if (mic.has_data()) + { + mic_queue->send(0, CELL_MIC_DATA, mic_entry.first, 0); + } + } + } + } + + // Cleanup + for (auto& mic_entry : mic_list) + { + mic_entry.second.close_microphone(); } } +void mic_context::load_config_and_init() +{ + auto device_list = fmt::split(g_cfg.audio.microphone_devices, {"@@@"}); + + if (device_list.size()) + { + switch (g_cfg.audio.microphone_type) + { + case microphone_handler::standard: + { + for (u32 index = 0; index < device_list.size(); index++) + { + mic_list.emplace(std::piecewise_construct, std::forward_as_tuple(index), std::forward_as_tuple(microphone_handler::standard)); + mic_list.at(index).add_device(device_list[index]); + } + break; + } + case microphone_handler::singstar: + { + mic_list.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(microphone_handler::singstar)); + mic_list.at(0).add_device(device_list[0]); + if (device_list.size() >= 2) + mic_list.at(0).add_device(device_list[1]); + break; + } + case microphone_handler::real_singstar: + case microphone_handler::rocksmith: + { + mic_list.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(g_cfg.audio.microphone_type)); + mic_list.at(0).add_device(device_list[0]); + break; + } + default: break; + } + } +} + +// Static functions + +void microphone_device::variable_byteswap(const void* src, void* dst, const u32 bytesize) +{ + switch (bytesize) + { + case 4: *(u32*)dst = *(be_t*)src; break; + case 2: *(u16*)dst = *(be_t*)src; 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); +} + +s32 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: + if (num_channels > 2) + { + cellMic.warning("Reducing number of mic channels from %d to 2 for %s", num_channels, device_name[0]); + num_channels = 2; + } + break; + case microphone_handler::singstar: + case microphone_handler::real_singstar: + // Singstar mic has always 2 channels, each channel represent a physical microphone + ASSERT(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; + default: ASSERT(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: + if (alcIsExtensionPresent(NULL, "AL_EXT_MCFORMATS") == AL_TRUE) + { + num_al_channels = AL_FORMAT_QUAD16; + } + else + { + cellMic.error("Requested 4 channels but AL_EXT_MCFORMATS not available, settling down to 2"); + num_channels = 2; + num_al_channels = AL_FORMAT_STEREO16; + } + break; + default: + cellMic.fatal("Requested an invalid number of channels: %d", num_channels); + num_al_channels = AL_FORMAT_STEREO16; + break; + } + + 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_MIC_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; +} + +s32 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; +} + +s32 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_MIC_ERROR_FATAL; + } + } + + mic_started = true; + + return CELL_OK; +} + +s32 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_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_opened && mic_started && (rbuf_raw.has_data() || rbuf_dsp.has_data()); +} + +u32 microphone_device::capture_audio() +{ + 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_MIC_ERROR_FATAL; + } + num_samples = std::min(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: + verify(HERE), 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; + default: ASSERT(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: + verify(HERE), 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; + default: ASSERT(false); break; + } + + rbuf_dsp.write_bytes(tmp_ptr, num_samples * sample_size); +}; + /// Initialization/Shutdown Functions s32 cellMicInit() { cellMic.notice("cellMicInit()"); - const auto micThread = fxm::make("Mic Thread"); - if (!micThread) + auto mic_thr = g_idm->lock(id_new); + if (!mic_thr) return CELL_MIC_ERROR_ALREADY_INIT; + mic_thr.create("Microphone Thread"); + + mic_thr->load_config_and_init(); + return CELL_OK; } -s32 cellMicEnd() +s32 cellMicEnd(ppu_thread& ppu) { cellMic.notice("cellMicEnd()"); - const auto micThread = fxm::withdraw(); - if (!micThread) + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - // Join - micThread->operator()(); + *mic_thr.get() = thread_state::aborting; + mic_thr.unlock(); + + while (true) + { + if (ppu.is_stopped()) + { + return 0; + } + + thread_ctrl::wait_for(1000); + + auto mic_thr = g_idm->lock(0); + + if (*mic_thr.get() == thread_state::finished) + { + mic_thr.destroy(); + mic_thr.unlock(); + break; + } + } + return CELL_OK; } /// Open/Close Microphone Functions -s32 cellMicOpen(u32 deviceNumber, u32 sampleRate) +s32 cellMicOpen(u32 dev_num, u32 sampleRate) { - cellMic.todo("cellMicOpen(deviceNumber=%um sampleRate=%u)", deviceNumber, sampleRate); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicOpen(dev_num=%um sampleRate=%u)", dev_num, sampleRate); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - if (micThread->micOpened) + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + if (device.is_opened()) return CELL_MIC_ERROR_ALREADY_OPEN; - micThread->DspFrequency = sampleRate; - micThread->micOpened = true; - return CELL_OK; + return device.open_microphone(CELLMIC_SIGTYPE_DSP, sampleRate, sampleRate); } -s32 cellMicOpenRaw(u32 deviceNumber, u32 sampleRate, u32 maxChannels) +s32 cellMicOpenRaw(u32 dev_num, u32 sampleRate, u32 maxChannels) { - cellMic.todo("cellMicOpenRaw(deviceNumber=%d, sampleRate=%d, maxChannels=%d)", deviceNumber, sampleRate, maxChannels); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicOpenRaw(dev_num=%d, sampleRate=%d, maxChannels=%d)", dev_num, sampleRate, maxChannels); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - if (micThread->micOpened) + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + if (device.is_opened()) return CELL_MIC_ERROR_ALREADY_OPEN; - micThread->rawFrequency = sampleRate; - micThread->micOpened = true; - return CELL_OK; + return device.open_microphone(CELLMIC_SIGTYPE_DSP | CELLMIC_SIGTYPE_RAW, sampleRate, sampleRate, maxChannels); } -s32 cellMicOpenEx(u32 deviceNumber, u32 rawSampleRate, u32 rawChannel, u32 DSPSampleRate, u32 bufferSizeMS, u8 signalType) +s32 cellMicOpenEx(u32 dev_num, u32 rawSampleRate, u32 rawChannel, u32 DSPSampleRate, u32 bufferSizeMS, u8 signalType) { - cellMic.todo("cellMicOpenEx(deviceNumber=%d, rawSampleRate=%d, rawChannel=%d, DSPSampleRate=%d, bufferSizeMS=%d, signalType=0x%x)", deviceNumber, rawSampleRate, rawChannel, DSPSampleRate, - bufferSizeMS, signalType); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace( + "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_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - if (micThread->micOpened) + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + if (device.is_opened()) return CELL_MIC_ERROR_ALREADY_OPEN; - micThread->rawFrequency = rawSampleRate; - micThread->DspFrequency = DSPSampleRate; - micThread->micOpened = true; - return CELL_OK; + // TODO: bufferSizeMS + + return device.open_microphone(signalType, DSPSampleRate, rawSampleRate, rawChannel); } -u8 cellMicIsOpen(u32 deviceNumber) +u8 cellMicIsOpen(u32 dev_num) { - cellMic.warning("cellMicIsOpen(deviceNumber=%d)", deviceNumber); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicIsOpen(dev_num=%d)", dev_num); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return false; - return micThread->micOpened; + if (!mic_thr->mic_list.count(dev_num)) + return false; + + return mic_thr->mic_list.at(dev_num).is_opened(); } -s32 cellMicIsAttached(u32 deviceNumber) +s32 cellMicIsAttached(u32 dev_num) { - cellMic.warning("cellMicIsAttached(deviceNumber=%d)", deviceNumber); + cellMic.notice("cellMicIsAttached(dev_num=%d)", dev_num); return 1; } -s32 cellMicClose(u32 deviceNumber) +s32 cellMicClose(u32 dev_num) { - cellMic.warning("cellMicClose(deviceNumber=%d)", deviceNumber); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicClose(dev_num=%d)", dev_num); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; - micThread->micOpened = false; + auto& device = mic_thr->mic_list.at(dev_num); + if (!device.is_opened()) + return CELL_MIC_ERROR_NOT_OPEN; + + device.close_microphone(); return CELL_OK; } /// Starting/Stopping Microphone Functions -s32 cellMicStart(u32 deviceNumber) +s32 cellMicStart(u32 dev_num) { - cellMic.warning("cellMicStart(deviceNumber=%d)", deviceNumber); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicStart(dev_num=%d)", dev_num); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - if (!micThread->micOpened) + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + if (!device.is_opened()) return CELL_MIC_ERROR_NOT_OPEN; - micThread->micStarted = true; - - return CELL_OK; + return device.start_microphone(); } -s32 cellMicStartEx(u32 deviceNumber, u32 flags) +s32 cellMicStartEx(u32 dev_num, u32 flags) { - cellMic.todo("cellMicStartEx(deviceNumber=%d, flags=%d)", deviceNumber, flags); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.todo("cellMicStartEx(dev_num=%d, flags=%d)", dev_num, flags); + + // TODO: flags + + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - if (!micThread->micOpened) + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + if (!device.is_opened()) return CELL_MIC_ERROR_NOT_OPEN; - micThread->micStarted = true; + cellMic.error("We're getting started mate!"); - return CELL_OK; + return device.start_microphone(); } -s32 cellMicStop(u32 deviceNumber) +s32 cellMicStop(u32 dev_num) { - cellMic.todo("cellMicStop(deviceNumber=%d)", deviceNumber); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicStop(dev_num=%d)", dev_num); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; - if (!micThread->micOpened) + auto& device = mic_thr->mic_list.at(dev_num); + + if (!device.is_opened()) return CELL_MIC_ERROR_NOT_OPEN; - micThread->micStarted = false; + if (device.is_started()) + { + device.stop_microphone(); + } return CELL_OK; } /// Microphone Attributes/States Functions -s32 cellMicGetDeviceAttr(u32 deviceNumber, vm::ptr deviceAttributes, vm::ptr arg1, vm::ptr arg2) +s32 cellMicGetDeviceAttr(u32 dev_num, CellMicDeviceAttr deviceAttributes, vm::ptr arg1, vm::ptr arg2) { - cellMic.todo("cellMicGetDeviceAttr(deviceNumber=%d, deviceAttribute=*0x%x, arg1=*0x%x, arg2=*0x%x)", deviceNumber, deviceAttributes, arg1, arg2); + cellMic.trace("cellMicGetDeviceAttr(dev_num=%d, deviceAttribute=%d, arg1=*0x%x, arg2=*0x%x)", dev_num, (u32)deviceAttributes, arg1, arg2); + + if (!arg1 || (!arg2 && deviceAttributes == CELLMIC_DEVATTR_CHANVOL)) + return CELL_MIC_ERROR_PARAM; + + auto mic_thr = g_idm->lock(0); + if (!mic_thr) + return CELL_MIC_ERROR_NOT_INIT; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + switch (deviceAttributes) + { + 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_CHANVOL: *arg1 = device.attr_volume; break; + case CELLMIC_DEVATTR_DSPTYPE: *arg1 = device.attr_dsptype; break; + default: return CELL_MIC_ERROR_PARAM; + } + return CELL_OK; } -s32 cellMicSetDeviceAttr() +s32 cellMicSetDeviceAttr(u32 dev_num, CellMicDeviceAttr deviceAttributes, u32 arg1, u32 arg2) { - UNIMPLEMENTED_FUNC(cellMic); + cellMic.trace("cellMicSetDeviceAttr(dev_num=%d, deviceAttributes=%d, arg1=%d, arg2=%d)", dev_num, (u32)deviceAttributes, arg1, arg2); + + auto mic_thr = g_idm->lock(0); + if (!mic_thr) + return CELL_MIC_ERROR_NOT_INIT; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + switch (deviceAttributes) + { + case CELLMIC_DEVATTR_CHANVOL: + // Used by Singstar to set the volume of each mic + if (arg1 > 2) + return CELL_MIC_ERROR_PARAM; + device.attr_chanvol[arg1] = 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_MIC_ERROR_PARAM; + } + return CELL_OK; } @@ -204,17 +738,17 @@ s32 cellMicSetSignalAttr() return CELL_OK; } -s32 cellMicGetSignalState(u32 deviceNumber, CellMicSignalState signalState, vm::ptr value) +s32 cellMicGetSignalState(u32 dev_num, CellMicSignalState sig_state, vm::ptr value) { - cellMic.todo("cellMicGetSignalState(deviceNumber=%d, signalSate=%d, value=0x%x)", deviceNumber, (int)signalState, value); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.todo("cellMicGetSignalState(dev_num=%d, signalSate=%d, value=*0x%x)", dev_num, (u32)sig_state, value); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; be_t* ival = (be_t*)value.get_ptr(); be_t* fval = (be_t*)value.get_ptr(); - switch (signalState) + switch (sig_state) { case CELL_MIC_SIGSTATE_LOCTALK: *ival = 9; // Someone is probably talking @@ -234,72 +768,67 @@ s32 cellMicGetSignalState(u32 deviceNumber, CellMicSignalState signalState, vm:: case CELL_MIC_SIGSTATE_SPKENG: // TODO break; + default: return CELL_MIC_ERROR_PARAM; } return CELL_OK; } -s32 cellMicGetFormatRaw(u32 deviceNumber, vm::ptr format) +s32 cellMicGetFormatRaw(u32 dev_num, vm::ptr format) { - cellMic.warning("cellMicGetFormatRaw(deviceNumber=%d, format=0x%x)", deviceNumber, format); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicGetFormatRaw(dev_num=%d, format=0x%x)", dev_num, format); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - format->channelNum = 4; - format->subframeSize = 2; - format->bitResolution = micThread->bitResolution; - format->dataType = 1; - format->sampleRate = micThread->rawFrequency; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; + + auto& device = mic_thr->mic_list.at(dev_num); + + 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; } -s32 cellMicGetFormatAux(u32 deviceNumber, vm::ptr format) +s32 cellMicGetFormatAux(u32 dev_num, vm::ptr format) { - cellMic.warning("cellMicGetFormatAux(deviceNumber=%d, format=0x%x)", deviceNumber, format); - const auto micThread = fxm::get(); - if (!micThread) - return CELL_MIC_ERROR_NOT_INIT; + cellMic.todo("cellMicGetFormatAux(dev_num=%d, format=0x%x)", dev_num, format); - format->channelNum = 4; - format->subframeSize = 2; - format->bitResolution = micThread->bitResolution; - format->dataType = 1; - format->sampleRate = micThread->AuxFrequency; - - return CELL_OK; + return cellMicGetFormatRaw(dev_num, format); } -s32 cellMicGetFormatDsp(u32 deviceNumber, vm::ptr format) +s32 cellMicGetFormatDsp(u32 dev_num, vm::ptr format) { - cellMic.warning("cellMicGetFormatDsp(deviceNumber=%d, format=0x%x)", deviceNumber, format); - const auto micThread = fxm::get(); - if (!micThread) - return CELL_MIC_ERROR_NOT_INIT; + cellMic.todo("cellMicGetFormatDsp(dev_num=%d, format=0x%x)", dev_num, format); - format->channelNum = 4; - format->subframeSize = 2; - format->bitResolution = micThread->bitResolution; - format->dataType = 1; - format->sampleRate = micThread->DspFrequency; - - return CELL_OK; + return cellMicGetFormatRaw(dev_num, format); } /// Event Queue Functions s32 cellMicSetNotifyEventQueue(u64 key) { - cellMic.warning("cellMicSetNotifyEventQueue(key=0x%llx)", key); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.todo("cellMicSetNotifyEventQueue(key=0x%llx)", key); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; // default mic queue size = 4 - auto micQueue = lv2_event_queue::find(key); - micQueue->send(0, CELL_MIC_ATTACH, 0, 0); - micThread->eventQueueKey = key; + auto mic_queue = lv2_event_queue::find(key); + if (!mic_queue) + return CELL_MIC_ERROR_EVENT_QUEUE; + + mic_thr->event_queue_key = key; + + for (auto& mic_entry : mic_thr->mic_list) + { + mic_queue->send(0, CELL_MIC_ATTACH, mic_entry.first, 0); + } return CELL_OK; } @@ -308,14 +837,17 @@ s32 cellMicSetNotifyEventQueue2(u64 key, u64 source) { // TODO: Actually do things with the source variable cellMic.todo("cellMicSetNotifyEventQueue2(key=0x%llx, source=0x%llx", key, source); - const auto micThread = fxm::get(); - if (!micThread) + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; // default mic queue size = 4 - auto micQueue = lv2_event_queue::find(key); - micQueue->send(0, CELL_MIC_ATTACH, 0, 0); - micThread->eventQueueKey = key; + auto mic_queue = lv2_event_queue::find(key); + if (!mic_queue) + return CELL_MIC_ERROR_EVENT_QUEUE; + + mic_queue->send(0, CELL_MIC_ATTACH, 0, 0); + mic_thr->event_queue_key = key; return CELL_OK; } @@ -323,39 +855,51 @@ s32 cellMicSetNotifyEventQueue2(u64 key, u64 source) s32 cellMicRemoveNotifyEventQueue(u64 key) { cellMic.warning("cellMicRemoveNotifyEventQueue(key=0x%llx)", key); - const auto micThread = fxm::get(); - if (!micThread) + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; - micThread->eventQueueKey = 0; + mic_thr->event_queue_key = 0; return CELL_OK; } /// Reading Functions -s32 cellMicRead(u32 deviceNumber, vm::ptr data, u32 maxBytes) +s32 cellMicReadRaw(u32 dev_num, vm::ptr data, u32 maxBytes) { - cellMic.warning("cellMicRead(deviceNumber=%d, data=0x%x, maxBytes=0x%x)", deviceNumber, data, maxBytes); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.trace("cellMicReadRaw(dev_num=%d, data=0x%x, maxBytes=%d)", dev_num, data, maxBytes); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; - const s32 size = std::min(maxBytes, bufferSize); + auto& mic = mic_thr->mic_list.at(dev_num); - return size; + if (!mic.is_opened() || !(mic.get_signal_types() & CELLMIC_SIGTYPE_RAW)) + return CELL_MIC_ERROR_NOT_OPEN; + + u8* res_buf = (u8*)data.get_ptr(); + return mic.read_raw(res_buf, maxBytes); } -s32 cellMicReadRaw(u32 deviceNumber, vm::ptr data, int maxBytes) +s32 cellMicRead(u32 dev_num, vm::ptr data, u32 maxBytes) { - cellMic.warning("cellMicReadRaw(deviceNumber=%d, data=0x%x, maxBytes=%d)", deviceNumber, data, maxBytes); - const auto micThread = fxm::get(); - if (!micThread) + cellMic.todo("cellMicRead(dev_num=%d, data=0x%x, maxBytes=0x%x)", dev_num, data, maxBytes); + auto mic_thr = g_idm->lock(0); + if (!mic_thr) return CELL_MIC_ERROR_NOT_INIT; + if (!mic_thr->mic_list.count(dev_num)) + return CELL_MIC_ERROR_DEVICE_NOT_FOUND; - const s32 size = std::min(maxBytes, bufferSize); + auto& mic = mic_thr->mic_list.at(dev_num); - return size; + if (!mic.is_opened() || !(mic.get_signal_types() & CELLMIC_SIGTYPE_DSP)) + return CELL_MIC_ERROR_NOT_OPEN; + + u8* res_buf = (u8*)data.get_ptr(); + return mic.read_dsp(res_buf, maxBytes); } s32 cellMicReadAux() @@ -384,9 +928,15 @@ s32 cellMicGetDeviceGUID() return CELL_OK; } -s32 cellMicGetType() +s32 cellMicGetType(u32 dev_num, vm::ptr ptr_type) { - UNIMPLEMENTED_FUNC(cellMic); + cellMic.todo("cellMicGetType(dev_num=%d, ptr_type=*0x%x)", dev_num, ptr_type); + + if (!ptr_type) + return CELL_MIC_ERROR_PARAM; + + *ptr_type = CELLMIC_TYPE_USBAUDIO; + return CELL_OK; } @@ -467,8 +1017,8 @@ s32 cellMicGetDeviceIdentifier() return CELL_OK; } -DECLARE(ppu_module_manager::cellMic)("cellMic", []() -{ +DECLARE(ppu_module_manager::cellMic) +("cellMic", []() { REG_FUNC(cellMic, cellMicInit); REG_FUNC(cellMic, cellMicEnd); REG_FUNC(cellMic, cellMicOpen); diff --git a/rpcs3/Emu/Cell/Modules/cellMic.h b/rpcs3/Emu/Cell/Modules/cellMic.h index 699aef9d2e..87b477bf6c 100644 --- a/rpcs3/Emu/Cell/Modules/cellMic.h +++ b/rpcs3/Emu/Cell/Modules/cellMic.h @@ -1,40 +1,42 @@ -#pragma once +#pragma once #include "Utilities/BEType.h" #include "Utilities/Thread.h" +#include "3rdparty/OpenAL/include/alext.h" + // Error Codes enum { - CELL_MIC_ERROR_ALREADY_INIT = 0x80140101, - CELL_MIC_ERROR_SYSTEM = 0x80140102, - CELL_MIC_ERROR_NOT_INIT = 0x80140103, - CELL_MIC_ERROR_PARAM = 0x80140104, - CELL_MIC_ERROR_PORT_FULL = 0x80140105, - CELL_MIC_ERROR_ALREADY_OPEN = 0x80140106, - CELL_MIC_ERROR_NOT_OPEN = 0x80140107, - CELL_MIC_ERROR_NOT_RUN = 0x80140108, - CELL_MIC_ERROR_TRANS_EVENT = 0x80140109, - CELL_MIC_ERROR_OPEN = 0x8014010a, - CELL_MIC_ERROR_SHAREDMEMORY = 0x8014010b, - CELL_MIC_ERROR_MUTEX = 0x8014010c, - CELL_MIC_ERROR_EVENT_QUEUE = 0x8014010d, - CELL_MIC_ERROR_DEVICE_NOT_FOUND = 0x8014010e, - CELL_MIC_ERROR_SYSTEM_NOT_FOUND = 0x8014010e, - CELL_MIC_ERROR_FATAL = 0x8014010f, + CELL_MIC_ERROR_ALREADY_INIT = 0x80140101, + CELL_MIC_ERROR_SYSTEM = 0x80140102, + CELL_MIC_ERROR_NOT_INIT = 0x80140103, + CELL_MIC_ERROR_PARAM = 0x80140104, + CELL_MIC_ERROR_PORT_FULL = 0x80140105, + CELL_MIC_ERROR_ALREADY_OPEN = 0x80140106, + CELL_MIC_ERROR_NOT_OPEN = 0x80140107, + CELL_MIC_ERROR_NOT_RUN = 0x80140108, + CELL_MIC_ERROR_TRANS_EVENT = 0x80140109, + CELL_MIC_ERROR_OPEN = 0x8014010a, + CELL_MIC_ERROR_SHAREDMEMORY = 0x8014010b, + CELL_MIC_ERROR_MUTEX = 0x8014010c, + CELL_MIC_ERROR_EVENT_QUEUE = 0x8014010d, + CELL_MIC_ERROR_DEVICE_NOT_FOUND = 0x8014010e, + CELL_MIC_ERROR_SYSTEM_NOT_FOUND = 0x8014010e, + CELL_MIC_ERROR_FATAL = 0x8014010f, CELL_MIC_ERROR_DEVICE_NOT_SUPPORT = 0x80140110, }; struct CellMicInputFormat { - u8 channelNum; - u8 subframeSize; - u8 bitResolution; - u8 dataType; - be_t sampleRate; + u8 channelNum; + u8 subframeSize; + u8 bitResolution; + u8 dataType; + be_t sampleRate; }; -enum CellMicSignalState +enum CellMicSignalState : u32 { CELL_MIC_SIGSTATE_LOCTALK = 0, CELL_MIC_SIGSTATE_FARTALK = 1, @@ -47,32 +49,220 @@ enum CellMicSignalState enum CellMicCommand { CELL_MIC_ATTACH = 2, - CELL_MIC_DATA = 5, + CELL_MIC_DATA = 5, }; -// TODO: generate this from input from an actual microphone -const u32 bufferSize = 1; +enum CellMicDeviceAttr : u32 +{ + CELLMIC_DEVATTR_LED = 9, + CELLMIC_DEVATTR_GAIN = 10, + CELLMIC_DEVATTR_VOLUME = 201, + CELLMIC_DEVATTR_AGC = 202, + CELLMIC_DEVATTR_CHANVOL = 301, + CELLMIC_DEVATTR_DSPTYPE = 302, +}; + +enum CellMicSignalType : u8 +{ + CELLMIC_SIGTYPE_NULL = 0, + CELLMIC_SIGTYPE_DSP = 1, + CELLMIC_SIGTYPE_AUX = 2, + CELLMIC_SIGTYPE_RAW = 4, +}; + +enum CellMicType : s32 +{ + CELLMIC_TYPE_UNDEF = -1, + CELLMIC_TYPE_UNKNOWN = 0, + CELLMIC_TYPE_EYETOY1 = 1, + CELLMIC_TYPE_EYETOY2 = 2, + CELLMIC_TYPE_USBAUDIO = 3, + CELLMIC_TYPE_BLUETOOTH = 4, + CELLMIC_TYPE_A2DP = 5, +} CellMicType; + +template +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) + { + ASSERT(size <= S); + + 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 m_container; + u32 m_head = 0, m_tail = 0, m_used = 0; +}; + +class microphone_device +{ +public: + microphone_device(microphone_handler type); + + void add_device(const std::string& name); + + s32 open_microphone(const u8 type, const u32 dsp_r, const u32 raw_r, const u8 channels = 2); + s32 close_microphone(); + + s32 start_microphone(); + s32 stop_microphone(); + + void update_audio(); + bool has_data() const; + + bool is_opened() const { return mic_opened; } + bool is_started() const { return mic_started; } + u8 get_signal_types() const { return signal_types; } + u8 get_bit_resolution() const { return bit_resolution; } + u32 get_raw_samplingrate() const { return raw_samplingrate; } + u8 get_num_channels() const { return num_channels; } + u8 get_datatype() const + { + switch(device_type) + { + case microphone_handler::real_singstar: + case microphone_handler::singstar: + return 0; // LE + default: + return 1; // BE + } + } + + u32 read_raw(u8* buf, u32 size) { return rbuf_raw.read_bytes(buf, size); } + u32 read_dsp(u8* buf, u32 size) { return rbuf_dsp.read_bytes(buf, size); } + + // Microphone attributes + u32 attr_gain = 3; + u32 attr_volume = 145; + u32 attr_agc = 0; + u32 attr_chanvol[2] = {145, 145}; + u32 attr_led = 0; + u32 attr_dsptype = 0; + +private: + static void variable_byteswap(const void* src, void* dst, const u32 bytesize); + + u32 capture_audio(); + + void get_raw(const u32 num_samples); + void get_dsp(const u32 num_samples); + +private: + microphone_handler device_type; + std::vector device_name; + + bool mic_opened = false; + bool mic_started = false; + + std::vector input_devices; + std::vector> internal_bufs; + std::vector temp_buf; + + // Sampling information provided at opening of mic + u32 raw_samplingrate = 48000; + u32 dsp_samplingrate = 48000; + u32 aux_samplingrate = 48000; + u8 bit_resolution = 16; + u8 num_channels = 2; + + u8 signal_types = CELLMIC_SIGTYPE_NULL; + + u32 sample_size; // Determined at opening for internal use + + static constexpr std::size_t inbuf_size = 400000; // Default value unknown + + simple_ringbuf rbuf_raw; + simple_ringbuf rbuf_dsp; + simple_ringbuf rbuf_aux; +}; class mic_context { public: void operator()(); + void load_config_and_init(); - // Default value of 48000 for no particular reason - u32 DspFrequency = 48000; // DSP is the default type - u32 rawFrequency = 48000; - u32 AuxFrequency = 48000; - u8 bitResolution = 32; - bool micOpened = false; - bool micStarted = false; - u64 eventQueueKey = 0; + u64 event_queue_key = 0; - u32 signalStateLocalTalk = 9; // value is in range 0-10. 10 indicates talking, 0 indicating none. - u32 signalStateFarTalk = 0; // value is in range 0-10. 10 indicates talking from far away, 0 indicating none. - f32 signalStateNoiseSupression; // value is in decibels - f32 signalStateGainControl; - f32 signalStateMicSignalLevel; // value is in decibels - f32 signalStateSpeakerSignalLevel; // value is in decibels + std::unordered_map mic_list; + +protected: + const u64 start_time = get_system_time(); + u64 m_counter = 0; + + // u32 signalStateLocalTalk = 9; // value is in range 0-10. 10 indicates talking, 0 indicating none. + // u32 signalStateFarTalk = 0; // value is in range 0-10. 10 indicates talking from far away, 0 indicating none. + // f32 signalStateNoiseSupression; // value is in decibels + // f32 signalStateGainControl; + // f32 signalStateMicSignalLevel; // value is in decibels + // f32 signalStateSpeakerSignalLevel; // value is in decibels }; using mic_thread = named_thread; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 081cc34655..356bf237d8 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -119,6 +119,15 @@ enum class move_handler fake, }; +enum class microphone_handler +{ + null, + standard, + singstar, + real_singstar, + rocksmith, +}; + enum class video_resolution { _1080, @@ -546,7 +555,8 @@ struct cfg_root : cfg::node cfg::_int<1, 1000> sampling_period_multiplier{this, "Sampling Period Multiplier", 100}; cfg::_bool enable_time_stretching{this, "Enable Time Stretching", false}; cfg::_int<0, 100> time_stretching_threshold{this, "Time Stretching Threshold", 75}; - + cfg::_enum microphone_type{ this, "Microphone Type", microphone_handler::null }; + cfg::string microphone_devices{ this, "Microphone Devices", ";;;;" }; } audio{this}; struct node_io : cfg::node @@ -559,7 +569,6 @@ struct cfg_root : cfg::node cfg::_enum camera{this, "Camera", camera_handler::null}; cfg::_enum camera_type{this, "Camera type", fake_camera_type::unknown}; cfg::_enum move{this, "Move", move_handler::null}; - } io{this}; struct node_sys : cfg::node diff --git a/rpcs3/Json/tooltips.json b/rpcs3/Json/tooltips.json index c58177906c..b299b3ed84 100644 --- a/rpcs3/Json/tooltips.json +++ b/rpcs3/Json/tooltips.json @@ -9,7 +9,8 @@ "enableBuffering": "Enables audio buffering, which reduces crackle/stutter but increases audio latency (requires XAudio2 or OpenAL).", "audioBufferDuration": "Target buffer duration in milliseconds.\nHigher values make the buffering algorithm's job easier, but may introduce noticeable audio latency.", "enableTimeStretching": "Enables time stretching - requires buffering to be enabled.\nReduces crackle/stutter further, but may cause a very noticeable reduction in audio quality on slower CPUs.", - "timeStretchingThreshold": "Buffer fill level (in percentage) below which time stretching will start." + "timeStretchingThreshold": "Buffer fill level (in percentage) below which time stretching will start.", + "microphoneBox": "Standard should be used for most games.\nSingstar emulates a singstar device and should be used with Singstar games.\nReal Singstar should only be used with a REAL Singstar device with Singstar games.\nRocksmith should be used with a Rocksmith dongle." }, "cpu": { "PPU": { diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index bc5ef1c148..1ccce4b455 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -9,6 +9,7 @@ #include "Emu/System.h" #include "Utilities/Config.h" #include "Utilities/Thread.h" +#include "Utilities/StrUtil.h" #include @@ -24,6 +25,8 @@ #include "Emu/RSX/VK/VKHelpers.h" #endif +#include "3rdparty/OpenAL/include/alext.h" + extern std::string g_cfg_defaults; //! Default settings grabbed from Utilities/Config.h inline std::string sstr(const QString& _in) { return _in.toStdString(); } @@ -228,6 +231,58 @@ emu_settings::Render_Creator::Render_Creator() renderers = { &D3D12, &Vulkan, &OpenGL, &NullRender }; } +emu_settings::Microphone_Creator::Microphone_Creator() +{ + RefreshList(); +} + +void emu_settings::Microphone_Creator::RefreshList() +{ + microphones_list.clear(); + microphones_list.append(mic_none); + + if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_TRUE) + { + const char *devices = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + + while (*devices != 0) + { + microphones_list.append(qstr(devices)); + devices += strlen(devices) + 1; + } + } + else + { + // Without enumeration we can only use one device + microphones_list.append(qstr(alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER))); + } +} + +std::string emu_settings::Microphone_Creator::SetDevice(u32 num, QString& text) +{ + if (text == mic_none) + sel_list[num-1] = ""; + else + sel_list[num-1] = text.toStdString(); + + const std::string final_list = sel_list[0] + "@@@" + sel_list[1] + "@@@" + sel_list[2] + "@@@" + sel_list[3] + "@@@"; + return final_list; +} + +void emu_settings::Microphone_Creator::ParseDevices(std::string list) +{ + for (u32 index = 0; index < 4; index++) + { + sel_list[index] = ""; + } + + const auto devices_list = fmt::split(list, { "@@@" }); + for (u32 index = 0; index < std::min((u32)4, (u32)devices_list.size()); index++) + { + sel_list[index] = devices_list[index]; + } +} + emu_settings::emu_settings() : QObject() { } diff --git a/rpcs3/rpcs3qt/emu_settings.h b/rpcs3/rpcs3qt/emu_settings.h index 8c21ed6501..b29fdf45ca 100644 --- a/rpcs3/rpcs3qt/emu_settings.h +++ b/rpcs3/rpcs3qt/emu_settings.h @@ -109,6 +109,8 @@ public: AudioBufferDuration, EnableTimeStretching, TimeStretchingThreshold, + MicrophoneType, + MicrophoneDevices, // Input / Output PadHandler, @@ -180,6 +182,17 @@ public: Render_Creator(); }; + struct Microphone_Creator + { + QStringList microphones_list; + QString mic_none = tr("None"); + std::array sel_list; + std::string SetDevice(u32 num, QString& text); + void ParseDevices(std::string list); + void RefreshList(); + Microphone_Creator(); + }; + std::set m_broken_types; // list of broken settings /** Creates a settings object which reads in the config.yml file at rpcs3/bin/%path%/config.yml @@ -224,6 +237,9 @@ public: /** Gets all the renderer info for gpu settings.*/ Render_Creator m_render_creator; + /** Gets a list of all the microphones available.*/ + Microphone_Creator m_microphone_creator; + /** Loads the settings from path.*/ void LoadSettings(const std::string& title_id = ""); @@ -320,6 +336,8 @@ private: { AudioBufferDuration, { "Audio", "Desired Audio Buffer Duration"}}, { EnableTimeStretching, { "Audio", "Enable Time Stretching"}}, { TimeStretchingThreshold, { "Audio", "Time Stretching Threshold"}}, + { MicrophoneType, { "Audio", "Microphone Type" }}, + { MicrophoneDevices, { "Audio", "Microphone Devices" }}, // Input / Output { PadHandler, { "Input/Output", "Pad"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index d90d63174a..091f2becfd 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -774,6 +774,7 @@ settings_dialog::settings_dialog(std::shared_ptr guiSettings, std: ui->timeStretchingThresholdLabel->setEnabled(enabled); ui->timeStretchingThreshold->setEnabled(enabled); }; + auto EnableBufferingOptions = [this, EnableTimeStretchingOptions](bool enabled) { ui->audioBufferDuration->setEnabled(enabled); @@ -781,6 +782,7 @@ settings_dialog::settings_dialog(std::shared_ptr guiSettings, std: ui->enableTimeStretching->setEnabled(enabled); EnableTimeStretchingOptions(enabled && ui->enableTimeStretching->isChecked()); }; + auto EnableBuffering = [this, EnableBufferingOptions](const QString& text) { const bool enabled = text == "XAudio2" || text == "OpenAL"; @@ -788,6 +790,84 @@ settings_dialog::settings_dialog(std::shared_ptr guiSettings, std: EnableBufferingOptions(enabled && ui->enableBuffering->isChecked()); }; + auto ChangeMicrophoneType = [=](QString text) + { + std::string s_standard, s_singstar, s_realsingstar, s_rocksmith; + + auto enableMicsCombo = [=](u32 max) + { + ui->microphone1Box->setEnabled(true); + + if (max == 1 || ui->microphone1Box->currentText() == xemu_settings->m_microphone_creator.mic_none) + return; + + ui->microphone2Box->setEnabled(true); + + if (max > 2 && ui->microphone2Box->currentText() != xemu_settings->m_microphone_creator.mic_none) + { + ui->microphone3Box->setEnabled(true); + if (ui->microphone3Box->currentText() != xemu_settings->m_microphone_creator.mic_none) + { + ui->microphone4Box->setEnabled(true); + } + } + }; + + ui->microphone1Box->setEnabled(false); + ui->microphone2Box->setEnabled(false); + ui->microphone3Box->setEnabled(false); + ui->microphone4Box->setEnabled(false); + + fmt_class_string::format(s_standard, static_cast(microphone_handler::standard)); + fmt_class_string::format(s_singstar, static_cast(microphone_handler::singstar)); + fmt_class_string::format(s_realsingstar, static_cast(microphone_handler::real_singstar)); + fmt_class_string::format(s_rocksmith, static_cast(microphone_handler::rocksmith)); + + if (text == s_standard.c_str()) + { + enableMicsCombo(4); + return; + } + if (text == s_singstar.c_str()) + { + enableMicsCombo(2); + return; + } + if (text == s_realsingstar.c_str() || text == s_rocksmith.c_str()) + { + enableMicsCombo(1); + return; + } + }; + + auto PropagateUsedDevices = [=]() + { + for (u32 index = 0; index < 4; index++) + { + const QString cur_item = mics_combo[index]->currentText(); + QStringList cur_list = xemu_settings->m_microphone_creator.microphones_list; + for (u32 subindex = 0; subindex < 4; subindex++) + { + if (subindex != index && mics_combo[subindex]->currentText() != xemu_settings->m_microphone_creator.mic_none) + cur_list.removeOne(mics_combo[subindex]->currentText()); + } + mics_combo[index]->blockSignals(true); + mics_combo[index]->clear(); + mics_combo[index]->addItems(cur_list); + mics_combo[index]->setCurrentText(cur_item); + mics_combo[index]->blockSignals(false); + } + ChangeMicrophoneType(ui->microphoneBox->currentText()); + }; + + auto ChangeMicrophoneDevice = [=](u32 next_index, QString text) + { + xemu_settings->SetSetting(emu_settings::MicrophoneDevices, xemu_settings->m_microphone_creator.SetDevice(next_index, text)); + if (next_index < 4 && text == xemu_settings->m_microphone_creator.mic_none) + mics_combo[next_index]->setCurrentText(xemu_settings->m_microphone_creator.mic_none); + PropagateUsedDevices(); + }; + // Comboboxes xemu_settings->EnhanceComboBox(ui->audioOutBox, emu_settings::AudioRenderer); @@ -800,6 +880,36 @@ settings_dialog::settings_dialog(std::shared_ptr guiSettings, std: ui->audioOutBox->setItemText(ui->renderBox->findData("Null"), tr("Disable Audio Output")); connect(ui->audioOutBox, &QComboBox::currentTextChanged, EnableBuffering); + // Microphone Comboboxes + mics_combo[0] = ui->microphone1Box; + mics_combo[1] = ui->microphone2Box; + mics_combo[2] = ui->microphone3Box; + mics_combo[3] = ui->microphone4Box; + connect(mics_combo[0], &QComboBox::currentTextChanged, [=](const QString& text) { ChangeMicrophoneDevice(1, text); }); + connect(mics_combo[1], &QComboBox::currentTextChanged, [=](const QString& text) { ChangeMicrophoneDevice(2, text); }); + connect(mics_combo[2], &QComboBox::currentTextChanged, [=](const QString& text) { ChangeMicrophoneDevice(3, text); }); + connect(mics_combo[3], &QComboBox::currentTextChanged, [=](const QString& text) { ChangeMicrophoneDevice(4, text); }); + xemu_settings->m_microphone_creator.RefreshList(); + PropagateUsedDevices(); // Fills comboboxes list + + xemu_settings->m_microphone_creator.ParseDevices(xemu_settings->GetSetting(emu_settings::MicrophoneDevices)); + + for (s32 index = 3; index >= 0; index--) + { + if (xemu_settings->m_microphone_creator.sel_list[index] == "" || mics_combo[index]->findText(qstr(xemu_settings->m_microphone_creator.sel_list[index])) == -1) + { + mics_combo[index]->setCurrentText(xemu_settings->m_microphone_creator.mic_none); + ChangeMicrophoneDevice(index+1, xemu_settings->m_microphone_creator.mic_none); // Ensures the value is set in config + } + else + mics_combo[index]->setCurrentText(qstr(xemu_settings->m_microphone_creator.sel_list[index])); + } + + xemu_settings->EnhanceComboBox(ui->microphoneBox, emu_settings::MicrophoneType); + SubscribeTooltip(ui->microphoneBox, json_audio["microphoneBox"].toString()); + connect(ui->microphoneBox, &QComboBox::currentTextChanged, ChangeMicrophoneType); + PropagateUsedDevices(); // Enables/Disables comboboxes and checks values from config for sanity + // Checkboxes xemu_settings->EnhanceCheckBox(ui->audioDump, emu_settings::DumpToFile); diff --git a/rpcs3/rpcs3qt/settings_dialog.h b/rpcs3/rpcs3qt/settings_dialog.h index 5b92a62f71..3cae16a3f5 100644 --- a/rpcs3/rpcs3qt/settings_dialog.h +++ b/rpcs3/rpcs3qt/settings_dialog.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "gui_settings.h" #include "emu_settings.h" @@ -36,13 +36,15 @@ private Q_SLOTS: private: void EnhanceSlider(emu_settings::SettingsType settings_type, QSlider* slider, QLabel* label, const QString& label_text); - //emulator tab + // Emulator tab void AddConfigs(); void AddStylesheets(); QString m_currentStylesheet; QString m_currentConfig; - //gpu tab + // Gpu tab QString m_oldRender = ""; + // Audio tab + QComboBox *mics_combo[4]; int m_tab_Index; Ui::settings_dialog *ui; @@ -53,7 +55,7 @@ private: bool m_use_discord; QString m_discord_state; - // descriptions + // Descriptions QList> m_description_labels; QHash m_descriptions; void SubscribeDescription(QLabel* description); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index ba78b0601c..f79daebbef 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -780,7 +780,7 @@ Audio - + @@ -966,6 +966,157 @@ + + + + Microphone Settings + + + + + + + + Microphone Type: + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + + + + 0 + 0 + + + + Mic1: + + + + + + + + 0 + 0 + + + + Mic3: + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + + + + Mic2: + + + + + + + + 0 + 0 + + + + Mic4: + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + +