#include <3ds.h> #include #include #include #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; }