feat: refactor album art handling, implement progress bar display, and update application version to dev63
Some checks failed
Build (3DS) / build (push) Failing after 2m6s

This commit is contained in:
2025-12-07 15:39:55 -06:00
parent d09cf0739e
commit 36924ddfae
7 changed files with 79 additions and 274 deletions

View File

@@ -49,7 +49,7 @@ SOURCE_DIRS := source
EXTRA_OUTPUT_FILES :=
LIBRARY_DIRS := $(DEVKITPRO)/libctru $(DEVKITPRO)/portlibs/armv6k $(DEVKITPRO)/portlibs/3ds
LIBRARIES := citro2d citro3d png z sidplay mpg123 vorbisidec opusfile opus ogg ctru m
LIBRARIES := citro2d citro3d sidplay mpg123 vorbisidec opusfile opus ogg ctru m
BUILD_FLAGS := -Wall -Wextra -I$(DEVKITPRO)/libctru/include -I$(DEVKITPRO)/portlibs/armv6k/include/opus -I$(DEVKITPRO)/portlibs/3ds/include/opus -O3 -g3 -ffunction-sections -fdata-sections
# -O0 -g3 -fstack-protector-strong -fsanitize=undefined -fsanitize-trap

View File

@@ -109,10 +109,18 @@ void guiDisplayVersion(const char* version);
void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 color, float scale);
/**
* Display album art on top screen
* Display progress bar on top screen
*
* \param metadata Pointer to metadata structure with album art
* \param position Current position in seconds
* \param duration Total duration in seconds
*/
void guiDisplayAlbumArt(struct metadata_t* metadata);
void guiDisplayProgressBar(float position, float duration);
/**
* Display current directory path on bottom screen
*
* \param path Current directory path
*/
void guiDisplayCurrentPath(const char* path);
#endif

View File

@@ -1,6 +1,6 @@
/**
* mice - 3DS Music Player
* Copyright (C) 2016 Mahyar Koshkouei
* Copyright (C) 2016 sillyangel
*
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
* welcome to redistribute it under certain conditions; for details see the
@@ -13,7 +13,7 @@
#define mice_main_h
/* Application version */
#define MICE_VERSION "dev50"
#define MICE_VERSION "dev63"
/* Default folder */
#define DEFAULT_DIR "sdmc:/"

View File

@@ -14,12 +14,7 @@ struct metadata_t
char artist[METADATA_ARTIST_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);
/**
* Display album art on top screen if available
*
* \param metadata Pointer to metadata structure containing album art
*/
void displayAlbumArt(struct metadata_t* metadata);
#endif

View File

