Merge branch 'threads'

This commit is contained in:
Mahyar Koshkouei
2017-02-14 22:53:17 +00:00
9 changed files with 585 additions and 317 deletions

View File

@@ -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
View 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
View 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);

View File

@@ -2,6 +2,7 @@
#define DR_FLAC_IMPLEMENTATION
#include <./dr_libs/dr_flac.h>
#include "all.h"
#include "flac.h"

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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. */

View File

@@ -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;
}

View File

@@ -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