Compare commits

...

6 commits

Author SHA1 Message Date
Joshua de Reeper
b89b00b4ab
Merge 9bb6b32417 into ec2d7c086a 2025-01-30 03:49:30 +01:00
Exzap
ec2d7c086a coreinit: Clean up time functions
Some checks failed
Generate translation template / generate-pot (push) Failing after 34s
Build check / build (push) Failing after 40s
2025-01-30 03:49:17 +01:00
Exzap
c714e8cb6b coreinit: Time to tick conversion is unsigned
The result is treated as signed in most cases, but the calculation uses unsigned arithmetic.

As a concrete example where this matters, DS VC passes -1 (2^64-1) to OSWaitEventWithTimeout which internally causes an overflow. But only with unsigned arithmetic this will result in a large positive number that behaves like the intended infinite timeout. With signed arithmetic the result is negative and the events will timeout immediately.
2025-01-30 03:32:24 +01:00
Joshua de Reeper
9bb6b32417 save config properly 2025-01-22 10:17:21 +01:00
Joshua de Reeper
c9c7afaa0f make portal audio a setting 2025-01-21 22:36:03 +01:00
Joshua de Reeper
317e44c25c Play portal audio via Cemu output 2025-01-21 22:24:58 +01:00
26 changed files with 303 additions and 99 deletions

View file

@ -114,13 +114,13 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
{
uint64 coreTime = coreinit::coreinit_getTimerTick();
uint64 coreTime = coreinit::OSGetSystemTime();
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
}
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
{
uint64 coreTime = coreinit::coreinit_getTimerTick();
uint64 coreTime = coreinit::OSGetSystemTime();
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
}

View file

@ -799,7 +799,7 @@ LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords)
{
cemu_assert_debug(nWords == 1);
MPTR timerMPTR = (MPTR)LatteReadCMD();
memory_writeU64(timerMPTR, coreinit::coreinit_getTimerTick());
memory_writeU64(timerMPTR, coreinit::OSGetSystemTime());
return cmd;
}

View file

@ -209,7 +209,7 @@ class BootSoundPlayer
try
{
bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample);
bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::TV, sampleRate, nChannels, samplesPerBlock, bitsPerSample);
if(!bootSndAudioDev)
return;
}

View file

@ -469,7 +469,7 @@ namespace iosu
entry->ukn0C = 0;
entry->sizeA = _swapEndianU64(0); // ukn
entry->sizeB = _swapEndianU64(dirSize);
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
count++;
}
@ -504,7 +504,7 @@ namespace iosu
entry->ukn0C = 0;
entry->sizeA = _swapEndianU64(0);
entry->sizeB = _swapEndianU64(0);
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
count++;
}
@ -584,7 +584,7 @@ namespace iosu
uint64 _ACPGetTimestamp()
{
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
return coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK;
}
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)

View file

@ -186,7 +186,7 @@ namespace camera
if (g_cameraCounter == 0)
{
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
}
g_cameraCounter++;

View file

