Add all the files

This commit is contained in:
Exzap 2022-08-22 22:21:23 +02:00
parent e3db07a16a
commit d60742f52b
1445 changed files with 430238 additions and 0 deletions

View file

@ -0,0 +1,391 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit.h" // for OSThread*
#include "util/helpers/fspinlock.h"
struct PPCInterpreter_t;
namespace snd_core
{
// sndcore2 - AX init param config
const int AX_RENDERER_FREQ_32KHZ = 0;
const int AX_RENDERER_FREQ_48KHZ = 1;
const int AX_FRAMELENGTH_3MS = 0;
const int AX_SAMPLES_PER_3MS_48KHZ = (144); // 48000*3/1000
const int AX_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000
const int AX_SAMPLES_MAX = AX_SAMPLES_PER_3MS_48KHZ; // the maximum amount of samples in a single frame
const int AX_DEV_TV = 0;
const int AX_DEV_DRC = 1;
const int AX_DEV_RMT = 2;
const int AX_DEV_COUNT = 3;
const int AX_UPSAMPLE_STAGE_BEFORE_FINALMIX = 0;
const int AX_UPSAMPLE_STAGE_AFTER_FINALMIX = 1;
const int AX_PIPELINE_SINGLE = 0;
struct AXINITPARAM
{
uint32be freq; // AX_RENDERER_FREQ_*
uint32be frameLength; // AX_FRAMELENGTH_*
uint32be pipelineMode; // AX_PIPELINE_*
};
// maximum number of supported channels per device
const int AX_TV_CHANNEL_COUNT = 6;
const int AX_DRC_CHANNEL_COUNT = 4;
const int AX_RMT_CHANNEL_COUNT = 1;
const int AX_APP_FRAME_CALLBACK_MAX = 64;
const int AX_MODE_STEREO = 0;
const int AX_MODE_SURROUND = 1;
const int AX_MODE_DPL2 = 2;
const int AX_MODE_6CH = 3;
const int AX_MODE_MONO = 5;
const int AX_PRIORITY_MAX = 32;
const int AX_PRIORITY_FREE = 0;
const int AX_PRIORITY_NODROP = 31;
const int AX_PRIORITY_LOWEST = 1;
const int AX_MAX_VOICES = 96;
const int AX_AUX_BUS_COUNT = 3;
const int AX_MAX_NUM_BUS = 4;
const int AX_FORMAT_ADPCM = 0x0;
const int AX_FORMAT_PCM16 = 0xA;
const int AX_FORMAT_PCM8 = 0x19;
const int AX_LPF_OFF = 0x0;
const int AX_BIQUAD_OFF = 0x0;
const int AX_SRC_TYPE_NONE = 0x0;
const int AX_SRC_TYPE_LINEAR = 0x1;
const int AX_SRC_TYPE_LOWPASS1 = 0x2;
const int AX_SRC_TYPE_LOWPASS2 = 0x3;
const int AX_SRC_TYPE_LOWPASS3 = 0x4;
const int AX_FILTER_MODE_TAP = 0x0;
const int AX_FILTER_MODE_LINEAR = 0x1;
const int AX_FILTER_MODE_NONE = 0x2;
const int AX_FILTER_LOWPASS_8K = 0x0;
const int AX_FILTER_LOWPASS_12K = 0x1;
const int AX_FILTER_LOWPASS_16K = 0x2;
void loadExports();
bool isInitialized();
void reset();
// AX VPB
struct AXAUXCBCHANNELINFO
{
/* +0x00 */ uint32be numChannels;
/* +0x04 */ uint32be numSamples;
};
struct AXPBOFFSET_t
{
/* +0x00 | +0x34 */ uint16 format;
/* +0x02 | +0x36 */ uint16 loopFlag;
/* +0x04 | +0x38 */ uint32 loopOffset;
/* +0x08 | +0x3C */ uint32 endOffset;
/* +0x0C | +0x40 */ uint32 currentOffset;
/* +0x10 | +0x44 */ MPTR samples;
};
struct AXPBVE
{
uint16be currentVolume;
sint16be currentDelta;
};
struct AXVPBItd
{
uint8 ukn[64];
};
struct AXVPB
{
/* +0x00 */ uint32be index;
/* +0x04 */ uint32be playbackState;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ uint32be mixerSelect;
/* +0x10 */ MEMPTR<AXVPB> next;
/* +0x14 */ MEMPTR<AXVPB> prev;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint32be priority;
/* +0x20 */ uint32be callback;
/* +0x24 */ uint32be userParam;
/* +0x28 */ uint32be sync;
/* +0x2C */ uint32be depop;
/* +0x30 */ MEMPTR<AXVPBItd> itd;
/* +0x34 */ AXPBOFFSET_t offsets;
/* +0x48 */ uint32be callbackEx; // AXAcquireVoiceEx
/* +0x4C */ uint32be ukn4C_dropReason;
/* +0x50 */ float32be dspLoad;
/* +0x54 */ float32be ppcLoad;
};
struct AXPBLPF_t
{
/* +0x00 */ uint16 on;
/* +0x02 */ sint16 yn1;
/* +0x04 */ sint16 a0;
/* +0x06 */ sint16 b0;
};
struct AXPBBIQUAD_t
{
/* +0x00 */ uint16 on;
/* +0x02 */ sint16 xn1;
/* +0x04 */ sint16 xn2;
/* +0x06 */ sint16 yn1;
/* +0x08 */ sint16 yn2;
/* +0x0A */ uint16 b0;
/* +0x0C */ uint16 b1;
/* +0x0E */ uint16 b2;
/* +0x10 */ uint16 a1;
/* +0x12 */ uint16 a2;
};
struct AXRemixMatrix_t
{
/* +0x00 */ uint32be channelIn;
/* +0x04 */ uint32be channelOut;
/* +0x08 */ MEMPTR<float32be> matrix;
};
struct AXRemixMatrices_t
{
/* +0x00 */ AXRemixMatrix_t deviceEntry[3]; // tv, drc, remote
};
struct AXPBADPCM_t
{
uint16 a[16];
uint16 gain;
uint16 scale;
uint16 yn1;
uint16 yn2;
};
struct AXPBADPCMLOOP_t
{
uint16 loopScale;
uint16 loopYn1;
uint16 loopYn2;
};
struct AXPBSRC_t
{
/* +0x1B8 */ uint16 ratioHigh;
/* +0x1BA */ uint16 ratioLow;
/* +0x1BC */ uint16 currentFrac;
/* +0x1BE */ uint16 historySamples[4];
uint32 GetSrcRatio32() const
{
uint32 offset = (uint32)_swapEndianU16(ratioHigh) << 16;
return offset | (uint32)_swapEndianU16(ratioLow);
}
};
struct AXCHMIX_DEPR
{
uint16 vol;
sint16 delta;
};
struct AXCHMIX2
{
uint16be vol;
sint16be delta;
};
void AXVPB_Init();
sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex);
AXVPB* AXAcquireVoiceEx(uint32 priority, MPTR callbackEx, MPTR userParam);
void AXFreeVoice(AXVPB* vpb);
bool AXUserIsProtected();
sint32 AXUserBegin();
sint32 AXUserEnd();
bool AXVoiceProtection_IsProtectedByAnyThread(AXVPB* vpb);
bool AXVoiceProtection_IsProtectedByCurrentThread(AXVPB* vpb);
sint32 AXVoiceBegin(AXVPB* voice);
sint32 AXVoiceEnd(AXVPB* voice);
sint32 AXSetVoiceDeviceMix(AXVPB* vpb, sint32 device, sint32 deviceIndex, AXCHMIX_DEPR* mix);
void AXSetVoiceState(AXVPB* vpb, sint32 voiceState);
sint32 AXIsVoiceRunning(AXVPB* vpb);
void AXSetVoiceType(AXVPB* vpb, uint16 voiceType);
void AXSetVoiceAdpcm(AXVPB* vpb, AXPBADPCM_t* adpcm);
void AXSetVoiceAdpcmLoop(AXVPB* vpb, AXPBADPCMLOOP_t* adpcmLoop);
void AXSetVoiceSrc(AXVPB* vpb, AXPBSRC_t* src);
void AXSetVoiceSrcType(AXVPB* vpb, uint32 srcType);
sint32 AXSetVoiceSrcRatio(AXVPB* vpb, float ratio);
void AXSetVoiceVe(AXVPB* vpb, AXPBVE* ve);
void AXComputeLpfCoefs(uint32 freq, uint16be* a0, uint16be* b0);
void AXSetVoiceLpf(AXVPB* vpb, AXPBLPF_t* lpf);
void AXSetVoiceLpfCoefs(AXVPB* vpb, uint16 a0, uint16 b0);
void AXSetVoiceBiquad(AXVPB* vpb, AXPBBIQUAD_t* biquad);
void AXSetVoiceBiquadCoefs(AXVPB* vpb, uint16 b0, uint16 b1, uint16 b2, uint16 a1, uint16 a2);
void AXSetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset);
void AXGetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset);
void AXGetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, MPTR sampleBase);
void AXSetVoiceCurrentOffset(AXVPB* vpb, uint32 currentOffset);
void AXSetVoiceLoopOffset(AXVPB* vpb, uint32 loopOffset);
void AXSetVoiceEndOffset(AXVPB* vpb, uint32 endOffset);
void AXSetVoiceCurrentOffsetEx(AXVPB* vpb, uint32 currentOffset, MPTR sampleBase);
void AXSetVoiceLoopOffsetEx(AXVPB* vpb, uint32 loopOffset, MPTR sampleBase);
void AXSetVoiceEndOffsetEx(AXVPB* vpb, uint32 endOffset, MPTR sampleBase);
uint32 AXGetVoiceCurrentOffsetEx(AXVPB* vpb, MPTR sampleBase);
void AXSetVoiceLoop(AXVPB* vpb, uint16 loopState);
// AXIst
void AXIst_Init();
void AXIst_ThreadEntry(PPCInterpreter_t* hCPU);
void AXIst_QueueFrame();
void AXResetCallbacks();
bool AXIst_IsFrameBeingProcessed();
sint32 AXSetDeviceUpsampleStage(sint32 device, int upsampleStage);
sint32 AXGetDeviceUpsampleStage(sint32 device, uint32be* upsampleStage);
sint32 AXGetDeviceFinalMixCallback(sint32 device, uint32be* funcAddrPtr);
sint32 AXRegisterDeviceFinalMixCallback(sint32 device, MPTR funcAddr);
sint32 AXSetDeviceRemixMatrix(sint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, const MEMPTR<float32be>& matrix);
sint32 AXGetDeviceRemixMatrix(uint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, MEMPTR<MEMPTR<float32be>>& matrix);
sint32 AXRegisterAppFrameCallback(MPTR funcAddr);
sint32 AXDeregisterAppFrameCallback(MPTR funcAddr);
MPTR AXRegisterFrameCallback(MPTR funcAddr);
sint32 AXGetInputSamplesPerFrame();
sint32 AXGetInputSamplesPerSec();
// AXOut
void resetNumProcessedFrames();
uint32 getNumProcessedFrames();
void AXOut_Init();
sint32 AIGetSamplesPerChannel(uint32 device);
sint32 AIGetChannelCount(uint32 device);
sint16* AIGetCurrentDMABuffer(uint32 device);
void AXOut_SubmitTVFrame(sint32 frameIndex);
void AXOut_SubmitDRCFrame(sint32 frameIndex);
sint32 AXGetDeviceMode(sint32 device);
extern uint32 numProcessedFrames;
// AUX
void AXAux_Init();
void AXAux_Process();
sint32be* AXAux_GetInputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus);
sint32be* AXAux_GetOutputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus);
sint32 AXGetAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MEMPTR<uint32be> funcPtrOut, MEMPTR<uint32be> contextPtrOut);
sint32 AXRegisterAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MPTR funcMPTR, MPTR userParam);
sint32 AXSetAuxReturnVolume(uint32 device, uint32 deviceIndex, uint32 auxBus, uint16 volume);
extern uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT];
// AXMix
// mixer select constants (for AXSetDefaultMixerSelect / AXGetDefaultMixerSelect)
const int AX_MIXER_SELECT_DSP = (0);
const int AX_MIXER_SELECT_PPC = (1);
const int AX_MIXER_SELECT_BOTH = (2);
void AXMix_Init();
void AXSetDefaultMixerSelect(uint32 mixerSelect);
uint32 AXGetDefaultMixerSelect();
void AXMix_DepopVoice(struct AXVPBInternal_t* internalShadowCopy);
void AXMix_process(struct AXVPBInternal_t* internalShadowCopyHead);
extern FSpinlock __AXVoiceListSpinlock;
// AX multi voice
struct AXVPBMULTI
{
/* +0x00 */ betype<uint32> isUsed;
/* +0x04 */ betype<uint32> channelCount;
/* +0x08 */ MEMPTR<AXVPB> voice[6];
// size: 0x20
};
static_assert(sizeof(AXVPBMULTI) == 0x20);
struct AXMULTIVOICEUKNSTRUCT
{
uint8 ukn[0x4A];
betype<sint16> channelCount;
};
struct AXDSPADPCM
{
/* +0x00 */ uint32be numSamples;
/* +0x04 */ uint32be ukn04;
/* +0x08 */ uint32be sampleRate;
/* +0x0C */ uint16be isLooped;
/* +0x0E */ uint16be format;
/* +0x10 */ uint32be ukn10;
/* +0x14 */ uint32be ukn14;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint16be coef[16];
/* +0x3C */ uint16be gain;
/* +0x3E */ uint16be scale;
/* +0x40 */ uint16be yn1;
/* +0x42 */ uint16be yn2;
/* +0x44 */ uint16be ukn44;
/* +0x46 */ uint16be ukn46;
/* +0x48 */ uint16be ukn48;
/* +0x4A */ uint16be channelCount;
/* +0x4C */ uint16be ukn4C;
/* +0x4E */ uint8 _padding4E[0x12];
};
static_assert(sizeof(AXDSPADPCM) == 0x60);
void AXMultiVoice_Init();
sint32 AXAcquireMultiVoice(sint32 voicePriority, void* cbFunc, void* cbData, AXMULTIVOICEUKNSTRUCT* uknR6, MEMPTR<AXVPBMULTI>* multiVoiceOut);
void AXFreeMultiVoice(AXVPBMULTI* multiVoice);
sint32 AXGetMultiVoiceReformatBufferSize(sint32 voiceFormat, uint32 channelCount, uint32 sizeInBytes, uint32be* sizeOutput);
void AXSetMultiVoiceType(AXVPBMULTI* mv, uint16 type);
void AXSetMultiVoiceAdpcm(AXVPBMULTI* mv, AXDSPADPCM* data);
void AXSetMultiVoiceSrcType(AXVPBMULTI* mv, uint32 type);
void AXSetMultiVoiceOffsets(AXVPBMULTI* mv, AXPBOFFSET_t* offsets);
void AXSetMultiVoiceVe(AXVPBMULTI* mv, AXPBVE* ve);
void AXSetMultiVoiceSrcRatio(AXVPBMULTI* mv, float ratio);
void AXSetMultiVoiceSrc(AXVPBMULTI* mv, AXPBSRC_t* src);
void AXSetMultiVoiceLoop(AXVPBMULTI* mv, uint16 loop);
void AXSetMultiVoiceState(AXVPBMULTI* mv, uint16 state);
void AXSetMultiVoiceAdpcmLoop(AXVPBMULTI* mv, AXPBADPCMLOOP_t* loops);
sint32 AXIsMultiVoiceRunning(AXVPBMULTI* mv);
void AXOut_init();
void AXOut_reset();
void AXOut_update();
void Initialize();
}

