1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
build
|
build
|
||||||
output
|
output
|
||||||
|
server
|
||||||
|
|
||||||
# Object files
|
# Object files
|
||||||
*.o
|
*.o
|
||||||
|
|||||||
13
Makefile
13
Makefile
@@ -39,7 +39,7 @@ endif
|
|||||||
|
|
||||||
# COMMON CONFIGURATION #
|
# COMMON CONFIGURATION #
|
||||||
|
|
||||||
NAME := ctrmus
|
NAME := mice
|
||||||
|
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
OUTPUT_DIR := output
|
OUTPUT_DIR := output
|
||||||
@@ -64,10 +64,10 @@ VERSION_MICRO := $(word 3, $(VERSION_PARTS))
|
|||||||
# 3DS CONFIGURATION #
|
# 3DS CONFIGURATION #
|
||||||
|
|
||||||
TITLE := $(NAME)
|
TITLE := $(NAME)
|
||||||
DESCRIPTION := 3DS Music Player
|
DESCRIPTION := local files music player
|
||||||
AUTHOR := Deltabeard
|
AUTHOR := sillyangel
|
||||||
PRODUCT_CODE := CTR-P-CMUS
|
PRODUCT_CODE := CTR-P-MICE
|
||||||
UNIQUE_ID := 0xFF3CC
|
UNIQUE_ID := 0xFB1CE
|
||||||
|
|
||||||
SYSTEM_MODE := 64MB
|
SYSTEM_MODE := 64MB
|
||||||
SYSTEM_MODE_EXT := Legacy
|
SYSTEM_MODE_EXT := Legacy
|
||||||
@@ -81,4 +81,7 @@ ICON := meta/icon.png
|
|||||||
|
|
||||||
# INTERNAL #
|
# INTERNAL #
|
||||||
|
|
||||||
|
# Auto-increment version before build
|
||||||
|
$(shell ./increment_version.sh >/dev/null 2>&1)
|
||||||
|
|
||||||
include buildtools/make_base
|
include buildtools/make_base
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<img align="right" alt="The ctrmus icon" src="meta/icon.png">
|
<img align="right" alt="The mice icon" src="meta/icon.png">
|
||||||
|
|
||||||
# ctrmus: a music player for the Nintendo 3DS
|
# mice: a music player for the Nintendo 3DS
|
||||||
|
|
||||||
The latest 3DSX/CIA/3DS download can be found on the <a href="https://github.com/deltabeard/ctrmus/releases">releases</a> page, or by scanning <a href="https://zxing.org/w/chart?cht=qr&chs=350x350&chld=M&choe=UTF-8&chl=https%3A%2F%2Fgithub.com%2Fdeltabeard%2Fctrmus%2Freleases%2Fdownload%2Fv0.5.3%2Fctrmus.cia">this QR code</a>.
|
The latest 3DSX/CIA/3DS download can be found on the <a href="https://github.com/deltabeard/ctrmus/releases">releases</a> page, or by scanning <a href="https://zxing.org/w/chart?cht=qr&chs=350x350&chld=M&choe=UTF-8&chl=https%3A%2F%2Fgithub.com%2Fdeltabeard%2Fctrmus%2Freleases%2Fdownload%2Fv0.5.3%2Fctrmus.cia">this QR code</a>.
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ struct errInfo_t
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return string describing error number. Extends strerror to include some
|
* Return string describing error number. Extends strerror to include some
|
||||||
* custom errors used in ctrmus.
|
* custom errors used in mice.
|
||||||
*
|
*
|
||||||
* \param err Error number.
|
* \param err Error number.
|
||||||
*/
|
*/
|
||||||
char* ctrmus_strerror(int err);
|
char* mice_strerror(int err);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* ctrmus - 3DS Music Player
|
* mice - 3DS Music Player
|
||||||
* Copyright (C) 2016 Mahyar Koshkouei
|
* Copyright (C) 2016 Mahyar Koshkouei
|
||||||
*
|
*
|
||||||
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
|
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
|
||||||
@@ -9,8 +9,11 @@
|
|||||||
|
|
||||||
#include <3ds.h>
|
#include <3ds.h>
|
||||||
|
|
||||||
#ifndef ctrmus_main_h
|
#ifndef mice_main_h
|
||||||
#define ctrmus_main_h
|
#define mice_main_h
|
||||||
|
|
||||||
|
/* Application version */
|
||||||
|
#define MICE_VERSION "dev20"
|
||||||
|
|
||||||
/* Default folder */
|
/* Default folder */
|
||||||
#define DEFAULT_DIR "sdmc:/"
|
#define DEFAULT_DIR "sdmc:/"
|
||||||
|
|||||||
56
include/metadata.h
Normal file
56
include/metadata.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef mice_metadata_h
|
||||||
|
#define mice_metadata_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define METADATA_TITLE_MAX 64
|
||||||
|
#define METADATA_ARTIST_MAX 64
|
||||||
|
#define METADATA_ALBUM_MAX 64
|
||||||
|
|
||||||
|
struct metadata_t
|
||||||
|
{
|
||||||
|
char title[METADATA_TITLE_MAX];
|
||||||
|
char artist[METADATA_ARTIST_MAX];
|
||||||
|
char album[METADATA_ALBUM_MAX];
|
||||||
|
|
||||||
|
/* Album art */
|
||||||
|
uint8_t* albumArt;
|
||||||
|
size_t albumArtSize;
|
||||||
|
int albumArtWidth;
|
||||||
|
int albumArtHeight;
|
||||||
|
bool hasAlbumArt;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract metadata from an audio file
|
||||||
|
*
|
||||||
|
* \param file Path to the audio file
|
||||||
|
* \param metadata Pointer to metadata structure to fill
|
||||||
|
* \return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int extractMetadata(const char* file, struct metadata_t* metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear metadata structure and free any allocated memory
|
||||||
|
*
|
||||||
|
* \param metadata Pointer to metadata structure to clear
|
||||||
|
*/
|
||||||
|
void clearMetadata(struct metadata_t* metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display metadata on the top screen
|
||||||
|
*
|
||||||
|
* \param metadata Pointer to metadata structure to display
|
||||||
|
* \param filename Filename to display if no title is available
|
||||||
|
*/
|
||||||
|
void displayMetadata(struct metadata_t* metadata, const char* filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display album art on top screen if available
|
||||||
|
*
|
||||||
|
* \param metadata Pointer to metadata structure containing album art
|
||||||
|
*/
|
||||||
|
void displayAlbumArt(struct metadata_t* metadata);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#ifndef ctrmus_playback_h
|
#ifndef mice_playback_h
|
||||||
#define ctrmus_playback_h
|
#define mice_playback_h
|
||||||
|
|
||||||
/* Channel to play music on */
|
/* Channel to play music on */
|
||||||
#define CHANNEL 0x08
|
#define CHANNEL 0x08
|
||||||
|
|||||||
19
increment_version.sh
Executable file
19
increment_version.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Auto-increment development version number
|
||||||
|
VERSION_FILE="include/main.h"
|
||||||
|
CURRENT_VERSION=$(grep "MICE_VERSION" "$VERSION_FILE" | sed -n 's/.*"dev\([0-9]*\)".*/\1/p')
|
||||||
|
|
||||||
|
if [ -z "$CURRENT_VERSION" ]; then
|
||||||
|
echo "Could not find version number, starting at dev1"
|
||||||
|
NEW_VERSION=1
|
||||||
|
else
|
||||||
|
NEW_VERSION=$((CURRENT_VERSION + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Incrementing version: dev$CURRENT_VERSION -> dev$NEW_VERSION"
|
||||||
|
|
||||||
|
# Update the version in the header file
|
||||||
|
sed -i "s/dev[0-9]*/dev$NEW_VERSION/g" "$VERSION_FILE"
|
||||||
|
|
||||||
|
echo "Version updated to dev$NEW_VERSION"
|
||||||
BIN
meta/banner.png
BIN
meta/banner.png
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.4 KiB |
BIN
meta/banner.xcf
BIN
meta/banner.xcf
Binary file not shown.
BIN
meta/icon.png
BIN
meta/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 1.8 KiB |
BIN
meta/icon.xcf
BIN
meta/icon.xcf
Binary file not shown.
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return string describing error number. Extends strerror to include some
|
* Return string describing error number. Extends strerror to include some
|
||||||
* custom errors used in ctrmus.
|
* custom errors used in mice.
|
||||||
*
|
*
|
||||||
* \param err Error number.
|
* \param err Error number.
|
||||||
*/
|
*/
|
||||||
char* ctrmus_strerror(int err)
|
char* mice_strerror(int err)
|
||||||
{
|
{
|
||||||
char* error;
|
char* error;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ char* ctrmus_strerror(int err)
|
|||||||
error = "NDSP Initialisation failed.\nYou may need to dump DSP firmware.\n"
|
error = "NDSP Initialisation failed.\nYou may need to dump DSP firmware.\n"
|
||||||
"You can do this within Rosalina (using Luma CFW):\n"
|
"You can do this within Rosalina (using Luma CFW):\n"
|
||||||
" 1. Press L+Down+Select\n 2. Misc. Options.\n 3. Dump DSP Firmware.\n"
|
" 1. Press L+Down+Select\n 2. Misc. Options.\n 3. Dump DSP Firmware.\n"
|
||||||
" 4. Restart ctrmus.";
|
" 4. Restart mice.";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DECODER_INIT_FAIL:
|
case DECODER_INIT_FAIL:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* ctrmus - 3DS Music Player
|
* mice - 3DS Music Player
|
||||||
* Copyright (C) 2016 Mahyar Koshkouei
|
* Copyright (C) 2016 Mahyar Koshkouei
|
||||||
*
|
*
|
||||||
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
|
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "metadata.h"
|
||||||
#include "playback.h"
|
#include "playback.h"
|
||||||
|
|
||||||
volatile bool runThreads = true;
|
volatile bool runThreads = true;
|
||||||
@@ -58,7 +59,7 @@ void playbackWatchdog(void* infoIn)
|
|||||||
continue;
|
continue;
|
||||||
consoleSelect(info->screen);
|
consoleSelect(info->screen);
|
||||||
printf("Error %d: %s\n", *info->errInfo->error,
|
printf("Error %d: %s\n", *info->errInfo->error,
|
||||||
ctrmus_strerror(*info->errInfo->error));
|
mice_strerror(*info->errInfo->error));
|
||||||
}
|
}
|
||||||
else if (*info->errInfo->error == -1)
|
else if (*info->errInfo->error == -1)
|
||||||
{
|
{
|
||||||
@@ -118,7 +119,8 @@ static int changeFile(const char* ep_file, struct playbackInfo_t* playbackInfo)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Playing: %s\n", playbackInfo->file);
|
/* Remove "Playing:" message that was causing display issues */
|
||||||
|
/* printf("Playing: %s\n", playbackInfo->file); */
|
||||||
playbackInfo->samples_total = 0;
|
playbackInfo->samples_total = 0;
|
||||||
playbackInfo->samples_played = 0;
|
playbackInfo->samples_played = 0;
|
||||||
playbackInfo->samples_per_second = 0;
|
playbackInfo->samples_per_second = 0;
|
||||||
@@ -301,6 +303,7 @@ int main(int argc, char **argv)
|
|||||||
struct playbackInfo_t playbackInfo = { 0 };
|
struct playbackInfo_t playbackInfo = { 0 };
|
||||||
volatile int error = 0;
|
volatile int error = 0;
|
||||||
struct dirList_t dirList = { 0 };
|
struct dirList_t dirList = { 0 };
|
||||||
|
struct metadata_t currentMetadata = { 0 };
|
||||||
|
|
||||||
/* ignore key release of L/R if L+R or L+down was pressed */
|
/* ignore key release of L/R if L+R or L+down was pressed */
|
||||||
bool keyLComboPressed = false;
|
bool keyLComboPressed = false;
|
||||||
@@ -318,6 +321,9 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
consoleSelect(&bottomScreen);
|
consoleSelect(&bottomScreen);
|
||||||
|
|
||||||
|
/* Display version in bottom right corner */
|
||||||
|
printf("\033[28;30H%s", MICE_VERSION);
|
||||||
|
|
||||||
svcCreateEvent(&playbackFailEvent, RESET_ONESHOT);
|
svcCreateEvent(&playbackFailEvent, RESET_ONESHOT);
|
||||||
errInfo.error = &error;
|
errInfo.error = &error;
|
||||||
errInfo.failEvent = &playbackFailEvent;
|
errInfo.failEvent = &playbackFailEvent;
|
||||||
@@ -376,7 +382,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
consoleSelect(&bottomScreen);
|
consoleSelect(&bottomScreen);
|
||||||
|
|
||||||
/* Exit ctrmus */
|
/* Exit mice */
|
||||||
if(kDown & KEY_START)
|
if(kDown & KEY_START)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -571,6 +577,13 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
consoleSelect(&topScreenInfo);
|
consoleSelect(&topScreenInfo);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
|
|
||||||
|
/* Extract and display metadata */
|
||||||
|
char fullPath[512];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s", dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
extractMetadata(fullPath, ¤tMetadata);
|
||||||
|
displayMetadata(¤tMetadata, dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
|
||||||
consoleSelect(&topScreenLog);
|
consoleSelect(&topScreenLog);
|
||||||
//consoleClear();
|
//consoleClear();
|
||||||
|
|
||||||
@@ -597,6 +610,13 @@ int main(int argc, char **argv)
|
|||||||
from++;
|
from++;
|
||||||
consoleSelect(&topScreenInfo);
|
consoleSelect(&topScreenInfo);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
|
|
||||||
|
/* Extract and display metadata */
|
||||||
|
char fullPath[512];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s", dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
extractMetadata(fullPath, ¤tMetadata);
|
||||||
|
displayMetadata(¤tMetadata, dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
|
||||||
consoleSelect(&topScreenLog);
|
consoleSelect(&topScreenLog);
|
||||||
//consoleClear();
|
//consoleClear();
|
||||||
changeFile(dirList.files[fileNum - dirList.dirNum - 1], &playbackInfo);
|
changeFile(dirList.files[fileNum - dirList.dirNum - 1], &playbackInfo);
|
||||||
@@ -621,6 +641,13 @@ int main(int argc, char **argv)
|
|||||||
from--;
|
from--;
|
||||||
consoleSelect(&topScreenInfo);
|
consoleSelect(&topScreenInfo);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
|
|
||||||
|
/* Extract and display metadata */
|
||||||
|
char fullPath[512];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s", dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
extractMetadata(fullPath, ¤tMetadata);
|
||||||
|
displayMetadata(¤tMetadata, dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
|
||||||
consoleSelect(&topScreenLog);
|
consoleSelect(&topScreenLog);
|
||||||
//consoleClear();
|
//consoleClear();
|
||||||
changeFile(dirList.files[fileNum - dirList.dirNum - 1], &playbackInfo);
|
changeFile(dirList.files[fileNum - dirList.dirNum - 1], &playbackInfo);
|
||||||
@@ -640,6 +667,13 @@ int main(int argc, char **argv)
|
|||||||
fileNum += 1;
|
fileNum += 1;
|
||||||
consoleSelect(&topScreenInfo);
|
consoleSelect(&topScreenInfo);
|
||||||
consoleClear();
|
consoleClear();
|
||||||
|
|
||||||
|
/* Extract and display metadata */
|
||||||
|
char fullPath[512];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s", dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
extractMetadata(fullPath, ¤tMetadata);
|
||||||
|
displayMetadata(¤tMetadata, dirList.files[fileNum - dirList.dirNum - 1]);
|
||||||
|
|
||||||
consoleSelect(&topScreenLog);
|
consoleSelect(&topScreenLog);
|
||||||
//consoleClear();
|
//consoleClear();
|
||||||
changeFile(dirList.files[fileNum - dirList.dirNum - 1], &playbackInfo);
|
changeFile(dirList.files[fileNum - dirList.dirNum - 1], &playbackInfo);
|
||||||
@@ -652,9 +686,9 @@ int main(int argc, char **argv)
|
|||||||
/* After 1000ms, update playback time. */
|
/* After 1000ms, update playback time. */
|
||||||
while(osGetTime() - mill > 1000)
|
while(osGetTime() - mill > 1000)
|
||||||
{
|
{
|
||||||
consoleSelect(&topScreenInfo);
|
consoleSelect(&topScreenLog);
|
||||||
/* Reset cursor position and print status. */
|
/* Position cursor at bottom of log area for time display */
|
||||||
printf("\033[0;0H");
|
printf("\033[29;0H\033[K"); /* Move to line 29, clear line */
|
||||||
|
|
||||||
/* Avoid divide by zero. */
|
/* Avoid divide by zero. */
|
||||||
if(playbackInfo.samples_per_second == 0)
|
if(playbackInfo.samples_per_second == 0)
|
||||||
@@ -694,6 +728,7 @@ int main(int argc, char **argv)
|
|||||||
out:
|
out:
|
||||||
puts("Exiting...");
|
puts("Exiting...");
|
||||||
runThreads = false;
|
runThreads = false;
|
||||||
|
clearMetadata(¤tMetadata);
|
||||||
svcSignalEvent(playbackFailEvent);
|
svcSignalEvent(playbackFailEvent);
|
||||||
changeFile(NULL, &playbackInfo);
|
changeFile(NULL, &playbackInfo);
|
||||||
|
|
||||||
|
|||||||
657
source/metadata.c
Normal file
657
source/metadata.c
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
#include <3ds.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "metadata.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "all.h"
|
||||||
|
|
||||||
|
/* Internal helper functions */
|
||||||
|
static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata);
|
||||||
|
static int extractId3v1Metadata(FILE* fp, struct metadata_t* metadata);
|
||||||
|
static int extractVorbisComment(FILE* fp, struct metadata_t* metadata);
|
||||||
|
static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata);
|
||||||
|
static void trimWhitespace(char* str);
|
||||||
|
static void parseFilename(const char* file, struct metadata_t* metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract metadata from an audio file
|
||||||
|
*/
|
||||||
|
int extractMetadata(const char* file, struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
FILE* fp;
|
||||||
|
enum file_types fileType;
|
||||||
|
|
||||||
|
if(!file || !metadata)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Clear metadata structure */
|
||||||
|
clearMetadata(metadata);
|
||||||
|
|
||||||
|
fileType = getFileType(file);
|
||||||
|
fp = fopen(file, "rb");
|
||||||
|
if(!fp)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
switch(fileType)
|
||||||
|
{
|
||||||
|
case FILE_TYPE_MP3:
|
||||||
|
/* Try ID3v2 first, then ID3v1 */
|
||||||
|
if(extractId3v2Metadata(fp, metadata) != 0)
|
||||||
|
extractId3v1Metadata(fp, metadata);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILE_TYPE_VORBIS:
|
||||||
|
case FILE_TYPE_OPUS:
|
||||||
|
extractVorbisComment(fp, metadata);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILE_TYPE_FLAC:
|
||||||
|
extractFlacMetadata(fp, metadata);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILE_TYPE_WAV:
|
||||||
|
case FILE_TYPE_SID:
|
||||||
|
default:
|
||||||
|
/* No metadata support for these formats yet */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
/* If no metadata was found, try to parse filename */
|
||||||
|
if(!metadata->title[0] && !metadata->artist[0] && !metadata->album[0])
|
||||||
|
{
|
||||||
|
parseFilename(file, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear metadata structure and free any allocated memory
|
||||||
|
*/
|
||||||
|
void clearMetadata(struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
if(!metadata)
|
||||||
|
return;
|
||||||
|
|
||||||
|
memset(metadata->title, 0, METADATA_TITLE_MAX);
|
||||||
|
memset(metadata->artist, 0, METADATA_ARTIST_MAX);
|
||||||
|
memset(metadata->album, 0, METADATA_ALBUM_MAX);
|
||||||
|
|
||||||
|
if(metadata->albumArt)
|
||||||
|
{
|
||||||
|
free(metadata->albumArt);
|
||||||
|
metadata->albumArt = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata->albumArtSize = 0;
|
||||||
|
metadata->albumArtWidth = 0;
|
||||||
|
metadata->albumArtHeight = 0;
|
||||||
|
metadata->hasAlbumArt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display metadata on the top screen
|
||||||
|
*/
|
||||||
|
void displayMetadata(struct metadata_t* metadata, const char* filename)
|
||||||
|
{
|
||||||
|
if(!metadata || !filename)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Clear the top screen info area */
|
||||||
|
consoleClear();
|
||||||
|
|
||||||
|
/* Extract just the filename without path and extension for fallback */
|
||||||
|
const char* basename = strrchr(filename, '/');
|
||||||
|
if(!basename)
|
||||||
|
basename = filename;
|
||||||
|
else
|
||||||
|
basename++; /* Skip the '/' */
|
||||||
|
|
||||||
|
/* Remove file extension for display */
|
||||||
|
char displayName[64];
|
||||||
|
strncpy(displayName, basename, sizeof(displayName) - 1);
|
||||||
|
displayName[sizeof(displayName) - 1] = '\0';
|
||||||
|
char* dot = strrchr(displayName, '.');
|
||||||
|
if(dot) *dot = '\0';
|
||||||
|
|
||||||
|
/* Display song title */
|
||||||
|
if(metadata->title[0])
|
||||||
|
printf("%.47s\n", metadata->title);
|
||||||
|
else
|
||||||
|
printf("%.47s\n", displayName);
|
||||||
|
|
||||||
|
/* Display album */
|
||||||
|
if(metadata->album[0])
|
||||||
|
printf("%.47s\n", metadata->album);
|
||||||
|
else
|
||||||
|
printf("Unknown Album\n");
|
||||||
|
|
||||||
|
/* Display artist with album art indicator */
|
||||||
|
if(metadata->artist[0])
|
||||||
|
{
|
||||||
|
printf("%.45s", metadata->artist);
|
||||||
|
if(metadata->hasAlbumArt)
|
||||||
|
printf(" 🖼️");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Unknown Artist");
|
||||||
|
if(metadata->hasAlbumArt)
|
||||||
|
printf(" 🖼️");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display album art on top screen if available
|
||||||
|
*/
|
||||||
|
void displayAlbumArt(struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
if(!metadata || !metadata->hasAlbumArt || !metadata->albumArt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* For now, just indicate that album art is available */
|
||||||
|
/* Full implementation would require image decoding and display */
|
||||||
|
printf("🖼️ Album Art: %dx%d\n", metadata->albumArtWidth, metadata->albumArtHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract ID3v2 metadata from MP3 file
|
||||||
|
*/
|
||||||
|
static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
char header[10];
|
||||||
|
uint32_t tagSize;
|
||||||
|
uint8_t major, minor, flags;
|
||||||
|
|
||||||
|
if(!fp || !metadata)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
if(fread(header, 1, 10, fp) != 10)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Check ID3v2 header "ID3" */
|
||||||
|
if(memcmp(header, "ID3", 3) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
major = header[3];
|
||||||
|
minor = header[4];
|
||||||
|
flags = header[5];
|
||||||
|
|
||||||
|
/* Calculate tag size (synchsafe integer) */
|
||||||
|
tagSize = ((header[6] & 0x7F) << 21) |
|
||||||
|
((header[7] & 0x7F) << 14) |
|
||||||
|
((header[8] & 0x7F) << 7) |
|
||||||
|
(header[9] & 0x7F);
|
||||||
|
|
||||||
|
/* Skip extended header if present */
|
||||||
|
if(flags & 0x40)
|
||||||
|
{
|
||||||
|
uint32_t extSize;
|
||||||
|
if(fread(&extSize, 4, 1, fp) != 1)
|
||||||
|
return -1;
|
||||||
|
/* Convert from big-endian */
|
||||||
|
extSize = __builtin_bswap32(extSize);
|
||||||
|
fseek(fp, extSize - 4, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
long tagStart = ftell(fp);
|
||||||
|
long tagEnd = tagStart + tagSize;
|
||||||
|
|
||||||
|
/* Parse frames */
|
||||||
|
while(ftell(fp) < tagEnd - 10)
|
||||||
|
{
|
||||||
|
char frameId[5] = {0};
|
||||||
|
uint32_t frameSize;
|
||||||
|
uint16_t frameFlags;
|
||||||
|
|
||||||
|
if(fread(frameId, 1, 4, fp) != 4)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Check for padding */
|
||||||
|
if(frameId[0] == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(fread(&frameSize, 4, 1, fp) != 1)
|
||||||
|
break;
|
||||||
|
frameSize = __builtin_bswap32(frameSize);
|
||||||
|
|
||||||
|
if(fread(&frameFlags, 2, 1, fp) != 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Handle text frames */
|
||||||
|
if(strncmp(frameId, "TIT2", 4) == 0 || /* Title */
|
||||||
|
strncmp(frameId, "TPE1", 4) == 0 || /* Artist */
|
||||||
|
strncmp(frameId, "TALB", 4) == 0) /* Album */
|
||||||
|
{
|
||||||
|
char* frameData = malloc(frameSize + 1);
|
||||||
|
if(!frameData)
|
||||||
|
{
|
||||||
|
fseek(fp, frameSize, SEEK_CUR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fread(frameData, 1, frameSize, fp) == frameSize)
|
||||||
|
{
|
||||||
|
frameData[frameSize] = 0;
|
||||||
|
|
||||||
|
/* Skip text encoding byte */
|
||||||
|
char* text = frameData + 1;
|
||||||
|
int textLen = frameSize - 1;
|
||||||
|
|
||||||
|
/* Copy to appropriate field */
|
||||||
|
char* dest = NULL;
|
||||||
|
size_t maxLen = 0;
|
||||||
|
|
||||||
|
if(strncmp(frameId, "TIT2", 4) == 0)
|
||||||
|
{
|
||||||
|
dest = metadata->title;
|
||||||
|
maxLen = METADATA_TITLE_MAX - 1;
|
||||||
|
}
|
||||||
|
else if(strncmp(frameId, "TPE1", 4) == 0)
|
||||||
|
{
|
||||||
|
dest = metadata->artist;
|
||||||
|
maxLen = METADATA_ARTIST_MAX - 1;
|
||||||
|
}
|
||||||
|
else if(strncmp(frameId, "TALB", 4) == 0)
|
||||||
|
{
|
||||||
|
dest = metadata->album;
|
||||||
|
maxLen = METADATA_ALBUM_MAX - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dest)
|
||||||
|
{
|
||||||
|
strncpy(dest, text, maxLen);
|
||||||
|
dest[maxLen] = 0;
|
||||||
|
trimWhitespace(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(frameData);
|
||||||
|
}
|
||||||
|
else if(strncmp(frameId, "APIC", 4) == 0) /* Attached Picture */
|
||||||
|
{
|
||||||
|
/* Basic album art detection - just store size info for now */
|
||||||
|
if(frameSize > 10 && !metadata->hasAlbumArt)
|
||||||
|
{
|
||||||
|
metadata->hasAlbumArt = true;
|
||||||
|
metadata->albumArtSize = frameSize;
|
||||||
|
/* Estimate dimensions - actual implementation would decode image */
|
||||||
|
metadata->albumArtWidth = 300; /* Common album art size */
|
||||||
|
metadata->albumArtHeight = 300;
|
||||||
|
}
|
||||||
|
fseek(fp, frameSize, SEEK_CUR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Skip unknown frame */
|
||||||
|
fseek(fp, frameSize, SEEK_CUR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract ID3v1 metadata from MP3 file
|
||||||
|
*/
|
||||||
|
static int extractId3v1Metadata(FILE* fp, struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
char tag[128];
|
||||||
|
|
||||||
|
if(!fp || !metadata)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Seek to last 128 bytes */
|
||||||
|
fseek(fp, -128, SEEK_END);
|
||||||
|
|
||||||
|
if(fread(tag, 1, 128, fp) != 128)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Check for "TAG" signature */
|
||||||
|
if(memcmp(tag, "TAG", 3) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Extract fields */
|
||||||
|
if(!metadata->title[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->title, tag + 3, 30);
|
||||||
|
metadata->title[30] = 0;
|
||||||
|
trimWhitespace(metadata->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!metadata->artist[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->artist, tag + 33, 30);
|
||||||
|
metadata->artist[30] = 0;
|
||||||
|
trimWhitespace(metadata->artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!metadata->album[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->album, tag + 63, 30);
|
||||||
|
metadata->album[30] = 0;
|
||||||
|
trimWhitespace(metadata->album);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Vorbis comment metadata (for OGG Vorbis/Opus)
|
||||||
|
*/
|
||||||
|
static int extractVorbisComment(FILE* fp, struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
/* This is a simplified implementation for Vorbis comments.
|
||||||
|
* A full implementation would require parsing the OGG container structure.
|
||||||
|
* For now, we'll try to find common Vorbis comment fields by searching for them.
|
||||||
|
*/
|
||||||
|
char buffer[4096];
|
||||||
|
size_t bytesRead;
|
||||||
|
|
||||||
|
if(!fp || !metadata)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Reset to beginning */
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
|
||||||
|
/* Read and search for Vorbis comment patterns */
|
||||||
|
while((bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp)) > 0)
|
||||||
|
{
|
||||||
|
buffer[bytesRead] = '\0';
|
||||||
|
|
||||||
|
/* Look for TITLE= */
|
||||||
|
char* title = strstr(buffer, "TITLE=");
|
||||||
|
if(title && !metadata->title[0])
|
||||||
|
{
|
||||||
|
title += 6; /* Skip "TITLE=" */
|
||||||
|
char* end = strchr(title, '\1'); /* Vorbis comments end with \1 or \0 */
|
||||||
|
if(!end) end = strchr(title, '\0');
|
||||||
|
if(end)
|
||||||
|
{
|
||||||
|
size_t len = end - title;
|
||||||
|
if(len >= METADATA_TITLE_MAX) len = METADATA_TITLE_MAX - 1;
|
||||||
|
strncpy(metadata->title, title, len);
|
||||||
|
metadata->title[len] = '\0';
|
||||||
|
trimWhitespace(metadata->title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for ARTIST= or ALBUMARTIST= */
|
||||||
|
char* artist = strstr(buffer, "ARTIST=");
|
||||||
|
if(!artist)
|
||||||
|
artist = strstr(buffer, "ALBUMARTIST=");
|
||||||
|
|
||||||
|
if(artist && !metadata->artist[0])
|
||||||
|
{
|
||||||
|
if(strncmp(artist, "ALBUMARTIST=", 12) == 0)
|
||||||
|
artist += 12; /* Skip "ALBUMARTIST=" */
|
||||||
|
else
|
||||||
|
artist += 7; /* Skip "ARTIST=" */
|
||||||
|
|
||||||
|
char* end = strchr(artist, '\1');
|
||||||
|
if(!end) end = strchr(artist, '\0');
|
||||||
|
if(end)
|
||||||
|
{
|
||||||
|
size_t len = end - artist;
|
||||||
|
if(len >= METADATA_ARTIST_MAX) len = METADATA_ARTIST_MAX - 1;
|
||||||
|
strncpy(metadata->artist, artist, len);
|
||||||
|
metadata->artist[len] = '\0';
|
||||||
|
trimWhitespace(metadata->artist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for ALBUM= */
|
||||||
|
char* album = strstr(buffer, "ALBUM=");
|
||||||
|
if(album && !metadata->album[0])
|
||||||
|
{
|
||||||
|
album += 6; /* Skip "ALBUM=" */
|
||||||
|
char* end = strchr(album, '\1');
|
||||||
|
if(!end) end = strchr(album, '\0');
|
||||||
|
if(end)
|
||||||
|
{
|
||||||
|
size_t len = end - album;
|
||||||
|
if(len >= METADATA_ALBUM_MAX) len = METADATA_ALBUM_MAX - 1;
|
||||||
|
strncpy(metadata->album, album, len);
|
||||||
|
metadata->album[len] = '\0';
|
||||||
|
trimWhitespace(metadata->album);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract FLAC metadata
|
||||||
|
*/
|
||||||
|
static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
char header[4];
|
||||||
|
|
||||||
|
if(!fp || !metadata)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Check for FLAC signature */
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
if(fread(header, 1, 4, fp) != 4 || memcmp(header, "fLaC", 4) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* FLAC metadata blocks follow the signature */
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
uint8_t blockHeader[4];
|
||||||
|
uint32_t blockSize;
|
||||||
|
bool isLast;
|
||||||
|
uint8_t blockType;
|
||||||
|
|
||||||
|
if(fread(blockHeader, 1, 4, fp) != 4)
|
||||||
|
break;
|
||||||
|
|
||||||
|
isLast = (blockHeader[0] & 0x80) != 0;
|
||||||
|
blockType = blockHeader[0] & 0x7F;
|
||||||
|
blockSize = (blockHeader[1] << 16) | (blockHeader[2] << 8) | blockHeader[3];
|
||||||
|
|
||||||
|
if(blockType == 4) /* VORBIS_COMMENT */
|
||||||
|
{
|
||||||
|
/* FLAC uses Vorbis comments for metadata */
|
||||||
|
/* This is a simplified implementation */
|
||||||
|
char* commentData = malloc(blockSize);
|
||||||
|
if(commentData)
|
||||||
|
{
|
||||||
|
if(fread(commentData, 1, blockSize, fp) == blockSize)
|
||||||
|
{
|
||||||
|
/* Skip vendor string length (4 bytes) and vendor string */
|
||||||
|
if(blockSize > 4)
|
||||||
|
{
|
||||||
|
uint32_t vendorLength = commentData[0] | (commentData[1] << 8) |
|
||||||
|
(commentData[2] << 16) | (commentData[3] << 24);
|
||||||
|
|
||||||
|
if(vendorLength + 8 < blockSize) /* 4 bytes vendor len + vendor + 4 bytes comment count */
|
||||||
|
{
|
||||||
|
uint32_t commentCount = commentData[4 + vendorLength] |
|
||||||
|
(commentData[5 + vendorLength] << 8) |
|
||||||
|
(commentData[6 + vendorLength] << 16) |
|
||||||
|
(commentData[7 + vendorLength] << 24);
|
||||||
|
|
||||||
|
uint32_t offset = 8 + vendorLength;
|
||||||
|
for(uint32_t i = 0; i < commentCount && offset < blockSize; i++)
|
||||||
|
{
|
||||||
|
if(offset + 4 > blockSize) break;
|
||||||
|
|
||||||
|
uint32_t commentLength = commentData[offset] | (commentData[offset + 1] << 8) |
|
||||||
|
(commentData[offset + 2] << 16) | (commentData[offset + 3] << 24);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
if(offset + commentLength > blockSize) break;
|
||||||
|
|
||||||
|
/* Parse comment */
|
||||||
|
char* comment = commentData + offset;
|
||||||
|
if(commentLength > 0 && commentLength < 1000) /* Reasonable size check */
|
||||||
|
{
|
||||||
|
char tempComment[1001];
|
||||||
|
strncpy(tempComment, comment, commentLength);
|
||||||
|
tempComment[commentLength] = '\0';
|
||||||
|
|
||||||
|
if(strncmp(tempComment, "TITLE=", 6) == 0 && !metadata->title[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->title, tempComment + 6, METADATA_TITLE_MAX - 1);
|
||||||
|
metadata->title[METADATA_TITLE_MAX - 1] = '\0';
|
||||||
|
trimWhitespace(metadata->title);
|
||||||
|
}
|
||||||
|
else if(strncmp(tempComment, "ARTIST=", 7) == 0 && !metadata->artist[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->artist, tempComment + 7, METADATA_ARTIST_MAX - 1);
|
||||||
|
metadata->artist[METADATA_ARTIST_MAX - 1] = '\0';
|
||||||
|
trimWhitespace(metadata->artist);
|
||||||
|
}
|
||||||
|
else if(strncmp(tempComment, "ALBUMARTIST=", 12) == 0 && !metadata->artist[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->artist, tempComment + 12, METADATA_ARTIST_MAX - 1);
|
||||||
|
metadata->artist[METADATA_ARTIST_MAX - 1] = '\0';
|
||||||
|
trimWhitespace(metadata->artist);
|
||||||
|
}
|
||||||
|
else if(strncmp(tempComment, "ALBUM=", 6) == 0 && !metadata->album[0])
|
||||||
|
{
|
||||||
|
strncpy(metadata->album, tempComment + 6, METADATA_ALBUM_MAX - 1);
|
||||||
|
metadata->album[METADATA_ALBUM_MAX - 1] = '\0';
|
||||||
|
trimWhitespace(metadata->album);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += commentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(commentData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Skip other block types */
|
||||||
|
fseek(fp, blockSize, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isLast)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim leading and trailing whitespace from string
|
||||||
|
*/
|
||||||
|
static void trimWhitespace(char* str)
|
||||||
|
{
|
||||||
|
char* start = str;
|
||||||
|
char* end;
|
||||||
|
|
||||||
|
if(!str || *str == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Trim leading space */
|
||||||
|
while(isspace((unsigned char)*start))
|
||||||
|
start++;
|
||||||
|
|
||||||
|
if(*start == 0) /* All spaces? */
|
||||||
|
{
|
||||||
|
*str = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trim trailing space */
|
||||||
|
end = start + strlen(start) - 1;
|
||||||
|
while(end > start && isspace((unsigned char)*end))
|
||||||
|
end--;
|
||||||
|
|
||||||
|
/* Write new null terminator */
|
||||||
|
end[1] = '\0';
|
||||||
|
|
||||||
|
/* Move trimmed string to beginning if needed */
|
||||||
|
if(start != str)
|
||||||
|
{
|
||||||
|
memmove(str, start, strlen(start) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse filename for metadata when tags are not available
|
||||||
|
* Handles formats like "Artist - Album - Track - Title.ext"
|
||||||
|
*/
|
||||||
|
static void parseFilename(const char* file, struct metadata_t* metadata)
|
||||||
|
{
|
||||||
|
if(!file || !metadata)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Extract just the filename without path */
|
||||||
|
const char* basename = strrchr(file, '/');
|
||||||
|
if(!basename)
|
||||||
|
basename = file;
|
||||||
|
else
|
||||||
|
basename++; /* Skip the '/' */
|
||||||
|
|
||||||
|
/* Remove file extension */
|
||||||
|
char filename[256];
|
||||||
|
strncpy(filename, basename, sizeof(filename) - 1);
|
||||||
|
filename[sizeof(filename) - 1] = '\0';
|
||||||
|
char* dot = strrchr(filename, '.');
|
||||||
|
if(dot) *dot = '\0';
|
||||||
|
|
||||||
|
/* Look for pattern: "Artist - Album - Track - Title" */
|
||||||
|
char* parts[4] = {NULL, NULL, NULL, NULL};
|
||||||
|
int partCount = 0;
|
||||||
|
|
||||||
|
char* token = strtok(filename, " - ");
|
||||||
|
while(token && partCount < 4)
|
||||||
|
{
|
||||||
|
parts[partCount] = token;
|
||||||
|
partCount++;
|
||||||
|
token = strtok(NULL, " - ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assign parts based on count */
|
||||||
|
if(partCount >= 4)
|
||||||
|
{
|
||||||
|
/* Artist - Album - Track - Title */
|
||||||
|
strncpy(metadata->artist, parts[0], METADATA_ARTIST_MAX - 1);
|
||||||
|
strncpy(metadata->album, parts[1], METADATA_ALBUM_MAX - 1);
|
||||||
|
strncpy(metadata->title, parts[3], METADATA_TITLE_MAX - 1);
|
||||||
|
}
|
||||||
|
else if(partCount == 3)
|
||||||
|
{
|
||||||
|
/* Artist - Album - Title */
|
||||||
|
strncpy(metadata->artist, parts[0], METADATA_ARTIST_MAX - 1);
|
||||||
|
strncpy(metadata->album, parts[1], METADATA_ALBUM_MAX - 1);
|
||||||
|
strncpy(metadata->title, parts[2], METADATA_TITLE_MAX - 1);
|
||||||
|
}
|
||||||
|
else if(partCount == 2)
|
||||||
|
{
|
||||||
|
/* Artist - Title */
|
||||||
|
strncpy(metadata->artist, parts[0], METADATA_ARTIST_MAX - 1);
|
||||||
|
strncpy(metadata->title, parts[1], METADATA_TITLE_MAX - 1);
|
||||||
|
}
|
||||||
|
else if(partCount == 1)
|
||||||
|
{
|
||||||
|
/* Just Title */
|
||||||
|
strncpy(metadata->title, parts[0], METADATA_TITLE_MAX - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure null termination */
|
||||||
|
metadata->artist[METADATA_ARTIST_MAX - 1] = '\0';
|
||||||
|
metadata->album[METADATA_ALBUM_MAX - 1] = '\0';
|
||||||
|
metadata->title[METADATA_TITLE_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
/* Trim whitespace */
|
||||||
|
trimWhitespace(metadata->artist);
|
||||||
|
trimWhitespace(metadata->album);
|
||||||
|
trimWhitespace(metadata->title);
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ static int channels = SIDEMU_STEREO;
|
|||||||
static int selectedSong = 0;
|
static int selectedSong = 0;
|
||||||
static size_t buffSize = 0.5*frequency*channels; // 0.5 seconds
|
static size_t buffSize = 0.5*frequency*channels; // 0.5 seconds
|
||||||
|
|
||||||
// don't change anything below - only 16 bit/sigend PCM is supported my ctrmus
|
// don't change anything below - only 16 bit/sigend PCM is supported my mice
|
||||||
static int sampleFormat = SIDEMU_SIGNED_PCM;
|
static int sampleFormat = SIDEMU_SIGNED_PCM;
|
||||||
static int bitsPerSample = SIDEMU_16BIT;
|
static int bitsPerSample = SIDEMU_16BIT;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "wav.h"
|
#include "wav.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the various decoder modules in ctrmus.
|
* Test the various decoder modules in mice.
|
||||||
*/
|
*/
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user