@ -166,7 +166,7 @@ namespace coreinit
void alarm_update()
{
cemu_assert_debug(!__OSHasSchedulerLock());
uint64 currentTick = coreinit::coreinit_getOSTime();
uint64 currentTick = coreinit::OSGetTime();
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
return;
__OSLockScheduler();
@ -233,7 +233,7 @@ namespace coreinit
if (period == 0)
return;
uint64 currentTime = coreinit_getOSTime();
uint64 currentTime = OSGetTime();
uint64 ticksSinceStart = currentTime - startTime;
uint64 numPeriods = ticksSinceStart / period;
@ -267,7 +267,7 @@ namespace coreinit
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
{
__OSLockScheduler();
__OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false);
__OSInitiateAlarm(alarm, OSGetTime() + delayInTicks, 0, handlerFunc, false);
__OSUnlockScheduler();
}
@ -310,7 +310,7 @@ namespace coreinit
while( true )
{
OSWaitEvent(g_alarmEvent.GetPtr());
uint64 currentTick = coreinit_getOSTime();
uint64 currentTick = OSGetTime();
while (true)
{
// get alarm to fire

View file

@ -86,11 +86,11 @@ namespace coreinit
else
{
// loop until lock acquired or timeout occurred
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
if (coreinit_getTimerTick() >= timeoutValue)
if (OSGetSystemTime() >= timeoutValue)
{
return false;
}
@ -182,11 +182,11 @@ namespace coreinit
else
{
// loop until lock acquired or timeout occurred
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
if (coreinit_getTimerTick() >= timeoutValue)
if (OSGetSystemTime() >= timeoutValue)
{
return false;
}

View file

@ -73,8 +73,6 @@ namespace coreinit
}
}
uint64 coreinit_getOSTime();
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
{
__OSLockScheduler();
@ -95,14 +93,14 @@ namespace coreinit
// workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait)
// where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
if (timeout < 0x00FFFFFFFFFFFFFFULL)
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
WaitEventWithTimeoutData data;
data.thread = OSGetCurrentThread();
data.threadQueue = &event->threadQueue;
data.hasTimeout = false;
auto hostAlarm = coreinit::OSHostAlarmCreate(coreinit::coreinit_getOSTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
auto hostAlarm = coreinit::OSHostAlarmCreate(OSGetTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
event->threadQueue.queueAndWait(OSGetCurrentThread());
coreinit::OSHostAlarmDestroy(hostAlarm);
if (data.hasTimeout)

View file

@ -655,7 +655,7 @@ namespace coreinit
StackAllocator<OSThreadQueue> _threadQueue;
OSInitThreadQueue(_threadQueue.GetPointer());
__OSLockScheduler();
OSHostAlarm* hostAlarm = OSHostAlarmCreate(coreinit_getOSTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
OSHostAlarm* hostAlarm = OSHostAlarmCreate(OSGetTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
_threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread());
OSHostAlarmDestroy(hostAlarm);
__OSUnlockScheduler();

View file

@ -3,38 +3,32 @@
namespace coreinit
{
uint64 coreinit_getTimerTick()
uint64 coreinit_GetMFTB()
{
// bus clock is 1/5th of core clock
// timer clock is 1/4th of bus clock
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
}
uint64 coreinit_getOSTime()
uint64 OSGetSystemTime()
{
return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock;
}
void export_OSGetTick(PPCInterpreter_t* hCPU)
{
uint64 osTime = coreinit_getOSTime();
osLib_returnFromFunction(hCPU, (uint32)osTime);
return coreinit_GetMFTB();
}
uint64 OSGetTime()
{
return coreinit_getOSTime();
return OSGetSystemTime() + ppcCyclesSince2000TimerClock;
}
void export_OSGetSystemTime(PPCInterpreter_t* hCPU)
uint32 OSGetSystemTick()
{
osLib_returnFromFunction64(hCPU, coreinit_getTimerTick());
return static_cast<uint32>(coreinit_GetMFTB());
}
void export_OSGetSystemTick(PPCInterpreter_t* hCPU)
uint32 OSGetTick()
{
osLib_returnFromFunction(hCPU, (uint32)coreinit_getTimerTick());
uint64 osTime = OSGetTime();
return static_cast<uint32>(osTime);
}
uint32 getLeapDaysUntilYear(uint32 year)
@ -360,14 +354,13 @@ namespace coreinit
void InitializeTimeAndCalendar()
{
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime);
osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick);
osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick);
cafeExportRegister("coreinit", OSGetSystemTime, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetTick, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetSystemTick, LogType::Placeholder);
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
//timeTest();
}
};

View file

@ -40,25 +40,21 @@ namespace coreinit
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
{
return ((GetTimerClock() / 31250LL) * ((TimerTicks)ns) / 32000LL);
return static_cast<TimerTicks>((static_cast<uint64>(GetTimerClock()) / 31250ULL) * (ns) / 32000ULL);
}
inline TimerTicks ConvertMsToTimerTicks(uint64 ms)
{
return (TimerTicks)ms * GetTimerClock() / 1000LL;
return static_cast<TimerTicks>(ms * static_cast<uint64>(GetTimerClock()) / 1000ULL);
}
};
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
uint64 OSGetSystemTime();
uint64 OSGetTime();
uint64 coreinit_getOSTime();
uint64 coreinit_getTimerTick();
static uint64 OSGetSystemTime()
{
return coreinit_getTimerTick();
}
uint32 OSGetSystemTick();
uint32 OSGetTick();
void InitializeTimeAndCalendar();
};

View file

@ -11,7 +11,7 @@ uint64 dmaeRetiredTimestamp = 0;
uint64 dmae_getTimestamp()
{
return coreinit::coreinit_getTimerTick();
return coreinit::OSGetSystemTime();
}
void dmae_setRetiredTimestamp(uint64 timestamp)

View file

@ -322,7 +322,7 @@ uint64 _prevReturnedGPUTime = 0;
uint64 Latte_GetTime()
{
uint64 gpuTime = coreinit::coreinit_getTimerTick();
uint64 gpuTime = coreinit::OSGetSystemTime();
gpuTime *= 20000ULL;
if (gpuTime <= _prevReturnedGPUTime)
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps

View file

@ -54,7 +54,7 @@ void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU)
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU)
{
cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]);
memory_writeU64(hCPU->gpr[3], coreinit::coreinit_getTimerTick());
memory_writeU64(hCPU->gpr[3], coreinit::OSGetSystemTime());
osLib_returnFromFunction(hCPU, 0);
}

