217 lines
5.9 KiB
C
217 lines
5.9 KiB
C
#include <ultra64.h>
|
|
#include "constants.h"
|
|
#include "bss.h"
|
|
#include "lib/audiodma.h"
|
|
#include "data.h"
|
|
#include "types.h"
|
|
|
|
#define ADMA_MAX_ITEMS 80
|
|
#define ADMA_ITEM_SIZE 0x400
|
|
|
|
struct admaitem {
|
|
ALLink node;
|
|
s32 startaddr;
|
|
s32 lastframe;
|
|
u8 *ptr;
|
|
};
|
|
|
|
struct admastate {
|
|
u8 initialised;
|
|
struct admaitem *firstused;
|
|
struct admaitem *firstfree;
|
|
};
|
|
|
|
s32 g_AdmaNumItemsThisFrame = 0;
|
|
|
|
struct admastate g_AdmaState;
|
|
struct admaitem g_AdmaItems[ADMA_MAX_ITEMS];
|
|
OSIoMesg g_AdmaIoMsgs[ADMA_MAX_ITEMS];
|
|
OSMesgQueue g_AdmaMesgQueue;
|
|
OSMesg g_AdmaMesgs[ADMA_MAX_ITEMS];
|
|
u32 g_AdmaCurFrame;
|
|
|
|
void admaInit(void)
|
|
{
|
|
osCreateMesgQueue(&g_AdmaMesgQueue, g_AdmaMesgs, ARRAYCOUNT(g_AdmaMesgs));
|
|
}
|
|
|
|
/**
|
|
* This routine handles the DMA'ing of samples from ROM to RAM. First it checks
|
|
* the current buffers to see if the samples needed are already in place.
|
|
* Because buffers are linked sequentially by the addresses where the samples
|
|
* are on ROM, it doesn't need to check all of them, only up to the address that
|
|
* that it needs. If it finds one, it returns the address of that buffer. If it
|
|
* doesn't find the samples that it needs, it will initiate a DMA of the samples
|
|
* that it needs. In either case, it updates the lastframe variable, to indicate
|
|
* that this buffer was last used in this frame. This is important for the
|
|
* admaBeginFrame routine.
|
|
*/
|
|
s32 admaExec(s32 offset, s32 len, void *state)
|
|
{
|
|
void *foundbuffer;
|
|
s32 delta;
|
|
struct admaitem *item = g_AdmaState.firstused;
|
|
struct admaitem *lastitem = NULL;
|
|
s32 end = offset + len;
|
|
s32 buffend;
|
|
|
|
// Check to see if a buffer already contains the sample
|
|
while (item) {
|
|
buffend = item->startaddr + ADMA_ITEM_SIZE;
|
|
|
|
if (item->startaddr > offset) {
|
|
break;
|
|
}
|
|
|
|
if (end <= buffend) {
|
|
item->lastframe = g_AdmaCurFrame;
|
|
foundbuffer = item->ptr + offset - item->startaddr;
|
|
|
|
return osVirtualToPhysical(foundbuffer);
|
|
}
|
|
|
|
lastitem = item;
|
|
item = (struct admaitem *) item->node.next;
|
|
}
|
|
|
|
// We'll have to DMA it
|
|
item = g_AdmaState.firstfree;
|
|
|
|
// If there are no free slots, the N64 SDK's demo program instructs you to
|
|
// "send back a bogus pointer, it's better than nothing".
|
|
// @bug: This can make incorrect audio play when the DMA queue is full.
|
|
if (item == NULL) {
|
|
return osVirtualToPhysical(g_AdmaState.firstused);
|
|
}
|
|
|
|
g_AdmaState.firstfree = (struct admaitem *) item->node.next;
|
|
alUnlink(&item->node);
|
|
|
|
if (lastitem) {
|
|
// Insert in the middle or at the tail of the list.
|
|
// (The list is sorted by ROM offset, and lastitem is the
|
|
// last item prior to the ROM offset of the current DMA.)
|
|
alLink(&item->node, &lastitem->node);
|
|
} else if (g_AdmaState.firstused) {
|
|
// Prepend to the head of the list
|
|
lastitem = g_AdmaState.firstused;
|
|
g_AdmaState.firstused = item;
|
|
item->node.next = &lastitem->node;
|
|
item->node.prev = NULL;
|
|
lastitem->node.prev = &item->node;
|
|
} else {
|
|
// The list was empty
|
|
g_AdmaState.firstused = item;
|
|
item->node.next = NULL;
|
|
item->node.prev = NULL;
|
|
}
|
|
|
|
foundbuffer = item->ptr;
|
|
delta = offset & 1;
|
|
offset -= delta;
|
|
item->startaddr = offset;
|
|
item->lastframe = g_AdmaCurFrame;
|
|
|
|
osInvalDCache((void *) PHYS_TO_K0(offset), ADMA_ITEM_SIZE);
|
|
|
|
osPiStartDma(&g_AdmaIoMsgs[g_AdmaNumItemsThisFrame++], OS_MESG_PRI_HIGH, OS_READ, offset, foundbuffer, ADMA_ITEM_SIZE, &g_AdmaMesgQueue);
|
|
|
|
return osVirtualToPhysical(foundbuffer) + delta;
|
|
}
|
|
|
|
/**
|
|
* Initialize the DMA buffers and return the address of the procedure that will
|
|
* be used to DMA the samples from ROM to RAM. This routine will be called once
|
|
* for each physical voice that is created. In this case, because we know where
|
|
* all the buffers are, and since they are not attached to a specific voice, we
|
|
* will only really do any initialization the first time. After that we just
|
|
* return the address to the DMA routine.
|
|
*/
|
|
void *admaNew(struct admastate **state)
|
|
{
|
|
#if PAL
|
|
s32 max = ADMA_MAX_ITEMS;
|
|
#else
|
|
s32 max = IS4MB() ? ADMA_MAX_ITEMS - 20 : ADMA_MAX_ITEMS;
|
|
#endif
|
|
s32 i;
|
|
|
|
if (!g_AdmaState.initialised) {
|
|
g_AdmaState.firstfree = g_AdmaItems;
|
|
|
|
for (i = 0; i < max - 1; i++) {
|
|
alLink(&g_AdmaItems[i + 1].node, &g_AdmaItems[i].node);
|
|
g_AdmaItems[i].ptr = alHeapAlloc(&g_SndHeap, 1, ADMA_ITEM_SIZE);
|
|
}
|
|
|
|
g_AdmaState.initialised = true;
|
|
}
|
|
|
|
*state = &g_AdmaState;
|
|
|
|
return &admaExec;
|
|
}
|
|
|
|
/**
|
|
* Remove old items from the list, increment the
|
|
* frame counter and reset the number of items to 0.
|
|
*/
|
|
void admaBeginFrame(void)
|
|
{
|
|
struct admaitem *item = g_AdmaState.firstused;
|
|
|
|
while (item) {
|
|
struct admaitem *next = (struct admaitem *) item->node.next;
|
|
|
|
// If item->lastframe is 0xfffffffe then that value + 1 will never
|
|
// be less than g_AdmaCurFrame and the item would never be removed from
|
|
// the list. But it would take over 2 years of play time for this
|
|
// situation to occur, so not flagging this as a bug.
|
|
if (item->lastframe + 1 < g_AdmaCurFrame) {
|
|
// Remove it
|
|
if (g_AdmaState.firstused == item) {
|
|
g_AdmaState.firstused = (struct admaitem *) item->node.next;
|
|
}
|
|
|
|
alUnlink(&item->node);
|
|
|
|
if (g_AdmaState.firstfree) {
|
|
alLink(&item->node, &g_AdmaState.firstfree->node);
|
|
} else {
|
|
g_AdmaState.firstfree = item;
|
|
item->node.next = NULL;
|
|
item->node.prev = NULL;
|
|
}
|
|
}
|
|
|
|
item = next;
|
|
}
|
|
|
|
g_AdmaCurFrame++;
|
|
g_AdmaNumItemsThisFrame = 0;
|
|
}
|
|
|
|
/**
|
|
* Receive all messages from the message queue.
|
|
*
|
|
* The message queue isn't really used. The audio DMA system just wants to fire
|
|
* off DMA requests and then forget about them. If the data arrives in time for
|
|
* playback then great, otherwise nothing can be done. But osPiStartDma requires
|
|
* a message queue, and that means the messages have to be read off it at some
|
|
* point. This is done here.
|
|
*/
|
|
void admaReceiveAll(void)
|
|
{
|
|
s32 i;
|
|
|
|
/**
|
|
* Don't block here. If dma's aren't complete, you've had an audio
|
|
* overrun. Bad news, but go for it anyway, and try and recover.
|
|
*/
|
|
for (i = 0; i < g_AdmaNumItemsThisFrame; i++) {
|
|
if (osRecvMesg(&g_AdmaMesgQueue, 0, OS_MESG_NOBLOCK) == -1) {
|
|
// empty
|
|
}
|
|
}
|
|
}
|