View file

@ -0,0 +1,295 @@
#include "Cafe/OS/libs/snd_core/ax.h"
#include "Cafe/OS/libs/snd_core/ax_internal.h"
#include "Cafe/HW/Espresso/PPCState.h"
namespace snd_core
{
const int AX_AUX_FRAME_COUNT = 2;
// old (deprecated) style AUX callbacks
MPTR __AXOldAuxDRCCallbackFunc[AX_AUX_BUS_COUNT * 2];
MPTR __AXOldAuxDRCCallbackUserParam[AX_AUX_BUS_COUNT * 2];
MPTR __AXOldAuxTVCallbackFunc[AX_AUX_BUS_COUNT];
MPTR __AXOldAuxTVCallbackUserParam[AX_AUX_BUS_COUNT];
// new style AUX callbacks
MPTR __AXAuxDRCCallbackFunc[AX_AUX_BUS_COUNT * 2]; // 2 DRCs
MPTR __AXAuxDRCCallbackUserParam[AX_AUX_BUS_COUNT * 2];
MPTR __AXAuxTVCallbackFunc[AX_AUX_BUS_COUNT];
MPTR __AXAuxTVCallbackUserParam[AX_AUX_BUS_COUNT];
struct AUXTVBuffer
{
sint32be _buf[AX_AUX_FRAME_COUNT * AX_TV_CHANNEL_COUNT * AX_AUX_BUS_COUNT * AX_SAMPLES_MAX];
sint32be* GetBuffer(uint32 auxBus, uint32 auxFrame, uint32 channel = 0)
{
const size_t samplesPerChannel = AXGetInputSamplesPerFrame();
const size_t samplesPerBus = AX_SAMPLES_PER_3MS_48KHZ * AX_TV_CHANNEL_COUNT;
const size_t samplesPerFrame = samplesPerBus * AX_AUX_BUS_COUNT;
return _buf + auxFrame * samplesPerFrame + auxBus * samplesPerBus + channel * samplesPerChannel;
}
void ClearBuffer()
{
memset(_buf, 0, sizeof(_buf));
}
};
struct AUXDRCBuffer
{
sint32be _buf[AX_AUX_FRAME_COUNT * AX_DRC_CHANNEL_COUNT * AX_AUX_BUS_COUNT * AX_SAMPLES_MAX];
sint32be* GetBuffer(uint32 auxBus, uint32 auxFrame, uint32 channel = 0)
{
const size_t samplesPerChannel = AXGetInputSamplesPerFrame();
const size_t samplesPerBus = AX_SAMPLES_PER_3MS_48KHZ * AX_DRC_CHANNEL_COUNT;
const size_t samplesPerFrame = samplesPerBus * AX_AUX_BUS_COUNT;
return _buf + auxFrame * samplesPerFrame + auxBus * samplesPerBus + channel * samplesPerChannel;
}
void ClearBuffer()
{
memset(_buf, 0, sizeof(_buf));
}
};
SysAllocator<AUXTVBuffer> __AXAuxTVBuffer;
SysAllocator<AUXDRCBuffer, 2> __AXAuxDRCBuffer;
uint32 __AXCurrentAuxInputFrameIndex = 0;
uint32 AXAux_GetOutputFrameIndex()
{
return 1 - __AXCurrentAuxInputFrameIndex;
}
sint32be* AXAux_GetInputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus)
{
if (auxBus < 0 || auxBus >= AX_AUX_BUS_COUNT)
return nullptr;
if (device == AX_DEV_TV)
{
cemu_assert_debug(deviceIndex == 0);
if (__AXOldAuxTVCallbackFunc[auxBus] == MPTR_NULL && __AXAuxTVCallbackFunc[auxBus] == MPTR_NULL)
return nullptr;
return __AXAuxTVBuffer->GetBuffer(auxBus, __AXCurrentAuxInputFrameIndex);
}
else if (device == AX_DEV_DRC)
{
cemu_assert_debug(deviceIndex >= 0 && deviceIndex <= 1);
if (__AXOldAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL && __AXAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL)
return nullptr;
return __AXAuxDRCBuffer[deviceIndex].GetBuffer(auxBus, __AXCurrentAuxInputFrameIndex);
}
else
{
cemu_assert_debug(false);
}
return nullptr;
}
sint32be* AXAux_GetOutputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus)
{
uint32 outputFrameIndex = AXAux_GetOutputFrameIndex();
if (device == AX_DEV_TV)
{
cemu_assert_debug(deviceIndex == 0);
if (__AXOldAuxTVCallbackFunc[auxBus] == MPTR_NULL && __AXAuxTVCallbackFunc[auxBus] == MPTR_NULL)
return nullptr;
return __AXAuxTVBuffer->GetBuffer(auxBus, outputFrameIndex);
}
else if (device == AX_DEV_DRC)
{
cemu_assert_debug(deviceIndex >= 0 && deviceIndex <= 1);
if (__AXOldAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL && __AXAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL)
return nullptr;
return __AXAuxDRCBuffer[deviceIndex].GetBuffer(auxBus, outputFrameIndex);
}
else
{
cemu_assert_debug(false);
}
return nullptr;
}
void AXAux_Init()
{
__AXCurrentAuxInputFrameIndex = 0;
__AXAuxTVBuffer->ClearBuffer();
__AXAuxDRCBuffer[0].ClearBuffer();
__AXAuxDRCBuffer[1].ClearBuffer();
memset(__AXAuxTVCallbackFunc, 0, sizeof(__AXAuxTVCallbackFunc));
memset(__AXAuxTVCallbackUserParam, 0, sizeof(__AXAuxTVCallbackUserParam));
memset(__AXOldAuxTVCallbackFunc, 0, sizeof(__AXOldAuxTVCallbackFunc));
memset(__AXOldAuxTVCallbackUserParam, 0, sizeof(__AXOldAuxTVCallbackUserParam));
memset(__AXAuxDRCCallbackFunc, 0, sizeof(__AXAuxDRCCallbackFunc));
memset(__AXAuxDRCCallbackUserParam, 0, sizeof(__AXAuxDRCCallbackUserParam));
memset(__AXOldAuxDRCCallbackFunc, 0, sizeof(__AXOldAuxDRCCallbackFunc));
memset(__AXOldAuxDRCCallbackUserParam, 0, sizeof(__AXOldAuxDRCCallbackUserParam));
// init aux return volume
__AXTVAuxReturnVolume[0] = 0x8000;
__AXTVAuxReturnVolume[1] = 0x8000;
__AXTVAuxReturnVolume[2] = 0x8000;
}
sint32 AXRegisterAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MPTR funcMPTR, MPTR userParam)
{
sint32 r = AXIsValidDevice(device, deviceIndex);
if (r != 0)
return r;
if (auxBusIndex >= AX_AUX_BUS_COUNT)
return -5;
if (device == AX_DEV_TV)
{
__AXAuxTVCallbackFunc[auxBusIndex] = funcMPTR;
__AXAuxTVCallbackUserParam[auxBusIndex] = userParam;
}
else if (device == AX_DEV_DRC)
{
__AXAuxDRCCallbackFunc[auxBusIndex + deviceIndex * 3] = funcMPTR;
__AXAuxDRCCallbackUserParam[auxBusIndex + deviceIndex * 3] = userParam;
}
else if (device == AX_DEV_RMT)
{
cemu_assert_debug(false);
}
return 0;
}
sint32 AXGetAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MEMPTR<uint32be> funcPtrOut, MEMPTR<uint32be> contextPtrOut)
{
sint32 r = AXIsValidDevice(device, deviceIndex);
if (r != 0)
return r;
if (auxBusIndex >= AX_AUX_BUS_COUNT)
return -5;
if (device == AX_DEV_TV)
{
*funcPtrOut = __AXAuxTVCallbackFunc[auxBusIndex];
*contextPtrOut = __AXAuxTVCallbackUserParam[auxBusIndex];
}
else if (device == AX_DEV_DRC)
{
*funcPtrOut = __AXAuxDRCCallbackFunc[auxBusIndex + deviceIndex * 3];
*contextPtrOut = __AXAuxDRCCallbackUserParam[auxBusIndex + deviceIndex * 3];
}
else if (device == AX_DEV_RMT)
{
cemu_assert_debug(false);
*funcPtrOut = MPTR_NULL;
*contextPtrOut = MPTR_NULL;
}
return 0;
}
SysAllocator<MEMPTR<sint32be>, 6> __AXAuxCB_dataPtrs;
SysAllocator<AXAUXCBCHANNELINFO> __AXAuxCB_auxCBStruct;
void AXAux_Process()
{
uint32 processedAuxFrameIndex = AXAux_GetOutputFrameIndex();
uint32 sampleCount = AXGetInputSamplesPerFrame();
// TV aux callbacks
uint32 tvChannelCount = AX_TV_CHANNEL_COUNT;
for (sint32 auxBusIndex = 0; auxBusIndex < AX_AUX_BUS_COUNT; auxBusIndex++)
{
MPTR auxCBFuncMPTR = MPTR_NULL;
auxCBFuncMPTR = __AXAuxTVCallbackFunc[auxBusIndex];
if (auxCBFuncMPTR == MPTR_NULL)
auxCBFuncMPTR = __AXOldAuxTVCallbackFunc[auxBusIndex];
if (auxCBFuncMPTR == MPTR_NULL)
{
void* auxOutput = __AXAuxTVBuffer->GetBuffer(auxBusIndex, processedAuxFrameIndex);
memset(auxOutput, 0, sampleCount * AX_TV_CHANNEL_COUNT * sizeof(sint32));
continue;
}
for (sint32 channelIndex = 0; channelIndex < AX_TV_CHANNEL_COUNT; channelIndex++)
__AXAuxCB_dataPtrs[channelIndex] = __AXAuxTVBuffer->GetBuffer(auxBusIndex, processedAuxFrameIndex, channelIndex);
// do callback
if (__AXAuxTVCallbackFunc[auxBusIndex] != MPTR_NULL)
{
// new style callback
AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr();
cbStruct->numChannels = tvChannelCount;
cbStruct->numSamples = sampleCount;
ppcInterpreterCurrentInstance->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR();
ppcInterpreterCurrentInstance->gpr[4] = __AXAuxTVCallbackUserParam[auxBusIndex];
ppcInterpreterCurrentInstance->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR();
PPCCore_executeCallbackInternal(auxCBFuncMPTR);
}
else
{
// old style callback
cemu_assert_debug(false); // todo
}
}
// DRC aux callbacks
for (sint32 drcIndex = 0; drcIndex < 2; drcIndex++)
{
uint32 drcChannelCount = AX_DRC_CHANNEL_COUNT;
for (sint32 auxBusIndex = 0; auxBusIndex < AX_AUX_BUS_COUNT; auxBusIndex++)
{
MPTR auxCBFuncMPTR = MPTR_NULL;
auxCBFuncMPTR = __AXAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3];
if (auxCBFuncMPTR == MPTR_NULL)
{
auxCBFuncMPTR = __AXOldAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3];
}
if (auxCBFuncMPTR == MPTR_NULL)
{
void* auxOutput = __AXAuxDRCBuffer[drcIndex].GetBuffer(auxBusIndex, processedAuxFrameIndex);
memset(auxOutput, 0, 96 * 4 * sizeof(sint32));
continue;
}
if (__AXAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3] != MPTR_NULL)
{
// new style callback
for (sint32 channelIndex = 0; channelIndex < AX_DRC_CHANNEL_COUNT; channelIndex++)
__AXAuxCB_dataPtrs[channelIndex] = __AXAuxDRCBuffer[drcIndex].GetBuffer(auxBusIndex, processedAuxFrameIndex, channelIndex);
AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr();
cbStruct->numChannels = drcChannelCount;
cbStruct->numSamples = sampleCount;
ppcInterpreterCurrentInstance->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR();
ppcInterpreterCurrentInstance->gpr[4] = __AXAuxDRCCallbackUserParam[auxBusIndex + drcIndex * 3];
ppcInterpreterCurrentInstance->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR();
PPCCore_executeCallbackInternal(auxCBFuncMPTR);
}
else
{
// old style callback
cemu_assert_debug(false);
}
}
}
}
void AXAux_incrementBufferIndex()
{
__AXCurrentAuxInputFrameIndex = 1 - __AXCurrentAuxInputFrameIndex;
}
sint32 AXSetAuxReturnVolume(uint32 device, uint32 deviceIndex, uint32 auxBus, uint16 volume)
{
sint32 r = AXIsValidDevice(device, deviceIndex);
if (r)
return r;
if (auxBus >= AX_AUX_BUS_COUNT)
return -5;
if( device == AX_DEV_TV )
{
__AXTVAuxReturnVolume[auxBus] = volume;
}
else
{
forceLogDebug_printf("sndcore2.AXSetAuxReturnVolume() - unsupported device %d", device);
}
return 0;
}
}