View file

@ -315,7 +315,7 @@ namespace acp
ppcDefineParamU32BEPtr(timestamp64, 0);
ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed
uint64 t = coreinit::coreinit_getOSTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
uint64 t = coreinit::OSGetTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
timestamp64[0] = (uint32)(t >> 32);
timestamp64[1] = (uint32)(t & 0xFFFFFFFF);

View file

@ -6,6 +6,8 @@
#include "Backend.h"
#include "Common/FileStream.h"
#include "audio/IAudioAPI.h"
#include "config/CemuConfig.h"
namespace nsyshid
{
@ -558,6 +560,26 @@ namespace nsyshid
Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message)
{
if (message->length != 64) {
cemu_assert_error();
}
if (!g_portalAudio)
{
// Portal audio is mono channel, 16 bit audio.
// Audio is unsigned 16 bit, supplied as 64 bytes which is 32 samples per block
g_portalAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::Portal, 8000, 32, 16);
}
std::array<sint16, 32> mono_samples;
for (unsigned int i = 0; i < mono_samples.size(); ++i)
{
sint16 sample = static_cast<uint16>(message->data[i * 2 + 1]) << 8 | static_cast<uint16>(message->data[i * 2]);
mono_samples[i] = sample;
}
if (g_portalAudio)
{
g_portalAudio->FeedBlock(mono_samples.data());
}
message->bytesWritten = message->length;
return Device::WriteResult::Success;
}
@ -604,20 +626,20 @@ namespace nsyshid
*(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength
currentWritePtr = currentWritePtr + 9;
// endpoint descriptor 1
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
// endpoint descriptor 2
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29);
@ -628,8 +650,8 @@ namespace nsyshid
}
bool SkylanderPortalDevice::SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration)
uint8 reportId,
uint8 duration)
{
return true;
}

View file

@ -760,7 +760,7 @@ namespace padscore
void start()
{
OSCreateAlarm(&g_padscore.alarm);
const uint64 start_tick = coreinit::coreinit_getOSTime();
const uint64 start_tick = coreinit::OSGetTime();
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);

View file

