Merge branch 'main' into win-arm64-msvc

This commit is contained in:
Live session user 2026-06-21 11:51:00 -07:00
commit f368efc413
31 changed files with 1007 additions and 675 deletions

47
.gdbinit Normal file
View File

@ -0,0 +1,47 @@
python
import gdb
import re
class BetypePrinter:
PATTERN = re.compile("^betype<.*>$")
def __init__(self, obj):
self._obj = obj
def to_string(self):
underlying = self._obj["m_value"]
reversed_bytes = bytes(reversed(underlying.bytes))
return gdb.Value(reversed_bytes, underlying.type)
class MemptrPrinter:
PATTERN = re.compile("^MEMPTR<.*>$")
def __init__(self, obj):
self._ptr_type = obj.type.strip_typedefs().unqualified().template_argument(0).pointer()
self._guest_address = obj["m_value"].cast(gdb.lookup_type("uint32"))
def ptr(self):
if self._guest_address == 0:
return gdb.Value(0, self._ptr_type)
base_addr = gdb.parse_and_eval('memory_base', global_context=True)
return (self._guest_address + base_addr).reinterpret_cast(self._ptr_type)
def children(self):
return [("raw", self.ptr())]
def to_string(self):
return self._guest_address.format_string(format="x")
def lookup(val):
tag = val.type.strip_typedefs().unqualified().tag
if tag is None:
return None
printers = [BetypePrinter, MemptrPrinter]
for printer in printers:
if printer.PATTERN.match(tag):
return printer(val)
return None
gdb.pretty_printers.append(lookup)

View File

@ -169,7 +169,7 @@ find_package(ZLIB REQUIRED)
find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available
find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED)
find_package(glm REQUIRED)
find_package(fmt 9 REQUIRED)
find_package(fmt 12.1 REQUIRED)
find_package(PNG REQUIRED)
if(ENABLE_SDL)

View File

@ -16,14 +16,6 @@
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}"
},
{
"name": "ARM64-Release",
"configurationType": "Release",
"generator": "Ninja",
"inheritEnvironments": [ "msvc_arm64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}"
},
{
"name": "Debug",
"configurationType": "Debug",

View File

@ -50,5 +50,17 @@ If coding isn't your thing, testing games and making detailed bug reports or upd
Questions about Cemu's software architecture can also be answered on Discord (or through the Matrix bridge).
#### AI generated contributions:
We ask that all code submitted is written and understood by a human. You can use AI for planning, designing, reviewing and for asking questions about the codebase, but the code itself needs to be written by you. As a small exception you can use intellisense-style AI code autocompletion for pure boilerplate code as long as it's only a small part of your submission. To further clarify, when we ask for "human written" that excludes letting an AI write the code and then paraphrasing it. In other words, we are asking for human effort.
Why this policy exists:
We have relatively low reviewing capacity and requiring human-written code increases the quality and trustworthyness of submitted pull requests. There are also general concerns with AI usage in emulation:
- LLMs tend to make up solutions that work on the surface but are generally not accurate in the emulation sense
- There is evidence that LLMs have been trained on leaked proprietary SDKs and we cannot verify the origin of the knowledge. This is especially a problem for core emulation logic
Please keep these points in mind when contributing to Cemu. Contributions that do not follow this policy may be rejected.
## License
Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively.

View File

@ -142,6 +142,9 @@ ih264_weighted_bi_pred_luma_av8:
sxtw x4, w4
sxtw x5, w5
stp x19, x20, [sp, #-16]!
add w6, w6, #1 //w6 = log_WD + 1
neg w10, w6 //w10 = -(log_WD + 1)
dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit)
#ifndef __APPLE__
ldr w8, [sp, #80] //Load wt2 in w8
ldr w9, [sp, #88] //Load ofst1 in w9
@ -155,9 +158,6 @@ ih264_weighted_bi_pred_luma_av8:
ldr w11, [sp, #92] //Load ht in w11
ldr w12, [sp, #96] //Load wd in w12
#endif
add w6, w6, #1 //w6 = log_WD + 1
neg w10, w6 //w10 = -(log_WD + 1)
dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit)
add w9, w9, #1 //w9 = ofst1 + 1
add w9, w9, w10 //w9 = ofst1 + ofst2 + 1
mov v2.s[0], w7

View File

@ -2,7 +2,7 @@
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges>

View File

@ -499,21 +499,43 @@ namespace CafeSystem
std::string GetWindowsNamedVersion(uint32& buildNumber)
{
char productName[256];
char buildNumberStr[32];
char featureVersion[32];
HKEY hKey;
DWORD dwType = REG_SZ;
DWORD dwSize = sizeof(productName);
buildNumber = 0;
featureVersion[0] = '\0';
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
{
if (RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)productName, &dwSize) != ERROR_SUCCESS)
strcpy(productName, "Windows");
dwType = REG_SZ;
dwSize = sizeof(buildNumberStr);
if (RegQueryValueExA(hKey, "CurrentBuildNumber", NULL, &dwType, (LPBYTE)buildNumberStr, &dwSize) == ERROR_SUCCESS)
buildNumber = (uint32)atoi(buildNumberStr);
dwType = REG_SZ;
dwSize = sizeof(featureVersion);
if (RegQueryValueExA(hKey, "DisplayVersion", NULL, &dwType, (LPBYTE)featureVersion, &dwSize) != ERROR_SUCCESS)
{
dwType = REG_SZ;
dwSize = sizeof(featureVersion);
if (RegQueryValueExA(hKey, "ReleaseId", NULL, &dwType, (LPBYTE)featureVersion, &dwSize) != ERROR_SUCCESS)
featureVersion[0] = '\0';
}
RegCloseKey(hKey);
}
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
buildNumber = osvi.dwBuildNumber;
return std::string(productName);
std::string result(productName);
// ProductName still reads as "Windows 10" on Windows 11. Find and replace with "Windows 11" based on build number.
if (buildNumber >= 22000)
{
size_t pos = result.find("Windows 10");
if (pos != std::string::npos)
result.replace(pos, 10, "Windows 11");
}
if (featureVersion[0] != '\0')
result += fmt::format(" {}", featureVersion);
return result;
}
#endif
@ -950,19 +972,30 @@ namespace CafeSystem
{
if (sLaunchModeIsStandalone)
return CosCapabilityBits::All;
CosCapabilityBits resultMask = static_cast<CosCapabilityBits>(0);
for (const auto& pack : GraphicPack2::GetActiveGraphicPacks())
{
for (const auto& permissionOverrides : pack->GetPermissionOverrides())
{
if (permissionOverrides.first == group)
resultMask |= static_cast<CosCapabilityBits>(permissionOverrides.second);
}
}
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
if (update.IsValid())
{
ParsedCosXml* cosXml = update.GetCosInfo();
if (cosXml)
return cosXml->GetCapabilityBits(group);
return cosXml->GetCapabilityBits(group) | resultMask;
}
auto& base = sGameInfo_ForegroundTitle.GetBase();
if(base.IsValid())
{
ParsedCosXml* cosXml = base.GetCosInfo();
if (cosXml)
return cosXml->GetCapabilityBits(group);
return cosXml->GetCapabilityBits(group) | resultMask;
}
return CosCapabilityBits::All;
}

View File

@ -451,6 +451,39 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
}
}
}
else if (boost::iequals(currentSectionName, "Permissions"))
{
std::array<std::pair<std::string_view, CosCapabilityGroup>, 15> permissionTable
{{
{"BSP", CosCapabilityGroup::BSP},
{"DK", CosCapabilityGroup::DK},
{"USB", CosCapabilityGroup::USB},
{"UHS", CosCapabilityGroup::UHS},
{"FS", CosCapabilityGroup::FS},
{"MCP", CosCapabilityGroup::MCP},
{"NIM", CosCapabilityGroup::NIM},
{"ACT", CosCapabilityGroup::ACT},
{"FPD", CosCapabilityGroup::FPD},
{"BOSS", CosCapabilityGroup::BOSS},
{"ACP", CosCapabilityGroup::ACP},
{"PDM", CosCapabilityGroup::PDM},
{"AC", CosCapabilityGroup::AC},
{"NDM", CosCapabilityGroup::NDM},
{"NSEC", CosCapabilityGroup::NSEC}
}};
for (const auto& [name, group] : permissionTable)
{
const auto permissionOption = rules.FindOption(name);
if (permissionOption)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has permission mask {} for {}", GetNormalizedPathString(), *permissionOption, name);
uint64 permissionMask = ConvertString<uint64>(*permissionOption, 16);
m_permissions.push_back({group, permissionMask});
}
}
}
}
if (m_version >= 5)

View File

@ -2,6 +2,7 @@
#include "util/helpers/helpers.h"
#include "Cemu/ExpressionParser/ExpressionParser.h"
#include "Cafe/TitleList/TitleInfo.h"
#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h"
#include "util/helpers/Serializer.h"
#include "Cafe/OS/RPL/rpl.h"
@ -147,6 +148,9 @@ public:
[[nodiscard]] const std::vector<PresetPtr>& GetPresets() const { return m_presets; }
[[nodiscard]] std::unordered_map<std::string, std::vector<PresetPtr>> GetCategorizedPresets(std::vector<std::string>& order) const;
// permissions
const std::vector<std::pair<CosCapabilityGroup, uint64>>& GetPermissionOverrides() { return m_permissions; }
// shaders
void LoadShaders();
@ -264,6 +268,9 @@ private:
// ram mappings
std::vector<std::pair<MPTR, MPTR>> m_ramMappings;
// permissions
std::vector<std::pair<CosCapabilityGroup, uint64>> m_permissions;
// patches
void LoadPatchFiles(); // loads Cemuhook or Cemu patches

View File

@ -4,14 +4,22 @@
#include "util/highresolutiontimer/HighResolutionTimer.h"
#include "Common/cpu_features.h"
#include "Common/Intrinsics.h"
#include <chrono>
#include <thread>
#if defined(ARCH_X86_64)
#include <immintrin.h>
#pragma intrinsic(__rdtsc)
#endif
uint64 _rdtscLastMeasure = 0;
uint64 _rdtscFrequency = 0;
struct uint128_t
{
uint64 low;
uint64 high;
};
static_assert(sizeof(uint128_t) == 16);
uint128_t _rdtscAcc{};
uint64 muldiv64(uint64 a, uint64 b, uint64 d)
@ -25,28 +33,25 @@ uint64 muldiv64(uint64 a, uint64 b, uint64 d)
uint64 PPCTimer_estimateRDTSCFrequency()
{
#if defined(ARCH_X86_64) || defined(__x86_64__) || defined(_M_X64)
#if defined(ARCH_X86_64)
if (!g_CPUFeatures.x86.invariant_tsc)
cemuLog_log(LogType::Force, "Invariant TSC not supported");
#endif
BARRIER_FENCE();
uint64 tscStart = READ_TSC();
auto startTime = std::chrono::steady_clock::now();
_mm_mfence();
uint64 tscStart = __rdtsc();
unsigned int startTime = GetTickCount();
HRTick startTick = HighResolutionTimer::now().getTick();
// wait roughly 3 seconds
while (true)
{
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - startTime).count();
if (elapsed >= 3000)
if ((GetTickCount() - startTime) >= 3000)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
BARRIER_FENCE();
_mm_mfence();
HRTick stopTick = HighResolutionTimer::now().getTick();
uint64 tscEnd = READ_TSC();
uint64 tscEnd = __rdtsc();
// derive frequency approximation from measured time difference
uint64 tsc_diff = tscEnd - tscStart;
uint64 hrtFreq = 0;
@ -60,7 +65,7 @@ uint64 PPCTimer_estimateRDTSCFrequency()
//cemuLog_log(LogType::Force, "HPC-diff: 0x{:016x}", qpc_diff);
//cemuLog_log(LogType::Force, "HPC-freq: 0x{:016x}", (uint64)qpc_freq.QuadPart);
//cemuLog_log(LogType::Force, "Multiplier: 0x{:016x}", freqMultiplier);
return tsc_freq;
}
@ -74,20 +79,20 @@ void PPCTimer_init()
{
std::thread t(PPCTimer_initThread);
t.detach();
_rdtscLastMeasure = READ_TSC();
_rdtscLastMeasure = __rdtsc();
}
uint64 _tickSummary = 0;
void PPCTimer_start()
{
_rdtscLastMeasure = READ_TSC();
_rdtscLastMeasure = __rdtsc();
_tickSummary = 0;
}
uint64 PPCTimer_getRawTsc()
{
return READ_TSC();
return __rdtsc();
}
uint64 PPCTimer_microsecondsToTsc(uint64 us)
@ -98,10 +103,10 @@ uint64 PPCTimer_microsecondsToTsc(uint64 us)
uint64 PPCTimer_tscToMicroseconds(uint64 us)
{
uint128_t r{};
r.low = portable_umul128(us, 1000000ULL, &r.high);
r.low = _umul128(us, 1000000ULL, &r.high);
uint64 remainder;
const uint64 microseconds = portable_udiv128(r.high, r.low, _rdtscFrequency, &remainder);
const uint64 microseconds = _udiv128(r.high, r.low, _rdtscFrequency, &remainder);
return microseconds;
}
@ -122,25 +127,30 @@ FSpinlock sTimerSpinlock;
uint64 PPCTimer_getFromRDTSC()
{
sTimerSpinlock.lock();
BARRIER_FENCE();
uint64 rdtscCurrentMeasure = READ_TSC();
_mm_mfence();
uint64 rdtscCurrentMeasure = __rdtsc();
uint64 rdtscDif = rdtscCurrentMeasure - _rdtscLastMeasure;
// optimized max(rdtscDif, 0) without conditionals
rdtscDif = rdtscDif & ~(uint64)((sint64)rdtscDif >> 63);
uint128_t diff{};
diff.low = portable_umul128(rdtscDif, Espresso::CORE_CLOCK, &diff.high);
diff.low = _umul128(rdtscDif, Espresso::CORE_CLOCK, &diff.high);
if(rdtscCurrentMeasure > _rdtscLastMeasure)
_rdtscLastMeasure = rdtscCurrentMeasure; // only travel forward in time
uint64 old_low = _rdtscAcc.low;
_rdtscAcc.low += diff.low;
uint64 carry = (_rdtscAcc.low < old_low) ? 1 : 0;
_rdtscAcc.high += diff.high + carry;
uint8 c = 0;
#if BOOST_OS_WINDOWS
c = _addcarry_u64(c, _rdtscAcc.low, diff.low, &_rdtscAcc.low);
_addcarry_u64(c, _rdtscAcc.high, diff.high, &_rdtscAcc.high);
#else
// requires casting because of long / long long nonesense
c = _addcarry_u64(c, _rdtscAcc.low, diff.low, (unsigned long long*)&_rdtscAcc.low);
_addcarry_u64(c, _rdtscAcc.high, diff.high, (unsigned long long*)&_rdtscAcc.high);
#endif
uint64 remainder;
uint64 elapsedTick = portable_udiv128(_rdtscAcc.high, _rdtscAcc.low, _rdtscFrequency, &remainder);
uint64 elapsedTick = _udiv128(_rdtscAcc.high, _rdtscAcc.low, _rdtscFrequency, &remainder);
_rdtscAcc.low = remainder;
_rdtscAcc.high = 0;

View File

@ -125,6 +125,7 @@ void LatteTextureReadback_StartTransfer(LatteTextureView* textureView);
bool LatteTextureReadback_Update(bool forceStart = false);
void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture);
void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish);
bool LatteTextureReadback_ReadbackToLinearBlocking(LatteTextureView* sourceView, uint8* dstPtr, uint32 dstWidth, uint32 dstHeight, uint32 dstPitch);
// query