View file

@ -0,0 +1,519 @@
#include "Cafe/OS/libs/snd_core/ax.h"
#include "Cafe/OS/libs/snd_core/ax_internal.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h"
namespace snd_core
{
sndGeneric_t sndGeneric;
void resetToDefaultState()
{
memset(&sndGeneric, 0x00, sizeof(sndGeneric));
resetNumProcessedFrames();
}
bool AXIsInit()
{
return sndGeneric.isInitialized;
}
void __AXInit(bool isSoundCore2, uint32 frameLength, uint32 rendererFreq, uint32 pipelineMode)
{
cemu_assert(frameLength == AX_FRAMELENGTH_3MS);
cemu_assert(rendererFreq == AX_RENDERER_FREQ_32KHZ || rendererFreq == AX_RENDERER_FREQ_48KHZ);
sndGeneric.isSoundCore2 = isSoundCore2;
sndGeneric.initParam.frameLength = frameLength;
sndGeneric.initParam.rendererFreq = rendererFreq;
sndGeneric.initParam.pipelineMode = pipelineMode;
// init submodules
AXIst_Init();
AXOut_Init();
AXVPB_Init();
AXAux_Init();
AXMix_Init();
AXMultiVoice_Init();
AXIst_InitThread();
sndGeneric.isInitialized = true;
}
void sndcore2_AXInitWithParams(AXINITPARAM* initParam)
{
if (sndGeneric.isInitialized)
return;
__AXInit(true, initParam->frameLength, initParam->freq, initParam->pipelineMode);
}
void sndcore2_AXInit()
{
if (sndGeneric.isInitialized)
return;
__AXInit(true, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE);
}
void sndcore1_AXInit()
{
if (sndGeneric.isInitialized)
return;
__AXInit(false, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE);
}
void sndcore2_AXInitEx(uint32 uknParam)
{
cemu_assert_debug(uknParam == 0);
if (sndGeneric.isInitialized)
return;
__AXInit(true, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE);
}
void sndcore1_AXInitEx(uint32 uknParam)
{
cemu_assert_debug(uknParam == 0);
if (sndGeneric.isInitialized)
return;
__AXInit(false, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE);
}
void AXQuit()
{
forceLogDebug_printf("AXQuit called from 0x%08x", ppcInterpreterCurrentInstance->spr.LR);
// clean up
AXResetCallbacks();
AXVoiceList_ResetFreeVoiceList();
// todo - should we wait to make sure any active callbacks are finished with execution before we exit AXQuit?
sndGeneric.isInitialized = false;
// request worker thread stop and wait until complete
AXIst_StopThread();
}
sint32 AXGetMaxVoices()
{
return sndGeneric.isInitialized ? AX_MAX_VOICES : 0;
}
void export_AXGetDeviceFinalMixCallback(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(device, 0);
ppcDefineParamU32BEPtr(funcAddrPtr, 1);
sint32 r = AXGetDeviceFinalMixCallback(device, funcAddrPtr);
sndApiLog_printf("AXGetDeviceFinalMixCallback(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]);
osLib_returnFromFunction(hCPU, r);
}
void export_AXRegisterDeviceFinalMixCallback(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(device, 0);
ppcDefineParamMPTR(funcAddr, 1);
sndApiLog_printf("AXRegisterDeviceFinalMixCallback(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]);
sint32 r = AXRegisterDeviceFinalMixCallback(device, funcAddr);
osLib_returnFromFunction(hCPU, r);
}
void export_AXRegisterAppFrameCallback(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXRegisterAppFrameCallback(0x%08x)", hCPU->gpr[3]);
ppcDefineParamMPTR(funcAddr, 0);
sint32 r = AXRegisterAppFrameCallback(funcAddr);
osLib_returnFromFunction(hCPU, r);
}
void export_AXDeregisterAppFrameCallback(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXDeregisterAppFrameCallback(0x%08x)", hCPU->gpr[3]);
ppcDefineParamMPTR(funcAddr, 0);
sint32 r = AXDeregisterAppFrameCallback(funcAddr);
osLib_returnFromFunction(hCPU, r);
}
void export_AXRegisterFrameCallback(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXRegisterFrameCallback(0x%08x)", hCPU->gpr[3]);
ppcDefineParamMPTR(funcAddr, 0);
sint32 r = AXRegisterFrameCallback(funcAddr);
osLib_returnFromFunction(hCPU, r);
}
void export_AXRegisterCallback(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXRegisterCallback(0x%08x)", hCPU->gpr[3]);
ppcDefineParamMPTR(funcAddr, 0);
sint32 r = AXRegisterFrameCallback(funcAddr);
osLib_returnFromFunction(hCPU, r);
}
void export_AXRegisterAuxCallback(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXRegisterAuxCallback(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x) LR %08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->spr.LR);
ppcDefineParamU32(device, 0);
ppcDefineParamU32(deviceIndex, 1);
ppcDefineParamU32(auxBusIndex, 2);
ppcDefineParamMPTR(funcAddr, 3);
ppcDefineParamMPTR(userParam, 4);
sint32 r = AXRegisterAuxCallback(device, deviceIndex, auxBusIndex, funcAddr, userParam);
osLib_returnFromFunction(hCPU, r);
}
void export_AXGetAuxCallback(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXGetAuxCallback(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
ppcDefineParamU32(device, 0);
ppcDefineParamU32(deviceIndex, 1);
ppcDefineParamU32(auxBusIndex, 2);
ppcDefineParamMEMPTR(funcAddrOut, uint32be, 3);
ppcDefineParamMEMPTR(userParamOut, uint32be, 4);
sint32 r = AXGetAuxCallback(device, deviceIndex, auxBusIndex, funcAddrOut, userParamOut);
osLib_returnFromFunction(hCPU, r);
}
void export_AXSetAuxReturnVolume(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXSetAuxReturnVolume(0x%08x,0x%08x,0x%08x,0x%04x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]);
ppcDefineParamU32(device, 0);
ppcDefineParamU32(deviceIndex, 1);
ppcDefineParamU32(auxBusIndex, 2);
ppcDefineParamU16(volume, 3);
sint32 r = AXSetAuxReturnVolume(device, deviceIndex, auxBusIndex, volume);
osLib_returnFromFunction(hCPU, r);
}
void export_AXGetDeviceMode(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXGetDeviceMode(%d)", hCPU->gpr[3]);
ppcDefineParamS32(device, 0);
ppcDefineParamU32BEPtr(mode, 1);
*mode = AXGetDeviceMode(device);
osLib_returnFromFunction(hCPU, 0);
}
void export_AXSetDeviceUpsampleStage(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXSetDeviceUpsampleStage(%d,%d)", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamS32(device, 0);
ppcDefineParamS32(upsampleStage, 1);
sint32 r = AXSetDeviceUpsampleStage(device, upsampleStage);
osLib_returnFromFunction(hCPU, r);
}
void export_AXGetDeviceUpsampleStage(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXGetDeviceUpsampleStage(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamS32(device, 0);
ppcDefineParamU32BEPtr(upsampleStagePtr, 1);
sint32 r = AXGetDeviceUpsampleStage(device, upsampleStagePtr);
osLib_returnFromFunction(hCPU, r);
}
void export_AXAcquireVoiceEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(priority, 0);
ppcDefineParamMPTR(callbackEx, 1);
ppcDefineParamMPTR(userParam, 2);
sndApiLog_printf("AXAcquireVoiceEx(%d,0x%08x,0x%08x)", priority, callbackEx, userParam);
MEMPTR<AXVPB> r = AXAcquireVoiceEx(priority, callbackEx, userParam);
osLib_returnFromFunction(hCPU, r.GetMPTR());
}
void export_AXAcquireVoice(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(priority, 0);
ppcDefineParamMPTR(callback, 1);
ppcDefineParamMPTR(userParam, 2);
sndApiLog_printf("AXAcquireVoice(%d,0x%08x,0x%08x)", priority, callback, userParam);
MEMPTR<AXVPB> r = AXAcquireVoiceEx(priority, MPTR_NULL, MPTR_NULL);
if (r.IsNull() == false)
{
r->callback = (uint32be)callback;
r->userParam = (uint32be)userParam;
}
osLib_returnFromFunction(hCPU, r.GetMPTR());
}
void export_AXFreeVoice(PPCInterpreter_t* hCPU)
{
ppcDefineParamStructPtr(vpb, AXVPB, 0);
sndApiLog_printf("AXFreeVoice(0x%08x)", hCPU->gpr[3]);
AXFreeVoice(vpb);
osLib_returnFromFunction(hCPU, 0);
}
void export_AXUserIsProtected(PPCInterpreter_t* hCPU)
{
sint32 r = AXUserIsProtected();
sndApiLog_printf("AXUserIsProtected() -> %s", r!=0?"true":"false");
osLib_returnFromFunction(hCPU, r);
}
void export_AXUserBegin(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXUserBegin()");
sint32 r = AXUserBegin();
osLib_returnFromFunction(hCPU, r);
}
void export_AXUserEnd(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXUserEnd()");
sint32 r = AXUserEnd();
osLib_returnFromFunction(hCPU, r);
}
void export_AXVoiceBegin(PPCInterpreter_t* hCPU)
{
ppcDefineParamStructPtr(vpb, AXVPB, 0);
sndApiLog_printf("AXVoiceBegin(0x%08x)", hCPU->gpr[3]);
sint32 r = AXVoiceBegin(vpb);
osLib_returnFromFunction(hCPU, r);
}
void export_AXVoiceEnd(PPCInterpreter_t* hCPU)
{
ppcDefineParamStructPtr(vpb, AXVPB, 0);
sndApiLog_printf("AXVoiceEnd(0x%08x)", hCPU->gpr[3]);
sint32 r = AXVoiceEnd(vpb);
osLib_returnFromFunction(hCPU, r);
}
void export_AXVoiceIsProtected(PPCInterpreter_t* hCPU)
{
ppcDefineParamStructPtr(vpb, AXVPB, 0);
sndApiLog_printf("AXVoiceIsProtected(0x%08x)", hCPU->gpr[3]);
sint32 r = AXVoiceProtection_IsProtectedByCurrentThread(vpb)?1:0;
osLib_returnFromFunction(hCPU, r);
}
uint32 __AXCalculatePointerHighExtension(uint16 format, MPTR sampleBase, uint32 offset)
{
sampleBase = memory_virtualToPhysical(sampleBase);
uint32 ptrHighExtension;
if (format == AX_FORMAT_PCM8)
{
ptrHighExtension = ((sampleBase + offset) >> 29);
}
else if (format == AX_FORMAT_PCM16)
{
ptrHighExtension = ((sampleBase + offset * 2) >> 29);
}
else if (format == AX_FORMAT_ADPCM)
{
ptrHighExtension = ((sampleBase + offset / 2) >> 29);
}
return ptrHighExtension;
}
void export_AXCheckVoiceOffsets(PPCInterpreter_t* hCPU)
{
sndApiLog_printf("AXCheckVoiceOffsets(0x%08x)\n", hCPU->gpr[3]);
ppcDefineParamStructPtr(pbOffset, AXPBOFFSET_t, 0);
uint16 format = _swapEndianU16(pbOffset->format);
MPTR sampleBase = _swapEndianU32(pbOffset->samples);
uint32 highExtLoop = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->loopOffset));
uint32 highExtEnd = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->endOffset));
uint32 highExtCurrent = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->currentOffset));
bool isSameRange;
if (highExtLoop == highExtEnd && highExtEnd == highExtCurrent)
isSameRange = true;
else
isSameRange = false;
osLib_returnFromFunction(hCPU, isSameRange ? 1 : 0);
}
void export_AXSetDeviceRemixMatrix(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(device, 0);
ppcDefineParamU32(chanIn, 1);
ppcDefineParamU32(chanOut, 2);
ppcDefineParamMEMPTR(matrix, float32be, 3);
sndApiLog_printf("AXSetDeviceRemixMatrix(%d,%d,%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]);
const auto result = AXSetDeviceRemixMatrix(device, chanIn, chanOut, matrix);
osLib_returnFromFunction(hCPU, result);
}
void export_AXGetDeviceRemixMatrix(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(device, 0);
ppcDefineParamU32(chanIn, 1);
ppcDefineParamU32(chanOut, 2);
ppcDefineParamMEMPTR(matrix, MEMPTR<float32be>, 3);
sndApiLog_printf("AXGetDeviceRemixMatrix(%d,%d,%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]);
const auto result = AXGetDeviceRemixMatrix(device, chanIn, chanOut, matrix);
osLib_returnFromFunction(hCPU, result);
}
struct AXGetDeviceFinalOutput_t
{
/* +0x00 */ uint32be channelCount;
/* +0x04 */ uint32be uknValue;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ uint32be ukn0C;
/* +0x10 */ uint32be size;
// struct might be bigger?
};
sint32 AXGetDeviceFinalOutput(uint32 device, sint16be* sampleBufferOutput, uint32 bufferSize, AXGetDeviceFinalOutput_t* output)
{
if (device != AX_DEV_TV && device != AX_DEV_DRC)
return -1;
sint32 channelCount = AIGetChannelCount(device);
sint32 samplesPerChannel = AIGetSamplesPerChannel(device);
sint32 samplesToCopy = samplesPerChannel * channelCount;
if (bufferSize < (samplesToCopy * sizeof(sint16be)))
return -11; // buffer not large enough
// copy samples to buffer
sint16* samplesBuffer = AIGetCurrentDMABuffer(device);
for (sint32 i = 0; i < samplesToCopy; i++)
sampleBufferOutput[i] = samplesBuffer[i];
// set output struct
output->size = samplesToCopy * sizeof(sint16be);
output->channelCount = channelCount;
output->uknValue = 1; // always set to 1/true?
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()
{
resetToDefaultState();
loadExportsSndCore1();
loadExportsSndCore2();
}
bool isInitialized()
{
return sndGeneric.isInitialized;
}
void reset()
{
sndGeneric.isInitialized = false;
}
}

View file

@ -0,0 +1,239 @@
#pragma once
#include "Cafe/OS/libs/snd_core/ax.h"
namespace snd_core
{
typedef struct
{
bool isInitialized;
bool isSoundCore2;
// init params
struct
{
uint32 rendererFreq; // 32Khz or 48Khz
uint32 frameLength; // 3MS
uint32 pipelineMode;
}initParam;
}sndGeneric_t;
extern sndGeneric_t sndGeneric;
const uint32 AX_SYNCFLAG_SRCFILTER = 0x1; // Voice src type (AXSetVoiceSrcType)
const uint32 AX_SYNCFLAG_DEVICEMIXMASK = 0x2; // Voice mix related (AXSetVoiceDeviceMix)
const uint32 AX_SYNCFLAG_PLAYBACKSTATE = 0x4; // Voice play state (AXSetVoiceState)
const uint32 AX_SYNCFLAG_VOICETYPE = 0x8; // Voice type (AXSetVoiceType)
const uint32 AX_SYNCFLAG_DEVICEMIX = 0x10; // Voice mix related (AXSetVoiceDeviceMix)
const uint32 AX_SYNCFLAG_ITD20 = 0x20; // Voice initial time delay (AXSetVoiceItdOn)
const uint32 AX_SYNCFLAG_ITD40 = 0x40; // Voice initial time delay (AXSetVoiceItdOn, AXSetVoiceItdTarget)
const uint32 AX_SYNCFLAG_VE = 0x100; // Voice ve (AXSetVoiceVe)
const uint32 AX_SYNCFLAG_VEDELTA = 0x200; // Voice ve delta (AXSetVoiceVeDelta)
const uint32 AX_SYNCFLAG_OFFSETS = 0x400; // Voice offset data (AXSetVoiceOffsets)
const uint32 AX_SYNCFLAG_LOOPFLAG = 0x800; // Voice loop flag (AXSetVoiceLoop)
const uint32 AX_SYNCFLAG_LOOPOFFSET = 0x1000; // Voice loop offset (AXSetVoiceLoopOffset)
const uint32 AX_SYNCFLAG_ENDOFFSET = 0x2000; // Voice end offset (AXSetVoiceEndOffset)
const uint32 AX_SYNCFLAG_CURRENTOFFSET = 0x4000; // Voice current offset (AXSetVoiceCurrentOffset)
const uint32 AX_SYNCFLAG_ADPCMDATA = 0x8000; // Voice adpcm data (AXSetVoiceAdpcm)
const uint32 AX_SYNCFLAG_SRCDATA = 0x10000; // Voice src + src ratio (AXSetVoiceSrc)
const uint32 AX_SYNCFLAG_SRCRATIO = 0x20000; // Voice src ratio (AXSetVoiceSrcRatio)
const uint32 AX_SYNCFLAG_ADPCMLOOP = 0x40000; // Voice adpcm loop (AXSetVoiceAdpcmLoop)
const uint32 AX_SYNCFLAG_LPFDATA = 0x80000; // Voice lpf (AXSetVoiceLpf)
const uint32 AX_SYNCFLAG_LPFCOEF = 0x100000; // Voice lpf coef (AXSetVoiceLpfCoefs)
const uint32 AX_SYNCFLAG_BIQUADDATA = 0x200000; // Voice biquad (AXSetVoiceBiquad)
const uint32 AX_SYNCFLAG_BIQUADCOEF = 0x400000; // Voice biquad coef (AXSetVoiceBiquadCoefs)
const uint32 AX_SYNCFLAG_VOICEREMOTEON = 0x800000; // ??? (AXSetVoiceRmtOn?)
const uint32 AX_SYNCFLAG_4000000 = 0x4000000; // ???
const uint32 AX_SYNCFLAG_8000000 = 0x8000000; // ???
struct axADPCMInternal_t
{
/* +0x00 | +0x190 */ uint16 coef[16];
/* +0x20 | +0x1B0 */ uint16 gain;
/* +0x22 | +0x1B2 */ uint16 scale;
/* +0x24 | +0x1B4 */ uint16 yn1;
/* +0x26 | +0x1B6 */ uint16 yn2;
// size: 0x28
};
struct axADPCMLoopInternal_t
{
/* +0x00 | 0x1C6 */ uint16 loopScale;
/* +0x02 | 0x1C8 */ uint16 loopYn1;
/* +0x04 | 0x1CA */ uint16 loopYn2;
// size: 0x6
};
struct axOffsetsInternal_t
{
/* +0x00 / 0x17E */ uint16 loopFlag;
/* +0x02 / 0x180 */ uint16 format;
/* +0x04 / 0x182 */ uint16 ptrHighExtension; // defines 512mb range (highest 3 bit of current offset ptr counted in bytes)
// offsets (relative to NULL, counted in sample words)
// note: All offset ptr variables can only store values up to 512MB (PCM8 mask -> 0x1FFFFFFF, PCM16 mask -> 0x0FFFFFFF, ADPCM mask -> 0x3FFFFFFF)
/* +0x06 / 0x184 */ uint16 loopOffsetPtrHigh;
/* +0x08 / 0x186 */ uint16 loopOffsetPtrLow;
/* +0x0A / 0x188 */ uint16 endOffsetPtrHigh;
/* +0x0C / 0x18A */ uint16 endOffsetPtrLow;
/* +0x0E / 0x18C */ uint16 currentOffsetPtrHigh;
/* +0x10 / 0x18E */ uint16 currentOffsetPtrLow;
uint32 GetLoopOffset32() const
{
uint32 offset = (uint32)_swapEndianU16(loopOffsetPtrHigh) << 16;
return offset | (uint32)_swapEndianU16(loopOffsetPtrLow);
}
uint32 GetEndOffset32() const
{
uint32 offset = (uint32)_swapEndianU16(endOffsetPtrHigh) << 16;
return offset | (uint32)_swapEndianU16(endOffsetPtrLow);
}
uint32 GetCurrentOffset32() const
{
uint32 offset = (uint32)_swapEndianU16(currentOffsetPtrHigh) << 16;
return offset | (uint32)_swapEndianU16(currentOffsetPtrLow);
}
};
const int AX_BUS_COUNT = 4;
struct AXVPBInternal_t
{
/* matches what the DSP expects */
/* +0x000 */ uint16be nextAddrHigh; // points to next shadow copy (physical pointer, NULL for last voice in list)
/* +0x002 */ uint16be nextAddrLow;
/* +0x004 */ uint16be selfAddrHigh; // points to shadow copy of self (physical pointer)
/* +0x006 */ uint16be selfAddrLow;
/* +0x008 */ uint16 srcFilterMode; // AX_FILTER_MODE_*
/* +0x00A */ uint16 srcTapFilter; // AX_FILTER_TAP_*
/* +0x00C */ uint16be mixerSelect;
/* +0x00E */ uint16 voiceType;
/* +0x010 */ uint16 deviceMixMaskTV[4];
/* +0x018 */ uint16 deviceMixMaskDRC[4 * 2];
/* +0x028 */ AXCHMIX_DEPR deviceMixTV[AX_BUS_COUNT * AX_TV_CHANNEL_COUNT]; // TV device mix
/* +0x088 */ AXCHMIX_DEPR deviceMixDRC[AX_BUS_COUNT * AX_DRC_CHANNEL_COUNT * 2]; // DRC device mix
/* +0x108 */ AXCHMIX_DEPR deviceMixRMT[0x40 / 4]; // RMT device mix (size unknown)
/* +0x148 */ uint16 reserved148_voiceRmtOn;
/* +0x14A */ uint16 deviceMixMaskRMT[0x10];
/* +0x16A */ uint16 playbackState;
// itd (0x16C - 0x1B4?)
/* +0x16C */ uint16 reserved16C;
/* +0x16E */ uint16be itdAddrHigh; // points to AXItd_t (physical pointer)
/* +0x170 */ uint16be itdAddrLow;
/* +0x172 */ uint16 reserved172;
/* +0x174 */ uint16 reserved174;
/* +0x176 */ uint16 reserved176_itdRelated;
/* +0x178 */ uint16 reserved178_itdRelated;
/* +0x17A */ uint16be veVolume;
/* +0x17C */ uint16be veDelta;
/* +0x17E */ axOffsetsInternal_t internalOffsets;
/* +0x190 */ axADPCMInternal_t adpcmData;
/* +0x1B8 */ AXPBSRC_t src;
/* +0x1C6 */ axADPCMLoopInternal_t adpcmLoop;
struct
{
/* +0x1CC */ uint16 on;
/* +0x1CE */ uint16 yn1;
/* +0x1D0 */ uint16 a0;
/* +0x1D2 */ uint16 b0;
}lpf;
struct
{
/* +0x1D4 */ uint16 on;
/* +0x1D6 */ sint16 xn1;
/* +0x1D8 */ sint16 xn2;
/* +0x1DA */ sint16 yn1;
/* +0x1DC */ sint16 yn2;
/* +0x1DE */ uint16 b0;
/* +0x1E0 */ uint16 b1;
/* +0x1E2 */ uint16 b2;
/* +0x1E4 */ uint16 a1;
/* +0x1E6 */ uint16 a2;
}biquad;
uint16 reserved1E8[1];
uint16 reserved1EA;
uint16 reserved1EC;
uint16 reserved1EE;
uint32 reserved1F0[4];
uint16 reserved200;
uint16 reserved202;
uint16 reserved204;
uint16 reserved206;
uint16 reserved208;
uint16 reserved20A;
uint16 reserved20C;
uint16 reserved20E;
uint16 reserved210;
uint16 reserved212;
uint16 reserved214;
uint16 reserved216;
uint16 reserved218[0x20]; // not related to device mix?
uint16 reserved258[0x10]; // not related to device mix?
// rmt src related
uint16 reserved278;
uint16 reserved27A;
uint16 reserved27C;
uint16 reserved27E;
uint16 reserved280;
uint16 reserved282_rmtIIRGuessed;
uint32 reserved284;
uint32 reserved288;
uint32 reserved28C;
uint32 reserved290;
uint16 reserved294;
uint16 reserved296;
/* +0x298 */ uint16 reserved298;
/* +0x29A */ uint16 reserved29A;
/* +0x29C */ uint16 reserved29C;
/* +0x29E */ uint16 reserved29E;
/* +0x2A0 */ uint16be index;
/* +0x2A2 */ uint16be ukn2A2; // voice active/valid and being processed?
uint16 reserved2A4;
uint16 reserved2A6;
uint16 reserved2A8;
uint16 reserved2AA;
/* +0x2AC */ MEMPTR<AXVPBInternal_t> nextToProcess;
uint32 reserved2B0;
uint32 reserved2B4;
uint32 reserved2B8;
uint32 reserved2BC;
// size: 0x2C0
};
static_assert(sizeof(AXVPBInternal_t) == 0x2C0);
extern AXVPBInternal_t* __AXVPBInternalVoiceArray;
extern AXVPBInternal_t* __AXVPBInternalVoiceShadowCopyArrayPtr;
extern AXVPB* __AXVPBArrayPtr;
void AXResetVoiceLoopCount(AXVPB* vpb);
std::vector<AXVPB*>& AXVoiceList_GetListByPriority(uint32 priority);
std::vector<AXVPB*>& AXVoiceList_GetFreeVoices();
void AXVoiceList_ResetFreeVoiceList();
inline AXVPBInternal_t* GetInternalVoice(const AXVPB* vpb)
{
return __AXVPBInternalVoiceArray + (size_t)vpb->index;
}
inline uint32 GetVoiceIndex(const AXVPB* vpb)
{
return (uint32)vpb->index;
}
// AXIst
void AXIst_InitThread();
OSThread_t* AXIst_GetThread();
void AXIst_StopThread();
void AXIst_HandleFrameCallbacks();
// AXAux
void AXAux_incrementBufferIndex();
// internal mix buffers
extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVOutputBuffer;
extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCOutputBuffer;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,987 @@
#include "Cafe/OS/libs/snd_core/ax.h"
#include "Cafe/OS/libs/snd_core/ax_internal.h"
#include "Cafe/HW/MMU/MMU.h"
#include "config/ActiveSettings.h"
void mic_updateOnAXFrame();
namespace snd_core
{
uint32 __AXDefaultMixerSelect = AX_MIXER_SELECT_BOTH;
uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT];
void AXSetDefaultMixerSelect(uint32 mixerSelect)
{
__AXDefaultMixerSelect = mixerSelect;
}
uint32 AXGetDefaultMixerSelect()
{
return __AXDefaultMixerSelect;
}
void AXMix_Init()
{
AXSetDefaultMixerSelect(AX_MIXER_SELECT_PPC);
}
void AXMix_DepopVoice(AXVPBInternal_t* internalShadowCopy)
{
// todo
}
#define handleAdpcmDecodeLoop() \
if (internalShadowCopy->internalOffsets.loopFlag != 0) \
{ \
scale = _swapEndianU16(internalShadowCopy->adpcmLoop.loopScale); \
delta = 1 << (scale & 0xF); \
coefIndex = (scale >> 4) & 7; \
coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); \
coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); \
playbackNibbleOffset = vpbLoopOffset; \
} \
else \
{ \
/* no loop */ \
internalShadowCopy->playbackState = 0; \
memset(outputWriter, 0, sampleCount * sizeof(sint16)); \
break; \
}
uint32 _AX_fixAdpcmEndOffset(uint32 adpcmOffset)
{
// How to Survive uses an end offset of 0x40000 which is not a valid adpcm sample offset and thus can never be reached (the ppc side decoder jumps from 0x3FFFF to 0x40002)
// todo - investigate if the DSP decoder routines can handle invalid ADPCM end offsets
// for now we use this workaround to force a valid address
if ((adpcmOffset & 0xF) <= 1)
return adpcmOffset + 2;
return adpcmOffset;
}
void AX_readADPCMSamples(AXVPBInternal_t* internalShadowCopy, sint16* output, sint32 sampleCount)
{
if (internalShadowCopy->playbackState == 0)
{
memset(output, 0, sampleCount * sizeof(sint16));
return;
}
AXVPB* vpb = __AXVPBArrayPtr + (sint32)internalShadowCopy->index;
uint8* sampleBase = (uint8*)memory_getPointerFromVirtualOffset(_swapEndianU32(vpb->offsets.samples));
uint32 vpbLoopOffset = _swapEndianU32(*(uint32*)&vpb->offsets.loopOffset);
uint32 vpbEndOffset = _swapEndianU32(*(uint32*)&vpb->offsets.endOffset);
vpbEndOffset = _AX_fixAdpcmEndOffset(vpbEndOffset);
uint32 internalCurrentOffset = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh);
uint32 internalLoopOffset = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh);
sint32 scale = (sint32)_swapEndianU16(internalShadowCopy->adpcmData.scale);
sint32 delta = 1 << (scale & 0xF);
sint32 coefIndex = (scale >> 4) & 7;
sint32 coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]);
sint32 coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]);
sint32 hist0 = (sint32)_swapEndianS16(internalShadowCopy->adpcmData.yn1);
sint32 hist1 = (sint32)_swapEndianS16(internalShadowCopy->adpcmData.yn2);
uint32 playbackNibbleOffset = vpbLoopOffset + (internalCurrentOffset - internalLoopOffset);
uint32 playbackNibbleOffsetStart = playbackNibbleOffset;
sint16* outputWriter = output;
// decode samples from current partial ADPCM block
while (sampleCount && (playbackNibbleOffset & 0xF))
{
sint8 nibble = (sint8)sampleBase[(sint32)playbackNibbleOffset >> 1];
nibble <<= (4 * ((playbackNibbleOffset & 1)));
nibble &= 0xF0;
sint32 v = (sint32)nibble;
v <<= (11 - 4);
v *= delta;
v += (hist0 * coefA + hist1 * coefB);
v = (v + 0x400) >> 11;
v = std::clamp(v, -32768, 32767);
hist1 = hist0;
hist0 = v;
*outputWriter = v;
outputWriter++;
// check for loop / end offset
if (playbackNibbleOffset == vpbEndOffset)
{
handleAdpcmDecodeLoop();
}
else
{
playbackNibbleOffset++;
}
sampleCount--;
}
// optimized code to decode whole blocks
if (!(playbackNibbleOffset <= vpbEndOffset && ((uint64)playbackNibbleOffset + (uint64)(sampleCount * 16 / 14)) >= (uint64)vpbEndOffset))
{
while (sampleCount >= 14) // 14 samples per 16 byte block
{
// decode header
sint8* sampleInputData = (sint8*)sampleBase + ((sint32)playbackNibbleOffset >> 1);
scale = (uint8)sampleInputData[0];
delta = 1 << (scale & 0xF);
coefIndex = (scale >> 4) & 7;
coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]);
coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]);
playbackNibbleOffset += 2;
// decode samples
sampleInputData++;
for (sint32 i = 0; i < 7; i++)
{
// n0
sint8 nibble = *sampleInputData;
sampleInputData++;
sint32 v;
// upper nibble
v = (sint32)(sint8)(nibble & 0xF0);
v <<= (11 - 4);
v *= delta;
v += (hist0 * coefA + hist1 * coefB);
v = (v + 0x400) >> 11;
v = std::min(v, 32767);
v = std::max(v, -32768);
hist1 = hist0;
hist0 = v;
*outputWriter = v;
outputWriter++;
// lower nibble
v = (sint32)(sint8)(nibble << 4);
v <<= (11 - 4);
v *= delta;
v += (hist0 * coefA + hist1 * coefB);
v = (v + 0x400) >> 11;
v = std::min(v, 32767);
v = std::max(v, -32768);
hist1 = hist0;
hist0 = v;
*outputWriter = v;
outputWriter++;
}
playbackNibbleOffset += 7 * 2;
sampleCount -= 7 * 2;
}
}
// decode remaining samples
while (sampleCount)
{
if ((playbackNibbleOffset & 0xF) == 0)
{
scale = sampleBase[(sint32)playbackNibbleOffset >> 1];
delta = 1 << (scale & 0xF);
coefIndex = (scale >> 4) & 7;
coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]);
coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]);
playbackNibbleOffset += 2;
}
sint8 nibble = (sint8)sampleBase[(sint32)playbackNibbleOffset >> 1];
nibble <<= (4 * ((playbackNibbleOffset & 1)));
nibble &= 0xF0;
sint32 v = (sint32)nibble;
v <<= (11 - 4);
v *= delta;
v += (hist0 * coefA + hist1 * coefB);
v = (v + 0x400) >> 11;
v = std::min(v, 32767);
v = std::max(v, -32768);
hist1 = hist0;
hist0 = v;
*outputWriter = v;
outputWriter++;
// check for loop / end offset
if (playbackNibbleOffset == vpbEndOffset)
{
handleAdpcmDecodeLoop();
}
else
{
playbackNibbleOffset++;
}
sampleCount--;
}
// write updated values
internalShadowCopy->adpcmData.scale = _swapEndianU16(scale);
internalShadowCopy->adpcmData.yn1 = (uint16)_swapEndianS16(hist0);
internalShadowCopy->adpcmData.yn2 = (uint16)_swapEndianS16(hist1);
uint32 newInternalCurrentOffset = internalCurrentOffset + (playbackNibbleOffset - playbackNibbleOffsetStart);
*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(newInternalCurrentOffset);
}
void AX_DecodeSamplesADPCM_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
sint16 sampleBuffer[1024];
cemu_assert(sampleCount <= (sizeof(sampleBuffer) / sizeof(sampleBuffer[0])));
AX_readADPCMSamples(internalShadowCopy, sampleBuffer, sampleCount);
for (sint32 i = 0; i < sampleCount; i++)
{
// decode next sample
sint32 s = sampleBuffer[i];
s <<= 8;
*output = (float)s;
output++;
}
}
void AX_DecodeSamplesADPCM_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac);
uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh);
sint16 historySamples[4];
historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]);
historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]);
historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]);
historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]);
sint32 historyIndex = 0;
sint32 numberOfDecodedAdpcmSamples = (sint32)((currentFracPos + ratio * sampleCount) >> 16);
sint16 adpcmSampleBuffer[4096];
if (numberOfDecodedAdpcmSamples >= 4096)
{
memset(output, 0, sizeof(float)*sampleCount);
forceLog_printf("Too many ADPCM samples to decode. ratio = %08x", ratio);
return;
}
AX_readADPCMSamples(internalShadowCopy, adpcmSampleBuffer, numberOfDecodedAdpcmSamples);
sint32 readSampleCount = 0;
if (ratio == 0x10000 && currentFracPos == 0)
{
// optimized path when accessing only fully aligned samples
for (sint32 i = 0; i < sampleCount; i++)
{
cemu_assert_debug(readSampleCount < numberOfDecodedAdpcmSamples);
// get next sample
sint16 s = adpcmSampleBuffer[readSampleCount];
readSampleCount++;
historyIndex = (historyIndex + 1) & 3;
historySamples[historyIndex] = s;
// use previous sample
sint32 previousSample = historySamples[(historyIndex + 3) & 3];
sint32 p0 = previousSample << 8;
*output = (float)p0;
output++;
}
}
else
{
for (sint32 i = 0; i < sampleCount; i++)
{
// move playback pos
currentFracPos += ratio;
// get more samples if needed
while (currentFracPos >= 0x10000)
{
cemu_assert_debug(readSampleCount < numberOfDecodedAdpcmSamples);
sint16 s = adpcmSampleBuffer[readSampleCount];
readSampleCount++;
currentFracPos -= 0x10000;
historyIndex = (historyIndex + 1) & 3;
historySamples[historyIndex] = s;
}
// linear interpolation of current sample
sint32 previousSample = historySamples[(historyIndex + 3) & 3];
sint32 nextSample = historySamples[historyIndex];
sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos);
sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos);
p0 >>= 7;
p1 >>= 7;
sint32 interpolatedSample = p0 + p1;
interpolatedSample >>= 1;
*output = (float)interpolatedSample;
output++;
}
}
cemu_assert_debug(readSampleCount == numberOfDecodedAdpcmSamples);
// set variables
internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos));
internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]);
internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]);
internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]);
internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]);
}
void AX_DecodeSamplesADPCM_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
// todo - implement this
AX_DecodeSamplesADPCM_Linear(internalShadowCopy, output, sampleCount);
}
void AX_DecodeSamplesPCM8_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
// get variables
uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac);
uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh);
uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh);
uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh);
uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh);
uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension);
uint8* endOffsetAddr = memory_base + (endOffsetPtr | (ptrHighExtension << 29));
uint8* currentOffsetAddr = memory_base + (currentOffsetPtr | (ptrHighExtension << 29));
sint16 historySamples[4];
historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]);
historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]);
historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]);
historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]);
sint32 historyIndex = 0;
for (sint32 i = 0; i<sampleCount; i++)
{
currentFracPos += ratio;
while (currentFracPos >= 0x10000)
{
// read next sample
historyIndex = (historyIndex + 1) & 3;
if (internalShadowCopy->playbackState)
{
sint32 s = (sint32)(sint8)*currentOffsetAddr;
s <<= 8;
historySamples[historyIndex] = s;
if (currentOffsetAddr == endOffsetAddr)
{
if (internalShadowCopy->internalOffsets.loopFlag)
{
// loop
currentOffsetAddr = memory_base + (loopOffsetPtr | (ptrHighExtension << 29));
}
else
{
// stop playing
internalShadowCopy->playbackState = 0;
}
}
else
{
currentOffsetAddr++;
}
}
else
{
// voice not playing, read sample as 0
historySamples[historyIndex] = 0;
}
currentFracPos -= 0x10000;
}
// interpolate sample
sint32 previousSample = historySamples[(historyIndex + 3) & 3];
sint32 nextSample = historySamples[historyIndex];
sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos);
sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos);
p0 >>= 7;
p1 >>= 7;
sint32 interpolatedSample = p0 + p1;
interpolatedSample >>= 1;
*output = (float)interpolatedSample;
output++;
}
// set variables
internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos));
internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]);
internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]);
internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]);
internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]);
// store current offset
currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base);
currentOffsetPtr &= 0x1FFFFFFF; // is this correct?
*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr);
}
void AX_DecodeSamplesPCM8_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
// todo - implement this
AX_DecodeSamplesPCM8_Linear(internalShadowCopy, output, sampleCount);
}
void AX_DecodeSamplesPCM8_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
// get variables
uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac);
uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh);
uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh);
uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh);
uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension);
uint8* endOffsetAddr = memory_base + (endOffsetPtr | (ptrHighExtension << 29));
uint8* currentOffsetAddr = memory_base + (currentOffsetPtr | (ptrHighExtension << 29));
sint16 historySamples[4];
historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]);
historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]);
historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]);
historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]);
cemu_assert_debug(false); // todo
}
void AX_DecodeSamplesPCM16_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac);
uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh);
uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh);
uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh);
uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh);
uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension);
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]);
historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]);
historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]);
sint32 historyIndex = 0;
for (sint32 i = 0; i < sampleCount; i++)
{
currentFracPos += ratio;
while (currentFracPos >= 0x10000)
{
// read next sample
historyIndex = (historyIndex + 1) & 3;
if (internalShadowCopy->playbackState)
{
sint32 s = _swapEndianS16(*currentOffsetAddr);
historySamples[historyIndex] = s;
if (currentOffsetAddr == endOffsetAddr)
{
if (internalShadowCopy->internalOffsets.loopFlag)
{
// loop
currentOffsetAddr = (uint16*)(memory_base + ((loopOffsetPtr * 2) | (ptrHighExtension << 29)));
}
else
{
// stop playing
internalShadowCopy->playbackState = 0;
}
}
else
{
currentOffsetAddr++; // increment pointer only if not at end offset
}
}
else
{
// voice not playing -> sample is silent
historySamples[historyIndex] = 0;
}
currentFracPos -= 0x10000;
}
// interpolate sample
sint32 previousSample = historySamples[(historyIndex + 3) & 3];
sint32 nextSample = historySamples[historyIndex];
sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos);
sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos);
p0 >>= 7;
p1 >>= 7;
sint32 interpolatedSample = p0 + p1;
interpolatedSample >>= 1;
*output = (float)interpolatedSample;
output++;
}
// set variables
internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos));
internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]);
internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]);
internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]);
internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]);
// store current offset
currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base);
currentOffsetPtr &= 0x1FFFFFFF;
currentOffsetPtr >>= 1;
*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr);
}
void AX_DecodeSamplesPCM16_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
// todo - implement this
AX_DecodeSamplesPCM16_Linear(internalShadowCopy, output, sampleCount);
}
void AX_DecodeSamplesPCM16_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
// get variables
uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac);
uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh);
uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh);
uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh);
uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh);
uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension);
uint16* endOffsetAddr = (uint16*)(memory_base + (endOffsetPtr * 2 | (ptrHighExtension << 29)));
uint16* currentOffsetAddr = (uint16*)(memory_base + (currentOffsetPtr * 2 | (ptrHighExtension << 29)));
if (internalShadowCopy->playbackState == 0)
{
memset(output, 0, sizeof(float)*sampleCount);
return;
}
for (sint32 i = 0; i < sampleCount; i++)
{
sint32 s = _swapEndianS16(*currentOffsetAddr);
s <<= 8;
output[i] = (float)s;
if (currentOffsetAddr == endOffsetAddr)
{
if (internalShadowCopy->internalOffsets.loopFlag)
{
currentOffsetAddr = (uint16*)(memory_base + (loopOffsetPtr * 2 | (ptrHighExtension << 29)));
}
else
{
internalShadowCopy->playbackState = 0;
for (; i < sampleCount; i++)
{
output[i] = 0.0f;
}
break;
}
}
else
currentOffsetAddr++;
}
// store current offset
currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base);
currentOffsetPtr &= 0x1FFFFFFF;
currentOffsetPtr >>= 1;
*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr);
}
void AXVoiceMix_DecodeSamples(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount)
{
uint32 srcFilterMode = _swapEndianU16(internalShadowCopy->srcFilterMode);
uint16 format = _swapEndianU16(internalShadowCopy->internalOffsets.format);
if (srcFilterMode == AX_FILTER_MODE_LINEAR || srcFilterMode == AX_FILTER_MODE_TAP)
{
if (format == AX_FORMAT_ADPCM)
AX_DecodeSamplesADPCM_Tap(internalShadowCopy, output, sampleCount);
else if (format == AX_FORMAT_PCM16)
AX_DecodeSamplesPCM16_Tap(internalShadowCopy, output, sampleCount);
else if (format == AX_FORMAT_PCM8)
AX_DecodeSamplesPCM8_Tap(internalShadowCopy, output, sampleCount);
else
cemu_assert_debug(false);
}
else if (srcFilterMode == AX_FILTER_MODE_LINEAR)
{
if (format == AX_FORMAT_ADPCM)
AX_DecodeSamplesADPCM_Linear(internalShadowCopy, output, sampleCount);
else if (format == AX_FORMAT_PCM16)
AX_DecodeSamplesPCM16_Linear(internalShadowCopy, output, sampleCount);
else if (format == AX_FORMAT_PCM8)
AX_DecodeSamplesPCM8_Linear(internalShadowCopy, output, sampleCount);
else
cemu_assert_debug(false);
}
else if (srcFilterMode == AX_FILTER_MODE_NONE)
{
if (format == AX_FORMAT_ADPCM)
AX_DecodeSamplesADPCM_NoSrc(internalShadowCopy, output, sampleCount);
else if (format == AX_FORMAT_PCM16)
AX_DecodeSamplesPCM16_NoSrc(internalShadowCopy, output, sampleCount);
else if (format == AX_FORMAT_PCM8)
AX_DecodeSamplesPCM8_NoSrc(internalShadowCopy, output, sampleCount);
else
cemu_assert_debug(false);
}
}
sint32 AXVoiceMix_MergeInto(float* inputSamples, float* outputSamples, sint32 sampleCount, AXCHMIX_DEPR* mix, sint16 deltaI)
{
float vol = (float)_swapEndianU16(mix->vol) / (float)0x8000;
if (deltaI != 0)
{
float delta = (float)deltaI / (float)0x8000;
for (sint32 i = 0; i < sampleCount; i++)
{
vol += delta;
outputSamples[i] += inputSamples[i] * vol;
}
}
else
{
// optimized version for delta == 0.0
for (sint32 i = 0; i < sampleCount; i++)
{
outputSamples[i] += inputSamples[i] * vol;
}
}
uint16 volI = (uint16)(vol * 32768.0f);
mix->vol = _swapEndianU16(volI);
return volI;
}
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)
{
uint16 volume = internalShadowCopy->veVolume;
sint16 volumeDelta = (sint16)internalShadowCopy->veDelta;
if (volume == 0x8000 && volumeDelta == 0)
return;
float volumeScaler = (float)volume / 32768.0f;
if (volumeDelta == 0)
{
// without delta
for (sint32 i = 0; i < sampleCount; i++)
sampleData[i] *= volumeScaler;
return;
}
// with delta
double volumeScalerDelta = (double)volumeDelta / 32768.0;
volumeScalerDelta = volumeScalerDelta + volumeScalerDelta;
for (sint32 i = 0; i < sampleCount; i++)
{
volumeScaler += (float)volumeScalerDelta;
sampleData[i] *= volumeScaler;
}
if (volumeDelta != 0)
{
volume = (uint16)(volumeScaler * 32768.0);
internalShadowCopy->veVolume = volume;
}
}
void AXVoiceMix_ApplyBiquad(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
{
if (internalShadowCopy->biquad.on == AX_BIQUAD_OFF)
return;
#ifndef PUBLIC_RELEASE
if (internalShadowCopy->biquad.on != 0x0200)
{
forceLogDebug_printf("AX_ApplyBiquad() with incorrect biquad.on value 0x%04x", _swapEndianU16(internalShadowCopy->biquad.on));
}
#endif
float a1 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.a1) / 16384.0f;
float a2 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.a2) / 16384.0f;
float b0 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b0) / 16384.0f;
float b1 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b1) / 16384.0f;
float b2 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b2) / 16384.0f;
float yn1 = (float)_swapEndianS16(internalShadowCopy->biquad.yn1);
float yn2 = (float)_swapEndianS16(internalShadowCopy->biquad.yn2);
float xn1 = (float)_swapEndianS16(internalShadowCopy->biquad.xn1);
float xn2 = (float)_swapEndianS16(internalShadowCopy->biquad.xn2);
if (internalShadowCopy->biquad.b1 != 0)
{
for (sint32 i = 0; i < sampleCount; i++)
{
float inputSample = sampleData[i] / 256.0f;
float temp = b0 * inputSample + b1 * xn1 + b2 * xn2 + a1 * yn1 + a2 * yn2;
sampleData[i] = temp * 256.0f;
temp = std::min(32767.0f, temp);
temp = std::max(-32768.0f, temp);
yn2 = yn1;
xn2 = xn1;
yn1 = temp;
xn1 = inputSample;
}
}
else
{
// optimized variant where voiceInternal->biquad.b1 is hardcoded as zero (used heavily in BotW and Splatoon)
for (sint32 i = 0; i < sampleCount; i++)
{
float inputSample = sampleData[i] / 256.0f;
float temp = b0 * inputSample + b2 * xn2 + a1 * yn1 + a2 * yn2;
sampleData[i] = temp * 256.0f;
temp = std::min(32767.0f, temp);
temp = std::max(-32768.0f, temp);
yn2 = yn1;
xn2 = xn1;
yn1 = temp;
xn1 = inputSample;
}
}
internalShadowCopy->biquad.yn1 = _swapEndianU16((sint16)(yn1));
internalShadowCopy->biquad.yn2 = _swapEndianU16((sint16)(yn2));
internalShadowCopy->biquad.xn1 = _swapEndianU16((sint16)(xn1));
internalShadowCopy->biquad.xn2 = _swapEndianU16((sint16)(xn2));
}
void AXVoiceMix_ApplyLowPass(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount)
{
if (internalShadowCopy->lpf.on == _swapEndianU16(AX_LPF_OFF))
return;
float a0 = (float)_swapEndianS16(internalShadowCopy->lpf.a0) / 32767.0f;
float b0 = (float)_swapEndianS16(internalShadowCopy->lpf.b0) / 32767.0f;
float prevSample = (float)_swapEndianS16((sint16)internalShadowCopy->lpf.yn1) * 256.0f / 32767.0f;
for (sint32 i = 0; i < sampleCount; i++)
{
sampleData[i] = a0 * sampleData[i] - b0 * prevSample;
prevSample = sampleData[i];
}
internalShadowCopy->lpf.yn1 = (uint16)_swapEndianS16((sint16)(prevSample / 256.0f * 32767.0f));
}
// mix audio generated from voice into main bus and aux buses
void AXVoiceMix_MixIntoBuses(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount, sint32 samplesPerFrame)
{
// TV mixing
for (sint32 busIndex = 0; busIndex < AX_BUS_COUNT; busIndex++)
{
for (sint32 channel = 0; channel < 6; channel++)
{
uint32 channelMixMask = (_swapEndianU16(internalShadowCopy->deviceMixMaskTV[busIndex]) >> (channel * 2)) & 3;
if (channelMixMask == 0)
{
internalShadowCopy->reserved1E8[busIndex*AX_TV_CHANNEL_COUNT + channel] = 0;
continue;
}
AXCHMIX_DEPR* mix = internalShadowCopy->deviceMixTV + channel * 4 + busIndex;
float* output = __AXMixBufferTV + (busIndex * 6 + channel) * samplesPerFrame;
AXVoiceMix_MergeInto(sampleData, output, sampleCount, mix, _swapEndianS16(mix->delta));
internalShadowCopy->reserved1E8[busIndex*AX_TV_CHANNEL_COUNT + channel] = mix->vol;
}
}
// DRC0 mixing
for (sint32 busIndex = 0; busIndex < AX_BUS_COUNT; busIndex++)
{
for (sint32 channel = 0; channel < AX_DRC_CHANNEL_COUNT; channel++)
{
uint32 channelMixMask = (_swapEndianU16(internalShadowCopy->deviceMixMaskDRC[busIndex]) >> (channel * 2)) & 3;
if (channelMixMask == 0)
{
//internalShadowCopy->reserved1E8[busIndex*AX_DRC_CHANNEL_COUNT + channel] = 0;
continue;
}
AXCHMIX_DEPR* mix = internalShadowCopy->deviceMixDRC + channel * 4 + busIndex;
float* output = __AXMixBufferDRC + (busIndex * AX_DRC_CHANNEL_COUNT + channel) * samplesPerFrame;
AXVoiceMix_MergeInto(sampleData, output, sampleCount, mix, _swapEndianS16(mix->delta));
}
}
// DRC1 mixing + RMT mixing
// todo
}
void AXMix_ProcessVoices(AXVPBInternal_t* firstVoice)
{
if (firstVoice == nullptr)
return;
size_t sampleCount = AXGetInputSamplesPerFrame();
AXVPBInternal_t* internalVoice = firstVoice;
cemu_assert_debug(sndGeneric.initParam.frameLength == 0);
float tmpSampleBuffer[AX_SAMPLES_MAX];
while (internalVoice)
{
AXVoiceMix_DecodeSamples(internalVoice, tmpSampleBuffer, sampleCount);
AXVoiceMix_ApplyADSR(internalVoice, tmpSampleBuffer, sampleCount);
AXVoiceMix_ApplyBiquad(internalVoice, tmpSampleBuffer, sampleCount);
AXVoiceMix_ApplyLowPass(internalVoice, tmpSampleBuffer, sampleCount);
AXVoiceMix_MixIntoBuses(internalVoice, tmpSampleBuffer, sampleCount, sampleCount);
// next
internalVoice = internalVoice->nextToProcess.GetPtr();
}
}
void AXMix_MergeBusSamples(float* input, sint32* output, sint32 sampleCount, uint16& volume, sint16 delta)
{
float volumeF = (float)volume / 32768.0f;
float deltaF = (float)delta / 32768.0f;
if (delta)
{
for (sint32 i = 0; i < sampleCount; i++)
{
float s = *input;
input++;
s *= volumeF;
volumeF += deltaF;
*output = _swapEndianS32(_swapEndianS32(*output) + (sint32)s);
output++;
}
volume = (uint16)(volumeF * 32768.0f);
}
else
{
// no delta
for (sint32 i = 0; i < sampleCount; i++)
{
float s = *input;
input++;
s *= volumeF;
*output = _swapEndianS32(_swapEndianS32(*output) + (sint32)s);
output++;
}
}
}
void AXAuxMix_StoreAuxSamples(float* input, sint32be* output, sint32 sampleCount)
{
// Not 100% sure why but we need to temporarily right shift the aux samples by 8 to get the sample range the games expect for the AUX callback
// without this, Color Splash will apply it's effects incorrectly
// Its probably because AUX mixing always goes through the DSP which uses 16bit arithmetic?
// no delta
for (sint32 i = 0; i < sampleCount; i++)
{
float s = *input;
input++;
*output = ((sint32)s) >> 8;
output++;
}
}
void AXAuxMix_MixProcessedAuxSamplesIntoOutput(sint32be* input, float* output, sint32 sampleCount, uint16* volumePtr, sint16 delta)
{
uint16 volume = *volumePtr;
float volumeF = (float)volume / 32768.0f;
float deltaF = (float)delta / 32768.0f;
cemu_assert_debug(delta == 0); // todo
for (sint32 i = 0; i < sampleCount; i++)
{
float s = (float)(((sint32)*input)<<8);
input++;
s *= volumeF;
*output += s;
output++;
}
*volumePtr = volume;
}
uint16 __AXMasterVolume = 0x8000;
uint16 __AXDRCMasterVolume = 0x8000;
// mix into __AXTVOutputBuffer
void AXMix_mergeTVBuses()
{
size_t sampleCount = AXGetInputSamplesPerFrame();
// debug - Erase main bus and only output AUX
if (ActiveSettings::AudioOutputOnlyAux())
{
memset(__AXMixBufferTV, 0, sizeof(float) * sampleCount * 6);
}
// Mix aux into TV main bus
for (sint32 auxBus = 0; auxBus < AX_AUX_BUS_COUNT; auxBus++)
{
sint32be* auxOutput = AXAux_GetOutputBuffer(AX_DEV_TV, 0, auxBus);
if (auxOutput == nullptr)
continue;
// AUX return from output buffer
uint16 auxReturnVolume = __AXTVAuxReturnVolume[auxBus];
sint16 auxReturnDelta = 0;
AXAuxMix_MixProcessedAuxSamplesIntoOutput(auxOutput, __AXMixBufferTV, sampleCount * AX_TV_CHANNEL_COUNT, &auxReturnVolume, auxReturnDelta);
}
// mix TV main bus into output
float* input = __AXMixBufferTV;
uint16 masterVolume = __AXMasterVolume;
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;
}
}
// mix into __AXDRCOutputBuffer
void AXMix_mergeDRC0Buses()
{
sint32* output = __AXDRCOutputBuffer.GetPtr();
uint16 masterVolume = __AXDRCMasterVolume;
size_t sampleCount = AXGetInputSamplesPerFrame();
// todo - drc0 AUX
// mix DRC0 main bus into output
float* input = __AXMixBufferDRC;
cemu_assert_debug(masterVolume == 0x8000); // todo -> Calculate delta between old master volume and new volume
sint16 delta = 0;
for (uint16 c = 0; c < AX_DRC_CHANNEL_COUNT; c++)
{
AXMix_MergeBusSamples(input, output, sampleCount, masterVolume, delta);
output += sampleCount;
input += sampleCount;
}
}
void AXMix_process(AXVPBInternal_t* internalShadowCopyHead)
{
memset(__AXMixBufferTV, 0, sizeof(__AXMixBufferTV));
memset(__AXMixBufferDRC, 0, sizeof(__AXMixBufferDRC));
AXMix_ProcessVoices(internalShadowCopyHead);
AXAux_Process(); // apply AUX effects to previous frame
AXIst_HandleFrameCallbacks();
size_t sampleCount = AXGetInputSamplesPerFrame();
// TV aux store
for (sint32 auxBus = 0; auxBus < AX_AUX_BUS_COUNT; auxBus++)
{
sint32be* auxInput = AXAux_GetInputBuffer(AX_DEV_TV, 0, auxBus);
if (auxInput == nullptr)
continue;
float* tvInput = __AXMixBufferTV + (1 + auxBus) * (sampleCount * AX_TV_CHANNEL_COUNT);
AXAuxMix_StoreAuxSamples(tvInput, auxInput, sampleCount * AX_TV_CHANNEL_COUNT);
}
// DRC aux store
// todo
// merge main and aux buses
AXMix_mergeTVBuses();
AXMix_mergeDRC0Buses();
AXAux_incrementBufferIndex();
// update microphone
mic_updateOnAXFrame();
}
}