@ -404,7 +404,7 @@ namespace snd_core
{
try
{
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::TV, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
}
catch (std::runtime_error& ex)
{
@ -417,7 +417,7 @@ namespace snd_core
{
try
{
g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_padAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::Gamepad, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
if(g_padAudio)
g_padVolume = g_padAudio->GetVolume();
}
@ -442,6 +442,11 @@ namespace snd_core
g_padAudio->Stop();
g_padAudio.reset();
}
if (g_portalAudio)
{
g_portalAudio->Stop();
g_portalAudio.reset();
}
}
void AXOut_updateDevicePlayState(bool isPlaying)
@ -462,6 +467,14 @@ namespace snd_core
else
g_padAudio->Stop();
}
if (g_portalAudio)
{
if (isPlaying)
g_portalAudio->Play();
else
g_portalAudio->Stop();
}
}
// called periodically to check for AX updates

View file

@ -267,7 +267,7 @@ namespace vpad
{
if (channel <= 1 && vpadDelayEnabled)
{
uint64 currentTime = coreinit::coreinit_getOSTime();
uint64 currentTime = coreinit::OSGetTime();
const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime;
if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull))
{
@ -1149,7 +1149,7 @@ namespace vpad
void start()
{
coreinit::OSCreateAlarm(&g_vpad.alarm);
const uint64 start_tick = coreinit::coreinit_getOSTime();
const uint64 start_tick = coreinit::OSGetTime();
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000;
const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler);

View file

@ -13,13 +13,14 @@
std::shared_mutex g_audioMutex;
AudioAPIPtr g_tvAudio;
AudioAPIPtr g_padAudio;
AudioAPIPtr g_portalAudio;
std::atomic_int32_t g_padVolume = 0;
uint32 IAudioAPI::s_audioDelay = 2;
std::array<bool, IAudioAPI::AudioAPIEnd> IAudioAPI::s_availableApis{};
IAudioAPI::IAudioAPI(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_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);
InitWFX(m_samplerate, m_channels, m_bitsPerSample);
@ -80,7 +81,7 @@ void IAudioAPI::InitializeStatic()
#if BOOST_OS_WINDOWS
s_availableApis[DirectSound] = true;
s_availableApis[XAudio2] = XAudio2API::InitializeStatic();
if(!s_availableApis[XAudio2]) // don't try to initialize the older lib if the newer version is available
if (!s_availableApis[XAudio2]) // don't try to initialize the older lib if the newer version is available
s_availableApis[XAudio27] = XAudio27API::InitializeStatic();
#endif
#if HAS_CUBEB
@ -97,30 +98,29 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api)
return false;
}
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample)
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample)
{
auto& config = GetConfig();
sint32 channels = CemuConfig::AudioChannelsToNChannels(TV ? config.tv_channels : config.pad_channels);
sint32 channels = CemuConfig::AudioChannelsToNChannels(AudioTypeToChannels(type));
return CreateDeviceFromConfig(TV, rate, channels, samples_per_block, bits_per_sample);
}
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
{
AudioAPIPtr audioAPIDev;
auto& config = GetConfig();
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
auto& selectedDevice = TV ? config.tv_device : config.pad_device;
auto selectedDevice = GetDeviceFromType(type);
if(selectedDevice.empty())
if (selectedDevice.empty())
return {};
IAudioAPI::DeviceDescriptionPtr device_description;
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
{
auto devices = IAudioAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) {return d->GetIdentifier() == selectedDevice; });
const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) { return d->GetIdentifier() == selectedDevice; });
if (it != devices.end())
device_description = *it;
}
@ -137,7 +137,7 @@ AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& de
if (!IsAudioAPIAvailable(api))
return {};
switch(api)
switch (api)
{
#if BOOST_OS_WINDOWS
case DirectSound:
@ -157,11 +157,11 @@ AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& de
}
#endif
#if HAS_CUBEB
case Cubeb:
{
const auto tmp = std::dynamic_pointer_cast<CubebAPI::CubebDeviceDescription>(device);
return std::make_unique<CubebAPI>(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample);
}
case Cubeb:
{
const auto tmp = std::dynamic_pointer_cast<CubebAPI::CubebDeviceDescription>(device);
return std::make_unique<CubebAPI>(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample);
}
#endif
default:
throw std::runtime_error(fmt::format("invalid audio api: {}", api));
@ -172,8 +172,8 @@ std::vector<IAudioAPI::DeviceDescriptionPtr> IAudioAPI::GetDevices(AudioAPI api)
{
if (!IsAudioAPIAvailable(api))
return {};
switch(api)
switch (api)
{
#if BOOST_OS_WINDOWS
case DirectSound:
@ -209,3 +209,35 @@ uint32 IAudioAPI::GetAudioDelay() const
{
return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay;
}
AudioChannels IAudioAPI::AudioTypeToChannels(AudioType type)
{
auto& config = GetConfig();
switch (type)
{
case TV:
return config.tv_channels;
case Gamepad:
return config.pad_channels;
case Portal:
return config.portal_channels;
default:
return kMono;
}
}
std::wstring IAudioAPI::GetDeviceFromType(AudioType type)
{
auto& config = GetConfig();
switch (type)
{
case TV:
return config.tv_device;
case Gamepad:
return config.pad_device;
case Portal:
return config.portal_device;
default:
return L"";
}
}

View file

@ -4,6 +4,8 @@
#include <mmreg.h>
#endif
#include "config/CemuConfig.h"
class IAudioAPI
{
friend class GeneralSettings2;
@ -30,6 +32,13 @@ public:
using DeviceDescriptionPtr = std::shared_ptr<DeviceDescription>;
enum AudioType
{
TV = 0,
Gamepad,
Portal
};
enum AudioAPI
{
DirectSound = 0,
@ -62,8 +71,8 @@ public:
static void InitializeStatic();
static bool IsAudioAPIAvailable(AudioAPI api);
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample);
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample);
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api);
@ -84,6 +93,8 @@ protected:
private:
static uint32 s_audioDelay;
void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample);
static AudioChannels AudioTypeToChannels(AudioType type);
static std::wstring GetDeviceFromType(AudioType type);
};
@ -93,3 +104,5 @@ extern AudioAPIPtr g_tvAudio;
extern AudioAPIPtr g_padAudio;
extern std::atomic_int32_t g_padVolume;
extern AudioAPIPtr g_portalAudio;