View File

@ -29,7 +29,6 @@ void LatteSurfaceCopy_CopyInRAM(const LatteSurfaceCopyParam& src, const LatteSur
copyWidth = (copyWidth + 3) / 4;
copyHeight = (copyHeight + 3) / 4;
}
uint32 dstBpp = Latte::GetFormatBits(dstHwFormat);
gx2SurfaceCopySoftware((uint8*)MEMPTR<void>(src.physDataAddr).GetPtr(), src.heightInTexels, src.pitch, 1, src.sliceIndex, src.swizzle, (uint32)src.tilemode,
@ -44,10 +43,9 @@ void LatteSurfaceCopy_copySurfaceNew(const LatteSurfaceCopyParam& src, const Lat
cemu_assert_debug((rect.x + rect.width) <= dst.pitch * (Latte::IsCompressedFormat(dst.surfaceFormat)?4:1));
cemu_assert_debug((rect.x + rect.width) <= src.pitch * (Latte::IsCompressedFormat(src.surfaceFormat)?4:1));
if (src.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL || dst.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
if (src.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
{
// todo - it's technically possible for a matching linear texture to be in the texture cache already
// there is also a case of tiled to linear_special where we should trigger a readback without an actual destination texture
LatteSurfaceCopy_CopyInRAM(src, dst, rect);
return;
}
@ -69,6 +67,13 @@ void LatteSurfaceCopy_copySurfaceNew(const LatteSurfaceCopyParam& src, const Lat
LatteTexture_UpdateCacheFromDynamicTextures(sourceTexture);
sourceTexture->reloadFromDynamicTextures = false;
}
// special case: RAM copy from cached texture to linear special format
if (dst.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
{
cemu_assert_debug(rect.x == 0 && rect.y == 0);
LatteTextureReadback_ReadbackToLinearBlocking(sourceView, MEMPTR<uint8>(dst.physDataAddr).GetPtr(), rect.width, rect.height, dst.pitch);
return;
}
// look up destination texture
LatteTexture* destinationTexture = nullptr;
LatteTextureView* destinationView = LatteTextureViewLookupCache::lookupSliceMinSize(dst.physDataAddr, rect.x + rect.width, rect.y + rect.height, dst.pitch, 0, dst.sliceIndex, dst.surfaceFormat);

View File

@ -159,3 +159,25 @@ void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish)
}
performanceMonitor.gpuTime_waitForAsync.endMeasuring();
}
bool LatteTextureReadback_ReadbackToLinearBlocking(LatteTextureView* sourceView, uint8* dstPtr, uint32 dstWidth, uint32 dstHeight, uint32 dstPitch)
{
LatteTextureReadbackInfo* info = g_renderer->texture_createReadback(sourceView);
if (!info)
return false;
info->StartTransfer();
info->ForceFinish();
cemu_assert(info->IsFinished());
uint8* data = info->GetData(); // returned pixel format should match Latte format
uint32 bpp = Latte::GetFormatBits(sourceView->baseTexture->format) / 8;
uint32 srcRowBytes = sourceView->baseTexture->width * bpp;
uint32 dstRowBytes = dstWidth * bpp;
for (uint32 y = 0; y < dstHeight; y++)
memcpy(dstPtr + y * dstPitch * bpp, data + y * srcRowBytes, dstRowBytes);
info->ReleaseData();
delete info;
return true;
}

View File

@ -78,6 +78,7 @@ namespace snd_core
const int AX_FILTER_LOWPASS_16K = 0x2;
bool isInitialized();
bool IsSndCore2();
void reset();
// AX VPB

View File

@ -379,144 +379,23 @@ namespace snd_core
return 0;
}
// void loadExportsSndCore1()
// {
// cafeExportRegisterFunc(sndcore1_AXInit, "snd_core", "AXInit", LogType::SoundAPI);
// cafeExportRegisterFunc(sndcore1_AXInitEx, "snd_core", "AXInitEx", LogType::SoundAPI);
// cafeExportRegister("snd_core", AXIsInit, LogType::SoundAPI);
// cafeExportRegister("snd_core", AXQuit, LogType::SoundAPI);
//
// cafeExportRegister("snd_core", AXGetMaxVoices, LogType::SoundAPI);
// cafeExportRegister("snd_core", AXGetInputSamplesPerFrame, LogType::SoundAPI);
// cafeExportRegister("snd_core", AXGetInputSamplesPerSec, LogType::SoundAPI);
// cafeExportRegister("snd_core", AXSetDefaultMixerSelect, LogType::SoundAPI);
// cafeExportRegister("snd_core", AXGetDefaultMixerSelect, LogType::SoundAPI);
//
// osLib_addFunction("snd_core", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback);
// osLib_addFunction("snd_core", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback);
//
// osLib_addFunction("snd_core", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback);
// osLib_addFunction("snd_core", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback);
//
// osLib_addFunction("snd_core", "AXRegisterFrameCallback", export_AXRegisterFrameCallback);
// osLib_addFunction("snd_core", "AXRegisterCallback", export_AXRegisterCallback);
//
// osLib_addFunction("snd_core", "AXRegisterAuxCallback", export_AXRegisterAuxCallback);
// osLib_addFunction("snd_core", "AXGetAuxCallback", export_AXGetAuxCallback);
//
// osLib_addFunction("snd_core", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume);
//
// osLib_addFunction("snd_core", "AXGetDeviceMode", export_AXGetDeviceMode);
//
// osLib_addFunction("snd_core", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage);
// osLib_addFunction("snd_core", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage);
//
// osLib_addFunction("snd_core", "AXAcquireVoiceEx", export_AXAcquireVoiceEx);
// osLib_addFunction("snd_core", "AXAcquireVoice", export_AXAcquireVoice);
// osLib_addFunction("snd_core", "AXFreeVoice", export_AXFreeVoice);
//
// osLib_addFunction("snd_core", "AXUserIsProtected", export_AXUserIsProtected);
// osLib_addFunction("snd_core", "AXUserBegin", export_AXUserBegin);
// osLib_addFunction("snd_core", "AXUserEnd", export_AXUserEnd);
// osLib_addFunction("snd_core", "AXVoiceBegin", export_AXVoiceBegin);
// osLib_addFunction("snd_core", "AXVoiceEnd", export_AXVoiceEnd);
// osLib_addFunction("snd_core", "AXVoiceIsProtected", export_AXVoiceIsProtected);
//
// osLib_addFunction("snd_core", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets);
//
// osLib_addFunction("snd_core", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix);
// osLib_addFunction("snd_core", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix);
//
// cafeExportRegister("snd_core", AXGetDeviceFinalOutput, LogType::SoundAPI);
// }
//
// void loadExportsSndCore2()
// {
// cafeExportRegisterFunc(sndcore2_AXInitWithParams, "sndcore2", "AXInitWithParams", LogType::SoundAPI);
// cafeExportRegisterFunc(sndcore2_AXInit, "sndcore2", "AXInit", LogType::SoundAPI);
// cafeExportRegisterFunc(sndcore2_AXInitEx, "sndcore2", "AXInitEx", LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXIsInit, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXQuit, LogType::SoundAPI);
//
// cafeExportRegister("sndcore2", AXGetMaxVoices, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXGetInputSamplesPerFrame, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXGetInputSamplesPerSec, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetDefaultMixerSelect, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXGetDefaultMixerSelect, LogType::SoundAPI);
//
// osLib_addFunction("sndcore2", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback);
// osLib_addFunction("sndcore2", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback);
//
// osLib_addFunction("sndcore2", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback);
// osLib_addFunction("sndcore2", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback);
//
// osLib_addFunction("sndcore2", "AXRegisterFrameCallback", export_AXRegisterFrameCallback);
// osLib_addFunction("sndcore2", "AXRegisterCallback", export_AXRegisterCallback);
//
// osLib_addFunction("sndcore2", "AXRegisterAuxCallback", export_AXRegisterAuxCallback);
// osLib_addFunction("sndcore2", "AXGetAuxCallback", export_AXGetAuxCallback);
//
// osLib_addFunction("sndcore2", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume);
//
// osLib_addFunction("sndcore2", "AXGetDeviceMode", export_AXGetDeviceMode);
//
// osLib_addFunction("sndcore2", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage);
// osLib_addFunction("sndcore2", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage);
//
// osLib_addFunction("sndcore2", "AXAcquireVoiceEx", export_AXAcquireVoiceEx);
// osLib_addFunction("sndcore2", "AXAcquireVoice", export_AXAcquireVoice);
// osLib_addFunction("sndcore2", "AXFreeVoice", export_AXFreeVoice);
//
// osLib_addFunction("sndcore2", "AXUserIsProtected", export_AXUserIsProtected);
// osLib_addFunction("sndcore2", "AXUserBegin", export_AXUserBegin);
// osLib_addFunction("sndcore2", "AXUserEnd", export_AXUserEnd);
// osLib_addFunction("sndcore2", "AXVoiceBegin", export_AXVoiceBegin);
// osLib_addFunction("sndcore2", "AXVoiceEnd", export_AXVoiceEnd);
//
// osLib_addFunction("sndcore2", "AXVoiceIsProtected", export_AXVoiceIsProtected);
//
// osLib_addFunction("sndcore2", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets);
//
// osLib_addFunction("sndcore2", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix);
// osLib_addFunction("sndcore2", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix);
//
// cafeExportRegister("sndcore2", AXGetDeviceFinalOutput, LogType::SoundAPI);
//
// // multi voice
// cafeExportRegister("sndcore2", AXAcquireMultiVoice, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXFreeMultiVoice, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXGetMultiVoiceReformatBufferSize, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceType, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcm, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceSrcType, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceOffsets, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceVe, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceSrcRatio, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceSrc, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceLoop, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceState, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcmLoop, LogType::SoundAPI);
// cafeExportRegister("sndcore2", AXIsMultiVoiceRunning, LogType::SoundAPI);
// }
// void loadExports()
// {
// AXResetToDefaultState();
//
// loadExportsSndCore1();
// loadExportsSndCore2();
// }
bool isInitialized()
{
return sndGeneric.isInitialized;
}
bool IsSndCore2()
{
cemu_assert_debug(sndGeneric.isInitialized);
return sndGeneric.isSoundCore2;
}
void reset()
{
AXOut_reset();
AXResetToDefaultState();
sndGeneric.isInitialized = false;
sndGeneric.isSoundCore2 = false;
}
void RegisterVoiceFunctions()

View File

@ -8,7 +8,6 @@ void mic_updateOnAXFrame();
namespace snd_core
{
uint32 __AXDefaultMixerSelect = AX_MIXER_SELECT_BOTH;
uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT];
void AXSetDefaultMixerSelect(uint32 mixerSelect)
@ -226,7 +225,6 @@ namespace snd_core
uint32 newInternalCurrentOffset = internalCurrentOffset + (playbackNibbleOffset - playbackNibbleOffsetStart);
*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(newInternalCurrentOffset);
}
void AX_DecodeSamplesADPCM_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
@ -459,8 +457,6 @@ namespace snd_core
uint16* endOffsetAddr = (uint16*)(memory_base + ((endOffsetPtr * 2) | (ptrHighExtension << 29)));
uint16* currentOffsetAddr = (uint16*)(memory_base + ((currentOffsetPtr * 2) | (ptrHighExtension << 29)));
uint16* loopOffsetAddrDebug = (uint16*)(memory_base + ((loopOffsetPtr * 2) | (ptrHighExtension << 29)));
sint16 historySamples[4];
historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]);
historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]);
@ -469,17 +465,19 @@ namespace snd_core
sint32 historyIndex = 0;
sint32 s = 0;
for (sint32 i = 0; i < sampleCount; i++)
{
currentFracPos += ratio;
while (currentFracPos >= 0x10000)
{
// read next sample
sint32 prevIndex = historyIndex;
historyIndex = (historyIndex + 1) & 3;
if (internalShadowCopy->playbackState)
{
sint32 s = _swapEndianS16(*currentOffsetAddr);
historySamples[historyIndex] = s;
s = _swapEndianS16(*currentOffsetAddr);
historySamples[prevIndex] = s;
if (currentOffsetAddr == endOffsetAddr)
{
if (internalShadowCopy->internalOffsets.loopFlag)
@ -500,16 +498,16 @@ namespace snd_core
}
else
{
// voice not playing -> sample is silent
historySamples[historyIndex] = 0;
// voice not playing -> repeat previous sample
historySamples[prevIndex] = s;
}
currentFracPos -= 0x10000;
}
// interpolate sample
sint32 previousSample = historySamples[(historyIndex + 3) & 3];
sint32 nextSample = historySamples[historyIndex];
sint32 curSample = historySamples[historyIndex];
sint32 nextSample = historySamples[(historyIndex + 1) & 3];
sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos);
sint32 p0 = (sint32)curSample * (sint32)(0x10000 - currentFracPos);
sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos);
p0 >>= 7;
p1 >>= 7;
@ -659,7 +657,7 @@ namespace snd_core
float __AXMixBufferTV[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT * AX_BUS_COUNT];
float __AXMixBufferDRC[2 * AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * AX_BUS_COUNT];
void AXVoiceMix_ApplyADSR(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
void AXVoiceMix_ApplyADSR_NoClamp(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
{
uint16 volume = internalShadowCopy->veVolume;
sint16 volumeDelta = (sint16)internalShadowCopy->veDelta;
@ -669,23 +667,73 @@ namespace snd_core
if (volumeDelta == 0)
{
// without delta
for (sint32 i = 0; i < sampleCount; i++)
sampleData[i] *= volumeScaler;
for (sint32 i = 0; i < sampleCount; i += 4)
{
sampleData[i+0] *= volumeScaler;
sampleData[i+1] *= volumeScaler;
sampleData[i+2] *= volumeScaler;
sampleData[i+3] *= volumeScaler;
}
return;
}
// with delta
double volumeScalerDelta = (double)volumeDelta / 32768.0;
volumeScalerDelta = volumeScalerDelta + volumeScalerDelta;
float volumeScalerDelta = volumeDelta / 32768.0f;
for (sint32 i = 0; i < sampleCount; i++)
{
volumeScaler += (float)volumeScalerDelta;
sampleData[i] *= volumeScaler;
}
if (volumeDelta != 0)
internalShadowCopy->veVolume = (uint16)((sint32)internalShadowCopy->veVolume + volumeDelta * sampleCount);
}
FORCE_INLINE float ClampADSRSample(float x)
{
if (x > 8388352.0f)
return 8388352.0f;
if (x < -8388608.0f)
return -8388608.0f;
return x;
}
void AXVoiceMix_ApplyADSR_Sndcore2(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
{
// sndcore2 clamps samples to -8388608,8388352 range (16bit signed range << 8)
uint16 volume = internalShadowCopy->veVolume;
sint16 volumeDelta = (sint16)internalShadowCopy->veDelta;
uint16 finalVolume = (uint16)((sint32)volume + volumeDelta * sampleCount);
if (volume == 0x8000 && volumeDelta == 0)
return;
if (volume <= 0x8000 && finalVolume <= 0x8000) // if volume scaler doesn't exceed 1.0 then we don't need to clamp. We assume that volume never wraps around
return AXVoiceMix_ApplyADSR_NoClamp(internalShadowCopy, sampleData, sampleCount);
float volumeScaler = (float)volume / 32768.0f;
if (volumeDelta == 0)
{
volume = (uint16)(volumeScaler * 32768.0);
internalShadowCopy->veVolume = volume;
// without delta
for (sint32 i = 0; i < sampleCount; i += 4)
{
sampleData[i+0] = ClampADSRSample(sampleData[i+0] * volumeScaler);
sampleData[i+1] = ClampADSRSample(sampleData[i+1] * volumeScaler);
sampleData[i+2] = ClampADSRSample(sampleData[i+2] * volumeScaler);
sampleData[i+3] = ClampADSRSample(sampleData[i+3] * volumeScaler);
}
return;
}
// with delta
float volumeScalerDelta = volumeDelta / 32768.0f;
for (sint32 i = 0; i < sampleCount; i++)
{
volumeScaler += volumeScalerDelta;
sampleData[i] = ClampADSRSample(sampleData[i] * volumeScaler);
}
internalShadowCopy->veVolume = finalVolume;
}
void AXVoiceMix_ApplyADSR(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
{
if (sndGeneric.isSoundCore2)
AXVoiceMix_ApplyADSR_Sndcore2(internalShadowCopy, sampleData, sampleCount);
else
AXVoiceMix_ApplyADSR_NoClamp(internalShadowCopy, sampleData, sampleCount);
}
void AXVoiceMix_ApplyBiquad(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
@ -921,10 +969,8 @@ namespace snd_core
sint32* output = __AXTVOutputBuffer.GetPtr();
cemu_assert_debug(masterVolume == 0x8000); // todo -> Calculate delta between old master volume and new volume
sint16 delta = 0;
uint16 volVar;
for (uint16 c = 0; c < AX_TV_CHANNEL_COUNT; c++)
{
volVar = _swapEndianU16(masterVolume);
AXMix_MergeBusSamples(input, output, sampleCount, masterVolume, delta);
output += sampleCount;
input += sampleCount;

View File

@ -12,7 +12,8 @@ namespace snd_user
#define AX_MAX_NUM_DRC (2)
#define AX_MAX_NUM_RMT (4)
#define AX_UPDATE_MODE_10000000 (0x10000000)
#define AX_UPDATE_MODE_8 (0x8)
#define AX_UPDATE_MODE_10000000_INPUT_LEVEL (0x10000000)
#define AX_UPDATE_MODE_20000000 (0x20000000)
#define AX_UPDATE_MODE_40000000_VOLUME (0x40000000)
#define AX_UPDATE_MODE_50000000 (0x50000000)
@ -21,18 +22,18 @@ namespace snd_user
struct VolumeData
{
sint16 volume; // 0x00
sint16 volume_target; // 0x02
uint16 volume; // 0x00
uint16 volume_target; // 0x02
};
static_assert(sizeof(VolumeData) == 0x4, "sizeof(VolumeData)");
struct MixControl
{
sint16 aux[AX_AUX_BUS_COUNT];
sint16 pan;
sint16 span;
sint16 fader;
sint16 lfe;
sint16be aux[AX_AUX_BUS_COUNT];
sint16be pan;
sint16be span;
sint16be fader;
sint16be lfe;
};
static_assert(sizeof(MixControl) == 0xE, "sizeof(MixControl)");
@ -131,9 +132,9 @@ namespace snd_user
struct DeviceInfo
{
uint32 tv_sound_mode; // 0x00
uint32 drc_sound_mode; // 0x04
uint32 rmt_sound_mode; // 0x08
MixSoundMode tv_sound_mode;
MixSoundMode drc_sound_mode;
MixSoundMode rmt_sound_mode;
};
struct snd_user_data_t
@ -147,6 +148,7 @@ namespace snd_user
const uint16 volume[0x388 + 0x3C + 1] =
{
// (uint16)(0x8000 * pow(10.0,(i - 0x388) / 200.0))
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xC,
@ -175,36 +177,39 @@ namespace snd_user
0xDE70, 0xE103, 0xE39E, 0xE641, 0xE8EB, 0xEB9E, 0xEE58, 0xF11B, 0xF3E6, 0xF6B9, 0xF994, 0xFC78, 0xFF64
};
const uint32 pan_values[128] =
const sint16 pan_values[128] =
{
00, 00,
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFD, 0xFFFFFFFC, 0xFFFFFFFC, 0xFFFFFFFC, 0xFFFFFFFB, 0xFFFFFFFB, 0xFFFFFFFB,
0xFFFFFFFA, 0xFFFFFFFA, 0xFFFFFFF9, 0xFFFFFFF9, 0xFFFFFFF9, 0xFFFFFFF8, 0xFFFFFFF8, 0xFFFFFFF7, 0xFFFFFFF7, 0xFFFFFFF6, 0xFFFFFFF6, 0xFFFFFFF6, 0xFFFFFFF5, 0xFFFFFFF5,
0xFFFFFFF4, 0xFFFFFFF4, 0xFFFFFFF3, 0xFFFFFFF3, 0xFFFFFFF2, 0xFFFFFFF2, 0xFFFFFFF2, 0xFFFFFFF1, 0xFFFFFFF1, 0xFFFFFFF0, 0xFFFFFFF0, 0xFFFFFFEF, 0xFFFFFFEF, 0xFFFFFFEE,
0xFFFFFFEE, 0xFFFFFFED, 0xFFFFFFEC, 0xFFFFFFEC, 0xFFFFFFEB, 0xFFFFFFEB, 0xFFFFFFEA, 0xFFFFFFEA, 0xFFFFFFE9, 0xFFFFFFE9, 0xFFFFFFE8, 0xFFFFFFE7, 0xFFFFFFE7, 0xFFFFFFE6,
0xFFFFFFE6, 0xFFFFFFE5, 0xFFFFFFE4, 0xFFFFFFE4, 0xFFFFFFE3, 0xFFFFFFE2, 0xFFFFFFE2, 0xFFFFFFE1, 0xFFFFFFE0, 0xFFFFFFDF, 0xFFFFFFDF, 0xFFFFFFDE, 0xFFFFFFDD, 0xFFFFFFDC,
0xFFFFFFDC, 0xFFFFFFDB, 0xFFFFFFDA, 0xFFFFFFD9, 0xFFFFFFD8, 0xFFFFFFD8, 0xFFFFFFD7, 0xFFFFFFD6, 0xFFFFFFD5, 0xFFFFFFD4, 0xFFFFFFD3, 0xFFFFFFD2, 0xFFFFFFD1, 0xFFFFFFD0,
0xFFFFFFCF, 0xFFFFFFCE, 0xFFFFFFCD, 0xFFFFFFCC, 0xFFFFFFCA, 0xFFFFFFC9, 0xFFFFFFC8, 0xFFFFFFC7, 0xFFFFFFC5, 0xFFFFFFC4, 0xFFFFFFC3, 0xFFFFFFC1, 0xFFFFFFC0, 0xFFFFFFBE,
0xFFFFFFBD, 0xFFFFFFBB, 0xFFFFFFB9, 0xFFFFFFB8, 0xFFFFFFB6, 0xFFFFFFB4, 0xFFFFFFB2, 0xFFFFFFB0, 0xFFFFFFAD, 0xFFFFFFAB, 0xFFFFFFA9, 0xFFFFFFA6, 0xFFFFFFA3, 0xFFFFFFA0,
0xFFFFFF9D, 0xFFFFFF9A, 0xFFFFFF96, 0xFFFFFF92, 0xFFFFFF8D, 0xFFFFFF88, 0xFFFFFF82, 0xFFFFFF7B, 0xFFFFFF74, 0xFFFFFF6A, 0xFFFFFF5D, 0xFFFFFF4C, 0xFFFFFF2E, 0xFFFFFC78
// round(100 * log10((127 - index) / 127.0))
0, 0,
-1, -1, -1, -2, -2, -2, -3, -3, -4, -4, -4, -5, -5, -5,
-6, -6, -7, -7, -7, -8, -8, -9, -9, -10, -10, -10, -11, -11,
-12, -12, -13, -13, -14, -14, -14, -15, -15, -16, -16, -17, -17, -18,
-18, -19, -20, -20, -21, -21, -22, -22, -23, -23, -24, -25, -25, -26,
-26, -27, -28, -28, -29, -30, -30, -31, -32, -33, -33, -34, -35, -36,
-36, -37, -38, -39, -40, -40, -41, -42, -43, -44, -45, -46, -47, -48,
-49, -50, -51, -52, -54, -55, -56, -57, -59, -60, -61, -63, -64, -66,
-67, -69, -71, -72, -74, -76, -78, -80, -83, -85, -87, -90, -93, -96,
-99, -102, -106, -110, -115, -120, -126, -133, -140, -150, -163, -180, -210, -904
};
const uint16 pan_values_low[128] =
const sint16 pan_values_low[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFC, 0xFFFC, 0xFFFC, 0xFFFB, 0xFFFB, 0xFFFA, 0xFFFA, 0xFFFA, 0xFFF9, 0xFFF9, 0xFFF8, 0xFFF8,
0xFFF7, 0xFFF7, 0xFFF6, 0xFFF5, 0xFFF5, 0xFFF4, 0xFFF4, 0xFFF3, 0xFFF2, 0xFFF2, 0xFFF1, 0xFFF0, 0xFFEF, 0xFFEF, 0xFFEE, 0xFFED, 0xFFEC, 0xFFEB, 0xFFEB, 0xFFEA, 0xFFE9, 0xFFE8, 0xFFE7, 0xFFE6, 0xFFE5, 0xFFE4, 0xFFE3, 0xFFE2, 0xFFE1,
0xFFE0, 0xFFDE, 0xFFDD, 0xFFDC, 0xFFDB, 0xFFDA, 0xFFD8, 0xFFD7, 0xFFD6, 0xFFD4, 0xFFD3, 0xFFD1, 0xFFD0, 0xFFCE, 0xFFCC, 0xFFCB, 0xFFC9, 0xFFC7, 0xFFC6, 0xFFC4, 0xFFC2, 0xFFC0, 0xFFBE, 0xFFBC, 0xFFBA, 0xFFB7, 0xFFB5, 0xFFB3, 0xFFB0,
0xFFAE, 0xFFAB, 0xFFA8, 0xFFA6, 0xFFA3, 0xFFA0, 0xFF9C, 0xFF99, 0xFF96, 0xFF92, 0xFF8E, 0xFF8A, 0xFF86, 0xFF82, 0xFF7D, 0xFF78, 0xFF73, 0xFF6E, 0xFF68, 0xFF61, 0xFF5A, 0xFF53, 0xFF4B, 0xFF42, 0xFF37, 0xFF2C, 0xFF1F, 0xFF0F, 0xFEFB,
0xFEE2, 0xFEBF, 0xFE83, 0xFC40
};
// (200.0 * log10(cos((i * M_PI) / 254.0)))
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2, -2, -2, -3, -3, -3, -4, -4, -4, -5, -5, -6, -6, -6, -7, -7, -8, -8,
-9, -9, -10, -11, -11, -12, -12, -13, -14, -14, -15, -16, -17, -17, -18, -19, -20, -21, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31,
-32, -34, -35, -36, -37, -38, -40, -41, -42, -44, -45, -47, -48, -50, -52, -53, -55, -57, -58, -60, -62, -64, -66, -68, -70, -73, -75, -77, -80,
-82, -85, -88, -90, -93, -96, -100, -103, -106, -110, -114, -118, -122, -126, -131, -136, -141, -146, -152, -159, -166, -173, -181, -190, -201, -212, -225, -241, -261,
-286, -321, -381, -960
};
const uint16 pan_values_high[128] =
const sint16 pan_values_high[128] =
{
0xFFC3, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC5, 0xFFC6, 0xFFC6, 0xFFC7, 0xFFC8, 0xFFC8, 0xFFC9, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCB, 0xFFCC, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCE, 0xFFCF, 0xFFCF, 0xFFD0, 0xFFD0, 0xFFD1, 0xFFD1, 0xFFD2, 0xFFD2, 0xFFD3,
0xFFD3, 0xFFD4, 0xFFD4, 0xFFD5, 0xFFD5, 0xFFD6, 0xFFD6, 0xFFD7, 0xFFD7, 0xFFD8, 0xFFD8, 0xFFD9, 0xFFD9, 0xFFDA, 0xFFDA, 0xFFDA, 0xFFDB, 0xFFDB, 0xFFDC, 0xFFDC, 0xFFDD, 0xFFDD, 0xFFDD, 0xFFDE, 0xFFDE, 0xFFDF, 0xFFDF, 0xFFE0, 0xFFE0,
0xFFE0, 0xFFE1, 0xFFE1, 0xFFE1, 0xFFE2, 0xFFE2, 0xFFE3, 0xFFE3, 0xFFE3, 0xFFE4, 0xFFE4, 0xFFE4, 0xFFE5, 0xFFE5, 0xFFE5, 0xFFE6, 0xFFE6, 0xFFE6, 0xFFE7, 0xFFE7, 0xFFE7, 0xFFE8, 0xFFE8, 0xFFE8, 0xFFE9, 0xFFE9, 0xFFE9, 0xFFEA, 0xFFEA,
0xFFEA, 0xFFEB, 0xFFEB, 0xFFEB, 0xFFEC, 0xFFEC, 0xFFEC, 0xFFEC, 0xFFED, 0xFFED, 0xFFED, 0xFFEE, 0xFFEE, 0xFFEE, 0xFFEE, 0xFFEF, 0xFFEF, 0xFFEF, 0xFFEF, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFF1, 0xFFF1, 0xFFF1, 0xFFF1, 0xFFF2, 0xFFF2,
0xFFF2, 0xFFF2, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF4, 0xFFF4, 0xFFF4, 0xFFF4, 0xFFF5
// (sint16)(200.0 * log10(sin((M_PI / 4.0) + (((double)i - 63.5) * M_PI / 730.0))))
-61, -61, -60, -59, -59, -58, -58, -57, -56, -56, -55, -55, -54, -53, -53, -52, -52, -51, -50, -50, -49, -49, -48, -48, -47, -47, -46, -46, -45,
-45, -44, -44, -43, -43, -42, -42, -41, -41, -40, -40, -39, -39, -38, -38, -38, -37, -37, -36, -36, -35, -35, -35, -34, -34, -33, -33, -32, -32,
-32, -31, -31, -31, -30, -30, -29, -29, -29, -28, -28, -28, -27, -27, -27, -26, -26, -26, -25, -25, -25, -24, -24, -24, -23, -23, -23, -22, -22,
-22, -21, -21, -21, -20, -20, -20, -20, -19, -19, -19, -18, -18, -18, -18, -17, -17, -17, -17, -16, -16, -16, -16, -15, -15, -15, -15, -14, -14,
-14, -14, -13, -13, -13, -13, -13, -12, -12, -12, -12, -11
};
} g_snd_user_data{};
@ -215,15 +220,13 @@ namespace snd_user
channel->tv_mode = 0;
channel->tv_control.pan = 0x40;
channel->tv_control.span = 0x7F;
channel->tv_control.pan = 64;
channel->tv_control.span = 127;
channel->tv_control.fader = 0;
channel->tv_control.lfe = -960;
// channel->tv_control.lfe = -960; seems to accidentally set rmt[0] lfe instead of TV? -> Investigate
for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i)
{
channel->tv_control.aux[i] = -960;
}
for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i)
{
@ -308,17 +311,41 @@ namespace snd_user
if (device_type == AX_DEV_TV)
{
const uint32 sound_mode = g_snd_user_data.device_info.tv_sound_mode;
if (sound_mode == 3)
const auto soundMode = g_snd_user_data.device_info.tv_sound_mode;
if (soundMode == MixSoundMode::SurroundDPL2)
{
channels[0] = g_snd_user_data.pan_values_low[control->pan];
channels[1] = g_snd_user_data.pan_values_low[pandiff];
channels[2] = g_snd_user_data.pan_values_high[pandiff];
channels[3] = g_snd_user_data.pan_values_high[control->pan];
channels[4] = g_snd_user_data.pan_values_high[spandiff];
channels[5] = g_snd_user_data.pan_values_high[control->span];
channels[4] = g_snd_user_data.pan_values_low[spandiff];
channels[5] = g_snd_user_data.pan_values_low[control->span];
}
else if (sound_mode != 4)
else if (soundMode == MixSoundMode::Surround6CH)
{
uint32 pan = control->pan * 2;
pan = std::min<uint32>(pan, 0x7F);
channels[0] = g_snd_user_data.pan_values[pan] + g_snd_user_data.pan_values[spandiff];
uint32 span = pandiff * 2;
if (span >= 0x7E) // 0x7E clamps to 0x7F
span = 0x7F;
channels[1] = g_snd_user_data.pan_values[span] + g_snd_user_data.pan_values[spandiff];
uint32 surroundPan;
if (control->pan < 64)
surroundPan = (64 - control->pan) * 2;
else
surroundPan = (control->pan - 64) * 2;
surroundPan = std::min<uint32>(surroundPan, 0x7F);
channels[2] = g_snd_user_data.pan_values[control->span] + g_snd_user_data.pan_values[control->pan];
channels[3] = g_snd_user_data.pan_values[control->span] + g_snd_user_data.pan_values[pandiff];
channels[4] = g_snd_user_data.pan_values[surroundPan] + g_snd_user_data.pan_values[spandiff];
channels[5] = 0;
}
else // stereo fallback
{
channels[0] = g_snd_user_data.pan_values[control->pan];
channels[1] = g_snd_user_data.pan_values[pandiff];
@ -327,22 +354,6 @@ namespace snd_user
channels[4] = g_snd_user_data.pan_values[spandiff];
channels[5] = g_snd_user_data.pan_values[control->span];
}
else
{
uint32 pan = 0x7F;
if (((uint32)control->pan >> 1) < 0x7F)
pan = (uint32)control->pan >> 1;
channels[0] = g_snd_user_data.pan_values[pan] + g_snd_user_data.pan_values[spandiff];
uint32 span = 0x7F;
if (((uint32)pandiff >> 1) < 0x7E)
span = (uint32)pandiff >> 1;
channels[1] = g_snd_user_data.pan_values[span] + g_snd_user_data.pan_values[spandiff];
// TODO
}
}
else if (device_type == AX_DEV_DRC)
{
@ -350,19 +361,31 @@ namespace snd_user
}
}
sint16 __MIXTranslateVolume(sint16 input)
uint16 __MIXTranslateVolume(sint32 input)
{
if (input <= -904)
return 0;
if (input > 0x3C)
return -156;
return (sint16)g_snd_user_data.volume[input + 903];
if (input >= 0x3C)
return 0xFF64;
return g_snd_user_data.volume[input + 904];
}
void AXFXInitDefaultHooks();
static void CheckVoice(AXVPB* voice)
{
if (!voice)
{
cemuLog_log(LogType::APIErrors, "Voice 0x{:08x} nullptr passed to MIX function", MEMPTR<void>(voice).GetMPTR());
cemu_assert(false);
}
if (voice->index < 0 || voice->index >= AX_MAX_VOICES)
{
cemuLog_log(LogType::APIErrors, "Voice 0x{:08x} passed to MIX function has invalid index (0x{:08x})", MEMPTR<void>(voice).GetMPTR(), (uint32)voice->index);
cemu_assert(false);
}
}
void MIXInit()
{
cemuLog_log(LogType::SoundAPI, "MIXInit()");
@ -377,33 +400,103 @@ namespace snd_user
}
g_snd_user_data.initialized = true;
g_snd_user_data.device_info.tv_sound_mode = 1;
g_snd_user_data.device_info.drc_sound_mode = 1;
g_snd_user_data.device_info.rmt_sound_mode = 0;
g_snd_user_data.device_info.tv_sound_mode = MixSoundMode::Stereo;
g_snd_user_data.device_info.drc_sound_mode = MixSoundMode::Stereo;
g_snd_user_data.device_info.rmt_sound_mode = MixSoundMode::Mono;
AXFXInitDefaultHooks();
}
void MIXSetSoundMode(uint32 sound_mode)
void MIXSetSoundMode(MixSoundMode soundMode)
{
cemuLog_log(LogType::SoundAPI, "MIXSetSoundMode(0x{:x})", sound_mode);
cemuLog_log(LogType::SoundAPI, "MIXSetSoundMode(0x{:x})", (uint32)soundMode);
if (soundMode == MixSoundMode::Surround || soundMode == MixSoundMode::SurroundDPL2)
soundMode = MixSoundMode::Stereo;
if (sound_mode >= 2)
sound_mode = 1;
g_snd_user_data.device_info.tv_sound_mode = sound_mode;
if (soundMode != MixSoundMode::Mono && soundMode != MixSoundMode::Stereo)
{
cemuLog_log(LogType::APIErrors, "MIXSetSoundMode() invalid sound mode");
soundMode = MixSoundMode::Stereo;
}
g_snd_user_data.device_info.tv_sound_mode = soundMode;
}
uint32 MIXGetSoundMode()
MixSoundMode MIXGetSoundMode()
{
cemuLog_log(LogType::SoundAPI, "MIXGetSoundMode()");
return g_snd_user_data.device_info.tv_sound_mode;
}
void _MIXUpdateTV(MixChannel* channel, sint32 index)
void MIXSetDeviceSoundMode(uint32 device, MixSoundMode mode)
{
assert(index == 0);
cemuLog_log(LogType::SoundAPI, "MIXSetDeviceSoundMode(0x{:x}, {:})", device, (uint32)mode);
cemu_assert_debug(device < AX_DEV_COUNT);
bool is_tv_device = false;
bool is_drc_device = false;
if (device == AX_DEV_TV)
{
if (mode != MixSoundMode::Mono && mode != MixSoundMode::Stereo && mode != MixSoundMode::Surround && mode != MixSoundMode::SurroundDPL2 && mode != MixSoundMode::Surround6CH)
{
cemuLog_log(LogType::APIErrors, "MIXSetDeviceSoundMode(): Invalid mode");
mode = MixSoundMode::Stereo;
}
g_snd_user_data.device_info.tv_sound_mode = mode;
is_tv_device = true;
}
else if (device == AX_DEV_DRC)
{
if (mode != MixSoundMode::Mono && mode != MixSoundMode::Stereo && mode != MixSoundMode::Surround)
{
cemuLog_log(LogType::APIErrors, "MIXSetDeviceSoundMode(): Invalid mode");
mode = MixSoundMode::Stereo;
}
g_snd_user_data.device_info.drc_sound_mode = mode;
is_drc_device = true;
}
else if (device == AX_DEV_RMT)
{
if (mode != MixSoundMode::Mono)
{
cemuLog_log(LogType::APIErrors, "MIXSetDeviceSoundMode(): Invalid mode");
mode = MixSoundMode::Mono;
}
g_snd_user_data.device_info.rmt_sound_mode = mode;
}
else
{
cemuLog_log(LogType::SoundAPI, "ERROR: MIXSetDeviceSoundMode(0x{:x}, 0x{:x}) -> wrong device", device, mode);
}
for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i)
{
auto& channel = g_snd_user_data.mix_channel[i];
if (!channel.voice)
continue;
const auto voice = channel.voice.GetPtr();
AXVoiceBegin(voice);
if (is_tv_device)
{
channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels);
}
if (is_drc_device)
{
for (sint32 j = 0; j < AX_MAX_NUM_DRC; ++j)
{
channel.drc_mode[j] |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXControl_SetDevicePan(&channel.drc_control[j], AX_DEV_DRC, channel.drc_channels[j]);
}
}
AXVoiceEnd(voice);
}
}
void _MIXUpdateTV(MixChannel* channel)
{
bool updated_volume = false;
if ((channel->tv_mode & AX_UPDATE_MODE_80000000) != 0)
{
@ -426,7 +519,7 @@ namespace snd_user
}
else
{
if (g_snd_user_data.device_info.tv_sound_mode == 0)
if (g_snd_user_data.device_info.tv_sound_mode == MixSoundMode::Mono)
{
sint32 chan4 = channel->tv_channels[4];
if (chan4 < -0x78)
@ -464,7 +557,7 @@ namespace snd_user
channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME;
channel->tv_mode |= AX_UPDATE_MODE_80000000;
}
else if (g_snd_user_data.device_info.tv_sound_mode < 3)
else if (g_snd_user_data.device_info.tv_sound_mode == MixSoundMode::Stereo || g_snd_user_data.device_info.tv_sound_mode == MixSoundMode::Surround)
{
sint32 chan4 = channel->tv_channels[4];
if (chan4 < -0x78)
@ -504,13 +597,70 @@ namespace snd_user
channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME;
channel->tv_mode |= AX_UPDATE_MODE_80000000;
}
else if (g_snd_user_data.device_info.tv_sound_mode == 3)
else if (g_snd_user_data.device_info.tv_sound_mode == MixSoundMode::SurroundDPL2)
{
// TODO
const sint32 fader = channel->tv_control.fader;
const sint32 chan0 = channel->tv_channels[0];
const sint32 chan1 = channel->tv_channels[1];
const sint32 chan2 = channel->tv_channels[2];
const sint32 chan3 = channel->tv_channels[3];
const sint32 chan4 = channel->tv_channels[4];
const sint32 chan5 = channel->tv_channels[5];
channel->tv_volume[0][0].volume_target = __MIXTranslateVolume(fader + chan4 + chan0);
channel->tv_volume[0][1].volume_target = __MIXTranslateVolume(fader + chan4 + chan1);
channel->tv_volume[0][2].volume_target = __MIXTranslateVolume(fader + chan5);
channel->tv_volume[0][3].volume_target = __MIXTranslateVolume(fader + chan3 + chan5);
channel->tv_volume[0][4].volume_target = 0;
channel->tv_volume[0][0].volume_target = 0;
for (int i = 0; i < 2; ++i)
{
const sint32 aux = channel->tv_control.aux[i];
const sint32 base = ((channel->tv_mode & (1 << i)) != 0) ? aux : fader + aux;
channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(base + chan4 + chan0);
channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(base + chan4 + chan1);
channel->tv_volume[1 + i][2].volume_target = __MIXTranslateVolume(base + chan5 + chan2);
channel->tv_volume[1 + i][3].volume_target = __MIXTranslateVolume(base + chan5 + chan3);
channel->tv_volume[1 + i][0].volume_target = 0;
channel->tv_volume[1 + i][4].volume_target = 0;
}
channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME;
channel->tv_mode |= AX_UPDATE_MODE_80000000;
}
else if (g_snd_user_data.device_info.tv_sound_mode == 4)
else if (g_snd_user_data.device_info.tv_sound_mode == MixSoundMode::Surround6CH)
{
// TODO
const sint32 fader = channel->tv_control.fader;
const sint32 lfe = channel->tv_control.lfe;
const sint32 chan0 = channel->tv_channels[0];
const sint32 chan1 = channel->tv_channels[1];
const sint32 chan2 = channel->tv_channels[2];
const sint32 chan3 = channel->tv_channels[3];
const sint32 chan4 = channel->tv_channels[4];
channel->tv_volume[0][0].volume_target = __MIXTranslateVolume(fader + chan0);
channel->tv_volume[0][1].volume_target = __MIXTranslateVolume(fader + chan1);
channel->tv_volume[0][4].volume_target = __MIXTranslateVolume(fader + chan4);
channel->tv_volume[0][2].volume_target = __MIXTranslateVolume(fader + chan2);
channel->tv_volume[0][3].volume_target = __MIXTranslateVolume(fader + chan3);
channel->tv_volume[0][5].volume_target = __MIXTranslateVolume(fader + lfe);
for (int i = 0; i < 3; ++i)
{
const sint32 aux = channel->tv_control.aux[i];
const sint32 base = ((channel->tv_mode & (1 << i)) != 0) ? aux : fader + aux;
channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(base + chan0);
channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(base + chan1);
channel->tv_volume[1 + i][4].volume_target = __MIXTranslateVolume(base + chan4);
channel->tv_volume[1 + i][2].volume_target = __MIXTranslateVolume(base + chan2);
channel->tv_volume[1 + i][3].volume_target = __MIXTranslateVolume(base + chan3);
channel->tv_volume[0][5].volume_target = __MIXTranslateVolume(base + lfe);
}
channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME;
channel->tv_mode |= AX_UPDATE_MODE_80000000;
}
else
{
@ -520,18 +670,19 @@ namespace snd_user
}
AXCHMIX2 mix[AX_TV_CHANNEL_COUNT][AX_MAX_NUM_BUS];
sint32 inputSamples = AXGetInputSamplesPerFrame();
for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i)
{
for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j)
{
const sint16 target = channel->tv_volume[i][j].volume_target;
const sint16 volume = channel->tv_volume[i][j].volume;
const uint16 target = channel->tv_volume[i][j].volume_target;
const uint16 volume = channel->tv_volume[i][j].volume;
mix[j][i].vol = volume;
mix[j][i].delta = (target - volume) / 96; // 32000HZ SAMPLES_3MS
mix[j][i].delta = (sint16)(target - volume) / inputSamples;
}
}
AXSetVoiceDeviceMix(channel->voice.GetPtr(), AX_DEV_TV, index, (snd_core::AXCHMIX_DEPR*)&mix[0][0]);
AXSetVoiceDeviceMix(channel->voice.GetPtr(), AX_DEV_TV, 0, (snd_core::AXCHMIX_DEPR*)&mix[0][0]);
}
void _MIXUpdateDRC(MixChannel* channel, sint32 index)
@ -544,15 +695,14 @@ namespace snd_user
// todo
}
void MIXInitChannel(AXVPB* voice, uint16 mode, uint16 input, uint16 aux1, uint16 aux2, uint16 aux3, uint16 pan, uint16 span, uint16 fader)
void MIXInitChannel(AXVPB* voice, uint32 flags, sint16 input, sint16 aux1, sint16 aux2, sint16 aux3, sint16 pan, sint16 span, sint16 fader)
{
cemuLog_log(LogType::SoundAPI, "MIXInitChannel(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), mode, input, aux1, aux2, aux3, pan, span, fader);
cemu_assert_debug(voice);
cemuLog_log(LogType::SoundAPI, "MIXInitChannel(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), flags, input, aux1, aux2, aux3, pan, span, fader);
CheckVoice(voice);
AXVoiceBegin(voice);
MIXAssignChannel(voice);
MIXInitInputControl(voice, input, mode);
MIXInitInputControl(voice, input, flags);
const uint32 index = voice->index;
auto& channel = g_snd_user_data.mix_channel[index];
@ -564,49 +714,51 @@ namespace snd_user
channel.tv_control.pan = pan;
channel.tv_control.span = span;
channel.tv_control.fader = fader;
// channel.tv_control.lfe = lfe; // 0x1A -> not set?
// .lfe is not updated
channel.tv_mode = AX_UPDATE_MODE_40000007 & mode;
channel.tv_mode = flags & AX_UPDATE_MODE_40000007;
_MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels);
channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXUpdateTV(&channel, 0);
_MIXUpdateTV(&channel);
AXVoiceEnd(voice);
}
void MIXReleaseChannel(AXVPB* voice)
{
CheckVoice(voice);
AXVoiceBegin(voice);
MixChannel& channel = g_snd_user_data.mix_channel[voice->index];
channel.voice = nullptr;
AXVoiceEnd(voice);
}
void MIXAssignChannel(AXVPB* voice)
{
cemuLog_log(LogType::SoundAPI, "MIXAssignChannel(0x{:x})", MEMPTR(voice).GetMPTR());
cemu_assert_debug(voice);
CheckVoice(voice);
AXVoiceBegin(voice);
const uint32 voice_index = voice->index;
auto channel = &g_snd_user_data.mix_channel[voice_index];
MIXResetChannelData(channel);
channel->voice = voice;
MixChannel& channel = g_snd_user_data.mix_channel[voice->index];
MIXResetChannelData(&channel);
channel.voice = voice;
AXVoiceEnd(voice);
}
void MIXDRCInitChannel(AXVPB* voice, uint16 mode, uint16 vol1, uint16 vol2, uint16 vol3)
void MIXDRCInitChannel(AXVPB* voice, uint32 flags, uint16 aux, uint16 pan, uint16 fader)
{
cemuLog_log(LogType::SoundAPI, "MIXDRCInitChannel(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), mode, vol1, vol2, vol3);
cemu_assert_debug(voice);
cemuLog_log(LogType::SoundAPI, "MIXDRCInitChannel(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), flags, aux, pan, fader);
CheckVoice(voice);
AXVoiceBegin(voice);
const uint32 index = voice->index;
auto& channel = g_snd_user_data.mix_channel[index];
MixChannel& channel = g_snd_user_data.mix_channel[voice->index];
_MIXChannelResetDRC(&channel, 0);
channel.drc_volume[1][1][1].volume = vol1;
channel.drc_volume[1][1][2].volume_target = vol2;
channel.drc_volume[1][1][3].volume_target = vol3;
channel.drc_control[0].aux[0] = aux;
channel.drc_control[0].pan = pan;
channel.drc_control[0].fader = fader;
channel.drc_mode[0] = AX_UPDATE_MODE_40000007 & mode;
channel.drc_mode[0] = flags & AX_UPDATE_MODE_40000007;
_MIXControl_SetDevicePan(&channel.drc_control[0], AX_DEV_DRC, &channel.drc_channels[0][0]);
channel.drc_mode[0] |= AX_UPDATE_MODE_40000000_VOLUME;
@ -618,13 +770,10 @@ namespace snd_user
void MIXSetInput(AXVPB* voice, uint16 input)
{
cemuLog_log(LogType::SoundAPI, "MIXSetInput(0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), input);
const uint32 voice_index = voice->index;
const auto channel = &g_snd_user_data.mix_channel[voice_index];
channel->input_level = input;
channel->update_mode |= AX_UPDATE_MODE_10000000;
CheckVoice(voice);
MixChannel& channel = g_snd_user_data.mix_channel[voice->index];
channel.input_level = input;
channel.update_mode |= AX_UPDATE_MODE_10000000_INPUT_LEVEL;
}
void MIXUpdateSettings()
@ -646,7 +795,6 @@ namespace snd_user
const auto voice = channel.voice.GetPtr();
AXVoiceBegin(voice);
bool volume_updated = false;
if ((channel.update_mode & AX_UPDATE_MODE_20000000) != 0)
{
@ -655,45 +803,40 @@ namespace snd_user
volume_updated = true;
}
if ((channel.update_mode & AX_UPDATE_MODE_10000000) == 0)
if ((channel.update_mode & AX_UPDATE_MODE_10000000_INPUT_LEVEL) == 0)
{
if (volume_updated)
{
AXPBVE ve;
ve.currentVolume = channel.volume.volume;
ve.currentDelta = (channel.volume.volume_target - channel.volume.volume) / 96;
ve.currentDelta = (sint32(channel.volume.volume_target) - sint32(channel.volume.volume)) / 96;
AXSetVoiceVe(voice, &ve);
}
}
else
{
sint32 volume = 0;
uint16 volume = 0;
if ((channel.update_mode & 8) == 0)
volume = __MIXTranslateVolume(channel.input_level);
channel.volume.volume_target = volume;
channel.update_mode &= ~AX_UPDATE_MODE_10000000;
channel.update_mode &= ~AX_UPDATE_MODE_10000000_INPUT_LEVEL;
channel.update_mode |= AX_UPDATE_MODE_20000000;
AXPBVE ve;
ve.currentVolume = channel.volume.volume;
ve.currentDelta = (channel.volume.volume_target - channel.volume.volume) / 96;
ve.currentDelta = (sint32(channel.volume.volume_target) - sint32(channel.volume.volume)) / 96;
AXSetVoiceVe(voice, &ve);
}
_MIXUpdateTV(&channel, 0);
_MIXUpdateTV(&channel);
for (int i = 0; i < 2; ++i)
_MIXUpdateDRC(&channel, i);
// TODO remote mix
AXCHMIX2 mix[4];
for (int j = 0; j < 4; ++j)
{
AXSetVoiceDeviceMix(voice, AX_DEV_RMT, i, (snd_core::AXCHMIX_DEPR*)mix);
}
for (int j = 0; j < AX_MAX_NUM_RMT; ++j)
_MIXUpdateRmt(&channel, j);
AXVoiceEnd(voice);
}
@ -701,110 +844,63 @@ namespace snd_user
// TODO
}
void MIXSetDeviceSoundMode(uint32 device, uint32 mode)
void MIXInitDeviceControl(AXVPB* voice, uint32 device, uint32 deviceSubIndex, MixControl* control, uint32 mode)
{
cemuLog_log(LogType::SoundAPI, "MIXSetDeviceSoundMode(0x{:x}, 0x{:x})", device, mode);
// note - lfe is not copied from control
cemuLog_log(LogType::SoundAPI, "MIXInitDeviceControl(0x{:0x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x} )", MEMPTR(voice).GetMPTR(), device, deviceSubIndex, MEMPTR(control).GetMPTR(), mode);
CheckVoice(voice);
cemu_assert_debug(device < AX_DEV_COUNT);
cemu_assert_debug(mode <= 4);
bool is_tv_device = false;
bool is_drc_device = false;
if (device == AX_DEV_TV)
{
g_snd_user_data.device_info.tv_sound_mode = mode;
is_tv_device = true;
}
else if (device == AX_DEV_DRC)
{
cemu_assert_debug(mode <= 2);
g_snd_user_data.device_info.drc_sound_mode = mode;
is_drc_device = true;
}
else if (device == AX_DEV_RMT)
{
cemu_assert_debug(mode == 0);
g_snd_user_data.device_info.rmt_sound_mode = mode;
}
else
{
cemuLog_log(LogType::SoundAPI, "ERROR: MIXSetDeviceSoundMode(0x{:x}, 0x{:x}) -> wrong device", device, mode);
}
for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i)
{
auto& channel = g_snd_user_data.mix_channel[i];
if (!channel.voice)
continue;
const auto voice = channel.voice.GetPtr();
AXVoiceBegin(voice);
if (is_tv_device)
{
channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels);
}
if (is_drc_device)
{
for (sint32 j = 0; j < AX_MAX_NUM_DRC; ++j)
{
channel.drc_mode[j] |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXControl_SetDevicePan(&channel.drc_control[j], AX_DEV_DRC, channel.drc_channels[j]);
}
}
AXVoiceEnd(voice);
}
}
void MIXInitDeviceControl(AXVPB* voice, uint32 device_type, uint32 index, MixControl* control, uint32 mode)
{
cemuLog_log(LogType::SoundAPI, "MIXInitDeviceControl(0x{:0x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x} )", MEMPTR(voice).GetMPTR(), device_type, index, MEMPTR(control).GetMPTR(), mode);
cemu_assert_debug(device_type < AX_DEV_COUNT);
cemu_assert_debug(voice);
cemu_assert_debug(control);
AXVoiceBegin(voice);
const uint32 voice_index = voice->index;
auto& channel = g_snd_user_data.mix_channel[voice_index];
MixChannel& channel = g_snd_user_data.mix_channel[voice->index];
channel.voice = voice;
if (device_type == AX_DEV_TV)
if (device == AX_DEV_TV)
{
cemu_assert_debug(index == 0);
_MIXChannelResetTV(&channel, index);
memcpy(&channel.tv_control, control, sizeof(MixControl));
_MIXControl_SetDevicePan(&channel.tv_control, device_type, channel.tv_channels);
channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXUpdateTV(&channel, index);
cemu_assert_debug(deviceSubIndex == 0);
_MIXChannelResetTV(&channel, deviceSubIndex);
channel.tv_control.pan = control->pan;
channel.tv_control.span = control->span;
channel.tv_control.fader = control->fader;
for (sint32 i=0; i<AX_AUX_BUS_COUNT; i++)
channel.tv_control.aux[i] = control->aux[i];
_MIXControl_SetDevicePan(&channel.tv_control, device, channel.tv_channels);
channel.tv_mode = (mode & 0xF) | AX_UPDATE_MODE_40000000_VOLUME;
_MIXUpdateTV(&channel);
}
else if (device_type == AX_DEV_DRC)
else if (device == AX_DEV_DRC)
{
cemu_assert_debug(index < 2);
_MIXChannelResetDRC(&channel, index);
cemu_assert_debug(deviceSubIndex < 2);
_MIXChannelResetDRC(&channel, deviceSubIndex);
memcpy(&channel.drc_control[index], control, sizeof(MixControl));
_MIXControl_SetDevicePan(&channel.drc_control[index], device_type, channel.drc_channels[index]);
channel.drc_control[deviceSubIndex].pan = control->pan;
channel.drc_control[deviceSubIndex].span = control->span;
channel.drc_control[deviceSubIndex].fader = control->fader;
for (sint32 i=0; i<AX_AUX_BUS_COUNT; i++)
channel.drc_control[deviceSubIndex].aux[i] = control->aux[i];
_MIXControl_SetDevicePan(&channel.drc_control[deviceSubIndex], device, channel.drc_channels[deviceSubIndex]);
channel.drc_mode[index] |= AX_UPDATE_MODE_40000000_VOLUME;
_MIXUpdateDRC(&channel, index);
channel.drc_mode[deviceSubIndex] = (mode & 0xF) | AX_UPDATE_MODE_40000000_VOLUME;
_MIXUpdateDRC(&channel, deviceSubIndex);
}
else if (device_type == AX_DEV_RMT)
else if (device == AX_DEV_RMT)
{
cemu_assert_debug(index < 4);
_MIXChannelResetRmt(&channel, index);
cemu_assert_debug(deviceSubIndex < 4);
_MIXChannelResetRmt(&channel, deviceSubIndex);
memcpy(&channel.rmt_control[index], control, sizeof(MixControl));
_MIXControl_SetDevicePan(&channel.rmt_control[index], device_type, channel.rmt_channels[index]);
channel.rmt_control[deviceSubIndex].pan = control->pan;
channel.rmt_control[deviceSubIndex].span = control->span;
channel.rmt_control[deviceSubIndex].fader = control->fader;
for (sint32 i=0; i<AX_AUX_BUS_COUNT; i++)
channel.rmt_control[deviceSubIndex].aux[i] = control->aux[i];
channel.rmt_mode[index] = mode & 0xf;
_MIXUpdateRmt(&channel, index);
_MIXControl_SetDevicePan(&channel.rmt_control[deviceSubIndex], device, channel.rmt_channels[deviceSubIndex]);
channel.rmt_mode[deviceSubIndex] = (mode & 0xF);
_MIXUpdateRmt(&channel, deviceSubIndex);
}
AXVoiceEnd(voice);
@ -814,17 +910,11 @@ namespace snd_user
void MIXInitInputControl(AXVPB* voice, uint16 input, uint32 mode)
{
cemuLog_log(LogType::SoundAPI, "MIXInitInputControl(0x{:x}, 0x{:x}, 0x{:x} )", MEMPTR(voice).GetMPTR(), input, mode);
cemu_assert_debug(voice);
CheckVoice(voice);
AXVoiceBegin(voice);
const uint32 voice_index = voice->index;
auto& channel = g_snd_user_data.mix_channel[voice_index];
mode &= 8;
mode |= AX_UPDATE_MODE_10000000;
channel.update_mode = mode;
MixChannel& channel = g_snd_user_data.mix_channel[voice->index];
channel.update_mode = (mode & AX_UPDATE_MODE_8) | AX_UPDATE_MODE_10000000_INPUT_LEVEL;
channel.input_level = input;
AXVoiceEnd(voice);
@ -832,21 +922,18 @@ namespace snd_user
void MIXSetDeviceFader(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newFader)
{
// not well tested
CheckVoice(vpb);
cemu_assert(device < AX_DEV_COUNT);
MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index];
AXVoiceBegin(vpb);
MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex);
MixMode& mixMode = mixChannel.GetMode(device, deviceIndex);
if (mixControl.fader == newFader)
{
AXVoiceEnd(vpb);
return;
}
mixControl.fader = newFader;
mixMode |= AX_UPDATE_MODE_40000000_VOLUME;
AXVoiceEnd(vpb);
@ -854,25 +941,92 @@ namespace snd_user
void MIXSetDevicePan(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newPan)
{
// not well tested
CheckVoice(vpb);
cemu_assert(device < AX_DEV_COUNT);
MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index];
AXVoiceBegin(vpb);
MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex);
MixMode& mixMode = mixChannel.GetMode(device, deviceIndex);
sint16* deviceChannels = mixChannel.GetChannels(device, deviceIndex);
if (mixControl.pan == newPan)
{
AXVoiceEnd(vpb);
return;
}
mixControl.pan = newPan;
_MIXControl_SetDevicePan(&mixControl, device, deviceChannels);
mixMode |= AX_UPDATE_MODE_40000000_VOLUME;
AXVoiceEnd(vpb);
}
if (mixControl.fader == newPan)
void MIXSetDeviceSPan(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newSPan)
{
CheckVoice(vpb);
cemu_assert(device < AX_DEV_COUNT);
MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index];
AXVoiceBegin(vpb);
MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex);
MixMode& mixMode = mixChannel.GetMode(device, deviceIndex);
sint16* deviceChannels = mixChannel.GetChannels(device, deviceIndex);
if (mixControl.span == newSPan)
{
AXVoiceEnd(vpb);
return;
}
mixControl.span = newSPan;
_MIXControl_SetDevicePan(&mixControl, device, deviceChannels);
mixMode |= AX_UPDATE_MODE_40000000_VOLUME;
AXVoiceEnd(vpb);
}
void MIXSetDeviceLFE(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newLFE)
{
CheckVoice(vpb);
cemu_assert(device < AX_DEV_COUNT);
MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index];
AXVoiceBegin(vpb);
if (device != AX_DEV_TV)
{
cemuLog_log(LogType::APIErrors, "MIXSetDeviceLFE(): Device must be TV");
AXVoiceEnd(vpb);
return;
}
cemu_assert(deviceIndex == 0);
MixControl& mixControl = mixChannel.GetMixControl(device, 0);
MixMode& mixMode = mixChannel.GetMode(device, 0);
if (mixControl.lfe == newLFE)
{
AXVoiceEnd(vpb);
return;
}
mixControl.lfe = newLFE;
mixMode |= AX_UPDATE_MODE_40000000_VOLUME;
AXVoiceEnd(vpb);
}
void MIXSetDeviceAux(AXVPB* vpb, uint32 device, uint32 deviceIndex, uint32 aux, sint16 newAux)
{
CheckVoice(vpb);
cemu_assert(device < AX_DEV_COUNT);
cemu_assert(aux < AX_AUX_BUS_COUNT);
MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index];
AXVoiceBegin(vpb);
MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex);
MixMode& mixMode = mixChannel.GetMode(device, deviceIndex);
if (mixControl.aux[aux] == newAux)
{
AXVoiceEnd(vpb);
return;
}
mixControl.pan = newPan;
_MIXControl_SetDevicePan(&mixControl, device, deviceChannels);
mixControl.aux[aux] = newAux;
mixMode |= AX_UPDATE_MODE_40000000_VOLUME;
AXVoiceEnd(vpb);
}
@ -1193,6 +1347,7 @@ namespace snd_user
cafeExportRegister("snd_user", MIXSetSoundMode, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXGetSoundMode, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXInitChannel, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXReleaseChannel, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXAssignChannel, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXDRCInitChannel, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXSetInput, LogType::SoundAPI);
@ -1200,6 +1355,9 @@ namespace snd_user
cafeExportRegister("snd_user", MIXSetDeviceSoundMode, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXSetDeviceFader, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXSetDevicePan, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXSetDeviceSPan, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXSetDeviceLFE, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXSetDeviceAux, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXInitDeviceControl, LogType::SoundAPI);
cafeExportRegister("snd_user", MIXInitInputControl, LogType::SoundAPI);
@ -1237,6 +1395,7 @@ namespace snd_user
cafeExportRegister("snduser2", MIXSetSoundMode, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXGetSoundMode, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXInitChannel, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXReleaseChannel, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXAssignChannel, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXDRCInitChannel, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXSetInput, LogType::SoundAPI);
@ -1244,6 +1403,9 @@ namespace snd_user
cafeExportRegister("snduser2", MIXSetDeviceSoundMode, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXSetDeviceFader, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXSetDevicePan, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXSetDeviceSPan, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXSetDeviceLFE, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXSetDeviceAux, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXInitDeviceControl, LogType::SoundAPI);
cafeExportRegister("snduser2", MIXInitInputControl, LogType::SoundAPI);