View file

@ -0,0 +1,167 @@
#include "Cafe/OS/libs/snd_core/ax.h"
#include "Cafe/OS/libs/snd_core/ax_internal.h"
#include "Cafe/HW/MMU/MMU.h"
namespace snd_core
{
static_assert(sizeof(AXVPBMULTI) == 0x20, "");
SysAllocator<AXVPBMULTI, AX_MAX_VOICES> _buffer__AXVPBMultiVoiceArray;
AXVPBMULTI* __AXVPBMultiVoiceArray;
void AXMultiVoice_Init()
{
__AXVPBMultiVoiceArray = _buffer__AXVPBMultiVoiceArray.GetPtr();
for (sint32 i = 0; i < AX_MAX_VOICES; i++)
{
__AXVPBMultiVoiceArray[i].isUsed = 0;
}
}
sint32 AXAcquireMultiVoice(sint32 voicePriority, void* cbFunc, void* cbData, AXMULTIVOICEUKNSTRUCT* uknR6, MEMPTR<AXVPBMULTI>* multiVoiceOut)
{
for (sint32 i = 0; i < AX_MAX_VOICES; i++)
{
if (__AXVPBMultiVoiceArray[i].isUsed == (uint32)0)
{
sint16 channelCount = uknR6->channelCount;
if (channelCount <= 0 || channelCount > 6)
{
return -0x15;
}
for (sint32 f = 0; f < channelCount; f++)
{
__AXVPBMultiVoiceArray[i].voice[f] = nullptr;
}
__AXVPBMultiVoiceArray[i].isUsed = 1;
for (sint32 f = 0; f < channelCount; f++)
{
AXVPB* vpb = AXAcquireVoiceEx(voicePriority, memory_getVirtualOffsetFromPointer(cbFunc), memory_getVirtualOffsetFromPointer(cbData));
if (vpb == nullptr)
{
AXFreeMultiVoice(__AXVPBMultiVoiceArray + i);
return -0x16;
}
__AXVPBMultiVoiceArray[i].voice[f] = vpb;
}
__AXVPBMultiVoiceArray[i].channelCount = channelCount;
*multiVoiceOut = (__AXVPBMultiVoiceArray+i);
return 0;
}
}
return -0x14;
}
void AXFreeMultiVoice(AXVPBMULTI* multiVoice)
{
cemu_assert_debug(multiVoice->isUsed != (uint32)0);
uint16 numChannels = multiVoice->channelCount;
for (uint16 i = 0; i < numChannels; i++)
{
if(multiVoice->voice[i] != nullptr)
AXFreeVoice(multiVoice->voice[i].GetPtr());
multiVoice->voice[i] = nullptr;
}
multiVoice->isUsed = 0;
}
sint32 AXGetMultiVoiceReformatBufferSize(sint32 voiceFormat, uint32 channelCount, uint32 sizeInBytes, uint32be* sizeOutput)
{
// used by Axiom Verge
if (voiceFormat == AX_FORMAT_ADPCM)
{
sint32 alignedSize = (sizeInBytes + 7) & ~7;
*sizeOutput = alignedSize * channelCount;
}
else if (voiceFormat == AX_FORMAT_PCM16)
{
*sizeOutput = sizeInBytes;
}
else if (voiceFormat == AX_FORMAT_PCM8)
{
*sizeOutput = sizeInBytes<<1;
}
else
return -23;
return 0;
}
void AXSetMultiVoiceType(AXVPBMULTI* mv, uint16 type)
{
for(uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceType(mv->voice[i].GetPtr(), type);
}
void AXSetMultiVoiceAdpcm(AXVPBMULTI* mv, AXDSPADPCM* adpcm)
{
static_assert(sizeof(AXDSPADPCM) == 0x60);
for (uint32 i = 0; i < mv->channelCount; ++i)
{
AXPBADPCM_t tmp;
tmp.gain = adpcm[i].gain.bevalue();
tmp.yn1 = adpcm[i].yn1.bevalue();
tmp.yn2 = adpcm[i].yn2.bevalue();
tmp.scale = adpcm[i].scale.bevalue();
static_assert(sizeof(tmp.a) == sizeof(adpcm->coef));
memcpy(tmp.a, adpcm[i].coef, sizeof(tmp.a));
AXSetVoiceAdpcm(mv->voice[i].GetPtr(), &tmp);
}
}
void AXSetMultiVoiceSrcType(AXVPBMULTI* mv, uint32 type)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceSrcType(mv->voice[i].GetPtr(), type);
}
void AXSetMultiVoiceOffsets(AXVPBMULTI* mv, AXPBOFFSET_t* offsets)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceOffsets(mv->voice[i].GetPtr(), offsets + i);
}
void AXSetMultiVoiceVe(AXVPBMULTI* mv, AXPBVE* ve)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceVe(mv->voice[i].GetPtr(), ve);
}
void AXSetMultiVoiceSrcRatio(AXVPBMULTI* mv, float ratio)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceSrcRatio(mv->voice[i].GetPtr(), ratio);
}
void AXSetMultiVoiceSrc(AXVPBMULTI* mv, AXPBSRC_t* src)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceSrc(mv->voice[i].GetPtr(), src);
}
void AXSetMultiVoiceLoop(AXVPBMULTI* mv, uint16 loop)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceLoop(mv->voice[i].GetPtr(), loop);
}
void AXSetMultiVoiceState(AXVPBMULTI* mv, uint16 state)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceState(mv->voice[i].GetPtr(), state);
}
void AXSetMultiVoiceAdpcmLoop(AXVPBMULTI* mv, AXPBADPCMLOOP_t* loops)
{
for (uint32 i = 0; i < mv->channelCount; ++i)
AXSetVoiceAdpcmLoop(mv->voice[i].GetPtr(), loops + i);
}
sint32 AXIsMultiVoiceRunning(AXVPBMULTI* mv)
{
const sint32 result = AXIsVoiceRunning(mv->voice[0].GetPtr());
return result;
}
}

