palib/source/arm7/as_lib7.c
2025-01-06 22:43:23 +00:00

544 lines
22 KiB
C

/*
Advanced Sound Library (ASlib)
------------------------------
file : sound7.c
author : Lasorsa Yohan (Noda)
description : ARM7 sound functions
history :
02/12/2007 - v1.0
= Original release
21/12/2007 - v1.1
= corrected arm7/amr9 initialization (fix M3S/R4 problems)
= fixed stereo detection problem (thanks to ThomasS)
credits :
- IPC sound system and streaming is based on cold_as_ice streaming example
- audio stream filling / mp3 decoding based on ThomasS mp3 decoding example
- ASM stereo desinterleave function was made by Thoduv
*/
#include <nds.h>
#include <string.h>
#include <PA_Transfer.h>
#include "helix/mp3dec.h"
#include "helix/mp3common.h"
#include "helix/real/coder.h"
#include <arm7/as_lib7.h>
// internal functions
void AS_InitMP3();
void AS_MP3Stop();
static void AS_StartTimer(int freq);
static void AS_SetTimer(int freq);
static void AS_StopTimer();
void AS_RegenStream();
void AS_RegenStreamCallback(s16 *stream, u32 numsamples);
void AS_StereoDesinterleave(s16 *input, s16 *outputL, s16 *outputR, u32 samples);
void AS_MP3ClearBuffers();
// variables for the mp3 player
HMP3Decoder hMP3Decoder;
MP3FrameInfo mp3FrameInfo;
u8 *readPtr;
int bytesLeft;
s16 audioBuf[AS_DECODEBUFFER_SIZE]; // buffer for the decoded mp3 audio
int nAudioBufStart;
int nAudioBuf;
u8 stereo;
// the sound engine, must be called each VBlank
void AS_SoundVBL()
{
int i;
// adjust master volume
if(IPC_Sound->chan[0].cmd & SNDCMD_SETMASTERVOLUME) {
REG_MASTER_VOLUME = SOUND_VOL(IPC_Sound->volume & 127);
IPC_Sound->chan[0].cmd &= ~SNDCMD_SETMASTERVOLUME;
}
// manage sounds
for(i = 0; i < 16; i++) {
if(IPC_Sound->chan[i].cmd & SNDCMD_DELAY)
{
if(IPC_Sound->chan[i].snd.delay == 0) {
IPC_Sound->chan[i].cmd &= ~SNDCMD_DELAY;
IPC_Sound->chan[i].cmd |= SNDCMD_PLAY;
} else {
IPC_Sound->chan[i].snd.delay -= 1;
}
}
if(IPC_Sound->chan[i].cmd & SNDCMD_STOP)
{
SCHANNEL_CR(i) = 0;
IPC_Sound->chan[i].cmd &= ~SNDCMD_STOP;
}
if(IPC_Sound->chan[i].cmd & SNDCMD_PLAY)
{
SCHANNEL_TIMER(i) = 0x10000 - (0x1000000 / IPC_Sound->chan[i].snd.rate);
SCHANNEL_SOURCE(i) = (u32)IPC_Sound->chan[i].snd.data;
SCHANNEL_REPEAT_POINT(i) = 0;
SCHANNEL_LENGTH(i) = IPC_Sound->chan[i].snd.size >> 2 ;
SCHANNEL_CR(i) = SCHANNEL_ENABLE | ((SOUND_ONE_SHOT) >> (IPC_Sound->chan[i].snd.loop)) | SOUND_VOL(IPC_Sound->chan[i].volume & 127) | SOUND_PAN(IPC_Sound->chan[i].pan & 127) | ((IPC_Sound->chan[i].snd.format) << 29);
IPC_Sound->chan[i].cmd &= ~SNDCMD_PLAY;
}
if(IPC_Sound->chan[i].cmd & SNDCMD_SETVOLUME)
{
SCHANNEL_VOL(i) = IPC_Sound->chan[i].volume & 127;
IPC_Sound->chan[i].cmd &= ~SNDCMD_SETVOLUME;
}
if(IPC_Sound->chan[i].cmd & SNDCMD_SETPAN)
{
SCHANNEL_PAN(i) = IPC_Sound->chan[i].pan & 127;
IPC_Sound->chan[i].cmd &= ~SNDCMD_SETPAN;
}
if(IPC_Sound->chan[i].cmd & SNDCMD_SETRATE)
{
SCHANNEL_TIMER(i) = 0x10000 - (0x1000000 / IPC_Sound->chan[i].snd.rate);
IPC_Sound->chan[i].cmd &= ~SNDCMD_SETRATE;
}
IPC_Sound->chan[i].busy = SCHANNEL_CR(i) >> 31;
}
}
// the mp3 decoding engine, must be called on a regular basis (like after VBlank)
void AS_MP3Engine()
{
s32 curtimer, numsamples;
// All MP3 handling code needs to go here to avoid race conditions
if (IPC_Sound->mp3.cmd & MP3CMD_INIT)
{
AS_InitMP3();
IPC_Sound->mp3.cmd &= ~MP3CMD_INIT;
}
if (IPC_Sound->mp3.cmd & MP3CMD_SETRATE)
{
IPC_Sound->mp3.cmd &= ~MP3CMD_SETRATE;
AS_SetTimer(IPC_Sound->mp3.rate);
SCHANNEL_TIMER(IPC_Sound->mp3.channelL) = 0x10000 - (0x1000000 / IPC_Sound->mp3.rate);
SCHANNEL_TIMER(IPC_Sound->mp3.channelR) = 0x10000 - (0x1000000 / IPC_Sound->mp3.rate);
}
if (IPC_Sound->mp3.cmd & MP3CMD_PAUSE)
{
IPC_Sound->mp3.cmd &= ~MP3CMD_PAUSE;
// disable mp3 channels
SCHANNEL_CR(IPC_Sound->mp3.channelL) = 0;
SCHANNEL_CR(IPC_Sound->mp3.channelR) = 0;
AS_StopTimer();
// then wait for the restart
IPC_Sound->mp3.cmd |= MP3CMD_WAITING;
IPC_Sound->mp3.state = MP3ST_PAUSED;
}
if (IPC_Sound->mp3.cmd & MP3CMD_STOP)
{
IPC_Sound->mp3.cmd &= ~MP3CMD_STOP;
AS_MP3Stop();
return;
}
if (IPC_Sound->mp3.cmd & MP3CMD_PLAY)
{
IPC_Sound->mp3.cmd &= ~MP3CMD_PLAY;
if(IPC_Sound->mp3.state == MP3ST_PAUSED) {
// restart on a fresh basis
IPC_Sound->mp3.prevtimer = 0;
AS_RegenStreamCallback((s16*)IPC_Sound->mp3.mixbuffer, IPC_Sound->mp3.buffersize >> 1);
IPC_Sound->mp3.soundcursor = IPC_Sound->mp3.buffersize >> 1;
AS_StartTimer(IPC_Sound->mp3.rate);
IPC_Sound->mp3.cmd |= MP3CMD_MIX;
} else {
// set variables
IPC_Sound->mp3.prevtimer = 0;
IPC_Sound->mp3.numsamples = 0;
readPtr = IPC_Sound->mp3.mp3buffer;
bytesLeft = IPC_Sound->mp3.mp3filesize;
nAudioBuf = 0;
nAudioBufStart = 0;
// find the first sync word
u32 offset = MP3FindSyncWord(readPtr, bytesLeft);
readPtr += offset;
bytesLeft -= offset;
// gather information about the format
MP3GetNextFrameInfo(hMP3Decoder, &mp3FrameInfo, readPtr);
stereo = mp3FrameInfo.nChans >> 1;
// fill the half of the buffer
AS_RegenStreamCallback((s16*)IPC_Sound->mp3.mixbuffer, IPC_Sound->mp3.buffersize >> 1);
IPC_Sound->mp3.soundcursor = IPC_Sound->mp3.buffersize >> 1;
// set the mp3 to play at its original sampling rate
MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);
IPC_Sound->mp3.rate = mp3FrameInfo.samprate;
AS_StartTimer(mp3FrameInfo.samprate);
// start playing
IPC_Sound->mp3.cmd |= MP3CMD_MIX;
}
IPC_Sound->mp3.state = MP3ST_PLAYING;
}
// do the decoding
if (IPC_Sound->mp3.cmd & MP3CMD_MIXING)
{
curtimer = TIMER1_DATA;
if (IPC_Sound->mp3.cmd & MP3CMD_WAITING) {
IPC_Sound->mp3.cmd &= ~MP3CMD_WAITING;
} else {
numsamples = curtimer - IPC_Sound->mp3.prevtimer;
if(numsamples < 0)
numsamples += 65536;
IPC_Sound->mp3.numsamples = numsamples;
}
IPC_Sound->mp3.prevtimer = curtimer;
AS_RegenStream();
IPC_Sound->mp3.soundcursor += IPC_Sound->mp3.numsamples;
if (IPC_Sound->mp3.soundcursor > IPC_Sound->mp3.buffersize)
IPC_Sound->mp3.soundcursor -= IPC_Sound->mp3.buffersize;
}
else if(IPC_Sound->mp3.cmd & MP3CMD_MIX)
{
// set up the left channel
IPC_Sound->chan[IPC_Sound->mp3.channelL].snd.data = (u8*)IPC_Sound->mp3.mixbuffer;
IPC_Sound->chan[IPC_Sound->mp3.channelL].snd.size = IPC_Sound->mp3.buffersize << 1;
IPC_Sound->chan[IPC_Sound->mp3.channelL].snd.format = AS_PCM_16BIT;
IPC_Sound->chan[IPC_Sound->mp3.channelL].snd.rate = IPC_Sound->mp3.rate;
IPC_Sound->chan[IPC_Sound->mp3.channelL].snd.loop = true;
IPC_Sound->chan[IPC_Sound->mp3.channelL].snd.delay = 0;
IPC_Sound->chan[IPC_Sound->mp3.channelL].cmd |= SNDCMD_PLAY;
// set up the right channel
IPC_Sound->chan[IPC_Sound->mp3.channelR].snd.data = (stereo ? (u8*)IPC_Sound->mp3.mixbuffer + (IPC_Sound->mp3.buffersize << 1) : (u8*)IPC_Sound->mp3.mixbuffer);
IPC_Sound->chan[IPC_Sound->mp3.channelR].snd.size = IPC_Sound->mp3.buffersize << 1;
IPC_Sound->chan[IPC_Sound->mp3.channelR].snd.format = AS_PCM_16BIT;
IPC_Sound->chan[IPC_Sound->mp3.channelR].snd.rate = IPC_Sound->mp3.rate;
IPC_Sound->chan[IPC_Sound->mp3.channelR].snd.loop = true;
IPC_Sound->chan[IPC_Sound->mp3.channelR].snd.delay = IPC_Sound->mp3.delay;
IPC_Sound->chan[IPC_Sound->mp3.channelR].cmd |= SNDCMD_DELAY;
IPC_Sound->mp3.cmd &= ~MP3CMD_MIX;
IPC_Sound->mp3.cmd |= MP3CMD_MIXING;
}
}
void AS_StartTimer(int freq)
{
if(freq == 0)
return;
TIMER1_CR = 0;
TIMER0_CR = 0;
TIMER0_DATA = 0x10000 - (0x1000000 / freq) * 2;
TIMER1_DATA = 0;
TIMER1_CR = TIMER_ENABLE | TIMER_CASCADE | TIMER_DIV_1;
TIMER0_CR = TIMER_ENABLE | TIMER_DIV_1;
}
// set timer to the given period
void AS_SetTimer(int freq)
{
if(freq == 0)
return;
// Timer 0 is the one that controls the frequency, timer 1 is used as a
// reference to know how many samples need to be generated. It is
// important to not reset it to 0 or the decoder loop won't know how
// much to decode next time.
TIMER0_DATA = 0x10000 - (0x1000000 / freq) * 2;
//TIMER1_DATA = 0;
}
void AS_StopTimer()
{
TIMER1_CR = 0;
TIMER0_CR = 0;
}
// clear some buffers to avoid clicking on new mp3 start
inline void AS_MP3ClearBuffers(){
MP3DecInfo *mp3DecInfo = (MP3DecInfo*)hMP3Decoder;
memset(mp3DecInfo->FrameHeaderPS, 0, sizeof(FrameHeader));
memset(mp3DecInfo->SideInfoPS, 0, sizeof(SideInfo));
memset(mp3DecInfo->ScaleFactorInfoPS, 0, sizeof(ScaleFactorInfo));
memset(mp3DecInfo->HuffmanInfoPS, 0, sizeof(HuffmanInfo));
memset(mp3DecInfo->DequantInfoPS, 0, sizeof(DequantInfo));
memset(mp3DecInfo->IMDCTInfoPS, 0, sizeof(IMDCTInfo));
memset(mp3DecInfo->SubbandInfoPS, 0, sizeof(SubbandInfo));
memset(mp3DecInfo->IMDCTInfoPS, 0, sizeof(IMDCTInfo));
memset(mp3DecInfo->SubbandInfoPS, 0, sizeof(SubbandInfo));
}
// fill the given stream buffer with numsamples samples. (based on ThomasS code)
void AS_RegenStreamCallback(s16 *stream, u32 numsamples)
{
int outSample, restSample, minSamples, offset, err;
outSample = 0;
// fill buffered data into stream
minSamples = MIN(nAudioBuf, (int)numsamples);
if (minSamples > 0) {
// copy data from audioBuf to stream
numsamples -= minSamples;
if(stereo)
AS_StereoDesinterleave(audioBuf + nAudioBufStart, stream + outSample, stream + outSample + IPC_Sound->mp3.buffersize, minSamples);
else
memcpy(stream + outSample, audioBuf + nAudioBufStart, minSamples * sizeof(s16));
outSample += minSamples;
nAudioBufStart += (stereo ? minSamples << 1 : minSamples);
nAudioBuf -= minSamples;
if (nAudioBuf <= 0) {
nAudioBufStart = 0;
nAudioBuf = 0;
}
}
// if more data is still needed then decode some mp3 frames
if (numsamples > 0) {
// if mp3 is set to loop indefinitely, don't bother with how many data is left
if((bytesLeft < 2*MAINBUF_SIZE) && IPC_Sound->mp3.loop && IPC_Sound->mp3.stream)
bytesLeft += IPC_Sound->mp3.mp3filesize;
// decode a mp3 frame to outBuf
do {
// find the start of the next MP3 frame (assume EOF if no sync found)
offset = MP3FindSyncWord(readPtr, bytesLeft);
if (offset < 0) {
// if mp3 is set to loop & no frame is found, retry from the start
if(IPC_Sound->mp3.loop && !IPC_Sound->mp3.stream) {
bytesLeft = IPC_Sound->mp3.mp3filesize;
readPtr = IPC_Sound->mp3.mp3buffer;
offset = MP3FindSyncWord(readPtr, bytesLeft);
} else {
AS_MP3Stop();
IPC_Sound->mp3.state = MP3ST_OUT_OF_DATA;
}
return;
}
readPtr += offset;
bytesLeft -= offset;
// decode one MP3 frame to the audio buffer
err = MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, audioBuf, 0);
if (err) {
AS_MP3Stop();
IPC_Sound->mp3.state = MP3ST_DECODE_ERROR;
return;
}
// copy the decoded data to the stream
MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);
if(stereo)
mp3FrameInfo.outputSamps = mp3FrameInfo.outputSamps >> 1;
minSamples = MIN(mp3FrameInfo.outputSamps, (int)numsamples);
restSample = mp3FrameInfo.outputSamps - numsamples;
numsamples -= minSamples;
if(stereo)
AS_StereoDesinterleave(audioBuf, stream + outSample, stream + outSample + IPC_Sound->mp3.buffersize, minSamples);
else
memcpy(stream + outSample, audioBuf, minSamples * sizeof(s16));
outSample += minSamples;
// if still more data is needed, then decode the next frame
} while (numsamples > 0);
// set the rest of the decoded data to be used for the next frame
nAudioBufStart = (stereo ? minSamples << 1 : minSamples);
nAudioBuf = restSample;
}
// check if we moved onto the 2nd file data buffer, if so move it to the 1st one and request a refill
if(IPC_Sound->mp3.stream && (readPtr >= IPC_Sound->mp3.mp3buffer + IPC_Sound->mp3.mp3buffersize )) {
memcpy(IPC_Sound->mp3.mp3buffer, IPC_Sound->mp3.mp3buffer + IPC_Sound->mp3.mp3buffersize, IPC_Sound->mp3.mp3buffersize);
readPtr = readPtr - IPC_Sound->mp3.mp3buffersize;
IPC_Sound->mp3.needdata = true;
}
}
// regenerate the sound stream into the ring buffer.
void AS_RegenStream()
{
int remain;
// decode data to the ring buffer
if((IPC_Sound->mp3.soundcursor + IPC_Sound->mp3.numsamples) >= IPC_Sound->mp3.buffersize) {
AS_RegenStreamCallback((s16*)&IPC_Sound->mp3.mixbuffer[IPC_Sound->mp3.soundcursor << 1], IPC_Sound->mp3.buffersize - IPC_Sound->mp3.soundcursor);
remain = IPC_Sound->mp3.numsamples - (IPC_Sound->mp3.buffersize - IPC_Sound->mp3.soundcursor);
AS_RegenStreamCallback((s16*)IPC_Sound->mp3.mixbuffer, remain);
} else {
AS_RegenStreamCallback((s16*)&IPC_Sound->mp3.mixbuffer[IPC_Sound->mp3.soundcursor << 1], IPC_Sound->mp3.numsamples);
}
}
// stop playing the mp3
void AS_MP3Stop()
{
SCHANNEL_CR(IPC_Sound->mp3.channelL) = 0;
SCHANNEL_CR(IPC_Sound->mp3.channelR) = 0;
AS_StopTimer();
IPC_Sound->mp3.rate = 0;
IPC_Sound->mp3.cmd = MP3CMD_NONE;
IPC_Sound->mp3.state = MP3ST_STOPPED;
AS_MP3ClearBuffers();
}
// initialize the mp3 system
void AS_InitMP3()
{
// init the timers
AS_StopTimer();
// wait for the arm 9 to allocate data on main ram
while( !(IPC_Sound->mp3.cmd & MP3CMD_ARM9ALLOCDONE) )
swiWaitForVBlank();
// init the helix mp3 decoder
hMP3Decoder = MP3InitDecoder(IPC_Sound->mp3.alloc_ram);
}
// initialize the main system
void AS_Init()
{
// clear the sound structure
memset(IPC_Sound, 0, sizeof(IPC_SoundSystem));
// get the needed allocation size for the mp3 decoder
IPC_Sound->mp3.alloc_ram = (void*)getAllocationSize();
// tell the arm9 that we are ready
IPC_Sound->chan[0].cmd = SNDCMD_ARM7READY;
}
// desinterleave a stereo source (thanks to Thoduv for the code)
asm (
"@--------------------------------------------------------------------------------------\n"
" .text \n"
" .arm \n"
" \n"
"@ desinterleave an mp3 stereo source \n"
"@ r0 = interleaved data, r1 = left, r2 = right, r3 = len \n"
" \n"
" .global AS_StereoDesinterleave \n"
" \n"
"AS_StereoDesinterleave: \n"
" stmfd sp!, {r4-r11} \n"
" \n"
"_loop: \n"
" \n"
" ldmia r0!, {r4-r12} \n"
" \n"
" strh r4, [r1], #2 \n"
" mov r4, r4, lsr #16 \n"
" strh r4, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r5, [r1], #2 \n"
" mov r5, r5, lsr #16 \n"
" strh r5, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r6, [r1], #2 \n"
" mov r6, r6, lsr #16 \n"
" strh r6, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r7, [r1], #2 \n"
" mov r7, r7, lsr #16 \n"
" strh r7, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r8, [r1], #2 \n"
" mov r8, r8, lsr #16 \n"
" strh r8, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r9, [r1], #2 \n"
" mov r9, r9, lsr #16 \n"
" strh r9, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r10, [r1], #2 \n"
" mov r10, r10, lsr #16 \n"
" strh r10, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r11, [r1], #2 \n"
" mov r11, r11, lsr #16 \n"
" strh r11, [r2], #2 \n"
" subs r3, #1 \n"
" beq _done \n"
" \n"
" strh r12, [r1], #2 \n"
" mov r12, r12, lsr #16 \n"
" strh r12, [r2], #2 \n"
" subs r3, #1 \n"
" bne _loop \n"
"_done: \n"
" \n"
" ldmia sp!, {r4-r11} \n"
" bx lr \n"
"@--------------------------------------------------------------------------------------\n"
);