703 lines
15 KiB
C
703 lines
15 KiB
C
#include "ndsp-internal.h"
|
|
#include <3ds/services/cfgu.h>
|
|
#include <3ds/services/fs.h>
|
|
#include <3ds/env.h>
|
|
#include <3ds/thread.h>
|
|
|
|
#define NDSP_THREAD_STACK_SIZE 0x1000
|
|
|
|
u16 ndspFrameId, ndspBufferCurId, ndspBufferId;
|
|
void* ndspVars[16][2];
|
|
|
|
static bool bDspReady, bEnteringSleep, bSleeping, bCancelReceived;
|
|
static u32 droppedFrames, frameCount;
|
|
|
|
static const void* componentBin;
|
|
static u32 componentSize;
|
|
static u16 componentProgMask, componentDataMask;
|
|
static bool componentFree;
|
|
|
|
static dspHookCookie ndspHookCookie;
|
|
|
|
static Handle irqEvent, dspSem;
|
|
static LightEvent sleepEvent;
|
|
static LightLock ndspMutex;
|
|
|
|
static u8 dspVar5Backup[0x1080];
|
|
|
|
static volatile bool ndspThreadRun;
|
|
static Thread ndspThread;
|
|
|
|
static inline bool ndspWaitForIrq(u64 timeout_ns)
|
|
{
|
|
LightLock_Lock(&ndspMutex);
|
|
|
|
// BUG: Official sw has a race condition here when entering sleep mode. DSP state might
|
|
// have already been torn down, and thus this ends up waiting on an invalid (0) handle.
|
|
// There's code that tries to panic if an error happens, however said error handler fails
|
|
// to actually panic because it checks that the result code level is specifically 'Fatal'.
|
|
// Note that the "invalid handle" result code has a 'Permanent' level...
|
|
// We will instead handle invalid handles properly and immediately return.
|
|
bool waitOk = false;
|
|
if (irqEvent)
|
|
{
|
|
Result rc = svcWaitSynchronization(irqEvent, timeout_ns);
|
|
if (R_FAILED(rc))
|
|
svcBreak(USERBREAK_PANIC); // Shouldn't happen.
|
|
|
|
waitOk = R_DESCRIPTION(rc) != RD_TIMEOUT;
|
|
}
|
|
|
|
if (waitOk)
|
|
svcClearEvent(irqEvent);
|
|
|
|
LightLock_Unlock(&ndspMutex);
|
|
return waitOk;
|
|
}
|
|
|
|
static inline void ndspSetCounter(int a, int counter)
|
|
{
|
|
*(vu16*)ndspVars[0][a] = counter;
|
|
}
|
|
|
|
static inline int ndspGetCounter(int a)
|
|
{
|
|
return *(vu16*)ndspVars[0][a];
|
|
}
|
|
|
|
enum
|
|
{
|
|
MFLAG_MASTERVOL = BIT(0),
|
|
MFLAG_OUTPUTMODE = BIT(1),
|
|
MFLAG_CLIPPINGMODE = BIT(2),
|
|
MFLAG_OUTPUTCOUNT = BIT(3),
|
|
MFLAG_SYNCMODE = BIT(4),
|
|
MFLAG_SURR_DEPTH = BIT(5),
|
|
MFLAG_SURR_POS = BIT(6),
|
|
MFLAG_SURR_RRATIO = BIT(7),
|
|
|
|
#define MFLAG_AUX_ENABLE(i) BIT(8+(i))
|
|
#define MFLAG_AUX_BYPASS(i) BIT(10+(i))
|
|
#define MFLAG_AUX_VOLUME(i) BIT(12+(i))
|
|
};
|
|
|
|
static struct
|
|
{
|
|
LightLock lock;
|
|
u32 flags;
|
|
float masterVol;
|
|
u16 outputMode, clippingMode, outputCount, syncMode;
|
|
ndspWaveBuf* capture;
|
|
ndspCallback callback;
|
|
void* callbackData;
|
|
|
|
struct
|
|
{
|
|
u16 depth, pos, rearRatio;
|
|
} surround;
|
|
|
|
struct
|
|
{
|
|
u16 enable, frontBypass;
|
|
float volume;
|
|
ndspAuxCallback callback;
|
|
void* callbackData;
|
|
} aux[2];
|
|
} ndspMaster;
|
|
|
|
static void ndspDirtyMaster(void)
|
|
{
|
|
ndspMaster.flags = ~0;
|
|
}
|
|
|
|
static void ndspInitMaster(void)
|
|
{
|
|
memset(&ndspMaster, 0, sizeof(ndspMaster));
|
|
LightLock_Init(&ndspMaster.lock);
|
|
ndspMaster.flags = ~0;
|
|
ndspMaster.masterVol = 1.0f;
|
|
ndspMaster.outputMode = NDSP_OUTPUT_STEREO;
|
|
ndspMaster.clippingMode = NDSP_CLIP_SOFT;
|
|
ndspMaster.outputCount = 2;
|
|
ndspMaster.surround.depth = 0x7FFF;
|
|
ndspMaster.surround.rearRatio = 0x8000;
|
|
|
|
// Use the output mode set in system settings, if available
|
|
Result rc = cfguInit();
|
|
if (R_SUCCEEDED(rc))
|
|
{
|
|
u8 outMode;
|
|
rc = CFGU_GetConfigInfoBlk2(sizeof(outMode), 0x70001, &outMode);
|
|
if (R_SUCCEEDED(rc))
|
|
ndspMaster.outputMode = outMode;
|
|
cfguExit();
|
|
}
|
|
}
|
|
|
|
static void ndspUpdateMaster(void)
|
|
{
|
|
DspMasterStatus* m = ndspiGetMasterStatus();
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
|
|
u32 flags = m->flags, mflags = ndspMaster.flags;
|
|
int i;
|
|
|
|
m->headsetConnected = osIsHeadsetConnected();
|
|
flags |= 0x10000000;
|
|
|
|
if (mflags & MFLAG_MASTERVOL)
|
|
{
|
|
m->masterVol = ndspMaster.masterVol;
|
|
flags |= 0x00010000;
|
|
}
|
|
|
|
if (mflags & MFLAG_OUTPUTMODE)
|
|
{
|
|
m->outputMode = ndspMaster.outputMode;
|
|
flags |= 0x04000000;
|
|
}
|
|
|
|
if (mflags & MFLAG_CLIPPINGMODE)
|
|
{
|
|
m->clippingMode = ndspMaster.clippingMode;
|
|
flags |= 0x08000000;
|
|
}
|
|
|
|
if (mflags & MFLAG_OUTPUTCOUNT)
|
|
{
|
|
m->outBufCount = ndspMaster.outputCount;
|
|
flags |= 0x00008000;
|
|
}
|
|
|
|
if (mflags & MFLAG_SYNCMODE)
|
|
{
|
|
m->syncMode = ndspMaster.syncMode;
|
|
m->unknown |= 0x10000; //?
|
|
}
|
|
|
|
if (mflags & MFLAG_SURR_DEPTH)
|
|
{
|
|
m->surroundDepth = ndspMaster.surround.depth;
|
|
flags |= 0x20000000;
|
|
}
|
|
|
|
if (mflags & MFLAG_SURR_POS)
|
|
{
|
|
m->surroundSpeakerPos = ndspMaster.surround.pos;
|
|
flags |= 0x40000000;
|
|
}
|
|
|
|
if (mflags & MFLAG_SURR_RRATIO)
|
|
{
|
|
m->rearRatio = ndspMaster.surround.rearRatio;
|
|
flags |= 0x80000000;
|
|
}
|
|
|
|
for (i = 0; i < 2; i ++)
|
|
{
|
|
if (mflags & MFLAG_AUX_ENABLE(i))
|
|
{
|
|
m->auxBusEnable[i] = ndspMaster.aux[i].enable;
|
|
flags |= 0x00000100 << i;
|
|
}
|
|
|
|
if (mflags & MFLAG_AUX_BYPASS(i))
|
|
{
|
|
m->auxFrontBypass[i] = ndspMaster.aux[i].frontBypass;
|
|
flags |= 0x00000040 << i;
|
|
}
|
|
|
|
if (mflags & MFLAG_AUX_VOLUME(i))
|
|
{
|
|
m->auxReturnVol[i] = ndspMaster.aux[i].volume;
|
|
flags |= 0x01000000 << i;
|
|
}
|
|
}
|
|
|
|
m->flags = flags;
|
|
ndspMaster.flags = 0;
|
|
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
static void ndspUpdateCapture(s16* samples, u32 count)
|
|
{
|
|
ndspWaveBuf* buf = ndspMaster.capture;
|
|
if (!buf) return;
|
|
memcpy(&buf->data_pcm16[buf->offset*2], samples, count*4);
|
|
buf->offset += count;
|
|
if (buf->offset >= buf->nsamples)
|
|
buf->offset = 0;
|
|
}
|
|
|
|
static Result ndspInitialize(bool resume)
|
|
{
|
|
Result rc;
|
|
|
|
rc = svcCreateEvent(&irqEvent, RESET_STICKY);
|
|
if (R_FAILED(rc)) goto _fail1;
|
|
|
|
rc = DSP_RegisterInterruptEvents(irqEvent, DSP_INTERRUPT_PIPE, 2);
|
|
if (R_FAILED(rc)) goto _fail2;
|
|
|
|
rc = DSP_GetSemaphoreHandle(&dspSem);
|
|
if (R_FAILED(rc)) goto _fail3;
|
|
|
|
DSP_SetSemaphoreMask(0x2000);
|
|
|
|
if (resume)
|
|
{
|
|
memcpy(ndspVars[5][0], dspVar5Backup, sizeof(dspVar5Backup));
|
|
__dsb();
|
|
}
|
|
|
|
u16 val = resume ? 2 : 0;
|
|
DSP_WriteProcessPipe(2, &val, 4);
|
|
|
|
DSP_SetSemaphore(0x4000);
|
|
ndspWaitForIrq(U64_MAX);
|
|
|
|
DSP_ReadPipeIfPossible(2, 0, &val, sizeof(val), NULL);
|
|
|
|
u16 vars[16];
|
|
DSP_ReadPipeIfPossible(2, 0, vars, val*2, NULL);
|
|
for (unsigned i = 0; i < val; i ++)
|
|
{
|
|
DSP_ConvertProcessAddressFromDspDram(vars[i], (u32*)&ndspVars[i][0]);
|
|
DSP_ConvertProcessAddressFromDspDram(vars[i] | 0x10000, (u32*)&ndspVars[i][1]);
|
|
}
|
|
|
|
DSP_SetSemaphore(0x4000);
|
|
frameCount = 0;
|
|
ndspFrameId = 4;
|
|
ndspSetCounter(0, 4);
|
|
ndspFrameId++;
|
|
svcSignalEvent(dspSem);
|
|
|
|
ndspBufferCurId = ndspFrameId & 1;
|
|
ndspBufferId = ndspFrameId & 1;
|
|
bDspReady = true;
|
|
|
|
if (resume)
|
|
{
|
|
ndspiDirtyChn();
|
|
ndspiUpdateChn();
|
|
|
|
ndspDirtyMaster();
|
|
ndspUpdateMaster();
|
|
|
|
// TODO: force update effect params
|
|
}
|
|
return 0;
|
|
|
|
_fail3:
|
|
DSP_RegisterInterruptEvents(0, 2, 2);
|
|
_fail2:
|
|
svcCloseHandle(irqEvent);
|
|
_fail1:
|
|
return rc;
|
|
}
|
|
|
|
static void ndspFinalize(bool suspend)
|
|
{
|
|
u16 val = suspend ? 3 : 1;
|
|
DSP_WriteProcessPipe(2, &val, 4);
|
|
|
|
for (;;)
|
|
{
|
|
bool ready = false;
|
|
DSP_RecvDataIsReady(0, &ready);
|
|
if (ready)
|
|
{
|
|
val = 0;
|
|
DSP_RecvData(0, &val);
|
|
if (val == 1)
|
|
break;
|
|
}
|
|
svcSleepThread(4888000); // 4.888ms (approx. one sound frame)
|
|
}
|
|
|
|
if (suspend)
|
|
memcpy(dspVar5Backup, ndspVars[5][0], sizeof(dspVar5Backup));
|
|
|
|
LightLock_Lock(&ndspMutex);
|
|
bDspReady = false;
|
|
|
|
svcCloseHandle(irqEvent);
|
|
irqEvent = 0;
|
|
DSP_RegisterInterruptEvents(0, DSP_INTERRUPT_PIPE, 2);
|
|
|
|
svcCloseHandle(dspSem);
|
|
dspSem = 0;
|
|
|
|
LightLock_Unlock(&ndspMutex);
|
|
}
|
|
|
|
static void ndspHookCallback(DSP_HookType hook)
|
|
{
|
|
switch (hook)
|
|
{
|
|
case DSPHOOK_ONSLEEP:
|
|
if (!bSleeping)
|
|
{
|
|
bEnteringSleep = true;
|
|
ndspFinalize(true);
|
|
bSleeping = true;
|
|
}
|
|
break;
|
|
|
|
case DSPHOOK_ONWAKEUP:
|
|
if (bSleeping)
|
|
{
|
|
Result res = ndspInitialize(true);
|
|
if (R_FAILED(res))
|
|
svcBreak(USERBREAK_PANIC); // Shouldn't happen.
|
|
|
|
bSleeping = false;
|
|
bEnteringSleep = false;
|
|
__dsb();
|
|
LightEvent_Signal(&sleepEvent);
|
|
}
|
|
break;
|
|
|
|
case DSPHOOK_ONCANCEL:
|
|
if (bSleeping)
|
|
{
|
|
bCancelReceived = true;
|
|
bSleeping = false;
|
|
bEnteringSleep = false;
|
|
__dsb();
|
|
LightEvent_Signal(&sleepEvent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ndspSync(void)
|
|
{
|
|
// If we are about to sleep...
|
|
if (bEnteringSleep)
|
|
{
|
|
// Check whether the DSP is still running by attempting to wait with a timeout
|
|
if (ndspWaitForIrq(9776000)) // 9.776ms (approx. two sound frames)
|
|
goto _receiveState; // it's not, so just proceed as usual
|
|
|
|
// The wait failed, so the DSP is indeed sleeping
|
|
bSleeping = true;
|
|
}
|
|
|
|
// If we are sleeping, wait for the DSP to wake up
|
|
if (bSleeping)
|
|
{
|
|
LightEvent_Wait(&sleepEvent);
|
|
LightEvent_Clear(&sleepEvent);
|
|
}
|
|
|
|
// If we were cancelled, do a dummy wait instead
|
|
if (bCancelReceived)
|
|
{
|
|
svcSleepThread(4888000); // 4.888ms (approx. one sound frame)
|
|
return;
|
|
}
|
|
|
|
// In any other case - wait for the DSP to notify us
|
|
ndspWaitForIrq(U64_MAX);
|
|
|
|
_receiveState:
|
|
// Receive and update state (if the DSP is ready)
|
|
if (bDspReady)
|
|
{
|
|
int counter = ndspGetCounter(~ndspFrameId & 1);
|
|
if (counter)
|
|
{
|
|
int next = (counter + 1) & 0xFFFF;
|
|
ndspFrameId = next ? next : 2;
|
|
ndspBufferId = ndspFrameId & 1;
|
|
ndspiReadChnState();
|
|
//memcpy(dspVar9Backup, dspVars[9][ndspBufferId], sizeof(dspVar9Backup));
|
|
ndspUpdateCapture((s16*)ndspVars[6][ndspBufferId], 160);
|
|
droppedFrames += *((u16*)ndspVars[5][ndspBufferId] + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ndspThreadMain(void* arg)
|
|
{
|
|
ndspThreadRun = true;
|
|
while (ndspThreadRun)
|
|
{
|
|
ndspSync();
|
|
|
|
if (ndspMaster.callback)
|
|
ndspMaster.callback(ndspMaster.callbackData);
|
|
|
|
if (bSleeping || bCancelReceived || !bDspReady)
|
|
continue;
|
|
|
|
ndspUpdateMaster();
|
|
// TODO: call aux user callback if enabled
|
|
// TODO: execute DSP effects
|
|
ndspiUpdateChn();
|
|
|
|
ndspSetCounter(ndspBufferCurId, ndspFrameId++);
|
|
svcSignalEvent(dspSem);
|
|
ndspBufferCurId = ndspFrameId & 1;
|
|
|
|
frameCount++;
|
|
}
|
|
}
|
|
|
|
void ndspUseComponent(const void* binary, u32 size, u16 progMask, u16 dataMask)
|
|
{
|
|
componentBin = binary;
|
|
componentSize = size;
|
|
componentProgMask = progMask;
|
|
componentDataMask = dataMask;
|
|
componentFree = false;
|
|
}
|
|
|
|
static bool ndspFindAndLoadComponent(void)
|
|
{
|
|
Result rc;
|
|
Handle rsrc;
|
|
void* bin;
|
|
|
|
componentProgMask = 0xFF;
|
|
componentDataMask = 0xFF;
|
|
|
|
// Try loading the DSP component from the filesystem
|
|
do
|
|
{
|
|
static const char dsp_filename[] = "/3ds/dspfirm.cdc";
|
|
FS_Path archPath = { PATH_EMPTY, 1, "" };
|
|
FS_Path filePath = { PATH_ASCII, sizeof(dsp_filename), dsp_filename };
|
|
|
|
rc = FSUSER_OpenFileDirectly(&rsrc, ARCHIVE_SDMC, archPath, filePath, FS_OPEN_READ, 0);
|
|
if (R_FAILED(rc)) break;
|
|
|
|
u64 size = 0;
|
|
rc = FSFILE_GetSize(rsrc, &size);
|
|
if (R_FAILED(rc)) { FSFILE_Close(rsrc); break; }
|
|
|
|
bin = malloc(size);
|
|
if (!bin) { FSFILE_Close(rsrc); break; }
|
|
|
|
u32 dummy = 0;
|
|
rc = FSFILE_Read(rsrc, &dummy, 0, bin, size);
|
|
FSFILE_Close(rsrc);
|
|
if (R_FAILED(rc)) { free(bin); return false; }
|
|
|
|
componentBin = bin;
|
|
componentSize = size;
|
|
componentFree = true;
|
|
return true;
|
|
} while (0);
|
|
|
|
// Try loading the DSP component from hb:ndsp
|
|
rsrc = envGetHandle("hb:ndsp");
|
|
if (rsrc) do
|
|
{
|
|
extern u32 fake_heap_end;
|
|
u32 mapAddr = (fake_heap_end+0xFFF) &~ 0xFFF;
|
|
rc = svcMapMemoryBlock(rsrc, mapAddr, MEMPERM_READWRITE, MEMPERM_READWRITE);
|
|
if (R_FAILED(rc)) break;
|
|
|
|
componentSize = *(u32*)(mapAddr + 0x104);
|
|
bin = malloc(componentSize);
|
|
if (bin)
|
|
memcpy(bin, (void*)mapAddr, componentSize);
|
|
svcUnmapMemoryBlock(rsrc, mapAddr);
|
|
if (!bin) break;
|
|
|
|
componentBin = bin;
|
|
componentFree = true;
|
|
return true;
|
|
} while (0);
|
|
|
|
return false;
|
|
}
|
|
|
|
static int ndspRefCount = 0;
|
|
|
|
Result ndspInit(void)
|
|
{
|
|
Result rc = 0;
|
|
if (AtomicPostIncrement(&ndspRefCount)) return 0;
|
|
|
|
if (!componentBin && !ndspFindAndLoadComponent())
|
|
{
|
|
rc = MAKERESULT(RL_PERMANENT, RS_NOTFOUND, RM_DSP, RD_NOT_FOUND);
|
|
goto _fail0;
|
|
}
|
|
|
|
LightLock_Init(&ndspMutex);
|
|
LightEvent_Init(&sleepEvent, RESET_STICKY);
|
|
|
|
rc = dspInit();
|
|
if (R_FAILED(rc)) goto _fail1;
|
|
|
|
rc = DSP_LoadComponent(componentBin, componentSize, componentProgMask, componentDataMask, NULL);
|
|
if (R_FAILED(rc)) goto _fail2;
|
|
|
|
rc = ndspInitialize(false);
|
|
if (R_FAILED(rc)) goto _fail2;
|
|
|
|
ndspiInitChn();
|
|
ndspInitMaster();
|
|
ndspUpdateMaster(); // official sw does this upfront, not sure what's the point
|
|
// TODO: initialize effect params
|
|
|
|
ndspThread = threadCreate(ndspThreadMain, 0x0, NDSP_THREAD_STACK_SIZE, 0x18, -2, true);
|
|
if (!ndspThread) goto _fail3;
|
|
|
|
dspHook(&ndspHookCookie, ndspHookCallback);
|
|
return 0;
|
|
|
|
_fail3:
|
|
ndspFinalize(false);
|
|
_fail2:
|
|
dspExit();
|
|
_fail1:
|
|
if (componentFree)
|
|
{
|
|
free((void*)componentBin);
|
|
componentBin = NULL;
|
|
}
|
|
_fail0:
|
|
AtomicDecrement(&ndspRefCount);
|
|
return rc;
|
|
}
|
|
|
|
void ndspExit(void)
|
|
{
|
|
if (AtomicDecrement(&ndspRefCount)) return;
|
|
|
|
ndspThreadRun = false;
|
|
threadJoin(ndspThread, U64_MAX);
|
|
|
|
dspUnhook(&ndspHookCookie);
|
|
if (!bCancelReceived)
|
|
ndspFinalize(false);
|
|
|
|
bEnteringSleep = false;
|
|
bSleeping = false;
|
|
bCancelReceived = false;
|
|
dspExit();
|
|
|
|
if (componentFree)
|
|
{
|
|
free((void*)componentBin);
|
|
componentBin = NULL;
|
|
}
|
|
}
|
|
|
|
u32 ndspGetDroppedFrames(void)
|
|
{
|
|
return droppedFrames;
|
|
}
|
|
|
|
u32 ndspGetFrameCount(void)
|
|
{
|
|
return frameCount;
|
|
}
|
|
|
|
void ndspSetMasterVol(float volume)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.masterVol = volume;
|
|
ndspMaster.flags |= MFLAG_MASTERVOL;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspSetOutputMode(ndspOutputMode mode)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.outputMode = mode;
|
|
ndspMaster.flags |= MFLAG_OUTPUTMODE;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspSetClippingMode(ndspClippingMode mode)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.clippingMode = mode;
|
|
ndspMaster.flags |= MFLAG_CLIPPINGMODE;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspSetOutputCount(int count)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.outputCount = count;
|
|
ndspMaster.flags |= MFLAG_OUTPUTCOUNT;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspSetCapture(ndspWaveBuf* capture)
|
|
{
|
|
ndspMaster.capture = capture;
|
|
}
|
|
|
|
void ndspSetCallback(ndspCallback callback, void* data)
|
|
{
|
|
ndspMaster.callback = callback;
|
|
ndspMaster.callbackData = data;
|
|
}
|
|
|
|
void ndspSurroundSetDepth(u16 depth)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.surround.depth = depth;
|
|
ndspMaster.flags |= MFLAG_SURR_DEPTH;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspSurroundSetPos(ndspSpeakerPos pos)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.surround.pos = pos;
|
|
ndspMaster.flags |= MFLAG_SURR_POS;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspSurroundSetRearRatio(u16 ratio)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.surround.rearRatio = ratio;
|
|
ndspMaster.flags |= MFLAG_SURR_RRATIO;
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspAuxSetEnable(int id, bool enable)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.aux[id].enable = enable ? 1 : 0;
|
|
ndspMaster.flags |= MFLAG_AUX_ENABLE(id);
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspAuxSetFrontBypass(int id, bool bypass)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.aux[id].frontBypass = bypass ? 1 : 0;
|
|
ndspMaster.flags |= MFLAG_AUX_BYPASS(id);
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspAuxSetVolume(int id, float volume)
|
|
{
|
|
LightLock_Lock(&ndspMaster.lock);
|
|
ndspMaster.aux[id].volume = volume;
|
|
ndspMaster.flags |= MFLAG_AUX_VOLUME(id);
|
|
LightLock_Unlock(&ndspMaster.lock);
|
|
}
|
|
|
|
void ndspAuxSetCallback(int id, ndspAuxCallback callback, void* data)
|
|
{
|
|
ndspMaster.aux[id].callback = callback;
|
|
ndspMaster.aux[id].callbackData = data;
|
|
}
|