View file

@ -0,0 +1,577 @@
#include "Cafe/OS/libs/snd_core/ax.h"
#include "Cafe/OS/libs/snd_core/ax_internal.h"
#include "Cafe/HW/MMU/MMU.h"
#include "audio/IAudioAPI.h"
//#include "ax.h"
#include "config/CemuConfig.h"
namespace snd_core
{
uint32 numProcessedFrames = 0;
void resetNumProcessedFrames()
{
numProcessedFrames = 0;
}
uint32 getNumProcessedFrames()
{
return numProcessedFrames;
}
sint32 __AXMode[AX_DEV_COUNT]; // audio mode (AX_MODE_*) per device
bool AVMGetTVAudioMode(uint32be* tvAudioMode)
{
// 0 -> mono
// 1,2 -> stereo
// 3 -> surround
// 4 -> unknown mode
switch (GetConfig().tv_channels)
{
case kMono:
*tvAudioMode = 0;
break;
case kSurround:
*tvAudioMode = 3;
break;
default:
*tvAudioMode = 2;
break;
}
return true;
}
bool AVMGetDRCSystemAudioMode(uint32be* drcAudioMode)
{
*drcAudioMode = 1; // apparently the default is Stereo(?), MH3U exits if AXGetDeviceMode doesn't return 0 (DRCSystemAudioMode must return 1 to set DRC mode to 0)
return true;
}
sint32 __AXOutTVOutputChannelCount;
sint32 __AXOutDRCOutputChannelCount;
void __AXSetTVMode(sint32 mode)
{
cemu_assert(mode == AX_MODE_STEREO || mode == AX_MODE_6CH || mode == AX_MODE_MONO);
__AXMode[AX_DEV_TV] = mode;
}
void __AXSetDeviceMode(sint32 device, sint32 mode)
{
if (device == AX_DEV_TV)
__AXMode[AX_DEV_TV] = mode;
else if (device == AX_DEV_DRC)
__AXMode[AX_DEV_DRC] = mode;
else if (device == AX_DEV_RMT)
__AXMode[AX_DEV_RMT] = mode;
else
{
cemu_assert_debug(false);
}
}
sint32 AXGetDeviceMode(sint32 device)
{
if (device == AX_DEV_TV || device == AX_DEV_DRC || device == AX_DEV_RMT)
return __AXMode[device];
cemu_assert_debug(false);
return 0;
}
void _AXOutInitDeviceModes()
{
// TV mode
uint32be tvAudioMode;
AVMGetTVAudioMode(&tvAudioMode);
if (tvAudioMode == 0)
{
// mono
__AXSetTVMode(AX_MODE_MONO);
__AXOutTVOutputChannelCount = 1;
}
else if (tvAudioMode == 1 || tvAudioMode == 2)
{
// stereo
__AXSetTVMode(AX_MODE_STEREO);
__AXOutTVOutputChannelCount = 2;
}
else if (tvAudioMode == 3)
{
// surround (6ch)
__AXSetTVMode(AX_MODE_6CH);
__AXOutTVOutputChannelCount = 6;
}
else
{
assert_dbg();
}
// DRC mode
uint32be drcAudioMode;
AVMGetDRCSystemAudioMode(&drcAudioMode);
if (drcAudioMode == 0)
{
// mono
__AXSetDeviceMode(1, AX_MODE_MONO);
__AXOutDRCOutputChannelCount = 1;
}
else if (drcAudioMode == 2)
{
// surround
__AXSetDeviceMode(1, AX_MODE_SURROUND);
__AXOutDRCOutputChannelCount = 2; // output channel count still 2 for DRC 'surround'
}
else if (drcAudioMode == 1)
{
// stereo
__AXSetDeviceMode(1, AX_MODE_STEREO);
__AXOutDRCOutputChannelCount = 2;
}
else
{
assert_dbg();
}
}
void AXOut_Init()
{
_AXOutInitDeviceModes();
}
extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVBuffer48;
extern SysAllocator<sint32, AX_SAMPLES_MAX* AX_DRC_CHANNEL_COUNT * 2> __AXDRCBuffer48;
sint16 __buf_AXTVDMABuffers_0[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT];
sint16 __buf_AXTVDMABuffers_1[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT];
sint16 __buf_AXTVDMABuffers_2[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT];
sint16* __AXTVDMABuffers[3] = {__buf_AXTVDMABuffers_0, __buf_AXTVDMABuffers_1, __buf_AXTVDMABuffers_2};
#define AX_FRAMES_PER_GROUP (4)
sint16 tempTVChannelData[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT * AX_FRAMES_PER_GROUP] = {};
sint32 tempAudioBlockCounter = 0;
sint16 __buf_AXDRCDMABuffers_0[AX_SAMPLES_MAX * 6];
sint16 __buf_AXDRCDMABuffers_1[AX_SAMPLES_MAX * 6];
sint16 __buf_AXDRCDMABuffers_2[AX_SAMPLES_MAX * 6];
sint16* __AXDRCDMABuffers[3] = { __buf_AXDRCDMABuffers_0, __buf_AXDRCDMABuffers_1, __buf_AXDRCDMABuffers_2 };
sint16 tempDRCChannelData[AX_SAMPLES_MAX * 6 * AX_FRAMES_PER_GROUP] = {};
sint32 tempDRCAudioBlockCounter = 0;
void AIInitDMA(sint16* sampleData, sint32 size)
{
sint32 sampleCount = size / sizeof(sint16); // sample count in total (summed up for all channels)
if (sndGeneric.initParam.frameLength != 0)
{
cemu_assert(false);
}
std::shared_lock lock(g_audioMutex);
const uint32 channels = g_tvAudio ? g_tvAudio->GetChannels() : AX_TV_CHANNEL_COUNT;
sint16* outputChannel = tempTVChannelData + AX_SAMPLES_PER_3MS_48KHZ * tempAudioBlockCounter * channels;
for (sint32 i = 0; i < sampleCount; ++i)
{
outputChannel[i] = _swapEndianS16(sampleData[i]);
}
tempAudioBlockCounter++;
if (tempAudioBlockCounter == AX_FRAMES_PER_GROUP)
{
if(g_tvAudio)
g_tvAudio->FeedBlock(tempTVChannelData);
tempAudioBlockCounter = 0;
}
}
sint32 AIGetSamplesPerChannel(uint32 device)
{
// TV and DRC output the same number of samples
return AX_SAMPLES_PER_3MS_48KHZ;
}
sint32 AIGetChannelCount(uint32 device)
{
if (__AXMode[device] == AX_MODE_6CH)
return 6;
if (__AXMode[device] == AX_MODE_STEREO)
return 2;
// default to mono
return 1;
}
sint16* AIGetCurrentDMABuffer(uint32 device)
{
if (device == AX_DEV_TV)
return __AXTVDMABuffers[0];
else if (device == AX_DEV_DRC)
return __AXDRCDMABuffers[0];
cemu_assert_debug(false);
return nullptr;
}
void AXOut_SubmitTVFrame(sint32 frameIndex)
{
sint32 numSamples = AIGetSamplesPerChannel(AX_DEV_TV);
if (__AXMode[AX_DEV_TV] == AX_MODE_6CH)
{
sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0;
sint32* inputChannel1 = __AXTVBuffer48.GetPtr() + numSamples * 1;
sint32* inputChannel2 = __AXTVBuffer48.GetPtr() + numSamples * 2;
sint32* inputChannel3 = __AXTVBuffer48.GetPtr() + numSamples * 3;
sint32* inputChannel4 = __AXTVBuffer48.GetPtr() + numSamples * 4;
sint32* inputChannel5 = __AXTVBuffer48.GetPtr() + numSamples * 5;
sint16* dmaOutputBuffer = AIGetCurrentDMABuffer(AX_DEV_TV);
for (sint32 i = 0; i < numSamples; i++)
{
/*
* DirectSound surround order
LEFT 0
RIGHT 1
SUR_LEFT 2
SUR_RIGHT 3
CH_FC 4
CH_LFE 5
=>
Front Left - FL 0
Front Right - FR 1
Front Center - FC 2
Low Frequency - LF 3
Back Left - BL 4
Back Right - BR 5
*/
dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767));
dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767));
dmaOutputBuffer[4] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel2), -32768), 32767));
dmaOutputBuffer[5] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel3), -32768), 32767));
dmaOutputBuffer[2] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel4), -32768), 32767));
dmaOutputBuffer[3] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel5), -32768), 32767));
dmaOutputBuffer += 6;
// next sample
inputChannel0++;
inputChannel1++;
inputChannel2++;
inputChannel3++;
inputChannel4++;
inputChannel5++;
}
AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 6 * sizeof(sint16)); // 6ch output
}
else if (__AXMode[AX_DEV_TV] == AX_MODE_STEREO)
{
sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0;
sint32* inputChannel1 = __AXTVBuffer48.GetPtr() + numSamples * 1;
sint16* dmaOutputBuffer = __AXTVDMABuffers[frameIndex];
for (sint32 i = 0; i < numSamples; i++)
{
dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767));
dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767));
dmaOutputBuffer += 2;
// next sample
inputChannel0++;
inputChannel1++;
}
AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 2ch output
}
else if (__AXMode[AX_DEV_TV] == AX_MODE_MONO)
{
sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0;
sint16* dmaOutputBuffer = __AXTVDMABuffers[frameIndex];
for (sint32 i = 0; i < numSamples; i++)
{
dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767));
dmaOutputBuffer++;
// next sample
inputChannel0++;
}
AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 1 * sizeof(sint16)); // 1ch (output as stereo)
}
else
assert_dbg();
}
void AIInitDRCDMA(sint16* sampleData, sint32 size)
{
sint32 sampleCount = size / sizeof(sint16); // sample count in total (summed up for all channels)
if (sndGeneric.initParam.frameLength != 0)
{
cemu_assert(false);
}
std::shared_lock lock(g_audioMutex);
const uint32 channels = g_padAudio ? g_padAudio->GetChannels() : AX_DRC_CHANNEL_COUNT;
sint16* outputChannel = tempDRCChannelData + AX_SAMPLES_PER_3MS_48KHZ * tempDRCAudioBlockCounter * channels;
for (sint32 i = 0; i < sampleCount; ++i)
{
outputChannel[i] = _swapEndianS16(sampleData[i]);
}
tempDRCAudioBlockCounter++;
if (tempDRCAudioBlockCounter == AX_FRAMES_PER_GROUP)
{
if (g_padAudio)
g_padAudio->FeedBlock(tempDRCChannelData);
tempDRCAudioBlockCounter = 0;
}
}
void AXOut_SubmitDRCFrame(sint32 frameIndex)
{
sint32 numSamples = AIGetSamplesPerChannel(AX_DEV_DRC);
if (__AXMode[AX_DEV_DRC] == AX_MODE_6CH)
{
sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0;
sint32* inputChannel1 = __AXDRCBuffer48.GetPtr() + numSamples * 1;
sint32* inputChannel2 = __AXDRCBuffer48.GetPtr() + numSamples * 2;
sint32* inputChannel3 = __AXDRCBuffer48.GetPtr() + numSamples * 3;
sint16* dmaOutputBuffer = AIGetCurrentDMABuffer(AX_DEV_DRC);
for (sint32 i = 0; i < numSamples; i++)
{
dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767));
dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767));
dmaOutputBuffer[4] = 0;
dmaOutputBuffer[5] = 0;
dmaOutputBuffer[2] = 0;
dmaOutputBuffer[3] = 0;
dmaOutputBuffer += 6;
// next sample
inputChannel0++;
inputChannel1++;
inputChannel2++;
inputChannel3++;
}
AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 6 * sizeof(sint16)); // 6ch output
}
else if (__AXMode[AX_DEV_DRC] == AX_MODE_STEREO)
{
sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0;
sint32* inputChannel1 = __AXDRCBuffer48.GetPtr() + numSamples * 1;
sint16* dmaOutputBuffer = __AXDRCDMABuffers[frameIndex];
for (sint32 i = 0; i < numSamples; i++)
{
dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767));
dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767));
dmaOutputBuffer += 2;
// next sample
inputChannel0++;
inputChannel1++;
}
AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 2ch output
}
else if (__AXMode[AX_DEV_DRC] == AX_MODE_MONO)
{
sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0;
sint16* dmaOutputBuffer = __AXDRCDMABuffers[frameIndex];
for (sint32 i = 0; i < numSamples; i++)
{
// write mono input as stereo output
dmaOutputBuffer[1] = dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767));
dmaOutputBuffer += 2;
// next sample
inputChannel0++;
}
AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 1ch (output as stereo)
}
else
assert_dbg();
}
/* AX output */
uint32 numQueuedFramesSndGeneric = 0;
void AXOut_init()
{
auto& config = GetConfig();
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
numQueuedFramesSndGeneric = 0;
std::unique_lock lock(g_audioMutex);
if (!g_tvAudio)
{
sint32 channels;
switch (config.tv_channels)
{
case 0:
channels = 1; // will mix mono sound on both output channels
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
IAudioAPI::DeviceDescriptionPtr device_description;
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
{
auto devices = IAudioAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; });
if (it != devices.end())
device_description = *it;
}
if (device_description)
{
try
{
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_tvAudio->SetVolume(config.tv_volume);
}
catch (std::runtime_error& ex)
{
forceLog_printf("can't initialize tv audio: %s", ex.what());
exit(0);
}
}
}
if (!g_padAudio)
{
sint32 channels;
switch (config.pad_channels)
{
case 0:
channels = 1; // will mix mono sound on both output channels
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
IAudioAPI::DeviceDescriptionPtr device_description;
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
{
auto devices = IAudioAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; });
if (it != devices.end())
device_description = *it;
}
if (device_description)
{
try
{
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_padAudio->SetVolume(config.pad_volume);
g_padVolume = config.pad_volume;
}
catch (std::runtime_error& ex)
{
forceLog_printf("can't initialize pad audio: %s", ex.what());
exit(0);
}
}
}
}
void AXOut_reset()
{
std::unique_lock lock(g_audioMutex);
if (g_tvAudio)
{
g_tvAudio->Stop();
g_tvAudio.reset();
}
if (g_padAudio)
{
g_padAudio->Stop();
g_padAudio.reset();
}
}
void AXOut_updateDevicePlayState(bool isPlaying)
{
std::shared_lock lock(g_audioMutex);
if (g_tvAudio)
{
if (isPlaying)
g_tvAudio->Play();
else
g_tvAudio->Stop();
}
if (g_padAudio)
{
if (isPlaying)
g_padAudio->Play();
else
g_padAudio->Stop();
}
}
// called periodically to check for AX updates
void AXOut_update()
{
constexpr auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3)));
constexpr auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3));
constexpr auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900));
constexpr auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700));
// if we haven't buffered any blocks, we will wait less time than usual
bool additional_blocks_required = false;
{
const std::shared_lock lock(g_audioMutex, std::try_to_lock);
if (lock)
additional_blocks_required = (g_tvAudio && g_tvAudio->NeedAdditionalBlocks()) || (g_padAudio && g_padAudio->NeedAdditionalBlocks());
}
const auto wait_duration = additional_blocks_required ? kWaitDurationFast : kWaitDuration;
// s_ax_interval_timer increases by the wait period
// it can lag behind by multiple periods (up to kTimeout) if there is minor stutter in the CPU thread
// s_last_check is always set to the timestamp at the time of firing
// it's used to enforce the minimum wait delay (we want to avoid calling AX update in quick succession because other threads may need to do work first)
static auto s_ax_interval_timer = now_cached() - kWaitDuration;
static auto s_last_check = now_cached();
const auto now = now_cached();
const auto diff = (now - s_ax_interval_timer);
if (diff < wait_duration)
return;
// handle minimum wait time (1.7MS)
if ((now - s_last_check) < kWaitDurationMinimum)
return;
s_last_check = now;
// if we're too far behind, skip forward
if (diff >= kTimeout)
s_ax_interval_timer = (now - wait_duration);
else
s_ax_interval_timer += wait_duration;
if (snd_core::isInitialized())
{
if (numQueuedFramesSndGeneric == snd_core::getNumProcessedFrames())
{
AXOut_updateDevicePlayState(true);
snd_core::AXIst_QueueFrame();
numQueuedFramesSndGeneric++;
}
}
}
}

File diff suppressed because it is too large Load diff