View File

@ -7,17 +7,28 @@ namespace snd_user
{
struct MixControl;
enum class MixSoundMode : uint32
{
Mono = 0,
Stereo = 1,
Surround = 2,
SurroundDPL2 = 3,
Surround6CH = 4
};
void MIXInit();
void MIXInitInputControl(snd_core::AXVPB* voice, uint16 input, uint32 mode);
void MIXInitDeviceControl(snd_core::AXVPB* voice, uint32 device_type, uint32 index, MixControl* control, uint32 mode);
void MIXSetDeviceSoundMode(uint32 device, uint32 mode);
void MIXInitDeviceControl(snd_core::AXVPB* voice, uint32 device, uint32 deviceSubIndex, MixControl* control, uint32 mode);
void MIXSetSoundMode(MixSoundMode soundMode);
void MIXSetDeviceSoundMode(uint32 device, MixSoundMode mode);
void MIXUpdateSettings();
void MIXSetInput(snd_core::AXVPB* voice, uint16 input);
void MIXDRCInitChannel(snd_core::AXVPB* voice, uint16 mode, uint16 vol1, uint16 vol2, uint16 vol3);
void MIXDRCInitChannel(snd_core::AXVPB* voice, uint32 flags, uint16 aux, uint16 pan, uint16 fader);
void MIXAssignChannel(snd_core::AXVPB* voice);
void MIXInitChannel(snd_core::AXVPB* voice, uint16 mode, uint16 input, uint16 aux1, uint16 aux2, uint16 aux3, uint16 pan, uint16 span, uint16 fader);
uint32 MIXGetSoundMode();
void MIXSetSoundMode(uint32 sound_mode);
void MIXInitChannel(snd_core::AXVPB* voice, uint32 flags, sint16 input, sint16 aux1, sint16 aux2, sint16 aux3, sint16 pan, sint16 span, sint16 fader);
void MIXReleaseChannel(snd_core::AXVPB* voice);
MixSoundMode MIXGetSoundMode();
COSModule* GetModuleSndUser1();
COSModule* GetModuleSndUser2();

View File

@ -51,6 +51,18 @@ enum class CosCapabilityBits : uint64
All = 0xFFFFFFFFFFFFFFFFull
};
inline CosCapabilityBits operator|(CosCapabilityBits a, CosCapabilityBits b)
{
return static_cast<CosCapabilityBits>(
static_cast<uint64>(a) | static_cast<uint64>(b));
}
inline CosCapabilityBits& operator|=(CosCapabilityBits& a, CosCapabilityBits b)
{
a = a | b;
return a;
}
enum class CosCapabilityBitsFS : uint64
{
ODD_READ = (1llu << 0),

View File

@ -1,6 +1,6 @@
#pragma once
#include "nexTypes.h"
#include<mutex>
#include <mutex>
const int NEX_PROTOCOL_AUTHENTICATION = 0xA;
const int NEX_PROTOCOL_SECURE = 0xB;

View File

@ -1,101 +0,0 @@
#pragma once
#include <cstdint>
using uint64 = uint64_t;
using sint64 = int64_t;
using uint8 = uint8_t;
#if defined(_M_X64) || defined(__x86_64__)
#if defined(_MSC_VER)
#include <immintrin.h>
#pragma intrinsic(__rdtsc)
#define BARRIER_FENCE() _mm_mfence()
#define READ_TSC() __rdtsc()
#else
#include <x86intrin.h>
#define BARRIER_FENCE() __builtin_ia32_mfence()
#define READ_TSC() __rdtsc()
#endif
#elif defined(_M_ARM64) || defined(__aarch64__)
#if defined(_MSC_VER)
#include <intrin.h>
#define BARRIER_FENCE() __dmb(_ARM64_BARRIER_SY)
#define READ_TSC() _ReadStatusReg(ARM64_CNTVCT_EL0)
#else
#define BARRIER_FENCE() __asm__ __volatile__("dmb sy" : : : "memory")
inline uint64 READ_TSC() {
uint64 virtual_timer;
__asm__ __volatile__("mrs %0, cntvct_el0" : "=r" (virtual_timer));
return virtual_timer;
}
#endif
#endif
struct uint128_t {
uint64 low;
uint64 high;
};
inline uint64 portable_umul128(uint64 multiplier, uint64 multiplicand, uint64* high) {
#if defined(_MSC_VER)
#if defined(_M_X64)
return _umul128(multiplier, multiplicand, high);
#elif defined(_M_ARM64)
*high = __umulh(multiplier, multiplicand);
return multiplier * multiplicand;
#endif
#else
unsigned __int128 res = (unsigned __int128)multiplier * multiplicand;
*high = (uint64)(res >> 64);
return (uint64)res;
#endif
}
inline uint64 portable_udiv128(uint64 high, uint64 low, uint64 denominator, uint64* remainder) {
#if defined(_MSC_VER)
#if defined(_M_X64)
return _udiv128(high, low, denominator, remainder);
#else
if (high == 0) {
if (remainder) *remainder = low % denominator;
return low / denominator;
}
uint64 rem = 0;
uint64 quot = 0;
for (int i = 63; i >= 0; i--) {
rem = (rem << 1) | (high >> 63);
high <<= 1;
if (rem >= denominator) {
rem -= denominator;
quot |= (1ULL << i);
}
}
for (int i = 63; i >= 0; i--) {
rem = (rem << 1) | (low >> 63);
low <<= 1;
if (rem >= denominator) {
rem -= denominator;
quot |= (1ULL << i);
}
}
// Secure native software fallback block
uint64_t q = 0;
uint64_t r = 0;
for (int i = 127; i >= 0; i--) {
r = (r << 1) | ((i >= 64 ? high >> (i - 64) : low >> i) & 1);
if (r >= denominator) {
r -= denominator;
q |= (1ULL << (i % 64));
}
}
if (remainder) *remainder = r;
return q;
#endif
#else
unsigned __int128 dividend = ((unsigned __int128)high << 64) | low;
if (remainder) *remainder = (uint64)(dividend % denominator);
return (uint64)(dividend / denominator);
#endif
}

View File

@ -179,6 +179,12 @@ inline sint16 _swapEndianS16(sint16 v)
{
return (sint16)(((uint16)v >> 8) | ((uint16)v << 8));
}
#if defined(_M_ARM64)
inline uint64 _umul128(uint64 multiplier, uint64 multiplicand, uint64 *highProduct) {
*highProduct = __umulh(multiplier, multiplicand);
return multiplier * multiplicand;
}
#endif
#else
inline uint64 _swapEndianU64(uint64 v)
{
@ -290,6 +296,18 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor,
*remainder = (uint64)((dividend % divisor) & 0xFFFFFFFFFFFFFFFF);
return (uint64)((dividend / divisor) & 0xFFFFFFFFFFFFFFFF);
}
#elif defined(_M_ARM64)
inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor, uint64 *remainder)
{
uint64 high = highDividend;
uint64 low = lowDividend;
if (high >= divisor) {
high %= divisor;
}
unsigned __int128 dividend = (((unsigned __int128)highDividend) << 64) | lowDividend;
*remainder = (uint64)(dividend % divisor);
return (uint64)(dividend / divisor);
}
#endif
#if defined(_MSC_VER)
@ -384,12 +402,7 @@ inline uint64 __rdtsc()
inline void _mm_mfence()
{
#if defined(_MSC_VER)
__dmb(_ARM64_BARRIER_ISH); // Inner Shareable Data Memory Barrier
#else
asm volatile("" ::: "memory");
std::atomic_thread_fence(std::memory_order_seq_cst);
#endif
}
inline unsigned char _addcarry_u64(unsigned char carry, unsigned long long a, unsigned long long b, unsigned long long *result)

