Compare commits
5 Commits
5d9ce1fdb9
...
dev63
| Author | SHA1 | Date | |
|---|---|---|---|
|
906734a14c
|
|||
|
92647e2f1d
|
|||
|
36924ddfae
|
|||
|
d09cf0739e
|
|||
|
8be23ca4fc
|
6
.github/workflows/3ds.yml
vendored
6
.github/workflows/3ds.yml
vendored
@@ -51,9 +51,9 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: make -j"$(nproc)"
|
run: make -j"$(nproc)"
|
||||||
|
|
||||||
- name: Upload artefacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: mice-build
|
name: mice-build
|
||||||
path: output
|
path: output/
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ The latest 3DSX/CIA/3DS download can be found on the <a href="https://github.com
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Plays PCM WAV, AIFF, FLAC, Opus, Vorbis and MP3 files.
|
* Plays PCM WAV, AIFF, FLAC, Opus, Vorbis and MP3 files.
|
||||||
|
* M4A/AAC/ALAC support (file detection implemented, decoder integration in progress).
|
||||||
* Pause and play support.
|
* Pause and play support.
|
||||||
* Plays music via headphones whilst system is closed.
|
* Plays music via headphones whilst system is closed.
|
||||||
* Ability to browse directories.
|
* Ability to browse directories.
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ enum file_types
|
|||||||
FILE_TYPE_VORBIS,
|
FILE_TYPE_VORBIS,
|
||||||
FILE_TYPE_OPUS,
|
FILE_TYPE_OPUS,
|
||||||
FILE_TYPE_MP3,
|
FILE_TYPE_MP3,
|
||||||
FILE_TYPE_SID
|
FILE_TYPE_SID,
|
||||||
|
FILE_TYPE_M4A,
|
||||||
|
FILE_TYPE_AAC
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -108,4 +108,19 @@ void guiDisplayVersion(const char* version);
|
|||||||
*/
|
*/
|
||||||
void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 color, float scale);
|
void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 color, float scale);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display progress bar on top screen
|
||||||
|
*
|
||||||
|
* \param position Current position in seconds
|
||||||
|
* \param duration Total duration in seconds
|
||||||
|
*/
|
||||||
|
void guiDisplayProgressBar(float position, float duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display current directory path on bottom screen
|
||||||
|
*
|
||||||
|
* \param path Current directory path
|
||||||
|
*/
|
||||||
|
void guiDisplayCurrentPath(const char* path);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
21
include/m4a.h
Normal file
21
include/m4a.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef M4A_H
|
||||||
|
#define M4A_H
|
||||||
|
|
||||||
|
#include "playback.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set decoder parameters for M4A/AAC.
|
||||||
|
*
|
||||||
|
* \param decoder Structure to store parameters.
|
||||||
|
*/
|
||||||
|
void setM4a(struct decoder_fn* decoder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file is an M4A/AAC/ALAC file.
|
||||||
|
*
|
||||||
|
* \param file File location.
|
||||||
|
* \return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int isM4a(const char* file);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* mice - 3DS Music Player
|
* mice - 3DS Music Player
|
||||||
* Copyright (C) 2016 Mahyar Koshkouei
|
* Copyright (C) 2025 sillyangel
|
||||||
*
|
*
|
||||||
* 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
|
||||||
* welcome to redistribute it under certain conditions; for details see the
|
* welcome to redistribute it under certain conditions; for details see the
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
#define mice_main_h
|
#define mice_main_h
|
||||||
|
|
||||||
/* Application version */
|
/* Application version */
|
||||||
#define MICE_VERSION "dev37"
|
#define MICE_VERSION "dev63"
|
||||||
|
|
||||||
/* Default folder */
|
/* Default folder */
|
||||||
#define DEFAULT_DIR "sdmc:/"
|
#define DEFAULT_DIR "sdmc:/"
|
||||||
|
|||||||
@@ -14,12 +14,7 @@ struct metadata_t
|
|||||||
char artist[METADATA_ARTIST_MAX];
|
char artist[METADATA_ARTIST_MAX];
|
||||||
char album[METADATA_ALBUM_MAX];
|
char album[METADATA_ALBUM_MAX];
|
||||||
|
|
||||||
/* Album art */
|
|
||||||
uint8_t* albumArt;
|
|
||||||
size_t albumArtSize;
|
|
||||||
int albumArtWidth;
|
|
||||||
int albumArtHeight;
|
|
||||||
bool hasAlbumArt;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,11 +41,4 @@ void clearMetadata(struct metadata_t* metadata);
|
|||||||
*/
|
*/
|
||||||
void displayMetadata(struct metadata_t* metadata, const char* filename);
|
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
|
#endif
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "flac.h"
|
#include "flac.h"
|
||||||
|
#include "m4a.h"
|
||||||
#include "mp3.h"
|
#include "mp3.h"
|
||||||
#include "opus.h"
|
#include "opus.h"
|
||||||
#include "vorbis.h"
|
#include "vorbis.h"
|
||||||
@@ -26,7 +27,9 @@ const char* fileToStr(enum file_types ft)
|
|||||||
"VORBIS",
|
"VORBIS",
|
||||||
"OPUS",
|
"OPUS",
|
||||||
"MP3",
|
"MP3",
|
||||||
"SID"
|
"SID",
|
||||||
|
"M4A",
|
||||||
|
"AAC"
|
||||||
};
|
};
|
||||||
|
|
||||||
return file_types_str[ft];
|
return file_types_str[ft];
|
||||||
@@ -90,24 +93,37 @@ enum file_types getFileType(const char *file)
|
|||||||
file_type = FILE_TYPE_SID;
|
file_type = FILE_TYPE_SID;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
/*
|
/* Check for M4A/AAC/ALAC (MP4 container with ftyp atom) */
|
||||||
* MP3 without ID3 tag, ID3v1 tag is at the end of file, or MP3
|
if((fileSig == 0x70797466) || /* 'ftyp' at offset 4 */
|
||||||
* with ID3 tag at the beginning of the file.
|
(fileSig == 0x65657266)) /* 'free' at offset 4 (some M4A files) */
|
||||||
*/
|
{
|
||||||
if(isMp3(file) == 0)
|
file_type = FILE_TYPE_M4A;
|
||||||
{
|
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
err:
|
/* Check for raw AAC (ADTS format) - sync word 0xFFF */
|
||||||
|
if((fileSig & 0xFFF60000) == 0xFFF00000)
|
||||||
|
{
|
||||||
|
file_type = FILE_TYPE_AAC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MP3 without ID3 tag, ID3v1 tag is at the end of file, or MP3
|
||||||
|
* with ID3 tag at the beginning of the file.
|
||||||
|
*/
|
||||||
|
if(isMp3(file) == 0)
|
||||||
|
{
|
||||||
|
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);
|
fclose(ftest);
|
||||||
return file_type;
|
return file_type;
|
||||||
}
|
}
|
||||||
|
|||||||
68
source/gui.c
68
source/gui.c
@@ -3,6 +3,7 @@
|
|||||||
#include <citro3d.h>
|
#include <citro3d.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
|
||||||
@@ -180,14 +181,6 @@ void guiDisplayMetadata(struct metadata_t* metadata, const char* filename)
|
|||||||
C2D_TextOptimize(&text);
|
C2D_TextOptimize(&text);
|
||||||
C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM);
|
C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw album art indicator if available */
|
|
||||||
if(metadata->hasAlbumArt)
|
|
||||||
{
|
|
||||||
C2D_TextParse(&text, textBuf, "[Art]");
|
|
||||||
C2D_TextOptimize(&text);
|
|
||||||
C2D_DrawText(&text, C2D_WithColor, 350.0f, 10.0f, 0.5f, 0.4f, 0.4f, GUI_COLOR_ACCENT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,10 +224,10 @@ void guiDisplayFileList(const char** files, int count, int selected, int scroll)
|
|||||||
C2D_SceneBegin(bottomTarget);
|
C2D_SceneBegin(bottomTarget);
|
||||||
|
|
||||||
C2D_Text text;
|
C2D_Text text;
|
||||||
float y = 10.0f;
|
float y = 18.0f; /* Start below path display */
|
||||||
float scale = 0.5f;
|
float scale = 0.5f;
|
||||||
float lineHeight = 16.0f;
|
float lineHeight = 16.0f;
|
||||||
int maxLines = 14;
|
int maxLines = 13; /* One less line due to path at top */
|
||||||
|
|
||||||
C2D_TextBufClear(textBuf);
|
C2D_TextBufClear(textBuf);
|
||||||
|
|
||||||
@@ -343,3 +336,58 @@ void guiDisplayVersion(const char* version)
|
|||||||
C2D_TextOptimize(&text);
|
C2D_TextOptimize(&text);
|
||||||
C2D_DrawText(&text, C2D_WithColor, 80.0f, 220.0f, 0.5f, 0.45f, 0.45f, GUI_COLOR_TEXT_DIM);
|
C2D_DrawText(&text, C2D_WithColor, 80.0f, 220.0f, 0.5f, 0.45f, 0.45f, GUI_COLOR_TEXT_DIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display progress bar on top screen
|
||||||
|
*/
|
||||||
|
void guiDisplayProgressBar(float position, float duration)
|
||||||
|
{
|
||||||
|
if(duration <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
C2D_SceneBegin(topTarget);
|
||||||
|
|
||||||
|
/* Progress bar at bottom of top screen */
|
||||||
|
float barY = 205.0f;
|
||||||
|
float barX = 10.0f;
|
||||||
|
float barWidth = 380.0f;
|
||||||
|
float barHeight = 6.0f;
|
||||||
|
|
||||||
|
/* Background bar */
|
||||||
|
C2D_DrawRectSolid(barX, barY, 0.5f, barWidth, barHeight, C2D_Color32(50, 50, 60, 255));
|
||||||
|
|
||||||
|
/* Progress fill */
|
||||||
|
float progress = position / duration;
|
||||||
|
if(progress > 1.0f) progress = 1.0f;
|
||||||
|
if(progress < 0.0f) progress = 0.0f;
|
||||||
|
|
||||||
|
float fillWidth = barWidth * progress;
|
||||||
|
if(fillWidth > 0)
|
||||||
|
{
|
||||||
|
C2D_DrawRectSolid(barX, barY, 0.5f, fillWidth, barHeight, GUI_COLOR_ACCENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display current directory path on bottom screen
|
||||||
|
*/
|
||||||
|
void guiDisplayCurrentPath(const char* path)
|
||||||
|
{
|
||||||
|
if(!path || !textBuf)
|
||||||
|
return;
|
||||||
|
|
||||||
|
C2D_SceneBegin(bottomTarget);
|
||||||
|
|
||||||
|
C2D_Text text;
|
||||||
|
C2D_TextBufClear(textBuf);
|
||||||
|
|
||||||
|
/* Display path at top of bottom screen */
|
||||||
|
char pathBuf[64];
|
||||||
|
snprintf(pathBuf, sizeof(pathBuf), "%.55s", path);
|
||||||
|
|
||||||
|
C2D_TextParse(&text, textBuf, pathBuf);
|
||||||
|
C2D_TextOptimize(&text);
|
||||||
|
C2D_DrawText(&text, C2D_WithColor, 5.0f, 2.0f, 0.5f, 0.35f, 0.35f, GUI_COLOR_TEXT_DIM);
|
||||||
|
}
|
||||||
|
|||||||
133
source/m4a.c
Normal file
133
source/m4a.c
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <3ds.h>
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
#include "m4a.h"
|
||||||
|
#include "playback.h"
|
||||||
|
|
||||||
|
/* TODO: Integrate proper AAC decoder library (libfaad2, fdk-aac, or minimp4) */
|
||||||
|
/* For now, this is a stub implementation */
|
||||||
|
|
||||||
|
static size_t* buffSize;
|
||||||
|
static uint32_t rate = 44100;
|
||||||
|
static uint8_t channels = 2;
|
||||||
|
|
||||||
|
static int initM4a(const char* file);
|
||||||
|
static uint32_t rateM4a(void);
|
||||||
|
static uint8_t channelM4a(void);
|
||||||
|
static uint64_t decodeM4a(void* buffer);
|
||||||
|
static void exitM4a(void);
|
||||||
|
static size_t getFileSamplesM4a(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set decoder parameters for M4A/AAC.
|
||||||
|
*
|
||||||
|
* \param decoder Structure to store parameters.
|
||||||
|
*/
|
||||||
|
void setM4a(struct decoder_fn* decoder)
|
||||||
|
{
|
||||||
|
decoder->init = &initM4a;
|
||||||
|
decoder->rate = &rateM4a;
|
||||||
|
decoder->channels = &channelM4a;
|
||||||
|
buffSize = &(decoder->buffSize);
|
||||||
|
decoder->decode = &decodeM4a;
|
||||||
|
decoder->exit = &exitM4a;
|
||||||
|
decoder->getFileSamples = &getFileSamplesM4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file is an M4A/AAC/ALAC file.
|
||||||
|
*
|
||||||
|
* \param file File location.
|
||||||
|
* \return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int isM4a(const char* file)
|
||||||
|
{
|
||||||
|
FILE* ftest = fopen(file, "rb");
|
||||||
|
uint32_t fileSig;
|
||||||
|
uint32_t ftypSig;
|
||||||
|
|
||||||
|
if(ftest == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Read first 4 bytes (should be size of ftyp atom) */
|
||||||
|
if(fread(&fileSig, 4, 1, ftest) == 0)
|
||||||
|
{
|
||||||
|
fclose(ftest);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read next 4 bytes (should be 'ftyp') */
|
||||||
|
if(fread(&ftypSig, 4, 1, ftest) == 0)
|
||||||
|
{
|
||||||
|
fclose(ftest);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(ftest);
|
||||||
|
|
||||||
|
/* Check for 'ftyp' signature (0x70797466 in little-endian) */
|
||||||
|
if(ftypSig == 0x70797466)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int initM4a(const char* file)
|
||||||
|
{
|
||||||
|
(void)file;
|
||||||
|
|
||||||
|
/* TODO: Initialize AAC decoder */
|
||||||
|
/* This requires:
|
||||||
|
* 1. Parse MP4 container to find AAC audio track
|
||||||
|
* 2. Extract decoder config (sample rate, channels, etc.)
|
||||||
|
* 3. Initialize AAC decoder with config
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Set default values for now */
|
||||||
|
rate = 44100;
|
||||||
|
channels = 2;
|
||||||
|
*buffSize = rate * channels * sizeof(int16_t);
|
||||||
|
|
||||||
|
errno = FILE_NOT_SUPPORTED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t rateM4a(void)
|
||||||
|
{
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t channelM4a(void)
|
||||||
|
{
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t decodeM4a(void* buffer)
|
||||||
|
{
|
||||||
|
(void)buffer;
|
||||||
|
|
||||||
|
/* TODO: Decode AAC frame */
|
||||||
|
/* This requires:
|
||||||
|
* 1. Read next AAC frame from MP4 container
|
||||||
|
* 2. Decode AAC frame to PCM samples
|
||||||
|
* 3. Write PCM samples to buffer
|
||||||
|
* 4. Return number of samples decoded
|
||||||
|
*/
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exitM4a(void)
|
||||||
|
{
|
||||||
|
/* TODO: Clean up AAC decoder */
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t getFileSamplesM4a(void)
|
||||||
|
{
|
||||||
|
/* TODO: Calculate total samples from MP4 metadata */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* mice - 3DS Music Player
|
* mice - 3DS Music Player
|
||||||
* Copyright (C) 2016 Mahyar Koshkouei
|
* Copyright (C) 2025 sillyangel
|
||||||
*
|
*
|
||||||
* 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
|
||||||
* welcome to redistribute it under certain conditions; for details see the
|
* welcome to redistribute it under certain conditions; for details see the
|
||||||
@@ -614,17 +614,24 @@ int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
|
|||||||
/* Display metadata if we have any */
|
/* Display metadata if we have any */
|
||||||
if(currentMetadata.title[0] || currentMetadata.artist[0] || currentMetadata.album[0])
|
if(currentMetadata.title[0] || currentMetadata.artist[0] || currentMetadata.album[0])
|
||||||
{
|
{
|
||||||
const char* currentFile = (fileNum > 0 && fileNum <= dirList.dirNum + dirList.fileNum) ?
|
const char* currentFile = (fileNum > 0 && fileNum <= dirList.dirNum + dirList.fileNum) ?
|
||||||
(fileNum > dirList.dirNum ? dirList.files[fileNum - dirList.dirNum - 1] : "..") : "";
|
(fileNum > dirList.dirNum ? dirList.files[fileNum - dirList.dirNum - 1] : "..") : "";
|
||||||
guiDisplayMetadata(¤tMetadata, currentFile);
|
guiDisplayMetadata(¤tMetadata, currentFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate scroll position to keep selection visible (14 lines visible) */
|
/* Calculate scroll position to keep selection visible (13 lines visible with path) */
|
||||||
int scroll = from;
|
int scroll = from;
|
||||||
if(fileNum < scroll)
|
if(fileNum < scroll)
|
||||||
scroll = fileNum;
|
scroll = fileNum;
|
||||||
else if(fileNum >= scroll + 14)
|
else if(fileNum >= scroll + 13)
|
||||||
scroll = fileNum - 13;
|
scroll = fileNum - 12;
|
||||||
|
|
||||||
|
/* Display current directory path */
|
||||||
|
char currentPath[256];
|
||||||
|
if(getcwd(currentPath, sizeof(currentPath)))
|
||||||
|
{
|
||||||
|
guiDisplayCurrentPath(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
/* Display file list on bottom screen */
|
/* Display file list on bottom screen */
|
||||||
guiDisplayFileList(fileList, fileListCount, fileNum, scroll);
|
guiDisplayFileList(fileList, fileListCount, fileNum, scroll);
|
||||||
@@ -632,11 +639,12 @@ int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
|
|||||||
/* Display logs on top screen */
|
/* Display logs on top screen */
|
||||||
guiDisplayLog((const char**)logMessages, logMessageCount, logScroll);
|
guiDisplayLog((const char**)logMessages, logMessageCount, logScroll);
|
||||||
|
|
||||||
/* Display playback status */
|
/* Display playback status and progress bar */
|
||||||
if(playbackInfo.samples_per_second > 0)
|
if(playbackInfo.samples_per_second > 0)
|
||||||
{
|
{
|
||||||
float position = (float)playbackInfo.samples_played / playbackInfo.samples_per_second;
|
float position = (float)playbackInfo.samples_played / playbackInfo.samples_per_second;
|
||||||
float duration = (float)playbackInfo.samples_total / playbackInfo.samples_per_second;
|
float duration = (float)playbackInfo.samples_total / playbackInfo.samples_per_second;
|
||||||
|
guiDisplayProgressBar(position, duration);
|
||||||
guiDisplayPlaybackStatus(isPlaying(), isPaused(), position, duration);
|
guiDisplayPlaybackStatus(isPlaying(), isPaused(), position, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,16 +83,7 @@ void clearMetadata(struct metadata_t* metadata)
|
|||||||
memset(metadata->artist, 0, METADATA_ARTIST_MAX);
|
memset(metadata->artist, 0, METADATA_ARTIST_MAX);
|
||||||
memset(metadata->album, 0, METADATA_ALBUM_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,19 +95,6 @@ void displayMetadata(struct metadata_t* metadata, const char* filename)
|
|||||||
guiDisplayMetadata(metadata, filename);
|
guiDisplayMetadata(metadata, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Extract ID3v2 metadata from MP3 file
|
||||||
*/
|
*/
|
||||||
@@ -194,55 +172,91 @@ static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fread(frameData, 1, frameSize, fp) == frameSize)
|
if(fread(frameData, 1, frameSize, fp) == frameSize)
|
||||||
|
{
|
||||||
|
frameData[frameSize] = 0;
|
||||||
|
|
||||||
|
/* Text encoding byte:
|
||||||
|
* 0x00 = ISO-8859-1
|
||||||
|
* 0x01 = UTF-16 with BOM
|
||||||
|
* 0x02 = UTF-16BE without BOM
|
||||||
|
* 0x03 = UTF-8
|
||||||
|
*/
|
||||||
|
uint8_t encoding = frameData[0];
|
||||||
|
char* text = frameData + 1;
|
||||||
|
int textLen = frameSize - 1;
|
||||||
|
|
||||||
|
/* Skip UTF-16 BOM if present */
|
||||||
|
if(encoding == 0x01 && textLen >= 2)
|
||||||
{
|
{
|
||||||
frameData[frameSize] = 0;
|
if((uint8_t)text[0] == 0xFF && (uint8_t)text[1] == 0xFE)
|
||||||
|
|
||||||
/* 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;
|
text += 2;
|
||||||
maxLen = METADATA_TITLE_MAX - 1;
|
textLen -= 2;
|
||||||
}
|
}
|
||||||
else if(strncmp(frameId, "TPE1", 4) == 0)
|
else if((uint8_t)text[0] == 0xFE && (uint8_t)text[1] == 0xFF)
|
||||||
{
|
{
|
||||||
dest = metadata->artist;
|
text += 2;
|
||||||
maxLen = METADATA_ARTIST_MAX - 1;
|
textLen -= 2;
|
||||||
}
|
|
||||||
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);
|
/* 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)
|
||||||
|
{
|
||||||
|
/* Handle different encodings */
|
||||||
|
if(encoding == 0x01 || encoding == 0x02) /* UTF-16 */
|
||||||
|
{
|
||||||
|
/* Convert UTF-16 to ASCII (simplified - just take every other byte) */
|
||||||
|
int outPos = 0;
|
||||||
|
for(int i = 0; i < textLen && outPos < (int)maxLen; i += 2)
|
||||||
|
{
|
||||||
|
if(text[i] >= 0x20 && text[i] < 0x7F)
|
||||||
|
dest[outPos++] = text[i];
|
||||||
|
else if(text[i] == 0 && text[i+1] == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dest[outPos] = 0;
|
||||||
|
}
|
||||||
|
else /* ISO-8859-1 or UTF-8 */
|
||||||
|
{
|
||||||
|
/* Copy as-is, filtering out non-ASCII characters */
|
||||||
|
int outPos = 0;
|
||||||
|
for(int i = 0; i < textLen && outPos < (int)maxLen; i++)
|
||||||
|
{
|
||||||
|
if((uint8_t)text[i] >= 0x20 && (uint8_t)text[i] < 0x7F)
|
||||||
|
dest[outPos++] = text[i];
|
||||||
|
else if(text[i] == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dest[outPos] = 0;
|
||||||
|
}
|
||||||
|
trimWhitespace(dest);
|
||||||
|
}
|
||||||
|
} free(frameData);
|
||||||
}
|
}
|
||||||
else if(strncmp(frameId, "APIC", 4) == 0) /* Attached Picture */
|
else if(strncmp(frameId, "APIC", 4) == 0) /* Attached Picture */
|
||||||
{
|
{
|
||||||
/* Basic album art detection - just store size info for now */
|
/* Skip album art data */
|
||||||
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);
|
fseek(fp, frameSize, SEEK_CUR);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -415,7 +429,12 @@ static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata)
|
|||||||
blockType = blockHeader[0] & 0x7F;
|
blockType = blockHeader[0] & 0x7F;
|
||||||
blockSize = (blockHeader[1] << 16) | (blockHeader[2] << 8) | blockHeader[3];
|
blockSize = (blockHeader[1] << 16) | (blockHeader[2] << 8) | blockHeader[3];
|
||||||
|
|
||||||
if(blockType == 4) /* VORBIS_COMMENT */
|
if(blockType == 6) /* PICTURE */
|
||||||
|
{
|
||||||
|
/* Skip picture block */
|
||||||
|
fseek(fp, blockSize, SEEK_CUR);
|
||||||
|
}
|
||||||
|
else if(blockType == 4) /* VORBIS_COMMENT */
|
||||||
{
|
{
|
||||||
/* FLAC uses Vorbis comments for metadata */
|
/* FLAC uses Vorbis comments for metadata */
|
||||||
/* This is a simplified implementation */
|
/* This is a simplified implementation */
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "flac.h"
|
#include "flac.h"
|
||||||
|
#include "m4a.h"
|
||||||
#include "mp3.h"
|
#include "mp3.h"
|
||||||
#include "opus.h"
|
#include "opus.h"
|
||||||
#include "playback.h"
|
#include "playback.h"
|
||||||
@@ -98,15 +99,18 @@ void playFile(void* infoIn)
|
|||||||
setVorbis(&decoder);
|
setVorbis(&decoder);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FILE_TYPE_SID:
|
case FILE_TYPE_SID:
|
||||||
setSid(&decoder);
|
setSid(&decoder);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case FILE_TYPE_M4A:
|
||||||
goto err;
|
case FILE_TYPE_AAC:
|
||||||
}
|
setM4a(&decoder);
|
||||||
|
break;
|
||||||
|
|
||||||
if(ndspInit() < 0)
|
default:
|
||||||
|
goto err;
|
||||||
|
} if(ndspInit() < 0)
|
||||||
{
|
{
|
||||||
errno = NDSP_INIT_FAIL;
|
errno = NDSP_INIT_FAIL;
|
||||||
goto err;
|
goto err;
|
||||||
|
|||||||
Reference in New Issue
Block a user