View file

@ -275,9 +275,11 @@ void CemuConfig::Load(XMLConfigParser& parser)
tv_channels = audio.get("TVChannels", kStereo);
pad_channels = audio.get("PadChannels", kStereo);
input_channels = audio.get("InputChannels", kMono);
portal_channels = audio.get("PortalChannels", kMono);
tv_volume = audio.get("TVVolume", 20);
pad_volume = audio.get("PadVolume", 0);
input_volume = audio.get("InputVolume", 20);
portal_volume = audio.get("PortalVolume", 20);
const auto tv = audio.get("TVDevice", "");
try
@ -309,6 +311,16 @@ void CemuConfig::Load(XMLConfigParser& parser)
cemuLog_log(LogType::Force, "config load error: can't load input device: {}", input_device_name);
}
const auto portal_device_name = audio.get("PortalDevice", "");
try
{
portal_device = boost::nowide::widen(portal_device_name);
}
catch (const std::exception&)
{
cemuLog_log(LogType::Force, "config load error: can't load input device: {}", portal_device_name);
}
// account
auto acc = parser.get("Account");
account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id);
@ -508,12 +520,15 @@ void CemuConfig::Save(XMLConfigParser& parser)
audio.set("TVChannels", tv_channels);
audio.set("PadChannels", pad_channels);
audio.set("InputChannels", input_channels);
audio.set("InputChannels", portal_channels);
audio.set("TVVolume", tv_volume);
audio.set("PadVolume", pad_volume);
audio.set("InputVolume", input_volume);
audio.set("PortalVolume", portal_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());
audio.set("PortalDevice", boost::nowide::narrow(portal_device).c_str());
// account
auto acc = config.set("Account");

