sndcore: Improve PCM16 linear sampling + correctly clamp sndcore2 ADSR

This commit is contained in:
Exzap
2026-06-06 16:55:28 +02:00
parent c2d428e5b5
commit fc001cdf05
3 changed files with 74 additions and 146 deletions
+1
View File
@@ -78,6 +78,7 @@ namespace snd_core
const int AX_FILTER_LOWPASS_16K = 0x2;
bool isInitialized();
bool IsSndCore2();
void reset();
// AX VPB
+7 -128
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()
+66 -18
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)
@@ -501,15 +499,15 @@ namespace snd_core
else
{
// voice not playing -> sample is silent
historySamples[historyIndex] = 0;
historySamples[historyIndex] = 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)