Created generic playback handler, first with Opus support. Other decoders to follow. This is to remove duplicated code. 3DSX tested working with citra and N3DS. Signed-off-by: Mahyar Koshkouei <deltabeard@users.noreply.github.com>
439 lines
7.5 KiB
C
439 lines
7.5 KiB
C
/**
|
|
* ctrmus - 3DS Music Player
|
|
* Copyright (C) 2016 Mahyar Koshkouei
|
|
*
|
|
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
|
|
* welcome to redistribute it under certain conditions; for details see the
|
|
* LICENSE file.
|
|
*/
|
|
|
|
#include <3ds.h>
|
|
#include <dirent.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "all.h"
|
|
#include "flac.h"
|
|
#include "main.h"
|
|
#include "mp3.h"
|
|
#include "opus.h"
|
|
#include "playback.h"
|
|
#include "wav.h"
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
PrintConsole topScreen;
|
|
PrintConsole bottomScreen;
|
|
int fileMax;
|
|
int fileNum = 0;
|
|
int from = 0;
|
|
|
|
gfxInitDefault();
|
|
sdmcInit();
|
|
consoleInit(GFX_TOP, &topScreen);
|
|
consoleInit(GFX_BOTTOM, &bottomScreen);
|
|
consoleSelect(&bottomScreen);
|
|
|
|
chdir(DEFAULT_DIR);
|
|
chdir("MUSIC");
|
|
if(listDir(from, MAX_LIST, 0) < 0)
|
|
{
|
|
err_print("Unable to list directory.");
|
|
goto err;
|
|
}
|
|
|
|
fileMax = getNumberFiles();
|
|
|
|
consoleSelect(&topScreen);
|
|
puts("Log");
|
|
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((kHeld & KEY_UP) && fileNum > 0 && fileNum > 0)
|
|
{
|
|
/* Limit the speed of the selector */
|
|
if(osGetTime() - mill < 100)
|
|
continue;
|
|
|
|
fileNum--;
|
|
|
|
if(fileMax - fileNum >= from && from != 0)
|
|
from--;
|
|
|
|
mill = osGetTime();
|
|
|
|
consoleClear();
|
|
if(listDir(from, MAX_LIST, fileNum) < 0)
|
|
err_print("Unable to list directory.");
|
|
}
|
|
|
|
if((kHeld & KEY_DOWN) && fileNum < fileMax && fileNum < fileMax)
|
|
{
|
|
if(osGetTime() - mill < 100)
|
|
continue;
|
|
|
|
fileNum++;
|
|
|
|
if(fileNum >= MAX_LIST && fileMax - fileNum >= 0 &&
|
|
from < fileMax - MAX_LIST)
|
|
from++;
|
|
|
|
mill = osGetTime();
|
|
|
|
consoleClear();
|
|
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();
|
|
|
|
consoleClear();
|
|
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;
|
|
char* wd = getcwd(NULL, 0);
|
|
|
|
if(wd == NULL)
|
|
{
|
|
err_print("wd");
|
|
goto err;
|
|
}
|
|
|
|
dp = opendir(wd);
|
|
|
|
if(dp != NULL)
|
|
{
|
|
char* file = 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;
|
|
consoleClear();
|
|
fileMax = getNumberFiles();
|
|
if(listDir(from, MAX_LIST, fileNum) < 0)
|
|
err_print("Unable to list directory.");
|
|
|
|
closedir(dp);
|
|
free(wd);
|
|
continue;
|
|
}
|
|
|
|
if(asprintf(&file, "%s%s", wd, ep->d_name) == -1)
|
|
{
|
|
err_print("Constructing file name failed.");
|
|
file = NULL;
|
|
}
|
|
else
|
|
{
|
|
consoleSelect(&topScreen);
|
|
/* Move this to playback.c */
|
|
switch(getFileType(file))
|
|
{
|
|
case FILE_TYPE_WAV:
|
|
playWav(file);
|
|
break;
|
|
|
|
case FILE_TYPE_FLAC:
|
|
playFlac(file);
|
|
break;
|
|
|
|
case FILE_TYPE_OPUS:
|
|
playFile(file);
|
|
break;
|
|
|
|
case FILE_TYPE_MP3:
|
|
playMp3(file);
|
|
break;
|
|
|
|
default:
|
|
consoleSelect(&bottomScreen);
|
|
printf("Unsupported File type.\n");
|
|
}
|
|
}
|
|
|
|
consoleSelect(&bottomScreen);
|
|
|
|
free(file);
|
|
free(wd);
|
|
|
|
if(closedir(dp) != 0)
|
|
err_print("Closing directory failed.");
|
|
}
|
|
else
|
|
err_print("Unable to open directory.");
|
|
}
|
|
}
|
|
|
|
out:
|
|
puts("Exiting...");
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
DIR *dp;
|
|
struct dirent *ep;
|
|
int fileNum = 0;
|
|
int listed = 0;
|
|
char* wd = getcwd(NULL, 0);
|
|
|
|
if(wd == NULL)
|
|
goto err;
|
|
|
|
printf("Dir: %.30s\n", wd);
|
|
|
|
if((dp = opendir(wd)) == NULL)
|
|
goto err;
|
|
|
|
if(from == 0)
|
|
{
|
|
printf("%c../\n", select == 0 ? '>' : ' ');
|
|
listed++;
|
|
}
|
|
|
|
while((ep = readdir(dp)) != NULL)
|
|
{
|
|
fileNum++;
|
|
|
|
if(fileNum <= from)
|
|
continue;
|
|
|
|
listed++;
|
|
|
|
printf("%c%s%.37s%s\n",
|
|
select == fileNum ? '>' : ' ',
|
|
ep->d_type == DT_DIR ? "\x1b[34;1m" : "",
|
|
ep->d_name,
|
|
ep->d_type == DT_DIR ? "/\x1b[0m" : "");
|
|
|
|
if(fileNum == max + from)
|
|
break;
|
|
}
|
|
|
|
if(closedir(dp) != 0)
|
|
goto err;
|
|
|
|
out:
|
|
free(wd);
|
|
return listed;
|
|
|
|
err:
|
|
listed = -1;
|
|
goto out;
|
|
}
|
|
|
|
/**
|
|
* Get number of files in current working folder
|
|
*
|
|
* \return Number of files in current working folder, -1 on failure with
|
|
* errno set.
|
|
*/
|
|
int getNumberFiles(void)
|
|
{
|
|
DIR *dp;
|
|
struct dirent *ep;
|
|
char* wd = getcwd(NULL, 0);
|
|
int ret = 0;
|
|
|
|
if(wd == NULL)
|
|
goto err;
|
|
|
|
if((dp = opendir(wd)) == NULL)
|
|
goto err;
|
|
|
|
while((ep = readdir(dp)) != NULL)
|
|
ret++;
|
|
|
|
closedir(dp);
|
|
|
|
out:
|
|
return ret;
|
|
|
|
err:
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/**
|
|
* 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("\nFile type is OGG.");
|
|
}
|
|
|
|
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 << 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;
|
|
}
|