@@ -4,7 +4,6 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <png.h>
#include "gui.h"
#include "metadata.h"
@@ -225,10 +224,10 @@ void guiDisplayFileList(const char** files, int count, int selected, int scroll)
C2D_SceneBegin(bottomTarget);
C2D_Text text;
float y = 10.0f;
float y = 18.0f; /* Start below path display */
float scale = 0.5f;
float lineHeight = 16.0f;
int maxLines = 14;
int maxLines = 13; /* One less line due to path at top */
C2D_TextBufClear(textBuf);
@@ -339,128 +338,56 @@ void guiDisplayVersion(const char* version)
}
/**
* PNG read callback for memory buffer
*/
static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length)
{
uint8_t** buffer_ptr = (uint8_t**)png_get_io_ptr(png_ptr);
memcpy(data, *buffer_ptr, length);
*buffer_ptr += length;
}
/**
* Display album art on top screen
* Display progress bar on top screen
*/
void guiDisplayAlbumArt(struct metadata_t* metadata)
void guiDisplayProgressBar(float position, float duration)
{
if(!metadata || !metadata->hasAlbumArt || !metadata->albumArt)
if(duration <= 0)
return;
C2D_SceneBegin(topTarget);
/* Decode PNG image */
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if(!png_ptr)
return;
/* Progress bar at bottom of top screen */
float barY = 205.0f;
float barX = 10.0f;
float barWidth = 380.0f;
float barHeight = 6.0f;
png_infop info_ptr = png_create_info_struct(png_ptr);
if(!info_ptr)
/* 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)
{
png_destroy_read_struct(&png_ptr, NULL, NULL);
return;
C2D_DrawRectSolid(barX, barY, 0.5f, fillWidth, barHeight, GUI_COLOR_ACCENT);
}
if(setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return;
}
/* Set up custom read function */
uint8_t* buffer_ptr = metadata->albumArt;
png_set_read_fn(png_ptr, &buffer_ptr, pngReadCallback);
/* Read PNG info */
png_read_info(png_ptr, info_ptr);
int width = png_get_image_width(png_ptr, info_ptr);
int height = png_get_image_height(png_ptr, info_ptr);
png_byte color_type = png_get_color_type(png_ptr, info_ptr);
png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
/* Convert to RGBA8 */
if(bit_depth == 16)
png_set_strip_16(png_ptr);
if(color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png_ptr);
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr);
if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png_ptr);
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png_ptr);
png_read_update_info(png_ptr, info_ptr);
/* Allocate image buffer */
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
if(!row_pointers)
{
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return;
}
for(int y = 0; y < height; y++)
{
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, info_ptr));
if(!row_pointers[y])
{
for(int i = 0; i < y; i++)
free(row_pointers[i]);
free(row_pointers);
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return;
}
}
/* Read image data */
png_read_image(png_ptr, row_pointers);
/* Scale to fit in top-right corner (max 60x60) */
int displayWidth = width;
int displayHeight = height;
if(width > 60 || height > 60)
{
float scale = 60.0f / (width > height ? width : height);
displayWidth = (int)(width * scale);
displayHeight = (int)(height * scale);
}
/* Draw the image pixel by pixel */
float startX = 330.0f; /* Top right corner */
float startY = 10.0f;
for(int y = 0; y < displayHeight; y++)
{
int srcY = (y * height) / displayHeight;
for(int x = 0; x < displayWidth; x++)
{
int srcX = (x * width) / displayWidth;
png_bytep px = &(row_pointers[srcY][srcX * 4]);
u32 color = C2D_Color32(px[0], px[1], px[2], px[3]);
C2D_DrawRectSolid(startX + x, startY + y, 0.5f, 1, 1, color);
}
}
/* Clean up */
for(int y = 0; y < height; y++)
free(row_pointers[y]);
free(row_pointers);
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
}
/**
* 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);
}

View File

@@ -1,6 +1,6 @@
/**
* mice - 3DS Music Player
* Copyright (C) 2016 Mahyar Koshkouei
* Copyright (C) 2016 sillyangel
*
* This program comes with ABSOLUTELY NO WARRANTY and is free software. You are
* welcome to redistribute it under certain conditions; for details see the
@@ -617,15 +617,21 @@ int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
const char* currentFile = (fileNum > 0 && fileNum <= dirList.dirNum + dirList.fileNum) ?
(fileNum > dirList.dirNum ? dirList.files[fileNum - dirList.dirNum - 1] : "..") : "";
guiDisplayMetadata(&currentMetadata, currentFile);
guiDisplayAlbumArt(&currentMetadata);
}
/* 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;
if(fileNum < scroll)
scroll = fileNum;
else if(fileNum >= scroll + 14)
scroll = fileNum - 13;
else if(fileNum >= scroll + 13)
scroll = fileNum - 12;
/* Display current directory path */
char currentPath[256];
if(getcwd(currentPath, sizeof(currentPath)))
{
guiDisplayCurrentPath(currentPath);
}
/* Display file list on bottom screen */
guiDisplayFileList(fileList, fileListCount, fileNum, scroll);
@@ -633,11 +639,12 @@ int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
/* Display logs on top screen */
guiDisplayLog((const char**)logMessages, logMessageCount, logScroll);
/* Display playback status */
/* Display playback status and progress bar */
if(playbackInfo.samples_per_second > 0)
{
float position = (float)playbackInfo.samples_played / playbackInfo.samples_per_second;
float duration = (float)playbackInfo.samples_total / playbackInfo.samples_per_second;
guiDisplayProgressBar(position, duration);
guiDisplayPlaybackStatus(isPlaying(), isPaused(), position, duration);
}

