240 lines
5.0 KiB
C
240 lines
5.0 KiB
C
#include <3ds.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "all.h"
|
|
#include "error.h"
|
|
#include "file.h"
|
|
#include "flac.h"
|
|
#include "m4a.h"
|
|
#include "mp3.h"
|
|
#include "opus.h"
|
|
#include "playback.h"
|
|
#include "vorbis.h"
|
|
#include "wav.h"
|
|
#include "sid.h"
|
|
|
|
static volatile bool stop = true;
|
|
|
|
/**
|
|
* Pause or play current file.
|
|
*
|
|
* \return True if paused.
|
|
*/
|
|
bool togglePlayback(void)
|
|
{
|
|
bool paused = ndspChnIsPaused(CHANNEL);
|
|
ndspChnSetPaused(CHANNEL, !paused);
|
|
return !paused;
|
|
}
|
|
|
|
/**
|
|
* Stops current playback. Playback thread should exit as a result.
|
|
*/
|
|
void stopPlayback(void)
|
|
{
|
|
stop = true;
|
|
}
|
|
|
|
/**
|
|
* Returns whether music is playing or paused.
|
|
*/
|
|
bool isPlaying(void)
|
|
{
|
|
return !stop;
|
|
}
|
|
|
|
/**
|
|
* Returns whether playback is currently paused.
|
|
*/
|
|
bool isPaused(void)
|
|
{
|
|
if(stop)
|
|
return false;
|
|
return ndspChnIsPaused(CHANNEL);
|
|
}
|
|
|
|
/**
|
|
* Should only be called from a new thread only, and have only one playback
|
|
* thread at time. This function has not been written for more than one
|
|
* playback thread in mind.
|
|
*
|
|
* \param infoIn Playback information.
|
|
*/
|
|
void playFile(void* infoIn)
|
|
{
|
|
struct decoder_fn decoder = { 0 };
|
|
struct playbackInfo_t* info = infoIn;
|
|
int16_t* buffer1 = NULL;
|
|
int16_t* buffer2 = NULL;
|
|
ndspWaveBuf waveBuf[2];
|
|
bool lastbuf = false;
|
|
int ret = -1;
|
|
const char* file = info->file;
|
|
bool isNdspInit = false;
|
|
|
|
/* Reset previous stop command */
|
|
stop = false;
|
|
|
|
switch(getFileType(file))
|
|
{
|
|
case FILE_TYPE_WAV:
|
|
setWav(&decoder);
|
|
break;
|
|
|
|
case FILE_TYPE_FLAC:
|
|
setFlac(&decoder);
|
|
break;
|
|
|
|
case FILE_TYPE_OPUS:
|
|
setOpus(&decoder);
|
|
break;
|
|
|
|
case FILE_TYPE_MP3:
|
|
setMp3(&decoder);
|
|
break;
|
|
|
|
case FILE_TYPE_VORBIS:
|
|
setVorbis(&decoder);
|
|
break;
|
|
|
|
case FILE_TYPE_SID:
|
|
setSid(&decoder);
|
|
break;
|
|
|
|
case FILE_TYPE_M4A:
|
|
case FILE_TYPE_AAC:
|
|
setM4a(&decoder);
|
|
break;
|
|
|
|
default:
|
|
goto err;
|
|
} if(ndspInit() < 0)
|
|
{
|
|
errno = NDSP_INIT_FAIL;
|
|
goto err;
|
|
}
|
|
|
|
isNdspInit = true;
|
|
|
|
if((ret = (*decoder.init)(file)) != 0)
|
|
{
|
|
errno = DECODER_INIT_FAIL;
|
|
goto err;
|
|
}
|
|
|
|
if((*decoder.channels)() > 2 || (*decoder.channels)() < 1)
|
|
{
|
|
errno = UNSUPPORTED_CHANNELS;
|
|
goto err;
|
|
}
|
|
|
|
if(decoder.getFileSamples != NULL)
|
|
info->samples_total = decoder.getFileSamples();
|
|
|
|
info->samples_per_second = decoder.rate() * decoder.channels();
|
|
buffer1 = linearAlloc(decoder.buffSize * sizeof(int16_t));
|
|
buffer2 = linearAlloc(decoder.buffSize * sizeof(int16_t));
|
|
|
|
ndspChnReset(CHANNEL);
|
|
ndspChnWaveBufClear(CHANNEL);
|
|
ndspSetOutputMode(NDSP_OUTPUT_STEREO);
|
|
ndspChnSetInterp(CHANNEL, NDSP_INTERP_POLYPHASE);
|
|
ndspChnSetRate(CHANNEL, (*decoder.rate)());
|
|
ndspChnSetFormat(CHANNEL,
|
|
(*decoder.channels)() == 2 ? NDSP_FORMAT_STEREO_PCM16 :
|
|
NDSP_FORMAT_MONO_PCM16);
|
|
|
|
memset(waveBuf, 0, sizeof(waveBuf));
|
|
waveBuf[0].nsamples = (*decoder.decode)(&buffer1[0]) / (*decoder.channels)();
|
|
waveBuf[0].data_vaddr = &buffer1[0];
|
|
ndspChnWaveBufAdd(CHANNEL, &waveBuf[0]);
|
|
|
|
waveBuf[1].nsamples = (*decoder.decode)(&buffer2[0]) / (*decoder.channels)();
|
|
waveBuf[1].data_vaddr = &buffer2[0];
|
|
ndspChnWaveBufAdd(CHANNEL, &waveBuf[1]);
|
|
|
|
/**
|
|
* There may be a chance that the music has not started by the time we get
|
|
* to the while loop. So we ensure that music has started here.
|
|
*/
|
|
while(ndspChnIsPlaying(CHANNEL) == false);
|
|
|
|
while(stop == false)
|
|
{
|
|
svcSleepThread(100 * 1000);
|
|
|
|
/* When the last buffer has finished playing, break. */
|
|
if(lastbuf == true && waveBuf[0].status == NDSP_WBUF_DONE &&
|
|
waveBuf[1].status == NDSP_WBUF_DONE)
|
|
break;
|
|
|
|
if(ndspChnIsPaused(CHANNEL) == true || lastbuf == true)
|
|
continue;
|
|
|
|
if(waveBuf[0].status == NDSP_WBUF_DONE)
|
|
{
|
|
size_t read = (*decoder.decode)(&buffer1[0]);
|
|
/* The previous block of samples have finished playing,
|
|
* so accumulate them here. */
|
|
info->samples_played += waveBuf[0].nsamples * decoder.channels();
|
|
|
|
if(read <= 0)
|
|
{
|
|
lastbuf = true;
|
|
continue;
|
|
}
|
|
else if(read < decoder.buffSize)
|
|
waveBuf[0].nsamples = read / (*decoder.channels)();
|
|
|
|
ndspChnWaveBufAdd(CHANNEL, &waveBuf[0]);
|
|
}
|
|
|
|
if(waveBuf[1].status == NDSP_WBUF_DONE)
|
|
{
|
|
size_t read = (*decoder.decode)(&buffer2[0]);
|
|
info->samples_played += waveBuf[0].nsamples * decoder.channels();
|
|
|
|
if(read <= 0)
|
|
{
|
|
lastbuf = true;
|
|
continue;
|
|
}
|
|
else if(read < decoder.buffSize)
|
|
waveBuf[1].nsamples = read / (*decoder.channels)();
|
|
|
|
ndspChnWaveBufAdd(CHANNEL, &waveBuf[1]);
|
|
}
|
|
|
|
DSP_FlushDataCache(buffer1, decoder.buffSize * sizeof(int16_t));
|
|
DSP_FlushDataCache(buffer2, decoder.buffSize * sizeof(int16_t));
|
|
}
|
|
|
|
info->samples_played += waveBuf[0].nsamples * decoder.channels();
|
|
info->samples_played += waveBuf[0].nsamples * decoder.channels();
|
|
|
|
(*decoder.exit)();
|
|
out:
|
|
if(isNdspInit == true)
|
|
{
|
|
ndspChnWaveBufClear(CHANNEL);
|
|
ndspExit();
|
|
}
|
|
|
|
linearFree(buffer1);
|
|
linearFree(buffer2);
|
|
|
|
/* Signal Watchdog thread that we've stopped playing */
|
|
*info->errInfo->error = -1;
|
|
svcSignalEvent(*info->errInfo->failEvent);
|
|
|
|
threadExit(0);
|
|
return;
|
|
|
|
err:
|
|
*info->errInfo->error = errno;
|
|
svcSignalEvent(*info->errInfo->failEvent);
|
|
goto out;
|
|
}
|