mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a129d22a57 | ||
|
|
d4e14d2b05 | ||
|
|
dfa7774c4c | ||
|
|
c3182aedd9 | ||
|
|
f41f7b63e8 | ||
|
|
c143950dd1 | ||
|
|
4e991d603a |
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: "Install system dependencies"
|
||||
run: |
|
||||
sudo apt update -qq
|
||||
sudo apt install -y ninja-build cmake libgtk-3-dev libsecret-1-dev libgcrypt20-dev libsystemd-dev freeglut3-dev clang-12 nasm
|
||||
sudo apt install -y clang-12 cmake freeglut3-dev libgcrypt20-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build
|
||||
- name: "Bootstrap vcpkg"
|
||||
run: |
|
||||
bash ./dependencies/vcpkg/bootstrap-vcpkg.sh
|
||||
@@ -239,4 +239,4 @@ jobs:
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
with:
|
||||
name: cemu-bin-macos-x64
|
||||
path: ./bin/Cemu.dmg
|
||||
path: ./bin/Cemu.dmg
|
||||
|
||||
@@ -25,7 +25,7 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required
|
||||
### Installing dependencies
|
||||
|
||||
#### For Ubuntu and derivatives:
|
||||
`sudo apt install -y git curl cmake ninja-build nasm libgtk-3-dev libsecret-1-dev libgcrypt20-dev libsystemd-dev freeglut3-dev libpulse-dev`
|
||||
`sudo apt install -y cmake curl freeglut3-dev git libgcrypt20-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build`
|
||||
|
||||
*Additionally, for Ubuntu 22.04 only:*
|
||||
- `sudo apt install -y clang-12`
|
||||
@@ -33,10 +33,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required
|
||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||
|
||||
#### For Arch and derivatives:
|
||||
`sudo pacman -S --needed git cmake llvm clang ninja nasm base-devel linux-headers gtk3 libsecret libgcrypt systemd freeglut zip unzip libpulse`
|
||||
`sudo pacman -S --needed base-devel clang cmake freeglut git gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
|
||||
|
||||
#### For Fedora and derivatives:
|
||||
`sudo dnf install git cmake clang ninja-build nasm kernel-headers gtk3-devel libsecret-devel libgcrypt-devel systemd-devel freeglut-devel perl-core zlib-devel cubeb-devel`
|
||||
`sudo dnf install clang cmake cubeb-devel freeglut-devel git gtk3-devel kernel-headers libgcrypt-devel libsecret-devel nasm ninja-build perl-core systemd-devel zlib-devel`
|
||||
|
||||
### Build Cemu using cmake and clang
|
||||
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
|
||||
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
|
||||
#include "audio/IAudioAPI.h"
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
@@ -402,6 +403,7 @@ void cemu_initForGame()
|
||||
GraphicPack2::ActivateForCurrentTitle();
|
||||
// print audio log
|
||||
IAudioAPI::PrintLogging();
|
||||
IAudioInputAPI::PrintLogging();
|
||||
// everything initialized
|
||||
forceLog_printf("------- Run title -------");
|
||||
// wait till GPU thread is initialized
|
||||
|
||||
@@ -352,14 +352,19 @@ void OpenGLRenderer::NotifyLatteCommandProcessorIdle()
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void OpenGLRenderer::EnableVSync(int state)
|
||||
void OpenGLRenderer::UpdateVSyncState()
|
||||
{
|
||||
int configValue = GetConfig().vsync.GetValue();
|
||||
if(m_activeVSyncState != configValue)
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
if(wglSwapIntervalEXT)
|
||||
wglSwapIntervalEXT(state); // 1 = enabled, 0 = disabled
|
||||
if(wglSwapIntervalEXT)
|
||||
wglSwapIntervalEXT(configValue); // 1 = enabled, 0 = disabled
|
||||
#else
|
||||
cemuLog_log(LogType::Force, "OpenGL vsync not implemented");
|
||||
cemuLog_log(LogType::Force, "OpenGL vsync not implemented");
|
||||
#endif
|
||||
m_activeVSyncState = configValue;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRunningInWine();
|
||||
@@ -438,6 +443,7 @@ void OpenGLRenderer::EnableDebugMode()
|
||||
void OpenGLRenderer::SwapBuffers(bool swapTV, bool swapDRC)
|
||||
{
|
||||
GLCanvas_SwapBuffers(swapTV, swapDRC);
|
||||
UpdateVSyncState();
|
||||
|
||||
if (swapTV)
|
||||
cleanupAfterFrame();
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
void Flush(bool waitIdle = false) override;
|
||||
void NotifyLatteCommandProcessorIdle() override;
|
||||
|
||||
void EnableVSync(int state) override;
|
||||
void UpdateVSyncState();
|
||||
|
||||
void EnableDebugMode() override;
|
||||
void SwapBuffers(bool swapTV = true, bool swapDRC = true) override;
|
||||
@@ -168,6 +168,7 @@ private:
|
||||
GLuint m_defaultFramebufferId;
|
||||
GLuint m_pipeline = 0;
|
||||
|
||||
int m_activeVSyncState{};
|
||||
bool m_isPadViewContext{};
|
||||
|
||||
// rendertarget viewport
|
||||
@@ -282,4 +283,4 @@ private:
|
||||
std::vector<bufferCacheReleaseQueueEntry_t> bufferCacheEntries;
|
||||
}m_destructionQueues;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -58,7 +58,6 @@ public:
|
||||
virtual bool GetVRAMInfo(int& usageInMB, int& totalInMB) const;
|
||||
|
||||
virtual void EnableDebugMode() {}
|
||||
virtual void EnableVSync(int state) = 0;
|
||||
|
||||
virtual void ClearColorbuffer(bool padView) = 0;
|
||||
virtual void DrawEmptyFrame(bool mainWindow) = 0;
|
||||
@@ -176,4 +175,4 @@ protected:
|
||||
#endif
|
||||
};
|
||||
|
||||
extern std::unique_ptr<Renderer> g_renderer;
|
||||
extern std::unique_ptr<Renderer> g_renderer;
|
||||
|
||||
@@ -1382,22 +1382,22 @@ VkSurfaceFormatKHR VulkanRenderer::ChooseSwapSurfaceFormat(const std::vector<VkS
|
||||
|
||||
VkPresentModeKHR VulkanRenderer::ChooseSwapPresentMode(const std::vector<VkPresentModeKHR>& modes)
|
||||
{
|
||||
m_vsync_state = (VSync)GetConfig().vsync.GetValue();
|
||||
if (m_vsync_state == VSync::MAILBOX)
|
||||
const auto vsyncState = (VSync)GetConfig().vsync.GetValue();
|
||||
if (vsyncState == VSync::MAILBOX)
|
||||
{
|
||||
if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_MAILBOX_KHR) != modes.cend())
|
||||
return VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
|
||||
forceLog_printf("Vulkan: Can't find mailbox present mode");
|
||||
}
|
||||
else if (m_vsync_state == VSync::Immediate)
|
||||
else if (vsyncState == VSync::Immediate)
|
||||
{
|
||||
if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.cend())
|
||||
return VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
|
||||
forceLog_printf("Vulkan: Can't find immediate present mode");
|
||||
}
|
||||
else if (m_vsync_state == VSync::SYNC_AND_LIMIT)
|
||||
else if (vsyncState == VSync::SYNC_AND_LIMIT)
|
||||
{
|
||||
LatteTiming_EnableHostDrivenVSync();
|
||||
// use immediate mode if available, other wise fall back to
|
||||
@@ -1991,19 +1991,6 @@ void VulkanRenderer::QueryAvailableFormats()
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::EnableVSync(int state)
|
||||
{
|
||||
if (m_vsync_state == (VSync)state)
|
||||
return;
|
||||
|
||||
m_vsync_state = (VSync)state;
|
||||
|
||||
// recreate spawn chains (vsync state is checked from config in ChooseSwapPresentMode)
|
||||
RecreateSwapchain(true);
|
||||
if (m_padSwapchainInfo)
|
||||
RecreateSwapchain(false);
|
||||
}
|
||||
|
||||
bool VulkanRenderer::ImguiBegin(bool mainWindow)
|
||||
{
|
||||
if (!Renderer::ImguiBegin(mainWindow))
|
||||
@@ -2962,6 +2949,16 @@ void VulkanRenderer::RecreateSwapchain(bool main_window)
|
||||
ImguiInit();
|
||||
}
|
||||
|
||||
void VulkanRenderer::UpdateVSyncState(bool main_window)
|
||||
{
|
||||
auto& swapInfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo;
|
||||
const auto configValue = (VSync)GetConfig().vsync.GetValue();
|
||||
if(swapInfo.m_activeVSyncState != configValue){
|
||||
RecreateSwapchain(main_window);
|
||||
swapInfo.m_activeVSyncState = configValue;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::SwapBuffer(bool main_window)
|
||||
{
|
||||
auto& swapInfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo;
|
||||
@@ -2999,6 +2996,8 @@ void VulkanRenderer::SwapBuffer(bool main_window)
|
||||
}
|
||||
}
|
||||
|
||||
UpdateVSyncState(main_window);
|
||||
|
||||
auto& swapinfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo;
|
||||
AcquireNextSwapchainImage(main_window);
|
||||
|
||||
|
||||
@@ -195,8 +195,6 @@ public:
|
||||
void QueryMemoryInfo();
|
||||
void QueryAvailableFormats();
|
||||
|
||||
void EnableVSync(int state) override;
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
static VkSurfaceKHR CreateWinSurface(VkInstance instance, HWND hwindow);
|
||||
#endif
|
||||
@@ -466,6 +464,7 @@ private:
|
||||
VkExtent2D swapchainExtend{};
|
||||
uint32 swapchainImageIndex = (uint32)-1;
|
||||
uint32 m_acquireIndex = 0; // increases with every successful vkAcquireNextImageKHR
|
||||
VSync m_activeVSyncState = VSync::Immediate;
|
||||
|
||||
struct AcquireInfo
|
||||
{
|
||||
@@ -557,8 +556,8 @@ private:
|
||||
static bool CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info);
|
||||
static std::vector<const char*> CheckInstanceExtensionSupport(FeatureControl& info);
|
||||
|
||||
void UpdateVSyncState(bool main_window);
|
||||
void SwapBuffer(bool main_window);
|
||||
VSync m_vsync_state = VSync::Immediate;
|
||||
|
||||
struct SwapChainSupportDetails
|
||||
{
|
||||
|
||||
@@ -1196,30 +1196,15 @@ void VulkanRenderer::draw_setRenderPass()
|
||||
// update self-dependency flag
|
||||
if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk)
|
||||
{
|
||||
bool hadDep = m_state.hasRenderSelfDependency;
|
||||
m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
|
||||
}
|
||||
|
||||
auto vkObjRenderPass = fboVk->GetRenderPassObj();
|
||||
auto vkObjFramebuffer = fboVk->GetFramebufferObj();
|
||||
|
||||
if (m_state.hasRenderSelfDependency)
|
||||
{
|
||||
bool triggerBarrier = GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier;
|
||||
if (triggerBarrier)
|
||||
{
|
||||
VkMemoryBarrier memoryBarrier{};
|
||||
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||
memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
|
||||
performanceMonitor.vk.numDrawBarriersPerFrame.increment();
|
||||
}
|
||||
}
|
||||
bool overridePassReuse = m_state.hasRenderSelfDependency && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier);
|
||||
|
||||
if (m_state.activeRenderpassFBO == fboVk)
|
||||
if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk)
|
||||
{
|
||||
if (m_state.descriptorSetsChanged)
|
||||
sync_inputTexturesChanged();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "Cafe/OS/common/OSCommon.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
enum class MIC_RESULT
|
||||
{
|
||||
@@ -13,6 +15,7 @@ enum class MIC_RESULT
|
||||
|
||||
#define MIC_SAMPLERATE 32000
|
||||
|
||||
const int MIC_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000
|
||||
|
||||
enum class MIC_STATUS_FLAGS : uint32
|
||||
{
|
||||
@@ -22,8 +25,8 @@ enum class MIC_STATUS_FLAGS : uint32
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS);
|
||||
|
||||
#define MIC_HANDLE_DRC0 100
|
||||
#define MIC_HANDLE_DRC1 101
|
||||
#define MIC_HANDLE_DRC0 0
|
||||
#define MIC_HANDLE_DRC1 1
|
||||
|
||||
enum class MIC_STATEID
|
||||
{
|
||||
@@ -139,6 +142,36 @@ void micExport_MICInit(PPCInterpreter_t* hCPU)
|
||||
// return status
|
||||
memory_writeU32(hCPU->gpr[6], 0); // no error
|
||||
osLib_returnFromFunction(hCPU, (drcIndex==0)?MIC_HANDLE_DRC0:MIC_HANDLE_DRC1); // success
|
||||
|
||||
auto& config = GetConfig();
|
||||
const auto audio_api = IAudioInputAPI::Cubeb; // change this if more input apis get implemented
|
||||
|
||||
std::unique_lock lock(g_audioInputMutex);
|
||||
if (!g_inputAudio)
|
||||
{
|
||||
IAudioInputAPI::DeviceDescriptionPtr device_description;
|
||||
if (IAudioInputAPI::IsAudioInputAPIAvailable(audio_api))
|
||||
{
|
||||
auto devices = IAudioInputAPI::GetDevices(audio_api);
|
||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.input_device; });
|
||||
if (it != devices.end())
|
||||
device_description = *it;
|
||||
}
|
||||
|
||||
if (device_description)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_inputAudio = IAudioInputAPI::CreateDevice(audio_api, device_description, MIC_SAMPLERATE, 1, MIC_SAMPLES_PER_3MS_32KHZ, 16);
|
||||
g_inputAudio->SetVolume(config.input_volume);
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
forceLog_printf("can't initialize audio input: %s", ex.what());
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void micExport_MICOpen(PPCInterpreter_t* hCPU)
|
||||
@@ -172,6 +205,10 @@ void micExport_MICOpen(PPCInterpreter_t* hCPU)
|
||||
// success
|
||||
MICStatus.drc[drcIndex].isOpen = true;
|
||||
osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS);
|
||||
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
if(g_inputAudio)
|
||||
g_inputAudio->Play();
|
||||
}
|
||||
|
||||
void micExport_MICClose(PPCInterpreter_t* hCPU)
|
||||
@@ -198,6 +235,10 @@ void micExport_MICClose(PPCInterpreter_t* hCPU)
|
||||
// success
|
||||
MICStatus.drc[drcIndex].isOpen = false;
|
||||
osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS);
|
||||
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
if (g_inputAudio)
|
||||
g_inputAudio->Stop();
|
||||
}
|
||||
|
||||
void micExport_MICGetStatus(PPCInterpreter_t* hCPU)
|
||||
@@ -360,6 +401,17 @@ void micExport_MICSetDataConsumed(PPCInterpreter_t* hCPU)
|
||||
return;
|
||||
}
|
||||
|
||||
void mic_updateDevicePlayState(bool isPlaying)
|
||||
{
|
||||
if (g_inputAudio)
|
||||
{
|
||||
if (isPlaying)
|
||||
g_inputAudio->Play();
|
||||
else
|
||||
g_inputAudio->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void mic_updateOnAXFrame()
|
||||
{
|
||||
sint32 drcIndex = 0;
|
||||
@@ -368,26 +420,39 @@ void mic_updateOnAXFrame()
|
||||
drcIndex = 1;
|
||||
if (mic_isActive(1) == false)
|
||||
{
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
mic_updateDevicePlayState(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const sint32 micSampleCount = 32000/32;
|
||||
sint16 micSampleData[micSampleCount];
|
||||
|
||||
auto controller = InputManager::instance().get_vpad_controller(drcIndex);
|
||||
if( controller && controller->is_mic_active() )
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
mic_updateDevicePlayState(true);
|
||||
if (g_inputAudio)
|
||||
{
|
||||
for(sint32 i=0; i<micSampleCount; i++)
|
||||
{
|
||||
micSampleData[i] = (sint16)(sin((float)GetTickCount()*0.1f+sin((float)GetTickCount()*0.0001f)*100.0f)*30000.0f);
|
||||
}
|
||||
sint16 micSampleData[MIC_SAMPLES_PER_3MS_32KHZ];
|
||||
g_inputAudio->ConsumeBlock(micSampleData);
|
||||
mic_feedSamples(0, micSampleData, MIC_SAMPLES_PER_3MS_32KHZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(micSampleData, 0x00, sizeof(micSampleData));
|
||||
const sint32 micSampleCount = 32000 / 32;
|
||||
sint16 micSampleData[micSampleCount];
|
||||
|
||||
auto controller = InputManager::instance().get_vpad_controller(drcIndex);
|
||||
if( controller && controller->is_mic_active() )
|
||||
{
|
||||
for(sint32 i=0; i<micSampleCount; i++)
|
||||
{
|
||||
micSampleData[i] = (sint16)(sin((float)GetTickCount()*0.1f+sin((float)GetTickCount()*0.0001f)*100.0f)*30000.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(micSampleData, 0x00, sizeof(micSampleData));
|
||||
}
|
||||
mic_feedSamples(0, micSampleData, micSampleCount);
|
||||
}
|
||||
mic_feedSamples(0, micSampleData, micSampleCount);
|
||||
}
|
||||
|
||||
namespace mic
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
add_library(CemuAudio
|
||||
IAudioAPI.cpp
|
||||
IAudioAPI.h
|
||||
IAudioInputAPI.cpp
|
||||
IAudioInputAPI.h
|
||||
)
|
||||
|
||||
set_property(TARGET CemuAudio PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
@@ -26,6 +28,8 @@ if(ENABLE_CUBEB)
|
||||
target_sources(CemuAudio PRIVATE
|
||||
CubebAPI.cpp
|
||||
CubebAPI.h
|
||||
CubebInputAPI.cpp
|
||||
CubebInputAPI.h
|
||||
)
|
||||
#add_compile_definitions(HAS_CUBEB)
|
||||
endif()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#endif
|
||||
|
||||
|
||||
void state_cb(cubeb_stream* stream, void* user, cubeb_state state)
|
||||
static void state_cb(cubeb_stream* stream, void* user, cubeb_state state)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
#include "CubebInputAPI.h"
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include <combaseapi.h>
|
||||
#include <mmreg.h>
|
||||
#include <mmsystem.h>
|
||||
#pragma comment(lib, "Avrt.lib")
|
||||
#pragma comment(lib, "ksuser.lib")
|
||||
#endif
|
||||
|
||||
static void state_cb(cubeb_stream* stream, void* user, cubeb_state state)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
/*switch (state)
|
||||
{
|
||||
case CUBEB_STATE_STARTED:
|
||||
fprintf(stderr, "stream started\n");
|
||||
break;
|
||||
case CUBEB_STATE_STOPPED:
|
||||
fprintf(stderr, "stream stopped\n");
|
||||
break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}*/
|
||||
}
|
||||
|
||||
long CubebInputAPI::data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes)
|
||||
{
|
||||
auto* thisptr = (CubebInputAPI*)user;
|
||||
|
||||
const auto size = (size_t)nframes * thisptr->m_channels * (thisptr->m_bitsPerSample / 8);
|
||||
|
||||
std::unique_lock lock(thisptr->m_mutex);
|
||||
if (thisptr->m_buffer.capacity() <= thisptr->m_buffer.size() + size)
|
||||
{
|
||||
forceLogDebug_printf("dropped input sound block since too many buffers are queued");
|
||||
return nframes;
|
||||
}
|
||||
|
||||
thisptr->m_buffer.insert(thisptr->m_buffer.end(), (uint8*)inputbuffer, (uint8*)inputbuffer + size);
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
CubebInputAPI::CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block,
|
||||
uint32 bits_per_sample)
|
||||
: IAudioInputAPI(samplerate, channels, samples_per_block, bits_per_sample)
|
||||
{
|
||||
cubeb_stream_params input_params;
|
||||
|
||||
input_params.format = CUBEB_SAMPLE_S16LE;
|
||||
input_params.rate = samplerate;
|
||||
input_params.channels = channels;
|
||||
input_params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
switch (channels)
|
||||
{
|
||||
case 8:
|
||||
input_params.layout = CUBEB_LAYOUT_3F4_LFE;
|
||||
break;
|
||||
case 6:
|
||||
input_params.layout = CUBEB_LAYOUT_QUAD_LFE | CHANNEL_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
input_params.layout = CUBEB_LAYOUT_QUAD;
|
||||
break;
|
||||
case 2:
|
||||
input_params.layout = CUBEB_LAYOUT_STEREO;
|
||||
break;
|
||||
default:
|
||||
input_params.layout = CUBEB_LAYOUT_MONO;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32 latency = 1;
|
||||
cubeb_get_min_latency(s_context, &input_params, &latency);
|
||||
|
||||
m_buffer.reserve((size_t)m_bytesPerBlock * kBlockCount);
|
||||
|
||||
if (cubeb_stream_init(s_context, &m_stream, "Cemu Cubeb input",
|
||||
devid, &input_params,
|
||||
nullptr, nullptr,
|
||||
latency, data_cb, state_cb, this) != CUBEB_OK)
|
||||
{
|
||||
throw std::runtime_error("can't initialize cubeb device");
|
||||
}
|
||||
}
|
||||
|
||||
CubebInputAPI::~CubebInputAPI()
|
||||
{
|
||||
if (m_stream)
|
||||
{
|
||||
Stop();
|
||||
cubeb_stream_destroy(m_stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool CubebInputAPI::ConsumeBlock(sint16* data)
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (m_buffer.empty())
|
||||
{
|
||||
// we got no data, just write silence
|
||||
memset(data, 0x00, m_bytesPerBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto copied = std::min(m_buffer.size(), (size_t)m_bytesPerBlock);
|
||||
memcpy(data, m_buffer.data(), copied);
|
||||
m_buffer.erase(m_buffer.begin(), std::next(m_buffer.begin(), copied));
|
||||
lock.unlock();
|
||||
// fill rest with silence
|
||||
if (copied != m_bytesPerBlock)
|
||||
memset((uint8*)data + copied, 0x00, m_bytesPerBlock - copied);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CubebInputAPI::Play()
|
||||
{
|
||||
if (m_is_playing)
|
||||
return true;
|
||||
|
||||
if (cubeb_stream_start(m_stream) == CUBEB_OK)
|
||||
{
|
||||
m_is_playing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CubebInputAPI::Stop()
|
||||
{
|
||||
if (!m_is_playing)
|
||||
return true;
|
||||
|
||||
if (cubeb_stream_stop(m_stream) == CUBEB_OK)
|
||||
{
|
||||
m_is_playing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CubebInputAPI::SetVolume(sint32 volume)
|
||||
{
|
||||
IAudioInputAPI::SetVolume(volume);
|
||||
cubeb_stream_set_volume(m_stream, (float)volume / 100.0f);
|
||||
}
|
||||
|
||||
bool CubebInputAPI::InitializeStatic()
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
s_com_initialized = (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)));
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&s_context, "Cemu Input Cubeb", nullptr))
|
||||
{
|
||||
cemuLog_force("can't create cubeb audio api");
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (s_com_initialized)
|
||||
{
|
||||
CoUninitialize();
|
||||
s_com_initialized = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CubebInputAPI::Destroy()
|
||||
{
|
||||
if (s_context)
|
||||
cubeb_destroy(s_context);
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (s_com_initialized)
|
||||
CoUninitialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices()
|
||||
{
|
||||
cubeb_device_collection devices;
|
||||
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK)
|
||||
return {};
|
||||
|
||||
std::vector<DeviceDescriptionPtr> result;
|
||||
result.reserve(devices.count);
|
||||
for (size_t i = 0; i < devices.count; ++i)
|
||||
{
|
||||
//const auto& device = devices.device[i];
|
||||
if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED)
|
||||
{
|
||||
auto device = std::make_shared<CubebDeviceDescription>(devices.device[i].devid, devices.device[i].device_id,
|
||||
boost::nowide::widen(
|
||||
devices.device[i].friendly_name));
|
||||
result.emplace_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_device_collection_destroy(s_context, &devices);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "IAudioInputAPI.h"
|
||||
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
class CubebInputAPI : public IAudioInputAPI
|
||||
{
|
||||
public:
|
||||
class CubebDeviceDescription : public DeviceDescription
|
||||
{
|
||||
public:
|
||||
CubebDeviceDescription(cubeb_devid devid, std::string device_id, const std::wstring& name)
|
||||
: DeviceDescription(name), m_devid(devid), m_device_id(std::move(device_id)) { }
|
||||
|
||||
std::wstring GetIdentifier() const override { return boost::nowide::widen(m_device_id); }
|
||||
cubeb_devid GetDeviceId() const { return m_devid; }
|
||||
|
||||
private:
|
||||
cubeb_devid m_devid;
|
||||
std::string m_device_id;
|
||||
};
|
||||
|
||||
using CubebDeviceDescriptionPtr = std::shared_ptr<CubebDeviceDescription>;
|
||||
|
||||
CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample);
|
||||
~CubebInputAPI();
|
||||
|
||||
AudioInputAPI GetType() const override { return Cubeb; }
|
||||
|
||||
bool ConsumeBlock(sint16* data) override;
|
||||
bool Play() override;
|
||||
bool Stop() override;
|
||||
bool IsPlaying() const override { return m_is_playing; };
|
||||
void SetVolume(sint32 volume) override;
|
||||
|
||||
static std::vector<DeviceDescriptionPtr> GetDevices();
|
||||
|
||||
static bool InitializeStatic();
|
||||
static void Destroy();
|
||||
|
||||
private:
|
||||
inline static bool s_com_initialized = false;
|
||||
inline static cubeb* s_context = nullptr;
|
||||
|
||||
cubeb_stream* m_stream = nullptr;
|
||||
bool m_is_playing = false;
|
||||
|
||||
mutable std::shared_mutex m_mutex;
|
||||
std::vector<uint8> m_buffer;
|
||||
static long data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "IAudioInputAPI.h"
|
||||
#include "CubebInputAPI.h"
|
||||
|
||||
std::shared_mutex g_audioInputMutex;
|
||||
AudioInputAPIPtr g_inputAudio;
|
||||
|
||||
std::array<bool, IAudioInputAPI::AudioInputAPIEnd> IAudioInputAPI::s_availableApis{};
|
||||
|
||||
IAudioInputAPI::IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample)
|
||||
: m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample)
|
||||
{
|
||||
m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8);
|
||||
}
|
||||
|
||||
void IAudioInputAPI::PrintLogging()
|
||||
{
|
||||
forceLog_printf("------- Init Audio Input backend -------");
|
||||
forceLog_printf("Cubeb: %s", s_availableApis[Cubeb] ? "available" : "not supported");
|
||||
}
|
||||
|
||||
void IAudioInputAPI::InitializeStatic()
|
||||
{
|
||||
s_availableApis[Cubeb] = CubebInputAPI::InitializeStatic();
|
||||
}
|
||||
|
||||
bool IAudioInputAPI::IsAudioInputAPIAvailable(AudioInputAPI api)
|
||||
{
|
||||
if ((size_t)api < s_availableApis.size())
|
||||
return s_availableApis[api];
|
||||
|
||||
cemu_assert_debug(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioInputAPIPtr IAudioInputAPI::CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
||||
{
|
||||
if (!IsAudioInputAPIAvailable(api))
|
||||
return {};
|
||||
|
||||
switch(api)
|
||||
{
|
||||
case Cubeb:
|
||||
{
|
||||
const auto tmp = std::dynamic_pointer_cast<CubebInputAPI::CubebDeviceDescription>(device);
|
||||
return std::make_unique<CubebInputAPI>(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample);
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("invalid audio api: {}", api));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<IAudioInputAPI::DeviceDescriptionPtr> IAudioInputAPI::GetDevices(AudioInputAPI api)
|
||||
{
|
||||
if (!IsAudioInputAPIAvailable(api))
|
||||
return {};
|
||||
|
||||
switch(api)
|
||||
{
|
||||
case Cubeb:
|
||||
{
|
||||
return CubebInputAPI::GetDevices();
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("invalid audio api: {}", api));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
class IAudioInputAPI
|
||||
{
|
||||
friend class GeneralSettings2;
|
||||
|
||||
public:
|
||||
class DeviceDescription
|
||||
{
|
||||
public:
|
||||
explicit DeviceDescription(std::wstring name)
|
||||
: m_name(std::move(name)) { }
|
||||
|
||||
virtual ~DeviceDescription() = default;
|
||||
virtual std::wstring GetIdentifier() const = 0;
|
||||
const std::wstring& GetName() const { return m_name; }
|
||||
|
||||
bool operator==(const DeviceDescription& o) const
|
||||
{
|
||||
return GetIdentifier() == o.GetIdentifier();
|
||||
}
|
||||
|
||||
private:
|
||||
std::wstring m_name;
|
||||
};
|
||||
|
||||
using DeviceDescriptionPtr = std::shared_ptr<DeviceDescription>;
|
||||
|
||||
enum AudioInputAPI
|
||||
{
|
||||
Cubeb,
|
||||
|
||||
AudioInputAPIEnd,
|
||||
};
|
||||
static constexpr uint32 kBlockCount = 24;
|
||||
|
||||
IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample);
|
||||
virtual ~IAudioInputAPI() = default;
|
||||
virtual AudioInputAPI GetType() const = 0;
|
||||
|
||||
sint32 GetChannels() const { return m_channels; }
|
||||
|
||||
virtual sint32 GetVolume() const { return m_volume; }
|
||||
virtual void SetVolume(sint32 volume) { m_volume = volume; }
|
||||
|
||||
virtual bool ConsumeBlock(sint16* data) = 0;
|
||||
virtual bool Play() = 0;
|
||||
virtual bool Stop() = 0;
|
||||
virtual bool IsPlaying() const = 0;
|
||||
|
||||
static void PrintLogging();
|
||||
static void InitializeStatic();
|
||||
static bool IsAudioInputAPIAvailable(AudioInputAPI api);
|
||||
|
||||
static std::unique_ptr<IAudioInputAPI> CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
||||
static std::vector<DeviceDescriptionPtr> GetDevices(AudioInputAPI api);
|
||||
|
||||
protected:
|
||||
uint32 m_samplerate, m_channels, m_samplesPerBlock, m_bitsPerSample;
|
||||
uint32 m_bytesPerBlock;
|
||||
|
||||
sint32 m_volume = 0;
|
||||
|
||||
static std::array<bool, AudioInputAPIEnd> s_availableApis;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
using AudioInputAPIPtr = std::unique_ptr<IAudioInputAPI>;
|
||||
extern std::shared_mutex g_audioInputMutex;
|
||||
extern AudioInputAPIPtr g_inputAudio;
|
||||
@@ -295,8 +295,10 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||
audio_delay = audio.get("delay", 2);
|
||||
tv_channels = audio.get("TVChannels", kStereo);
|
||||
pad_channels = audio.get("PadChannels", kStereo);
|
||||
input_channels = audio.get("InputChannels", kMono);
|
||||
tv_volume = audio.get("TVVolume", 20);
|
||||
pad_volume = audio.get("PadVolume", 0);
|
||||
input_volume = audio.get("InputVolume", 20);
|
||||
|
||||
const auto tv = audio.get("TVDevice", "");
|
||||
try
|
||||
@@ -318,6 +320,16 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||
forceLog_printf("config load error: can't load pad device: %s", pad);
|
||||
}
|
||||
|
||||
const auto input_device_name = audio.get("InputDevice", "");
|
||||
try
|
||||
{
|
||||
input_device = boost::nowide::widen(input_device_name);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
forceLog_printf("config load error: can't load input device: %s", input_device_name);
|
||||
}
|
||||
|
||||
// account
|
||||
auto acc = parser.get("Account");
|
||||
account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id);
|
||||
@@ -488,10 +500,13 @@ void CemuConfig::Save(XMLConfigParser& parser)
|
||||
audio.set("delay", audio_delay);
|
||||
audio.set("TVChannels", tv_channels);
|
||||
audio.set("PadChannels", pad_channels);
|
||||
audio.set("InputChannels", input_channels);
|
||||
audio.set("TVVolume", tv_volume);
|
||||
audio.set("PadVolume", pad_volume);
|
||||
audio.set("InputVolume", input_volume);
|
||||
audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str());
|
||||
audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str());
|
||||
audio.set("InputDevice", boost::nowide::narrow(input_device).c_str());
|
||||
|
||||
// account
|
||||
auto acc = config.set("Account");
|
||||
|
||||
@@ -462,9 +462,9 @@ struct CemuConfig
|
||||
// audio
|
||||
sint32 audio_api = 0;
|
||||
sint32 audio_delay = 2;
|
||||
AudioChannels tv_channels = kStereo, pad_channels = kStereo;
|
||||
sint32 tv_volume = 50, pad_volume = 0;
|
||||
std::wstring tv_device{ L"default" }, pad_device;
|
||||
AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono;
|
||||
sint32 tv_volume = 50, pad_volume = 0, input_volume = 50;
|
||||
std::wstring tv_device{ L"default" }, pad_device, input_device;
|
||||
|
||||
// account
|
||||
struct
|
||||
|
||||
+170
-16
@@ -23,6 +23,8 @@
|
||||
#endif
|
||||
#include "audio/CubebAPI.h"
|
||||
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
||||
#include "Cafe/Account/Account.h"
|
||||
@@ -70,6 +72,15 @@ private:
|
||||
IAudioAPI::DeviceDescriptionPtr m_description;
|
||||
};
|
||||
|
||||
class wxInputDeviceDescription : public wxClientData
|
||||
{
|
||||
public:
|
||||
wxInputDeviceDescription(const IAudioInputAPI::DeviceDescriptionPtr& description) : m_description(description) {}
|
||||
const IAudioInputAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; }
|
||||
private:
|
||||
IAudioInputAPI::DeviceDescriptionPtr m_description;
|
||||
};
|
||||
|
||||
class wxVulkanUUID : public wxClientData
|
||||
{
|
||||
public:
|
||||
@@ -424,6 +435,47 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook)
|
||||
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Microphone"));
|
||||
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
auto audio_input_row = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
audio_input_row->SetFlexibleDirection(wxBOTH);
|
||||
audio_input_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
m_input_device = new wxChoice(box, wxID_ANY, wxDefaultPosition);
|
||||
m_input_device->SetMinSize(wxSize(300, -1));
|
||||
m_input_device->SetToolTip(_("Select the active audio input device for Wii U GamePad"));
|
||||
audio_input_row->Add(m_input_device, 0, wxEXPAND | wxALL, 5);
|
||||
audio_input_row->AddSpacer(0);
|
||||
|
||||
m_input_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this);
|
||||
|
||||
const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only
|
||||
|
||||
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
m_input_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices);
|
||||
|
||||
m_input_channels->SetSelection(0); // set default to stereo
|
||||
|
||||
m_input_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this);
|
||||
audio_input_row->Add(m_input_channels, 0, wxEXPAND | wxALL, 5);
|
||||
audio_input_row->AddSpacer(0);
|
||||
|
||||
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100);
|
||||
audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5);
|
||||
auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%"));
|
||||
audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
|
||||
|
||||
m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text));
|
||||
m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this);
|
||||
|
||||
box_sizer->Add(audio_input_row, 1, wxEXPAND, 5);
|
||||
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
audio_panel->SetSizerAndFit(audio_panel_sizer);
|
||||
return audio_panel;
|
||||
}
|
||||
@@ -857,9 +909,12 @@ void GeneralSettings2::StoreConfig()
|
||||
config.tv_channels = (AudioChannels)m_tv_channels->GetSelection();
|
||||
//config.pad_channels = (AudioChannels)m_pad_channels->GetSelection();
|
||||
config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection();
|
||||
//config.input_channels = (AudioChannels)m_input_channels->GetSelection();
|
||||
config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection();
|
||||
|
||||
config.tv_volume = m_tv_volume->GetValue();
|
||||
config.pad_volume = m_pad_volume->GetValue();
|
||||
config.input_volume = m_input_volume->GetValue();
|
||||
|
||||
config.tv_device.clear();
|
||||
const auto tv_device = m_tv_device->GetSelection();
|
||||
@@ -879,6 +934,15 @@ void GeneralSettings2::StoreConfig()
|
||||
config.pad_device = device_description->GetDescription()->GetIdentifier();
|
||||
}
|
||||
|
||||
config.input_device = L"";
|
||||
const auto input_device = m_input_device->GetSelection();
|
||||
if (input_device != wxNOT_FOUND && input_device != 0 && m_input_device->HasClientObjectData())
|
||||
{
|
||||
const auto* device_description = (wxDeviceDescription*)m_input_device->GetClientObject(input_device);
|
||||
if (device_description)
|
||||
config.input_device = device_description->GetDescription()->GetIdentifier();
|
||||
}
|
||||
|
||||
// graphics
|
||||
config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection();
|
||||
|
||||
@@ -977,19 +1041,29 @@ void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event)
|
||||
|
||||
void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event)
|
||||
{
|
||||
std::shared_lock lock(g_audioMutex);
|
||||
if(event.GetEventObject() == m_pad_volume)
|
||||
|
||||
if(event.GetEventObject() == m_input_volume)
|
||||
{
|
||||
if (g_padAudio)
|
||||
{
|
||||
g_padAudio->SetVolume(event.GetInt());
|
||||
g_padVolume = event.GetInt();
|
||||
}
|
||||
std::shared_lock lock(g_audioInputMutex);
|
||||
if (g_inputAudio)
|
||||
g_inputAudio->SetVolume(event.GetInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_tvAudio)
|
||||
g_tvAudio->SetVolume(event.GetInt());
|
||||
std::shared_lock lock(g_audioMutex);
|
||||
if(event.GetEventObject() == m_pad_volume)
|
||||
{
|
||||
if (g_padAudio)
|
||||
{
|
||||
g_padAudio->SetVolume(event.GetInt());
|
||||
g_padVolume = event.GetInt();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_tvAudio)
|
||||
g_tvAudio->SetVolume(event.GetInt());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1048,9 +1122,11 @@ void GeneralSettings2::UpdateAudioDeviceList()
|
||||
{
|
||||
m_tv_device->Clear();
|
||||
m_pad_device->Clear();
|
||||
m_input_device->Clear();
|
||||
|
||||
m_tv_device->Append(_("Disabled"));
|
||||
m_pad_device->Append(_("Disabled"));
|
||||
m_input_device->Append(_("Disabled"));
|
||||
|
||||
const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api;
|
||||
const auto devices = IAudioAPI::GetDevices(audio_api);
|
||||
@@ -1060,6 +1136,14 @@ void GeneralSettings2::UpdateAudioDeviceList()
|
||||
m_pad_device->Append(device->GetName(), new wxDeviceDescription(device));
|
||||
}
|
||||
|
||||
const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api;
|
||||
const auto input_devices = IAudioInputAPI::GetDevices(input_audio_api);
|
||||
|
||||
for (auto& device : input_devices)
|
||||
{
|
||||
m_input_device->Append(device->GetName(), new wxInputDeviceDescription(device));
|
||||
}
|
||||
|
||||
if(m_tv_device->GetCount() > 1)
|
||||
m_tv_device->SetSelection(1);
|
||||
else
|
||||
@@ -1067,6 +1151,8 @@ void GeneralSettings2::UpdateAudioDeviceList()
|
||||
|
||||
m_pad_device->SetSelection(0);
|
||||
|
||||
m_input_device->SetSelection(0);
|
||||
|
||||
// todo reset global instance of audio device
|
||||
}
|
||||
|
||||
@@ -1334,8 +1420,9 @@ void GeneralSettings2::HandleGraphicsApiSelection()
|
||||
m_vsync->AppendString(_("Off"));
|
||||
m_vsync->AppendString(_("Double buffering"));
|
||||
m_vsync->AppendString(_("Triple buffering"));
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
m_vsync->AppendString(_("Match emulated display (Experimental)"));
|
||||
#endif
|
||||
|
||||
m_vsync->Select(selection);
|
||||
|
||||
@@ -1452,6 +1539,8 @@ void GeneralSettings2::ApplyConfig()
|
||||
m_tv_channels->SetSelection(config.tv_channels);
|
||||
//m_pad_channels->SetSelection(config.pad_channels);
|
||||
m_pad_channels->SetSelection(0);
|
||||
//m_input_channels->SetSelection(config.pad_channels);
|
||||
m_input_channels->SetSelection(0);
|
||||
|
||||
SendSliderEvent(m_tv_volume, config.tv_volume);
|
||||
|
||||
@@ -1486,6 +1575,22 @@ void GeneralSettings2::ApplyConfig()
|
||||
else
|
||||
m_pad_device->SetSelection(0);
|
||||
|
||||
SendSliderEvent(m_input_volume, config.input_volume);
|
||||
if (!config.input_device.empty() && m_input_device->HasClientObjectData())
|
||||
{
|
||||
for (uint32 i = 0; i < m_input_device->GetCount(); ++i)
|
||||
{
|
||||
const auto device_description = (wxInputDeviceDescription*)m_input_device->GetClientObject(i);
|
||||
if (device_description && config.input_device == device_description->GetDescription()->GetIdentifier())
|
||||
{
|
||||
m_input_device->SetSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
m_input_device->SetSelection(0);
|
||||
|
||||
// account
|
||||
UpdateOnlineAccounts();
|
||||
m_active_account->SetSelection(0);
|
||||
@@ -1550,6 +1655,9 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
{
|
||||
auto& config = GetConfig();
|
||||
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
std::unique_lock inputLock(g_audioInputMutex);
|
||||
|
||||
// tv audio device
|
||||
{
|
||||
const auto selection = m_tv_device->GetSelection();
|
||||
@@ -1559,13 +1667,13 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
return;
|
||||
}
|
||||
|
||||
g_tvAudio.reset();
|
||||
|
||||
if (m_tv_device->HasClientObjectData())
|
||||
{
|
||||
const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection);
|
||||
if (description)
|
||||
{
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
|
||||
sint32 channels;
|
||||
if (m_game_launched && g_tvAudio)
|
||||
channels = g_tvAudio->GetChannels();
|
||||
@@ -1587,7 +1695,6 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
|
||||
try
|
||||
{
|
||||
g_tvAudio.reset();
|
||||
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
g_tvAudio->SetVolume(m_tv_volume->GetValue());
|
||||
}
|
||||
@@ -1608,13 +1715,13 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
return;
|
||||
}
|
||||
|
||||
g_padAudio.reset();
|
||||
|
||||
if (m_pad_device->HasClientObjectData())
|
||||
{
|
||||
const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection);
|
||||
if (description)
|
||||
{
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
|
||||
sint32 channels;
|
||||
if (m_game_launched && g_padAudio)
|
||||
channels = g_padAudio->GetChannels();
|
||||
@@ -1636,7 +1743,6 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
|
||||
try
|
||||
{
|
||||
g_padAudio.reset();
|
||||
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
g_padAudio->SetVolume(m_pad_volume->GetValue());
|
||||
g_padVolume = m_pad_volume->GetValue();
|
||||
@@ -1648,6 +1754,54 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// input audio device
|
||||
{
|
||||
const auto selection = m_input_device->GetSelection();
|
||||
if (selection == wxNOT_FOUND)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
return;
|
||||
}
|
||||
|
||||
g_inputAudio.reset();
|
||||
|
||||
if (m_input_device->HasClientObjectData())
|
||||
{
|
||||
const auto description = (wxInputDeviceDescription*)m_input_device->GetClientObject(selection);
|
||||
if (description)
|
||||
{
|
||||
sint32 channels;
|
||||
if (m_game_launched && g_inputAudio)
|
||||
channels = g_inputAudio->GetChannels();
|
||||
else
|
||||
{
|
||||
switch (config.input_channels)
|
||||
{
|
||||
case 0:
|
||||
channels = 1;
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
g_inputAudio = IAudioInputAPI::CreateDevice(IAudioInputAPI::AudioInputAPI::Cubeb, description->GetDescription(), 32000, channels, snd_core::AX_SAMPLES_PER_3MS_32KHZ, 16);
|
||||
g_inputAudio->SetVolume(m_input_volume->GetValue());
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
forceLog_printf("can't initialize pad audio: %s", ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event)
|
||||
|
||||
@@ -58,9 +58,9 @@ private:
|
||||
// Audio
|
||||
wxChoice* m_audio_api;
|
||||
wxSlider *m_audio_latency;
|
||||
wxSlider *m_tv_volume, *m_pad_volume;
|
||||
wxChoice *m_tv_channels, *m_pad_channels;
|
||||
wxChoice *m_tv_device, *m_pad_device;
|
||||
wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume;
|
||||
wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels;
|
||||
wxChoice *m_tv_device, *m_pad_device, *m_input_device;
|
||||
|
||||
// Account
|
||||
wxButton* m_create_account, * m_delete_account;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user