View File

@ -1,76 +1,67 @@
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "interface/WindowSystem.h"
#include "wxCemuConfig.h"
#include "wxgui/wxgui.h"
#include "wxgui/MainWindow.h"
#include "wxgui/GameUpdateWindow.h"
#include "wxgui/PadViewFrame.h"
#include "wxgui/windows/TextureRelationViewer/TextureRelationWindow.h"
#include "wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h"
#include "MainWindow.h"
// subwindows
#include "TitleManager.h"
#include "GeneralSettings2.h"
#include "GameUpdateWindow.h"
#include "CemuUpdateWindow.h"
#include "GraphicPacksWindow2.h"
#include "AudioDebuggerWindow.h"
#ifdef ENABLE_OPENGL
#include "wxgui/canvas/OpenGLCanvas.h"
#endif
#ifdef ENABLE_VULKAN
#include "wxgui/canvas/VulkanCanvas.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#endif
#ifdef ENABLE_METAL
#include "wxgui/canvas/MetalCanvas.h"
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#endif
#include "Cafe/OS/libs/nfc/nfc.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
#include "wxgui/debugger/DebuggerWindow2.h"
#include "util/helpers/helpers.h"
#include "config/CemuConfig.h"
#include "Cemu/DiscordPresence/DiscordPresence.h"
#include "util/ScreenSaver/ScreenSaver.h"
#include "wxgui/GeneralSettings2.h"
#include "wxgui/GraphicPacksWindow2.h"
#include "wxgui/CemuApp.h"
#include "wxgui/CemuUpdateWindow.h"
#include "wxgui/LoggingWindow.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"
#include "input/InputSettings2.h"
#include "input/HotkeySettings.h"
#include "debugger/DebuggerWindow2.h"
#include "EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"
#include "windows/PPCThreadsViewer/DebugPPCThreadsWindow.h"
#include "windows/TextureRelationViewer/TextureRelationWindow.h"
#include "Cafe/Filesystem/FST/FST.h"
#include "wxgui/TitleManager.h"
#include "wxgui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"
#include "Cafe/CafeSystem.h"
#include "util/helpers/SystemException.h"
#include "wxgui/DownloadGraphicPacksWindow.h"
#include "wxgui/GettingStartedDialog.h"
#include "wxgui/helpers/wxHelpers.h"
#include "wxgui/input/InputSettings2.h"
#include "wxgui/input/HotkeySettings.h"
#include "input/InputManager.h"
#if BOOST_OS_WINDOWS
#define exit(__c) ExitProcess(__c)
#else
#define exit(__c) _Exit(__c)
#endif
//wxgui + misc.
#include "wxgui.h"
#include "wxCemuConfig.h"
#include "interface/WindowSystem.h"
#include "wxHelper.h"
#include "helpers/wxHelpers.h"
#include "PadViewFrame.h"
#if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD
#include "resource/embedded/resources.h"
#endif
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
#include "wxgui/helpers/wxWayland.h"
#endif
// settings
#include "config/CemuConfig.h"
#include "config/LaunchSettings.h"
#include "config/ActiveSettings.h"
//GameMode support
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
#include "gamemode_client.h"
#endif
#include "Cafe/TitleList/TitleInfo.h"
// External functionality headers
#include "input/InputManager.h"
#include "Cafe/TitleList/TitleList.h"
#include "wxHelper.h"
#include "Cemu/DiscordPresence/DiscordPresence.h"
#include "util/ScreenSaver/ScreenSaver.h"
#include "util/helpers/SystemException.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
#include <gamemode_client.h>
#endif
#if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND
#include "helpers/wxWayland.h"
#endif
// Renderer Canvasses
#ifdef ENABLE_OPENGL
#include "canvas/OpenGLCanvas.h"
#endif
#ifdef ENABLE_VULKAN
#include "canvas/VulkanCanvas.h"
#endif
#ifdef ENABLE_METAL
#include "canvas/MetalCanvas.h"
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#endif
//Cafe libs
#include "Cafe/OS/libs/nfc/nfc.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h" // For renderer API checks
extern WindowSystem::WindowInfo g_window_info;
extern std::shared_mutex g_mutex;
@ -557,8 +548,7 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false;
}
else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::MENU ||
initiatedBy == wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE)
else
{
wxString t = _("Unable to launch game\nPath:\n");
t.append(_pathToUtf8(launchPath));
@ -575,13 +565,6 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false;
}
else
{
wxString t = _("Unable to launch game\nPath:\n");
t.append(_pathToUtf8(launchPath));
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return false;
}
}
if(launchTitle.IsValid())