View file

@ -479,9 +479,9 @@ struct CemuConfig
// audio
sint32 audio_api = 0;
sint32 audio_delay = 2;
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;
AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono, portal_channels = kMono;
sint32 tv_volume = 50, pad_volume = 0, input_volume = 50, portal_volume = 50;
std::wstring tv_device{ L"default" }, pad_device, input_device, portal_device;
// account
struct

View file

@ -542,6 +542,47 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook)
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
{
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Trap Team Portal"));
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto portal_audio_row = new wxFlexGridSizer(0, 3, 0, 0);
portal_audio_row->SetFlexibleDirection(wxBOTH);
portal_audio_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_portal_device = new wxChoice(box, wxID_ANY, wxDefaultPosition);
m_portal_device->SetMinSize(wxSize(300, -1));
m_portal_device->SetToolTip(_("Select the active audio output device for Wii U GamePad"));
portal_audio_row->Add(m_portal_device, 0, wxEXPAND | wxALL, 5);
portal_audio_row->AddSpacer(0);
m_portal_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this);
const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only
portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_portal_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices);
m_portal_channels->SetSelection(0); // set default to mono
m_portal_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this);
portal_audio_row->Add(m_portal_channels, 0, wxEXPAND | wxALL, 5);
portal_audio_row->AddSpacer(0);
portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_portal_volume = new wxSlider(box, wxID_ANY, 100, 0, 100);
portal_audio_row->Add(m_portal_volume, 0, wxEXPAND | wxALL, 5);
auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%");
portal_audio_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
m_portal_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text));
m_portal_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this);
box_sizer->Add(portal_audio_row, 1, wxEXPAND, 5);
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
}
audio_panel->SetSizerAndFit(audio_panel_sizer);
return audio_panel;
}
@ -989,10 +1030,12 @@ void GeneralSettings2::StoreConfig()
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.portal_channels = kMono; // (AudioChannels)m_portal_channels->GetSelection();
config.tv_volume = m_tv_volume->GetValue();
config.pad_volume = m_pad_volume->GetValue();
config.input_volume = m_input_volume->GetValue();
config.portal_volume = m_portal_volume->GetValue();
config.tv_device.clear();
const auto tv_device = m_tv_device->GetSelection();
@ -1021,6 +1064,15 @@ void GeneralSettings2::StoreConfig()
config.input_device = device_description->GetDescription()->GetIdentifier();
}
config.portal_device.clear();
const auto portal_device = m_portal_device->GetSelection();
if (portal_device != wxNOT_FOUND && portal_device != 0 && m_portal_device->HasClientObjectData())
{
const auto* device_description = (wxDeviceDescription*)m_portal_device->GetClientObject(portal_device);
if (device_description)
config.portal_device = device_description->GetDescription()->GetIdentifier();
}
// graphics
config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection();
@ -1131,11 +1183,16 @@ void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event)
g_padVolume = event.GetInt();
}
}
else
else if (event.GetEventObject() == m_tv_volume)
{
if (g_tvAudio)
g_tvAudio->SetVolume(event.GetInt());
}
else
{
if(g_portalAudio)
g_portalAudio->SetVolume(event.GetInt());
}
}
@ -1195,10 +1252,12 @@ void GeneralSettings2::UpdateAudioDeviceList()
m_tv_device->Clear();
m_pad_device->Clear();
m_input_device->Clear();
m_portal_device->Clear();
m_tv_device->Append(_("Disabled"));
m_pad_device->Append(_("Disabled"));
m_input_device->Append(_("Disabled"));
m_portal_device->Append(_("Disabled"));
const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api;
const auto devices = IAudioAPI::GetDevices(audio_api);
@ -1206,6 +1265,7 @@ void GeneralSettings2::UpdateAudioDeviceList()
{
m_tv_device->Append(device->GetName(), new wxDeviceDescription(device));
m_pad_device->Append(device->GetName(), new wxDeviceDescription(device));
m_portal_device->Append(device->GetName(), new wxDeviceDescription(device));
}
const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api;
@ -1225,6 +1285,8 @@ void GeneralSettings2::UpdateAudioDeviceList()
m_input_device->SetSelection(0);
m_portal_device->SetSelection(0);
// todo reset global instance of audio device
}
@ -1658,6 +1720,7 @@ void GeneralSettings2::ApplyConfig()
m_pad_channels->SetSelection(0);
//m_input_channels->SetSelection(config.pad_channels);
m_input_channels->SetSelection(0);
m_portal_channels->SetSelection(0);
SendSliderEvent(m_tv_volume, config.tv_volume);
@ -1708,6 +1771,22 @@ void GeneralSettings2::ApplyConfig()
else
m_input_device->SetSelection(0);
SendSliderEvent(m_portal_volume, config.portal_volume);
if (!config.portal_device.empty() && m_portal_device->HasClientObjectData())
{
for (uint32 i = 0; i < m_portal_device->GetCount(); ++i)
{
const auto device_description = (wxDeviceDescription*)m_portal_device->GetClientObject(i);
if (device_description && config.portal_device == device_description->GetDescription()->GetIdentifier())
{
m_portal_device->SetSelection(i);
break;
}
}
}
else
m_portal_device->SetSelection(0);
// account
UpdateOnlineAccounts();
m_active_account->SetSelection(0);
@ -1866,6 +1945,42 @@ void GeneralSettings2::UpdateAudioDevice()
}
}
}
// skylander portal audio device
{
const auto selection = m_portal_device->GetSelection();
if (selection == wxNOT_FOUND)
{
cemu_assert_debug(false);
return;
}
g_portalAudio.reset();
if (m_portal_device->HasClientObjectData())
{
const auto description = (wxDeviceDescription*)m_portal_device->GetClientObject(selection);
if (description)
{
sint32 channels;
if (m_game_launched && g_portalAudio)
channels = g_portalAudio->GetChannels();
else
channels = 1;
try
{
g_portalAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 8000, 1, 32, 16);
g_portalAudio->SetVolume(m_portal_volume->GetValue());
}
catch (std::runtime_error& ex)
{
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
}
}
}
}
}
void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event)
@ -1895,6 +2010,13 @@ void GeneralSettings2::OnAudioChannelsSelected(wxCommandEvent& event)
config.pad_channels = (AudioChannels)obj->GetSelection();
}
else if (obj == m_portal_channels)
{
if (config.portal_channels == (AudioChannels)obj->GetSelection())
return;
config.portal_channels = (AudioChannels)obj->GetSelection();
}
else
cemu_assert_debug(false);

View file

@ -63,9 +63,9 @@ private:
// Audio
wxChoice* m_audio_api;
wxSlider *m_audio_latency;
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;
wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume, *m_portal_volume;
wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels, *m_portal_channels;
wxChoice *m_tv_device, *m_pad_device, *m_input_device, *m_portal_device;
// Account
wxButton* m_create_account, * m_delete_account;

View file

@ -304,7 +304,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event)
auto new_node = info_node.append_child("account");
new_node.append_attribute("persistentId").set_value(new_persistend_id_string.c_str());
auto timestamp = new_node.append_child("timestamp");
timestamp.text().set(fmt::format("{:016x}", coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet?
timestamp.text().set(fmt::format("{:016x}", coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet?
if(!doc.save_file(saveinfo.c_str()))
cemuLog_log(LogType::Force, "couldn't insert save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo));