mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-07 07:21:18 +12:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
391
src/Cafe/OS/libs/snd_core/ax.h
Normal file
391
src/Cafe/OS/libs/snd_core/ax.h
Normal 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();
|
||||
}
|
295
src/Cafe/OS/libs/snd_core/ax_aux.cpp
Normal file
295
src/Cafe/OS/libs/snd_core/ax_aux.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
519
src/Cafe/OS/libs/snd_core/ax_exports.cpp
Normal file
519
src/Cafe/OS/libs/snd_core/ax_exports.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
239
src/Cafe/OS/libs/snd_core/ax_internal.h
Normal file
239
src/Cafe/OS/libs/snd_core/ax_internal.h
Normal 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;
|
||||
|
||||
}
|
1056
src/Cafe/OS/libs/snd_core/ax_ist.cpp
Normal file
1056
src/Cafe/OS/libs/snd_core/ax_ist.cpp
Normal file
File diff suppressed because it is too large
Load diff
987
src/Cafe/OS/libs/snd_core/ax_mix.cpp
Normal file
987
src/Cafe/OS/libs/snd_core/ax_mix.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
167
src/Cafe/OS/libs/snd_core/ax_multivoice.cpp
Normal file
167
src/Cafe/OS/libs/snd_core/ax_multivoice.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
577
src/Cafe/OS/libs/snd_core/ax_out.cpp
Normal file
577
src/Cafe/OS/libs/snd_core/ax_out.cpp
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1286
src/Cafe/OS/libs/snd_core/ax_voice.cpp
Normal file
1286
src/Cafe/OS/libs/snd_core/ax_voice.cpp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue