libctru/libctru/source/services/csnd.c

606 lines
13 KiB
C

#include <stdlib.h>
#include <string.h>
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/svc.h>
#include <3ds/srv.h>
#include <3ds/allocator/mappable.h>
#include <3ds/os.h>
#include <3ds/services/csnd.h>
#include <3ds/ipc.h>
#include <3ds/synchronization.h>
// See here regarding CSND shared-mem commands, etc: http://3dbrew.org/wiki/CSND_Shared_Memory
vu32* csndSharedMem;
u32 csndSharedMemSize;
u32 csndChannels;
u32 csndOffsets[4];
static Handle csndHandle;
static Handle csndMutex;
static Handle csndSharedMemBlock;
static int csndRefCount;
static u32 csndCmdBlockSize = 0x2000;
static u32 csndCmdStartOff;
static u32 csndCmdCurOff;
static Result CSND_Initialize(Handle* mutex, Handle* sharedMem, u32 sharedMemSize, u32* offsets)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x1,5,0); // 0x10140
cmdbuf[1] = sharedMemSize;
memcpy(&cmdbuf[2], &offsets[0], 4*sizeof(u32));
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
if(mutex) *mutex = cmdbuf[3];
if(sharedMem) *sharedMem = cmdbuf[4];
return (Result)cmdbuf[1];
}
static Result CSND_Shutdown()
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x2,0,0); // 0x20000
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return (Result)cmdbuf[1];
}
static Result CSND_ExecuteCommands(u32 offset)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x3,1,0); // 0x30040
cmdbuf[1] = offset;
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return (Result)cmdbuf[1];
}
static Result CSND_AcquireSoundChannels(u32* channelMask)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x5,0,0); // 0x50000
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
*channelMask = cmdbuf[2];
return (Result)cmdbuf[1];
}
static Result CSND_ReleaseSoundChannels(void)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x6,0,0); // 0x60000
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return (Result)cmdbuf[1];
}
Result CSND_AcquireCapUnit(u32* capUnit)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x7,0,0); // 0x70000
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
*capUnit = cmdbuf[2];
return (Result)cmdbuf[1];
}
Result CSND_ReleaseCapUnit(u32 capUnit)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x8,1,0); // 0x80040
cmdbuf[1] = capUnit;
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return (Result)cmdbuf[1];
}
Result CSND_FlushDataCache(const void* adr, u32 size)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x9,2,2); // 0x90082
cmdbuf[1] = (u32)adr;
cmdbuf[2] = size;
cmdbuf[3] = IPC_Desc_SharedHandles(1);
cmdbuf[4] = CUR_PROCESS_HANDLE;
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return cmdbuf[1];
}
Result CSND_StoreDataCache(const void* adr, u32 size)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0xA,2,2); // 0xA0082
cmdbuf[1] = (u32)adr;
cmdbuf[2] = size;
cmdbuf[3] = IPC_Desc_SharedHandles(1);
cmdbuf[4] = CUR_PROCESS_HANDLE;
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return cmdbuf[1];
}
Result CSND_InvalidateDataCache(const void* adr, u32 size)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0xB,2,2); // 0xB0082
cmdbuf[1] = (u32)adr;
cmdbuf[2] = size;
cmdbuf[3] = IPC_Desc_SharedHandles(1);
cmdbuf[4] = CUR_PROCESS_HANDLE;
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return cmdbuf[1];
}
Result CSND_Reset(void)
{
Result ret=0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0xC,0,0); // 0xC0000
if(R_FAILED(ret = svcSendSyncRequest(csndHandle)))return ret;
return (Result)cmdbuf[1];
}
Result csndInit(void)
{
Result ret=0;
if (AtomicPostIncrement(&csndRefCount)) return ret;
ret = srvGetServiceHandle(&csndHandle, "csnd:SND");
if (R_FAILED(ret)) goto cleanup0;
// Calculate offsets and sizes required by the CSND module
csndOffsets[0] = csndCmdBlockSize; // Offset to DSP semaphore and irq disable flags
csndOffsets[1] = csndOffsets[0] + 8; // Offset to sound channel information
csndOffsets[2] = csndOffsets[1] + 32*sizeof(CSND_ChnInfo); // Offset to capture unit information
csndOffsets[3] = csndOffsets[2] + 2*8; // Offset to the input of command 0x00040080
csndSharedMemSize = csndOffsets[3] + 0x3C; // Total size of the CSND shared memory
ret = CSND_Initialize(&csndMutex, &csndSharedMemBlock, csndSharedMemSize, csndOffsets);
if (R_FAILED(ret)) goto cleanup1;
csndSharedMem = (vu32*)mappableAlloc(csndSharedMemSize);
if(!csndSharedMem)
{
ret = -1;
goto cleanup1;
}
ret = svcMapMemoryBlock(csndSharedMemBlock, (u32)csndSharedMem, 3, 0x10000000);
if (R_FAILED(ret)) goto cleanup2;
memset((void*)csndSharedMem, 0, csndSharedMemSize);
ret = CSND_AcquireSoundChannels(&csndChannels);
if (R_SUCCEEDED(ret)) return 0;
cleanup2:
svcCloseHandle(csndSharedMemBlock);
if(csndSharedMem != NULL)
{
mappableFree((void*) csndSharedMem);
csndSharedMem = NULL;
}
cleanup1:
svcCloseHandle(csndHandle);
cleanup0:
AtomicDecrement(&csndRefCount);
return ret;
}
void csndExit(void)
{
if (AtomicDecrement(&csndRefCount)) return;
//CSND_Reset();
CSND_ReleaseSoundChannels();
svcUnmapMemoryBlock(csndSharedMemBlock, (u32)csndSharedMem);
svcCloseHandle(csndSharedMemBlock);
CSND_Shutdown();
svcCloseHandle(csndHandle);
if(csndSharedMem != NULL)
{
mappableFree((void*) csndSharedMem);
csndSharedMem = NULL;
}
}
u32* csndAddCmd(int cmdid)
{
vu16* ptr;
u32 prevoff;
s32 outindex=0;
svcWaitSynchronizationN(&outindex, &csndMutex, 1, 0, ~0);
if (csndCmdStartOff != csndCmdCurOff)
{
if (csndCmdCurOff>=0x20)
prevoff = csndCmdCurOff-0x20;
else
prevoff = csndCmdBlockSize-0x20;
ptr = (vu16*)&csndSharedMem[prevoff>>2];
*ptr = csndCmdCurOff;
}
ptr = (vu16*)&csndSharedMem[csndCmdCurOff>>2];
ptr[0] = 0xFFFF;
ptr[1] = cmdid;
ptr[2] = 0;
ptr[3] = 0;
u32* ret = (u32*)&ptr[4];
csndCmdCurOff += 0x20;
if (csndCmdCurOff >= csndCmdBlockSize)
csndCmdCurOff = 0;
svcReleaseMutex(csndMutex);
return ret;
}
void csndWriteCmd(int cmdid, u8 *cmdparams)
{
memcpy(csndAddCmd(cmdid), cmdparams, 0x18);
}
Result csndExecCmds(bool waitDone)
{
Result ret=0;
// Check we actually wrote commands
if (csndCmdStartOff == csndCmdCurOff)
return 0;
vu8* flag = (vu8*)&csndSharedMem[(csndCmdStartOff + 4) >> 2];
ret = CSND_ExecuteCommands(csndCmdStartOff);
csndCmdStartOff = csndCmdCurOff;
if (R_FAILED(ret)) return ret;
// FIXME: This is a really ugly busy waiting loop!
while (waitDone && *flag == 0);
return ret;
}
void CSND_SetPlayStateR(u32 channel, u32 value)
{
u32* cmdparams = csndAddCmd(0x000);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = value;
}
void CSND_SetPlayState(u32 channel, u32 value)
{
u32* cmdparams = csndAddCmd(0x001);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = value;
}
void CSND_SetEncoding(u32 channel, u32 value)
{
u32* cmdparams = csndAddCmd(0x002);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = value;
}
void CSND_SetBlock(u32 channel, int block, u32 physaddr, u32 size)
{
u32* cmdparams = csndAddCmd(block ? 0x003 : 0x00A);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = physaddr;
cmdparams[2] = size;
}
void CSND_SetLooping(u32 channel, u32 value)
{
u32* cmdparams = csndAddCmd(0x004);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = value;
}
void CSND_SetBit7(u32 channel, bool set)
{
u32* cmdparams = csndAddCmd(0x005);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = set ? 1 : 0;
}
void CSND_SetInterp(u32 channel, bool interp)
{
u32* cmdparams = csndAddCmd(0x006);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = interp ? 1 : 0;
}
void CSND_SetDuty(u32 channel, CSND_DutyCycle duty)
{
u32* cmdparams = csndAddCmd(0x007);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = duty;
}
void CSND_SetTimer(u32 channel, u32 timer)
{
u32* cmdparams = csndAddCmd(0x008);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = timer;
}
void CSND_SetVol(u32 channel, u32 chnVolumes, u32 capVolumes)
{
u32* cmdparams = csndAddCmd(0x009);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = chnVolumes;
cmdparams[2] = capVolumes;
}
void CSND_SetAdpcmState(u32 channel, int block, int sample, int index)
{
u32* cmdparams = csndAddCmd(block ? 0x00C : 0x00B);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = sample & 0xFFFF;
cmdparams[2] = index & 0x7F;
}
void CSND_SetAdpcmReload(u32 channel, bool reload)
{
u32* cmdparams = csndAddCmd(0x00D);
cmdparams[0] = channel & 0x1f;
cmdparams[1] = reload ? 1 : 0;
}
void CSND_SetChnRegs(u32 flags, u32 physaddr0, u32 physaddr1, u32 totalbytesize, u32 chnVolumes, u32 capVolumes)
{
u32* cmdparams = csndAddCmd(0x00E);
cmdparams[0] = flags;
cmdparams[1] = chnVolumes;
cmdparams[2] = capVolumes;
cmdparams[3] = physaddr0;
cmdparams[4] = physaddr1;
cmdparams[5] = totalbytesize;
}
void CSND_SetChnRegsPSG(u32 flags, u32 chnVolumes, u32 capVolumes, CSND_DutyCycle duty)
{
u32* cmdparams = csndAddCmd(0x00F);
cmdparams[0] = flags;
cmdparams[1] = chnVolumes;
cmdparams[2] = capVolumes;
cmdparams[3] = duty;
}
void CSND_SetChnRegsNoise(u32 flags, u32 chnVolumes, u32 capVolumes)
{
u32* cmdparams = csndAddCmd(0x010);
cmdparams[0] = flags;
cmdparams[1] = chnVolumes;
cmdparams[2] = capVolumes;
}
void CSND_CapEnable(u32 capUnit, bool enable)
{
u32* cmdparams = csndAddCmd(0x100);
cmdparams[0] = capUnit;
cmdparams[1] = enable ? 1 : 0;
}
void CSND_CapSetRepeat(u32 capUnit, bool repeat)
{
u32* cmdparams = csndAddCmd(0x101);
cmdparams[0] = capUnit;
cmdparams[1] = repeat ? 0 : 1;
}
void CSND_CapSetFormat(u32 capUnit, bool eightbit)
{
u32* cmdparams = csndAddCmd(0x102);
cmdparams[0] = capUnit;
cmdparams[1] = eightbit ? 1 : 0;
}
void CSND_CapSetBit2(u32 capUnit, bool set)
{
u32* cmdparams = csndAddCmd(0x103);
cmdparams[0] = capUnit;
cmdparams[1] = set ? 1 : 0;
}
void CSND_CapSetTimer(u32 capUnit, u32 timer)
{
u32* cmdparams = csndAddCmd(0x104);
cmdparams[0] = capUnit;
cmdparams[1] = timer & 0xFFFF;
}
void CSND_CapSetBuffer(u32 capUnit, u32 addr, u32 size)
{
u32* cmdparams = csndAddCmd(0x105);
cmdparams[0] = capUnit;
cmdparams[1] = addr;
cmdparams[2] = size;
}
void CSND_SetCapRegs(u32 capUnit, u32 flags, u32 addr, u32 size)
{
u32* cmdparams = csndAddCmd(0x106);
cmdparams[0] = capUnit;
cmdparams[1] = flags;
cmdparams[2] = addr;
cmdparams[3] = size;
}
Result CSND_SetDspFlags(bool waitDone)
{
csndAddCmd(0x200);
return csndExecCmds(waitDone);
}
Result CSND_UpdateInfo(bool waitDone)
{
csndAddCmd(0x300);
return csndExecCmds(waitDone);
}
Result csndPlaySound(int chn, u32 flags, u32 sampleRate, float vol, float pan, void* data0, void* data1, u32 size)
{
if (!(csndChannels & BIT(chn)))
return 1;
u32 paddr0 = 0, paddr1 = 0;
int encoding = (flags >> 12) & 3;
int loopMode = (flags >> 10) & 3;
if (!loopMode) flags |= SOUND_ONE_SHOT;
if (encoding != CSND_ENCODING_PSG)
{
if (data0) paddr0 = osConvertVirtToPhys(data0);
if (data1) paddr1 = osConvertVirtToPhys(data1);
if (data0 && encoding == CSND_ENCODING_ADPCM)
{
int adpcmSample = ((s16*)data0)[-2];
int adpcmIndex = ((u8*)data0)[-2];
CSND_SetAdpcmState(chn, 0, adpcmSample, adpcmIndex);
}
}
u32 timer = CSND_TIMER(sampleRate);
if (timer < 0x0042) timer = 0x0042;
else if (timer > 0xFFFF) timer = 0xFFFF;
flags &= ~0xFFFF001F;
flags |= SOUND_ENABLE | SOUND_CHANNEL(chn) | (timer << 16);
u32 volumes = CSND_VOL(vol, pan);
CSND_SetChnRegs(flags, paddr0, paddr1, size, volumes, volumes);
if (loopMode == CSND_LOOPMODE_NORMAL && paddr1 > paddr0)
{
// Now that the first block is playing, configure the size of the subsequent blocks
size -= paddr1 - paddr0;
CSND_SetBlock(chn, 1, paddr1, size);
}
return csndExecCmds(true);
}
void csndGetDspFlags(u32* outSemFlags, u32* outIrqFlags)
{
if (outSemFlags)
*outSemFlags = csndSharedMem[(csndOffsets[0] + 0) >> 2];
if (outIrqFlags)
*outIrqFlags = csndSharedMem[(csndOffsets[0] + 4) >> 2];
}
static inline u32 chnGetSharedMemIdx(u32 channel)
{
return __builtin_popcount(((1<<channel)-1) & csndChannels);
}
CSND_ChnInfo* csndGetChnInfo(u32 channel)
{
channel = chnGetSharedMemIdx(channel);
return (CSND_ChnInfo*)(&csndSharedMem[(csndOffsets[1] + channel*0xc) >> 2]);
}
CSND_CapInfo* csndGetCapInfo(u32 capUnit)
{
return (CSND_CapInfo*)(&csndSharedMem[(csndOffsets[2] + capUnit*8) >> 2]);
}
Result csndGetState(u32 channel, CSND_ChnInfo* out)
{
Result ret = 0;
channel = chnGetSharedMemIdx(channel);
if (R_FAILED(ret = CSND_UpdateInfo(true)))return ret;
memcpy(out, (const void*)&csndSharedMem[(csndOffsets[1] + channel*0xc) >> 2], 0xc);
//out[2] -= 0x0c000000;
return 0;
}
Result csndIsPlaying(u32 channel, u8* status)
{
Result ret;
CSND_ChnInfo entry;
ret = csndGetState(channel, &entry);
if(R_FAILED(ret))return ret;
*status = entry.active;
return 0;
}