Merge branch 'threads'
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Channel to play music on */
|
||||
#define CHANNEL 0x08
|
||||
@@ -11,6 +12,9 @@
|
||||
do { fprintf(stderr, "\nError %d:%s(): %s %s\n", __LINE__, __func__, \
|
||||
err, strerror(errno)); } while (0)
|
||||
|
||||
#define delete(ptr) \
|
||||
free((void*) ptr); ptr = NULL
|
||||
|
||||
struct decoder_fn
|
||||
{
|
||||
int (* init)(const char* file);
|
||||
|
||||
35
source/error.c
Normal file
35
source/error.c
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "error.h"
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Return string describing error number. Extends strerror to include some
|
||||
* custom errors used in ctrmus.
|
||||
*
|
||||
* \param err Error number.
|
||||
*/
|
||||
char* ctrmus_strerror(int err)
|
||||
{
|
||||
char* error;
|
||||
|
||||
switch(err)
|
||||
{
|
||||
case NDSP_INIT_FAIL:
|
||||
error = "NDSP Initialisation failed";
|
||||
break;
|
||||
|
||||
case DECODER_INIT_FAIL:
|
||||
error = "Unable to initialised decoder";
|
||||
break;
|
||||
|
||||
case FILE_NOT_SUPPORTED:
|
||||
error = "File type is not supported";
|
||||
break;
|
||||
|
||||
default:
|
||||
error = strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
29
source/error.h
Normal file
29
source/error.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <3ds.h>
|
||||
|
||||
/* Errors that can't be explained with errno */
|
||||
#define NDSP_INIT_FAIL 1000
|
||||
#define DECODER_INIT_FAIL 1001
|
||||
#define FILE_NOT_SUPPORTED 1002
|
||||
|
||||
/**
|
||||
* Struct to help error handling across threads.
|
||||
*/
|
||||
struct errInfo_t
|
||||
{
|
||||
/* errno code or from defines listed above */
|
||||
volatile int* error;
|
||||
|
||||
/* Extra information regarding error (Must be NULL if unused) */
|
||||
volatile char* errstr;
|
||||
|
||||
/* Event to trigger on error */
|
||||
Handle* failEvent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return string describing error number. Extends strerror to include some
|
||||
* custom errors used in ctrmus.
|
||||
*
|
||||
* \param err Error number.
|
||||
*/
|
||||
char* ctrmus_strerror(int err);
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#define DR_FLAC_IMPLEMENTATION
|
||||
#include <./dr_libs/dr_flac.h>
|
||||
|
||||
#include "all.h"
|
||||
#include "flac.h"
|
||||
|
||||
|
||||
476
source/main.c
476
source/main.c
@@ -16,188 +16,99 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "all.h"
|
||||
#include "error.h"
|
||||
#include "main.h"
|
||||
#include "playback.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
volatile bool runThreads = true;
|
||||
|
||||
/**
|
||||
* Prints the current key mappings to stdio.
|
||||
*/
|
||||
static void showControls(void)
|
||||
{
|
||||
PrintConsole topScreen;
|
||||
PrintConsole bottomScreen;
|
||||
int fileMax;
|
||||
int fileNum = 0;
|
||||
int from = 0;
|
||||
printf("Button mappings:\n"
|
||||
"Pause: L+R or L+Up\n"
|
||||
"Stop: L+B\n"
|
||||
"A: Open File\n"
|
||||
"B: Go up folder\n"
|
||||
"Start: Exit\n");
|
||||
}
|
||||
|
||||
gfxInitDefault();
|
||||
sdmcInit();
|
||||
consoleInit(GFX_TOP, &topScreen);
|
||||
consoleInit(GFX_BOTTOM, &bottomScreen);
|
||||
consoleSelect(&bottomScreen);
|
||||
/**
|
||||
* Allows the playback thread to return any error messages that it may
|
||||
* encounter.
|
||||
*
|
||||
* \param infoIn Struct containing addresses of the event, the error code,
|
||||
* and an optional error string.
|
||||
*/
|
||||
void playbackWatchdog(void* infoIn)
|
||||
{
|
||||
struct watchdogInfo* info = infoIn;
|
||||
|
||||
chdir(DEFAULT_DIR);
|
||||
chdir("MUSIC");
|
||||
if(listDir(from, MAX_LIST, 0) < 0)
|
||||
while(runThreads)
|
||||
{
|
||||
err_print("Unable to list directory.");
|
||||
goto err;
|
||||
}
|
||||
svcWaitSynchronization(*info->errInfo->failEvent, U64_MAX);
|
||||
svcClearEvent(*info->errInfo->failEvent);
|
||||
consoleSelect(info->screen);
|
||||
|
||||
fileMax = getNumberFiles();
|
||||
|
||||
consoleSelect(&bottomScreen);
|
||||
|
||||
/**
|
||||
* This allows for music to continue playing through the headphones whilst
|
||||
* the 3DS is closed.
|
||||
*/
|
||||
aptSetSleepAllowed(false);
|
||||
|
||||
while(aptMainLoop())
|
||||
{
|
||||
u32 kDown;
|
||||
u32 kHeld;
|
||||
static u64 mill = 0;
|
||||
|
||||
hidScanInput();
|
||||
|
||||
gfxSwapBuffers();
|
||||
gfxFlushBuffers();
|
||||
gspWaitForVBlank();
|
||||
|
||||
kDown = hidKeysDown();
|
||||
kHeld = hidKeysHeld();
|
||||
|
||||
if(kDown & KEY_START)
|
||||
break;
|
||||
|
||||
#ifdef DEBUG
|
||||
consoleSelect(&topScreen);
|
||||
printf("\rNum: %d, Max: %d, from: %d ", fileNum, fileMax, from);
|
||||
consoleSelect(&bottomScreen);
|
||||
#endif
|
||||
if(kDown)
|
||||
mill = osGetTime();
|
||||
|
||||
if((kDown & KEY_UP ||
|
||||
((kHeld & KEY_UP) && (osGetTime() - mill > 500))) &&
|
||||
fileNum > 0)
|
||||
if(*info->errInfo->error != 0)
|
||||
{
|
||||
fileNum--;
|
||||
printf("Error %d: %s", *info->errInfo->error,
|
||||
ctrmus_strerror(*info->errInfo->error));
|
||||
|
||||
/* 26 is the maximum number of entries that can be printed */
|
||||
if(fileMax - fileNum > 26 && from != 0)
|
||||
from--;
|
||||
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
}
|
||||
|
||||
if((kDown & KEY_DOWN ||
|
||||
((kHeld & KEY_DOWN) && (osGetTime() - mill > 500))) &&
|
||||
fileNum < fileMax)
|
||||
{
|
||||
fileNum++;
|
||||
|
||||
if(fileNum >= MAX_LIST && fileMax - fileNum >= 0 &&
|
||||
from < fileMax - MAX_LIST)
|
||||
from++;
|
||||
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Pressing B goes up a folder, as well as pressing A or R when ".."
|
||||
* is selected.
|
||||
*/
|
||||
if((kDown & KEY_B) ||
|
||||
((kDown & (KEY_A | KEY_R)) && (from == 0 && fileNum == 0)))
|
||||
{
|
||||
if(chdir("..") != 0)
|
||||
err_print("chdir");
|
||||
|
||||
fileNum = 0;
|
||||
from = 0;
|
||||
fileMax = getNumberFiles();
|
||||
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(kDown & (KEY_A | KEY_R))
|
||||
{
|
||||
int audioFileNum = 0;
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
|
||||
dp = opendir(".");
|
||||
|
||||
if(dp != NULL)
|
||||
if(info->errInfo->errstr != NULL)
|
||||
{
|
||||
while((ep = readdir(dp)) != NULL)
|
||||
{
|
||||
if(audioFileNum == fileNum - 1)
|
||||
break;
|
||||
|
||||
audioFileNum++;
|
||||
}
|
||||
|
||||
if(ep->d_type == DT_DIR)
|
||||
{
|
||||
/* file not allocated yet, so no need to clear it */
|
||||
if(chdir(ep->d_name) != 0)
|
||||
err_print("chdir");
|
||||
|
||||
fileNum = 0;
|
||||
from = 0;
|
||||
fileMax = getNumberFiles();
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
|
||||
closedir(dp);
|
||||
continue;
|
||||
}
|
||||
|
||||
consoleSelect(&topScreen);
|
||||
playFile(ep->d_name);
|
||||
consoleSelect(&bottomScreen);
|
||||
|
||||
if(closedir(dp) != 0)
|
||||
err_print("Closing directory failed.");
|
||||
printf(" %s", info->errInfo->errstr);
|
||||
delete(info->errInfo->errstr);
|
||||
}
|
||||
else
|
||||
err_print("Unable to open directory.");
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
consoleSelect(&topScreen);
|
||||
printf("\rNum: %d, Max: %d, from: %d ", fileNum, fileMax, from);
|
||||
consoleSelect(&bottomScreen);
|
||||
#endif
|
||||
|
||||
out:
|
||||
puts("Exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
gfxExit();
|
||||
sdmcExit();
|
||||
return 0;
|
||||
|
||||
err:
|
||||
puts("A fatal error occurred. Press START to exit.");
|
||||
|
||||
while(true)
|
||||
/**
|
||||
* Stop the currently playing file (if there is one) and play another file.
|
||||
*
|
||||
* \param ep_file File to play.
|
||||
* \param playbackInfo Information that the playback thread requires to
|
||||
* play file.
|
||||
*/
|
||||
static int changeFile(const char* ep_file, struct playbackInfo_t* playbackInfo)
|
||||
{
|
||||
s32 prio;
|
||||
static Thread thread = NULL;
|
||||
|
||||
/**
|
||||
* If music is playing, stop it. Only one playback thread should be playing
|
||||
* at any time.
|
||||
*/
|
||||
if(thread != NULL)
|
||||
{
|
||||
u32 kDown;
|
||||
/* Tell the thread to stop playback before we join it */
|
||||
stopPlayback();
|
||||
|
||||
hidScanInput();
|
||||
kDown = hidKeysDown();
|
||||
threadJoin(thread, U64_MAX);
|
||||
threadFree(thread);
|
||||
thread = NULL;
|
||||
|
||||
if(kDown & KEY_START)
|
||||
break;
|
||||
/* free allocated file string */
|
||||
delete(playbackInfo->file);
|
||||
}
|
||||
|
||||
goto out;
|
||||
if(ep_file == NULL || playbackInfo == NULL)
|
||||
return 0;
|
||||
|
||||
playbackInfo->file = strdup(ep_file);
|
||||
printf("Playing: %s\n", playbackInfo->file);
|
||||
|
||||
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
|
||||
thread = threadCreate(playFile, playbackInfo, 32 * 1024, prio - 1, -2, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +119,7 @@ err:
|
||||
* \param select File to show as selected. Must be > 0.
|
||||
* \return Number of entries listed or negative on error.
|
||||
*/
|
||||
int listDir(int from, int max, int select)
|
||||
static int listDir(int from, int max, int select)
|
||||
{
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
@@ -289,3 +200,244 @@ err:
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
PrintConsole topScreen;
|
||||
PrintConsole bottomScreen;
|
||||
int fileMax;
|
||||
int fileNum = 0;
|
||||
int from = 0;
|
||||
Thread watchdogThread;
|
||||
Handle playbackFailEvent;
|
||||
struct watchdogInfo watchdogInfoIn;
|
||||
struct errInfo_t errInfo;
|
||||
struct playbackInfo_t playbackInfo;
|
||||
volatile int error = 0;
|
||||
|
||||
gfxInitDefault();
|
||||
sdmcInit();
|
||||
consoleInit(GFX_TOP, &topScreen);
|
||||
consoleInit(GFX_BOTTOM, &bottomScreen);
|
||||
consoleSelect(&bottomScreen);
|
||||
|
||||
svcCreateEvent(&playbackFailEvent, RESET_ONESHOT);
|
||||
errInfo.error = &error;
|
||||
errInfo.failEvent = &playbackFailEvent;
|
||||
errInfo.errstr = NULL;
|
||||
|
||||
watchdogInfoIn.screen = &topScreen;
|
||||
watchdogInfoIn.errInfo = &errInfo;
|
||||
watchdogThread = threadCreate(playbackWatchdog,
|
||||
&watchdogInfoIn, 4 * 1024, 0x20, -2, true);
|
||||
|
||||
playbackInfo.file = NULL;
|
||||
playbackInfo.errInfo = &errInfo;
|
||||
|
||||
chdir(DEFAULT_DIR);
|
||||
chdir("MUSIC");
|
||||
if(listDir(from, MAX_LIST, 0) < 0)
|
||||
{
|
||||
err_print("Unable to list directory.");
|
||||
goto err;
|
||||
}
|
||||
|
||||
fileMax = getNumberFiles();
|
||||
|
||||
/**
|
||||
* This allows for music to continue playing through the headphones whilst
|
||||
* the 3DS is closed.
|
||||
*/
|
||||
aptSetSleepAllowed(false);
|
||||
|
||||
while(aptMainLoop())
|
||||
{
|
||||
u32 kDown;
|
||||
u32 kHeld;
|
||||
static u64 mill = 0;
|
||||
|
||||
hidScanInput();
|
||||
|
||||
gfxSwapBuffers();
|
||||
gfxFlushBuffers();
|
||||
gspWaitForVBlank();
|
||||
|
||||
kDown = hidKeysDown();
|
||||
kHeld = hidKeysHeld();
|
||||
|
||||
consoleSelect(&bottomScreen);
|
||||
|
||||
/* Exit ctrmus */
|
||||
if(kDown & KEY_START)
|
||||
break;
|
||||
|
||||
#ifdef DEBUG
|
||||
consoleSelect(&topScreen);
|
||||
printf("\rNum: %d, Max: %d, from: %d ", fileNum, fileMax, from);
|
||||
consoleSelect(&bottomScreen);
|
||||
#endif
|
||||
if(kDown)
|
||||
mill = osGetTime();
|
||||
|
||||
if(kHeld & KEY_L)
|
||||
{
|
||||
/* Pause/Play */
|
||||
if(kDown & (KEY_R | KEY_UP))
|
||||
{
|
||||
if(isPlaying() == false)
|
||||
continue;
|
||||
|
||||
consoleSelect(&topScreen);
|
||||
if(togglePlayback() == true)
|
||||
puts("Paused");
|
||||
else
|
||||
puts("Playing");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Show controls */
|
||||
if(kDown & KEY_LEFT)
|
||||
{
|
||||
consoleSelect(&topScreen);
|
||||
showControls();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Stop */
|
||||
if(kDown & KEY_B)
|
||||
{
|
||||
stopPlayback();
|
||||
changeFile(NULL, &playbackInfo);
|
||||
consoleSelect(&topScreen);
|
||||
puts("Stopped");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if((kDown & KEY_UP ||
|
||||
((kHeld & KEY_UP) && (osGetTime() - mill > 500))) &&
|
||||
fileNum > 0)
|
||||
{
|
||||
fileNum--;
|
||||
|
||||
/* 26 is the maximum number of entries that can be printed */
|
||||
if(fileMax - fileNum > 26 && from != 0)
|
||||
from--;
|
||||
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
}
|
||||
|
||||
if((kDown & KEY_DOWN ||
|
||||
((kHeld & KEY_DOWN) && (osGetTime() - mill > 500))) &&
|
||||
fileNum < fileMax)
|
||||
{
|
||||
fileNum++;
|
||||
|
||||
if(fileNum >= MAX_LIST && fileMax - fileNum >= 0 &&
|
||||
from < fileMax - MAX_LIST)
|
||||
from++;
|
||||
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Pressing B goes up a folder, as well as pressing A or R when ".."
|
||||
* is selected.
|
||||
*/
|
||||
if((kDown & KEY_B) ||
|
||||
((kDown & KEY_A) && (from == 0 && fileNum == 0)))
|
||||
{
|
||||
if(chdir("..") != 0)
|
||||
err_print("chdir");
|
||||
|
||||
fileNum = 0;
|
||||
from = 0;
|
||||
fileMax = getNumberFiles();
|
||||
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(kDown & KEY_A)
|
||||
{
|
||||
int audioFileNum = 0;
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
|
||||
dp = opendir(".");
|
||||
|
||||
if(dp != NULL)
|
||||
{
|
||||
while((ep = readdir(dp)) != NULL)
|
||||
{
|
||||
if(audioFileNum == fileNum - 1)
|
||||
break;
|
||||
|
||||
audioFileNum++;
|
||||
}
|
||||
|
||||
if(ep->d_type == DT_DIR)
|
||||
{
|
||||
/* file not allocated yet, so no need to clear it */
|
||||
if(chdir(ep->d_name) != 0)
|
||||
err_print("chdir");
|
||||
|
||||
fileNum = 0;
|
||||
from = 0;
|
||||
fileMax = getNumberFiles();
|
||||
if(listDir(from, MAX_LIST, fileNum) < 0)
|
||||
err_print("Unable to list directory.");
|
||||
|
||||
closedir(dp);
|
||||
continue;
|
||||
}
|
||||
|
||||
consoleSelect(&topScreen);
|
||||
changeFile(ep->d_name, &playbackInfo);
|
||||
consoleSelect(&bottomScreen);
|
||||
|
||||
if(closedir(dp) != 0)
|
||||
err_print("Closing directory failed.");
|
||||
}
|
||||
else
|
||||
err_print("Unable to open directory.");
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
consoleSelect(&topScreen);
|
||||
printf("\rNum: %d, Max: %d, from: %d ", fileNum, fileMax, from);
|
||||
consoleSelect(&bottomScreen);
|
||||
#endif
|
||||
|
||||
out:
|
||||
puts("Exiting...");
|
||||
runThreads = false;
|
||||
svcSignalEvent(playbackFailEvent);
|
||||
changeFile(NULL, &playbackInfo);
|
||||
|
||||
gfxExit();
|
||||
sdmcExit();
|
||||
return 0;
|
||||
|
||||
err:
|
||||
puts("A fatal error occurred. Press START to exit.");
|
||||
|
||||
while(true)
|
||||
{
|
||||
u32 kDown;
|
||||
|
||||
hidScanInput();
|
||||
kDown = hidKeysDown();
|
||||
|
||||
if(kDown & KEY_START)
|
||||
break;
|
||||
}
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,30 @@
|
||||
* LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef ctrmus_main_h
|
||||
#define ctrmus_main_h
|
||||
|
||||
/* Default folder */
|
||||
#define DEFAULT_DIR "sdmc:/"
|
||||
|
||||
/* Maximum number of lines that can be displayed */
|
||||
#define MAX_LIST 27
|
||||
|
||||
struct watchdogInfo
|
||||
{
|
||||
PrintConsole* screen;
|
||||
struct errInfo_t* errInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows the playback thread to return any error messages that it may
|
||||
* encounter.
|
||||
*
|
||||
* \param infoIn Struct containing addresses of the event, the error code,
|
||||
* and an optional error string.
|
||||
*/
|
||||
void playbackWatchdog(void* infoIn);
|
||||
|
||||
/**
|
||||
* Get number of files in current working folder
|
||||
*
|
||||
@@ -21,12 +39,4 @@
|
||||
*/
|
||||
int getNumberFiles(void);
|
||||
|
||||
/**
|
||||
* List current directory.
|
||||
*
|
||||
* \param from First entry in directory to list.
|
||||
* \param max Maximum number of entries to list. Must be > 0.
|
||||
* \param select File to show as selected. Must be > 0.
|
||||
* \return Number of entries listed or negative on error.
|
||||
*/
|
||||
int listDir(int from, int max, int select);
|
||||
#endif
|
||||
|
||||
@@ -71,7 +71,8 @@ uint8_t channelOpus(void)
|
||||
* Decode part of open Opus file.
|
||||
*
|
||||
* \param buffer Decoded output.
|
||||
* \return Samples read for each channel.
|
||||
* \return Samples read for each channel. 0 for end of file, negative
|
||||
* for error.
|
||||
*/
|
||||
uint64_t decodeOpus(void* buffer)
|
||||
{
|
||||
@@ -104,11 +105,7 @@ uint64_t fillOpusBuffer(OggOpusFile* opusFile, int16_t* bufferOut)
|
||||
samplesToRead > 120*48*2 ? 120*48*2 : samplesToRead);
|
||||
|
||||
if(samplesJustRead < 0)
|
||||
{
|
||||
/* TODO: Printing should not be done here. */
|
||||
printf("\nFatal error decoding Opus: %d.", samplesJustRead);
|
||||
return 0;
|
||||
}
|
||||
return samplesJustRead;
|
||||
else if(samplesJustRead == 0)
|
||||
{
|
||||
/* End of file reached. */
|
||||
|
||||
@@ -3,21 +3,141 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "all.h"
|
||||
#include "error.h"
|
||||
#include "flac.h"
|
||||
#include "mp3.h"
|
||||
#include "opus.h"
|
||||
#include "playback.h"
|
||||
#include "wav.h"
|
||||
|
||||
int playFile(const char* file)
|
||||
static volatile bool stop = false;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains file type.
|
||||
*
|
||||
* \param file File location.
|
||||
* \return File type, else negative and errno set.
|
||||
*/
|
||||
static int getFileType(const char *file)
|
||||
{
|
||||
FILE* ftest = fopen(file, "rb");
|
||||
int fileSig = 0;
|
||||
enum file_types file_type = FILE_TYPE_ERROR;
|
||||
|
||||
/* Failure opening file */
|
||||
if(ftest == NULL)
|
||||
return -1;
|
||||
|
||||
if(fread(&fileSig, 4, 1, ftest) == 0)
|
||||
goto err;
|
||||
|
||||
switch(fileSig)
|
||||
{
|
||||
// "RIFF"
|
||||
case 0x46464952:
|
||||
if(fseek(ftest, 4, SEEK_CUR) != 0)
|
||||
break;
|
||||
|
||||
// "WAVE"
|
||||
// Check required as AVI file format also uses "RIFF".
|
||||
if(fread(&fileSig, 4, 1, ftest) == 0)
|
||||
break;
|
||||
|
||||
if(fileSig != 0x45564157)
|
||||
break;
|
||||
|
||||
file_type = FILE_TYPE_WAV;
|
||||
break;
|
||||
|
||||
// "fLaC"
|
||||
case 0x43614c66:
|
||||
file_type = FILE_TYPE_FLAC;
|
||||
break;
|
||||
|
||||
// "OggS"
|
||||
case 0x5367674f:
|
||||
if(isOpus(file) == 0)
|
||||
file_type = FILE_TYPE_OPUS;
|
||||
else
|
||||
{
|
||||
//file_type = FILE_TYPE_OGG;
|
||||
errno = FILE_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
/*
|
||||
* MP3 without ID3 tag, ID3v1 tag is at the end of file, or MP3
|
||||
* with ID3 tag at the beginning of the file.
|
||||
*/
|
||||
if((fileSig << 16) == 0xFBFF0000 ||
|
||||
(fileSig << 16) == 0xFAFF0000 ||
|
||||
(fileSig << 8) == 0x33444900)
|
||||
{
|
||||
file_type = FILE_TYPE_MP3;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: Add this again at some point */
|
||||
//printf("Unknown magic number: %#010x\n.", fileSig);
|
||||
errno = FILE_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
|
||||
err:
|
||||
fclose(ftest);
|
||||
return file_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
struct playbackInfo_t* info = infoIn;
|
||||
int16_t* buffer1 = NULL;
|
||||
int16_t* buffer2 = NULL;
|
||||
ndspWaveBuf waveBuf[2];
|
||||
bool playing = true;
|
||||
bool lastbuf = false;
|
||||
int ret;
|
||||
int ret = -1;
|
||||
const char* file = info->file;
|
||||
|
||||
/* Reset previous stop command */
|
||||
stop = false;
|
||||
|
||||
switch(getFileType(file))
|
||||
{
|
||||
@@ -38,29 +158,24 @@ int playFile(const char* file)
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Unsupported File type.\n");
|
||||
return 0;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(R_FAILED(ndspInit()))
|
||||
{
|
||||
printf("Initialising ndsp failed.");
|
||||
goto out;
|
||||
errno = NDSP_INIT_FAIL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if((ret = (*decoder.init)(file)) != 0)
|
||||
{
|
||||
printf("Error initialising decoder: %d\n", ret);
|
||||
goto out;
|
||||
errno = DECODER_INIT_FAIL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
buffer1 = linearAlloc(decoder.buffSize * sizeof(int16_t));
|
||||
buffer2 = linearAlloc(decoder.buffSize * sizeof(int16_t));
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("\nRate: %lu\tChan: %d\n", (*decoder.rate)(), (*decoder.channels)());
|
||||
#endif
|
||||
|
||||
ndspChnReset(CHANNEL);
|
||||
ndspChnWaveBufClear(CHANNEL);
|
||||
ndspSetOutputMode(NDSP_OUTPUT_STEREO);
|
||||
@@ -79,48 +194,33 @@ int playFile(const char* file)
|
||||
waveBuf[1].data_vaddr = &buffer2[0];
|
||||
ndspChnWaveBufAdd(CHANNEL, &waveBuf[1]);
|
||||
|
||||
printf("Playing %s\n", file);
|
||||
|
||||
/**
|
||||
* 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(playing == false || ndspChnIsPlaying(CHANNEL) == true)
|
||||
while(stop == false)
|
||||
{
|
||||
u32 kDown;
|
||||
|
||||
/* Number of bytes read from file.
|
||||
* Static only for the purposes of the printf debug at the bottom.
|
||||
*/
|
||||
static size_t read = 0;
|
||||
|
||||
gfxSwapBuffers();
|
||||
gfxFlushBuffers();
|
||||
gspWaitForVBlank();
|
||||
|
||||
hidScanInput();
|
||||
kDown = hidKeysDown();
|
||||
svcSleepThread(100 * 1000);
|
||||
|
||||
if(kDown & KEY_B)
|
||||
/* 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(kDown & (KEY_A | KEY_R))
|
||||
{
|
||||
playing = !playing;
|
||||
ndspChnSetPaused(CHANNEL, !ndspChnIsPaused(CHANNEL));
|
||||
printf("\33[2K\r%s", playing == false ? "Paused" : "");
|
||||
}
|
||||
|
||||
if(playing == false || lastbuf == true)
|
||||
if(ndspChnIsPaused(CHANNEL) == true || lastbuf == true)
|
||||
continue;
|
||||
|
||||
if(waveBuf[0].status == NDSP_WBUF_DONE)
|
||||
{
|
||||
read = (*decoder.decode)(&buffer1[0]);
|
||||
size_t read = (*decoder.decode)(&buffer1[0]);
|
||||
|
||||
if(read == 0)
|
||||
if(read <= 0)
|
||||
{
|
||||
lastbuf = true;
|
||||
continue;
|
||||
@@ -133,9 +233,9 @@ int playFile(const char* file)
|
||||
|
||||
if(waveBuf[1].status == NDSP_WBUF_DONE)
|
||||
{
|
||||
read = (*decoder.decode)(&buffer2[0]);
|
||||
size_t read = (*decoder.decode)(&buffer2[0]);
|
||||
|
||||
if(read == 0)
|
||||
if(read <= 0)
|
||||
{
|
||||
lastbuf = true;
|
||||
continue;
|
||||
@@ -150,105 +250,17 @@ int playFile(const char* file)
|
||||
DSP_FlushDataCache(buffer2, decoder.buffSize * sizeof(int16_t));
|
||||
}
|
||||
|
||||
out:
|
||||
printf("\nStopping playback.\n");
|
||||
(*decoder.exit)();
|
||||
out:
|
||||
ndspChnWaveBufClear(CHANNEL);
|
||||
ndspExit();
|
||||
linearFree(buffer1);
|
||||
linearFree(buffer2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains file type.
|
||||
*
|
||||
* \param file File location.
|
||||
* \return File type, else negative.
|
||||
*/
|
||||
int getFileType(const char *file)
|
||||
{
|
||||
FILE* ftest = fopen(file, "rb");
|
||||
int fileSig = 0;
|
||||
enum file_types file_type = FILE_TYPE_ERROR;
|
||||
|
||||
if(ftest == NULL)
|
||||
{
|
||||
err_print("Opening file failed.");
|
||||
printf("file: %s\n", file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(fread(&fileSig, 4, 1, ftest) == 0)
|
||||
{
|
||||
err_print("Unable to read file.");
|
||||
fclose(ftest);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(fileSig)
|
||||
{
|
||||
// "RIFF"
|
||||
case 0x46464952:
|
||||
if(fseek(ftest, 4, SEEK_CUR) != 0)
|
||||
{
|
||||
err_print("Unable to seek.");
|
||||
break;
|
||||
}
|
||||
|
||||
// "WAVE"
|
||||
// Check required as AVI file format also uses "RIFF".
|
||||
if(fread(&fileSig, 4, 1, ftest) == 0)
|
||||
{
|
||||
err_print("Unable to read potential WAV file.");
|
||||
break;
|
||||
}
|
||||
|
||||
if(fileSig != 0x45564157)
|
||||
break;
|
||||
|
||||
file_type = FILE_TYPE_WAV;
|
||||
printf("File type is WAV.");
|
||||
break;
|
||||
|
||||
// "fLaC"
|
||||
case 0x43614c66:
|
||||
file_type = FILE_TYPE_FLAC;
|
||||
printf("File type is FLAC.");
|
||||
break;
|
||||
|
||||
// "OggS"
|
||||
case 0x5367674f:
|
||||
if(isOpus(file) == 0)
|
||||
{
|
||||
printf("\nFile type is Opus.");
|
||||
file_type = FILE_TYPE_OPUS;
|
||||
}
|
||||
else
|
||||
{
|
||||
file_type = FILE_TYPE_OGG;
|
||||
printf("\nUnsupported audio in OGG container.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
/*
|
||||
* MP3 without ID3 tag, ID3v1 tag is at the end of file, or MP3
|
||||
* with ID3 tag at the beginning of the file.
|
||||
*/
|
||||
if((fileSig << 16) == 0xFBFF0000 ||
|
||||
(fileSig << 16) == 0xFAFF0000 ||
|
||||
(fileSig << 8) == 0x33444900)
|
||||
{
|
||||
puts("File type is MP3.");
|
||||
file_type = FILE_TYPE_MP3;
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Unknown magic number: %#010x\n.", fileSig);
|
||||
}
|
||||
|
||||
fclose(ftest);
|
||||
return file_type;
|
||||
threadExit(0);
|
||||
return;
|
||||
|
||||
err:
|
||||
*info->errInfo->error = errno;
|
||||
svcSignalEvent(*info->errInfo->failEvent);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
enum file_types {
|
||||
#ifndef ctrmus_playback_h
|
||||
#define ctrmus_playback_h
|
||||
|
||||
enum file_types
|
||||
{
|
||||
FILE_TYPE_ERROR = -1,
|
||||
FILE_TYPE_WAV,
|
||||
FILE_TYPE_FLAC,
|
||||
@@ -7,12 +11,36 @@ enum file_types {
|
||||
FILE_TYPE_MP3
|
||||
};
|
||||
|
||||
int playFile(const char* file);
|
||||
struct playbackInfo_t
|
||||
{
|
||||
char* file;
|
||||
struct errInfo_t* errInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Obtains file type.
|
||||
* 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 file File location.
|
||||
* \return File type, else negative.
|
||||
* \param infoIn Playback information.
|
||||
*/
|
||||
int getFileType(const char *file);
|
||||
void playFile(void* infoIn);
|
||||
|
||||
/**
|
||||
* Pause or play current file.
|
||||
*
|
||||
* \return True if paused.
|
||||
*/
|
||||
bool togglePlayback(void);
|
||||
|
||||
/**
|
||||
* Stops current playback. Playback thread should exit as a result.
|
||||
*/
|
||||
void stopPlayback(void);
|
||||
|
||||
/**
|
||||
* Returns whether music is playing or paused.
|
||||
*/
|
||||
bool isPlaying(void);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user