View File

@@ -83,16 +83,7 @@ void clearMetadata(struct metadata_t* metadata)
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;
}
/**
@@ -104,19 +95,6 @@ void displayMetadata(struct metadata_t* metadata, const char* 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
*/
@@ -278,52 +256,9 @@ static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata)
}
else if(strncmp(frameId, "APIC", 4) == 0) /* Attached Picture */
{
/* Extract album art data */
if(frameSize > 10 && !metadata->hasAlbumArt)
{
uint8_t* frameData = malloc(frameSize);
if(frameData && fread(frameData, 1, frameSize, fp) == frameSize)
{
/* ID3v2 APIC frame format:
* - Text encoding (1 byte)
* - MIME type (null-terminated string)
* - Picture type (1 byte)
* - Description (null-terminated string)
* - Picture data
*/
uint8_t* ptr = frameData;
ptr++; /* Skip text encoding */
/* Skip MIME type */
while(*ptr != 0 && (ptr - frameData) < (int)frameSize) ptr++;
ptr++; /* Skip null terminator */
ptr++; /* Skip picture type */
/* Skip description */
while(*ptr != 0 && (ptr - frameData) < (int)frameSize) ptr++;
ptr++; /* Skip null terminator */
/* Remaining data is the image */
size_t imageSize = frameSize - (ptr - frameData);
if(imageSize > 0)
{
metadata->albumArt = malloc(imageSize);
if(metadata->albumArt)
{
memcpy(metadata->albumArt, ptr, imageSize);
metadata->albumArtSize = imageSize;
metadata->hasAlbumArt = true;
}
}
}
if(frameData) free(frameData);
}
else
{
/* Skip album art data */
fseek(fp, frameSize, SEEK_CUR);
}
}
else
{
/* Skip unknown frame */
@@ -496,69 +431,9 @@ static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata)
if(blockType == 6) /* PICTURE */
{
/* Extract album art from FLAC picture block */
if(!metadata->hasAlbumArt && blockSize > 32)
{
uint8_t* pictureData = malloc(blockSize);
if(pictureData && fread(pictureData, 1, blockSize, fp) == blockSize)
{
/* FLAC picture block format:
* - Picture type (4 bytes BE)
* - MIME type length (4 bytes BE)
* - MIME type string
* - Description length (4 bytes BE)
* - Description string
* - Width (4 bytes BE)
* - Height (4 bytes BE)
* - Depth (4 bytes BE)
* - Colors (4 bytes BE)
* - Picture data length (4 bytes BE)
* - Picture data
*/
uint32_t offset = 4; /* Skip picture type */
/* Skip MIME type */
uint32_t mimeLen = (pictureData[offset] << 24) | (pictureData[offset+1] << 16) |
(pictureData[offset+2] << 8) | pictureData[offset+3];
offset += 4 + mimeLen;
/* Skip description */
if(offset + 4 <= blockSize)
{
uint32_t descLen = (pictureData[offset] << 24) | (pictureData[offset+1] << 16) |
(pictureData[offset+2] << 8) | pictureData[offset+3];
offset += 4 + descLen;
}
/* Skip width, height, depth, colors (16 bytes) */
offset += 16;
/* Get picture data length */
if(offset + 4 <= blockSize)
{
uint32_t picLen = (pictureData[offset] << 24) | (pictureData[offset+1] << 16) |
(pictureData[offset+2] << 8) | pictureData[offset+3];
offset += 4;
if(offset + picLen <= blockSize && picLen > 0)
{
metadata->albumArt = malloc(picLen);
if(metadata->albumArt)
{
memcpy(metadata->albumArt, pictureData + offset, picLen);
metadata->albumArtSize = picLen;
metadata->hasAlbumArt = true;
}
}
}
}
if(pictureData) free(pictureData);
}
else
{
/* Skip picture block */
fseek(fp, blockSize, SEEK_CUR);
}
}
else if(blockType == 4) /* VORBIS_COMMENT */
{
/* FLAC uses Vorbis comments for metadata */