mirror of
https://github.com/zeldaret/oot.git
synced 2025-06-08 01:21:52 +00:00
* Cleanup includes in header files * include libc headers without libc/ prefix * fix * fix2 * fix3 * fix4 * some bss lol * bss * fix
221 lines
7 KiB
C
221 lines
7 KiB
C
#include "libaudio.h"
|
|
#include "synthInternals.h"
|
|
#include "stddef.h"
|
|
|
|
#ifndef MIN
|
|
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
|
#endif
|
|
|
|
static s32 __nextSampleTime(ALSynth* drvr, ALPlayer** client);
|
|
static s32 _timeToSamplesNoRound(ALSynth* synth, s32 micros);
|
|
|
|
void alSynNew(ALSynth* drvr, ALSynConfig* c) {
|
|
s32 i;
|
|
ALVoice* vv;
|
|
PVoice* pv;
|
|
ALVoice* vvoices;
|
|
PVoice* pvoices;
|
|
ALHeap* hp = c->heap;
|
|
ALSave* save;
|
|
ALFilter* sources;
|
|
ALParam* params;
|
|
ALParam* paramPtr;
|
|
|
|
drvr->head = NULL;
|
|
drvr->numPVoices = c->maxPVoices;
|
|
drvr->curSamples = 0;
|
|
drvr->paramSamples = 0;
|
|
drvr->outputRate = c->outputRate;
|
|
drvr->maxOutSamples = AL_MAX_RSP_SAMPLES;
|
|
drvr->dma = (ALDMANew)c->dmaproc;
|
|
|
|
save = alHeapAlloc(hp, 1, sizeof(ALSave));
|
|
alSaveNew(save);
|
|
drvr->outputFilter = &save->filter;
|
|
|
|
// allocate and initialize the auxilliary effects bus. at present we only support 1 effects bus.
|
|
drvr->auxBus = alHeapAlloc(hp, 1, sizeof(ALAuxBus));
|
|
drvr->maxAuxBusses = 1;
|
|
sources = alHeapAlloc(hp, c->maxPVoices, sizeof(ALFilter*));
|
|
alAuxBusNew(drvr->auxBus, sources, c->maxPVoices);
|
|
|
|
// allocate and initialize the main bus.
|
|
drvr->mainBus = alHeapAlloc(hp, 1, sizeof(ALMainBus));
|
|
sources = alHeapAlloc(hp, c->maxPVoices, sizeof(ALFilter*));
|
|
alMainBusNew(drvr->mainBus, sources, c->maxPVoices);
|
|
|
|
if (c->fxType != AL_FX_NONE) {
|
|
// Allocate an effect and set parameters
|
|
alSynAllocFX(drvr, 0, c, hp);
|
|
} else {
|
|
// Connect the aux bus to the main bus
|
|
alMainBusParam(drvr->mainBus, AL_FILTER_ADD_SOURCE, &drvr->auxBus[0]);
|
|
}
|
|
|
|
// Build the physical voice lists
|
|
drvr->pFreeList.next = NULL;
|
|
drvr->pFreeList.prev = NULL;
|
|
drvr->pLameList.next = NULL;
|
|
drvr->pLameList.prev = NULL;
|
|
drvr->pAllocList.next = NULL;
|
|
drvr->pAllocList.prev = NULL;
|
|
|
|
pvoices = alHeapAlloc(hp, c->maxPVoices, sizeof(PVoice));
|
|
for (i = 0; i < c->maxPVoices; i++) {
|
|
pv = &pvoices[i];
|
|
alLink(&pv->node, &drvr->pFreeList);
|
|
pv->vvoice = NULL;
|
|
|
|
alLoadNew(&pv->decoder, drvr->dma, hp);
|
|
alLoadParam(&pv->decoder, AL_FILTER_SET_SOURCE, NULL);
|
|
|
|
alResampleNew(&pv->resampler, hp);
|
|
alResampleParam(&pv->resampler, AL_FILTER_SET_SOURCE, &pv->decoder);
|
|
|
|
alEnvmixerNew(&pv->envmixer, hp);
|
|
alEnvmixerParam(&pv->envmixer, AL_FILTER_SET_SOURCE, &pv->resampler);
|
|
|
|
alAuxBusParam(drvr->auxBus, AL_FILTER_ADD_SOURCE, &pv->envmixer);
|
|
|
|
pv->channelKnob = &pv->envmixer.filter;
|
|
}
|
|
|
|
alSaveParam(save, AL_FILTER_SET_SOURCE, drvr->mainBus);
|
|
|
|
// build the parameter update list
|
|
params = alHeapAlloc(hp, c->maxUpdates, sizeof(ALParam));
|
|
drvr->paramList = NULL;
|
|
for (i = 0; i < c->maxUpdates; i++) {
|
|
paramPtr = ¶ms[i];
|
|
paramPtr->next = drvr->paramList;
|
|
drvr->paramList = paramPtr;
|
|
}
|
|
drvr->heap = hp;
|
|
}
|
|
|
|
/**
|
|
* slAudioFrame() is called every video frame, and is based on the video
|
|
* frame interrupt. It is assumed to be an accurate time source for the
|
|
* clients.
|
|
*/
|
|
Acmd* alAudioFrame(Acmd* cmdList, s32* cmdLen, s16* outBuf, s32 outLen) {
|
|
ALPlayer* client;
|
|
ALFilter* output;
|
|
ALSynth* drvr = &alGlobals->drvr;
|
|
s16 tmp = 0; // Starting buffer in DMEM
|
|
Acmd* cmdlEnd = cmdList;
|
|
Acmd* cmdPtr;
|
|
s32 nOut;
|
|
s16* lOutBuf = outBuf;
|
|
|
|
if (drvr->head == NULL) {
|
|
// nothing to do
|
|
*cmdLen = 0;
|
|
return cmdList;
|
|
}
|
|
|
|
// run down list of clients and execute callback if needed this
|
|
// subframe. Here we do all the work for the frame at the
|
|
// start. Time offsets that occur before the next frame are
|
|
// executed "early".
|
|
//
|
|
// paramSamples = time of next parameter change.
|
|
// curSamples = current sample time.
|
|
// so paramSamples - curSamples is the time until the next parameter change.
|
|
// if the next parameter change occurs within this frame time (outLen),
|
|
// then call back the client that contains the parameter change.
|
|
// Note, paramSamples must be rounded down to 16 sample boundary for use
|
|
// during the client handler.
|
|
|
|
for (drvr->paramSamples = __nextSampleTime(drvr, &client); drvr->paramSamples - drvr->curSamples < outLen;
|
|
drvr->paramSamples = __nextSampleTime(drvr, &client)) {
|
|
drvr->paramSamples &= ~0xF;
|
|
client->samplesLeft += _timeToSamplesNoRound(drvr, (*client->handler)(client));
|
|
}
|
|
|
|
// for safety's sake, always store paramSamples aligned to 16 sample boundary.
|
|
// this way, if an voice handler routine gets called outside the ALVoiceHandler
|
|
// routine (alSynAllocVoice) it will get timestamped with an aligned value and
|
|
// will be processed immediately next audio frame.
|
|
drvr->paramSamples &= ~0xF;
|
|
|
|
// Now build the command list in small chunks
|
|
while (outLen > 0) {
|
|
nOut = MIN(drvr->maxOutSamples, outLen);
|
|
|
|
// construct the command list for each physical voice by calling the head of the filter chain.
|
|
cmdPtr = cmdlEnd;
|
|
aSegment(cmdPtr++, 0, 0);
|
|
output = drvr->outputFilter;
|
|
(*output->setParam)(output, AL_FILTER_SET_DRAM, lOutBuf);
|
|
cmdlEnd = (*output->handler)(output, &tmp, nOut, drvr->curSamples, cmdPtr);
|
|
|
|
outLen -= nOut;
|
|
lOutBuf += nOut << 1; // For Stereo
|
|
drvr->curSamples += nOut;
|
|
}
|
|
*cmdLen = (s32)(cmdlEnd - cmdList);
|
|
|
|
_collectPVoices(drvr); // collect free physical voices
|
|
return cmdlEnd;
|
|
}
|
|
|
|
ALParam* __allocParam(void) {
|
|
ALParam* update = NULL;
|
|
ALSynth* drvr = &alGlobals->drvr;
|
|
|
|
if (drvr->paramList != NULL) {
|
|
update = drvr->paramList;
|
|
drvr->paramList = drvr->paramList->next;
|
|
update->next = NULL;
|
|
}
|
|
return update;
|
|
}
|
|
|
|
void __freeParam(ALParam* param) {
|
|
ALSynth* drvr = &alGlobals->drvr;
|
|
param->next = drvr->paramList;
|
|
drvr->paramList = param;
|
|
}
|
|
|
|
void _collectPVoices(ALSynth* drvr) {
|
|
ALLink* dl;
|
|
PVoice* pv;
|
|
|
|
while ((dl = drvr->pLameList.next) != NULL) {
|
|
pv = (PVoice*)dl;
|
|
alUnlink(&pv->node);
|
|
alLink(&pv->node, &drvr->pFreeList);
|
|
}
|
|
}
|
|
|
|
void _freePVoice(ALSynth* drvr, PVoice* pvoice) {
|
|
// move the voice from the allocated list to the lame list
|
|
alUnlink(&pvoice->node);
|
|
alLink(&pvoice->node, &drvr->pLameList);
|
|
}
|
|
|
|
static s32 _timeToSamplesNoRound(ALSynth* synth, s32 micros) {
|
|
// Add 0.5 to adjust the average affect of the truncation error produced by casting a float to an int.
|
|
f32 tmp = ((f32)micros) * synth->outputRate / 1000000.0 + 0.5;
|
|
return (s32)tmp;
|
|
}
|
|
|
|
s32 _timeToSamples(ALSynth* synth, s32 micros) {
|
|
return _timeToSamplesNoRound(synth, micros) & ~0xF;
|
|
}
|
|
|
|
static s32 __nextSampleTime(ALSynth* drvr, ALPlayer** client) {
|
|
ALMicroTime delta = 0x7FFFFFFF; // max delta for s32
|
|
ALPlayer* cl;
|
|
|
|
*client = NULL;
|
|
|
|
for (cl = drvr->head; cl != NULL; cl = cl->next) {
|
|
if (cl->samplesLeft - drvr->curSamples < delta) {
|
|
*client = cl;
|
|
delta = cl->samplesLeft - drvr->curSamples;
|
|
}
|
|
}
|
|
return (*client)->samplesLeft;
|
|
}
|