View File

@ -119,6 +119,7 @@ public:
void UpdateVSyncState()
{
int configValue = GetConfig().vsync.GetValue();
configValue = configValue > 0 ? 1 : 0;
if(m_activeVSyncState != configValue)
{
#if BOOST_OS_WINDOWS
@ -175,4 +176,4 @@ bool GLCanvasManager::MakeCurrent(bool padView)
return false;
m_glContext->SetCurrent(*canvas);
return true;
}
}

View File

@ -12,6 +12,14 @@
#define OFFSET_ADDRESS_RELATIVE (90)
#define OFFSET_MEMORY (450)
enum {
ID_WRITE_U8 = wxID_HIGHEST + 1,
ID_WRITE_U16,
ID_WRITE_U32,
ID_WRITE_FLOAT,
ID_WRITE_STRING
};
DumpCtrl::DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style)
: TextList(parent, id, pos, size, style)
{
@ -28,6 +36,8 @@ DumpCtrl::DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, c
m_memoryRegion.size = 0x1000;
Init();
}
Bind(wxEVT_MENU, &DumpCtrl::OnMenuSelected, this);
}
void DumpCtrl::Init()
@ -163,31 +173,36 @@ void DumpCtrl::OnMouseMove(const wxPoint& start_position, uint32 line)
position.x -= OFFSET_MEMORY;
}
uint32 DumpCtrl::PositionToAddress(const wxPoint& position, uint32 line)
{
wxPoint pos = position;
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
return MPTR_NULL;
pos.x -= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE;
if (pos.x > OFFSET_MEMORY)
return MPTR_NULL;
const uint32 byteIndex = (pos.x / m_char_width) / 3;
return LineToOffset(line) + byteIndex;
}
void DumpCtrl::OnMouseDClick(const wxPoint& position, uint32 line)
{
wxPoint pos = position;
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
uint32 address = PositionToAddress(position, line);
if (address == MPTR_NULL)
return;
pos.x -= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE;
if(pos.x <= OFFSET_MEMORY)
if (!memory_isAddressRangeAccessible(address, 1))
return;
if (WriteNumericDialog<uint8>(address))
{
const uint32 byte_index = (pos.x / m_char_width) / 3;
const uint32 offset = LineToOffset(line) + byte_index;
if (!memory_isAddressRangeAccessible(offset, 1))
return;
const uint8 value = memory_readU8(offset);
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set byte at address %08x"), offset), wxString::Format("%02x", value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const uint8 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
memory_writeU8(offset, new_value);
wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height);
RefreshControl(&update_rect);
}
return;
wxRect updateRect(0, line * m_line_height, GetSize().x, m_line_height);
RefreshControl(&updateRect);
}
}
@ -264,7 +279,6 @@ uint32 DumpCtrl::OffsetToLine(uint32 offset)
return (offset - m_memoryRegion.baseAddress) / 0x10;
}
void DumpCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
{
switch (key_code)
@ -284,3 +298,144 @@ wxSize DumpCtrl::DoGetBestSize() const
{
return TextList::DoGetBestSize();
}
void DumpCtrl::OnContextMenu(const wxPoint& position, uint32 line)
{
const uint32 address = PositionToAddress(position, line);
if (!memory_isAddressRangeAccessible(address, 1))
return;
m_writerContextAddress = address;
m_writerContextLine = line;
wxMenu menu;
menu.Append(ID_WRITE_U8, _("Write Byte"));
menu.Append(ID_WRITE_U16, _("Write Int16"));
menu.Append(ID_WRITE_U32, _("Write Int32"));
menu.Append(ID_WRITE_FLOAT, _("Write Float"));
menu.Append(ID_WRITE_STRING, _("Write String"));
PopupMenu(&menu);
}
void DumpCtrl::OnMenuSelected(wxCommandEvent& event)
{
bool update = false;
switch (event.GetId())
{
case ID_WRITE_U8:
update = WriteNumericDialog<uint8>(m_writerContextAddress);
break;
case ID_WRITE_U16:
update = WriteNumericDialog<uint16>(m_writerContextAddress);
break;
case ID_WRITE_U32:
update = WriteNumericDialog<uint32>(m_writerContextAddress);
break;
case ID_WRITE_FLOAT:
update = WriteNumericDialog<float>(m_writerContextAddress);
break;
case ID_WRITE_STRING:
update = WriteString(m_writerContextAddress);
break;
}
if (update)
{
wxRect updateRect(0, m_writerContextLine * m_line_height, GetSize().x, m_line_height);
RefreshControl(&updateRect);
}
}
template <typename T>
bool DumpCtrl::WriteNumericDialog(uint32 address)
{
static_assert(
std::is_same<T, uint8>::value ||
std::is_same<T, uint16>::value ||
std::is_same<T, uint32>::value ||
std::is_same<T, float>::value,
"Unsupported type"
);
T value;
const char* dataType;
wxString label;
if constexpr (std::is_same<T, uint8>::value)
{
value = memory_readU8(address);
dataType = "byte";
label = wxString::Format("0x%02x", value);
}
else if constexpr (std::is_same<T, uint16>::value)
{
value = memory_readU16(address);
dataType = "int16";
label = wxString::Format("0x%04x", value);
}
else if constexpr (std::is_same<T, uint32>::value)
{
value = memory_readU32(address);
dataType = "int32";
label = wxString::Format("0x%08x", value);
}
else if constexpr (std::is_same<T, float>::value)
{
value = memory_readFloat(address);
dataType = "float";
label = wxString::Format("%f", value);
}
wxTextEntryDialog dialog(
this,
_("Enter a new value."),
wxString::Format(_("Write %s at address 0x%08x"), dataType, address),
label
);
if (dialog.ShowModal() != wxID_OK)
return false;
const T newValue = ConvertString<T>(dialog.GetValue().ToStdString());
if constexpr (std::is_same<T, uint8>::value)
memory_writeU8(address, newValue);
else if constexpr (std::is_same<T, uint16>::value)
memory_writeU16(address, newValue);
else if constexpr (std::is_same<T, uint32>::value)
memory_writeU32(address, newValue);
else if constexpr (std::is_same<T, float>::value)
memory_writeFloat(address, newValue);
return true;
}
bool DumpCtrl::WriteString(uint32 address)
{
wxTextEntryDialog dialog(
this,
_("Enter string"),
wxString::Format(_("Write string at address 0x%08x"), address),
""
);
if (dialog.ShowModal() != wxID_OK)
return false;
std::string text = dialog.GetValue().ToStdString();
if (text.empty())
return false;
for (size_t i = 0; i < text.size(); i++)
memory_writeU8(address + i, static_cast<uint8>(text[i]));
// null-terminator
memory_writeU8(address + text.size(), 0);
return true;
}

View File

@ -1,7 +1,6 @@
#pragma once
#include "wxgui/components/TextList.h"
class DumpCtrl : public TextList
{
public:
@ -15,11 +14,18 @@ protected:
void CenterOffset(uint32 offset);
uint32 LineToOffset(uint32 line);
uint32 OffsetToLine(uint32 offset);
uint32 PositionToAddress(const wxPoint& position, uint32 line);
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override;
void OnMouseMove(const wxPoint& position, uint32 line) override;
void OnMouseDClick(const wxPoint& position, uint32 line) override;
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
void OnContextMenu(const wxPoint& position, uint32 line) override;
void OnMenuSelected(wxCommandEvent& event);
template <typename T>
bool WriteNumericDialog(uint32 address);
bool WriteString(uint32 address);
private:
struct
{
@ -27,4 +33,6 @@ private:
uint32 size;
}m_memoryRegion;
uint32 m_lastGotoOffset{0};
uint32 m_writerContextAddress {0};
uint32 m_writerContextLine {0};
};

View File

@ -8,6 +8,7 @@
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cafe/HW/Espresso/EspressoISA.h"
#include "util/helpers/helpers.h"
enum
{
@ -231,12 +232,11 @@ void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value,
void RegisterWindow::OnUpdateView()
{
PPCSnapshot snapshot = {};
if (PPCInterpreter_t* hCPU = debugger_lockDebugSession(); hCPU)
{
snapshot = debugger_getSnapshotFromSession(hCPU);
debugger_unlockDebugSession(hCPU);
}
PPCInterpreter_t* hCPU = debugger_lockDebugSession();
if (!hCPU)
return;
PPCSnapshot snapshot = debugger_getSnapshotFromSession(hCPU);
debugger_unlockDebugSession(hCPU);
for (int i = 0; i < 32; ++i)
{
const uint32 registerValue = snapshot.gpr[i];
@ -355,15 +355,17 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
{
const uint32 register_index = id - kRegisterValueR0;
const uint32 register_value = ppcSnapshot.gpr[register_index];
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set R%d value"), register_index), wxString::Format("%08x", register_value));
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set R%d value"), register_index), wxString::Format("0x%08x", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
const uint32 value = ConvertString<uint32>(set_value_dialog.GetValue().ToStdString());
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->gpr[register_index] = new_value;
debugSession->gpr[register_index] = value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;
@ -376,12 +378,14 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP0_%d value"), register_index), wxString::Format("%lf", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
const double value = ConvertString<double>(set_value_dialog.GetValue().ToStdString());
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->fpr[register_index].fp0 = new_value;
debugSession->fpr[register_index].fp0 = value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
@ -395,12 +399,14 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP1_%d value"), register_index), wxString::Format("%lf", register_value));
if (set_value_dialog.ShowModal() == wxID_OK)
{
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
const double value = ConvertString<double>(set_value_dialog.GetValue().ToStdString());
if (debugSession = debugger_lockDebugSession(); debugSession)
{
debugSession->fpr[register_index].fp1 = new_value;
debugSession->fpr[register_index].fp1 = value;
debugger_unlockDebugSession(debugSession);
}
OnUpdateView();
}
return;

View File

@ -46,7 +46,7 @@ namespace InputAPI
break;
}
throw std::runtime_error(fmt::format("unknown input api: {}", to_underlying(type)));
throw std::runtime_error(fmt::format("unknown input api: {}", stdx::to_underlying(type)));
}
constexpr Type from_string(std::string_view str)

View File

@ -16,7 +16,7 @@ std::string_view EmulatedController::type_to_string(Type type)
case Wiimote: return "Wiimote";
}
throw std::runtime_error(fmt::format("unknown emulated controller: {}", to_underlying(type)));
throw std::runtime_error(fmt::format("unknown emulated controller: {}", stdx::to_underlying(type)));
}
EmulatedController::Type EmulatedController::type_from_string(std::string_view str)

View File

@ -135,6 +135,6 @@ struct fmt::formatter<EmulatedController::Type> : formatter<string_view> {
case EmulatedController::Type::Classic: return formatter<string_view>::format("Wii U Classic Controller Pro", ctx);
case EmulatedController::Type::Wiimote: return formatter<string_view>::format("Wiimote", ctx);
}
throw std::invalid_argument(fmt::format("invalid emulated controller type with value {}", to_underlying(v)));
throw std::invalid_argument(fmt::format("invalid emulated controller type with value {}", stdx::to_underlying(v)));
}
};

View File

@ -12,12 +12,6 @@
#include "Common/unix/fast_float.h"
#endif
template <typename TType>
constexpr auto to_underlying(TType v) noexcept
{
return static_cast<std::underlying_type_t<TType>>(v);
}
// wrapper to allow reverse iteration with range-based loops before C++20
template<typename T>
class reverse_itr {