From 557e07876de0086b70e301b104547bf35ec57959 Mon Sep 17 00:00:00 2001 From: quou Date: Tue, 4 Jun 2024 21:42:04 +1000 Subject: Loading album art, seeking, pause/play. --- Makefile | 3 +- config.h | 3 ++ library.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- library.h | 7 ++- main.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- plat.c | 28 +++++++++--- plat.h | 1 + 7 files changed, 324 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 4505fe0..21c1210 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ defines = \ -Dplat_pulse \ -Dis64bit=1 \ -Dplat_x86 \ - -DDEBUG + -DDEBUG \ + -DUI_LINUX luigi = luigi.o sources = \ diff --git a/config.h b/config.h index 33108e8..47fb153 100644 --- a/config.h +++ b/config.h @@ -5,6 +5,9 @@ #define default_window_w 1280 #define default_window_h 960 +#define album_cover_w 600 +#define album_cover_h 600 + /* For each channel. */ #define audio_buffer_size (1024 * 4) diff --git a/library.c b/library.c index cf947cd..1ff2510 100644 --- a/library.c +++ b/library.c @@ -5,6 +5,7 @@ #include #include "dr_flac.h" +#include "stb_image.h" static unsigned hash(const char* s) { const unsigned char* p = (const unsigned char*)s; @@ -121,6 +122,68 @@ void flac_meta( } } +void conv_art( + Player* p, + unsigned char* pixels, + int w, + int h +) { + int tx, ty, i, j, k; + float rx, ry; + unsigned px; + rx = (float)w / (float)album_cover_w; + ry = (float)h / (float)album_cover_h; + for (j = 0; j < album_cover_h; j++) { + ty = (int)((float)j * ry); + for (i = 0; i < album_cover_w; i++) { + tx = (int)((float)i * rx); + px = 0; + px |= 0xff << 24; + k = (tx + ty * w) * 4; + px |= pixels[k + 0] << 16; + px |= pixels[k + 1] << 8; + px |= pixels[k + 2]; + p->cover[i + j * album_cover_w] = px; + } + } + p->has_art = 1; +} + +void load_cover(Player* p, const unsigned char* data, int size) { + unsigned char* pixels; + int w, h, c, rc = 4; + pixels = stbi_load_from_memory( + data, + size, + &w, + &h, + &c, + rc + ); + conv_art(p, pixels, w, h); + stbi_image_free(pixels); +} + +void art_meta( + void* uptr, + drflac_metadata* m +) { + switch (m->type) { + case DRFLAC_METADATA_BLOCK_TYPE_PICTURE: + if ( + m->data.picture.type == + DRFLAC_PICTURE_TYPE_COVER_FRONT + ) { + load_cover( + uptr, + m->data.picture.pPictureData, + m->data.picture.pictureDataSize + ); + } + break; + } +} + int get_song_meta(const char* path, Song* s) { drflac* f; f = drflac_open_file_with_metadata( @@ -147,12 +210,15 @@ int sound_mix( int r; (void)size; p = uptr; + if (!p->play) return 0; f = p->f; r = drflac_read_pcm_frames_s32( f, size / p->channels / 4, (int*)buf ); + if (!r) p->play = 0; + p->cs += r; return r * p->channels * 4; } @@ -162,20 +228,80 @@ void init_player(Player* p) { p->play = 0; } +void try_load_art(Player* p) { + const char* const tt[] = { + "cover.jpg", + "cover.png", + "Cover.jpg", + "Cover.png", + "folder.jpg", + "Folder.jpg", + "folder.png", + "Folder.png", + "../cover.jpg", + "../cover.png", + "../Cover.jpg", + "../Cover.png", + "../folder.jpg", + "../Folder.jpg", + "../folder.png", + "../Folder.png" + }; + unsigned char* pixels; + int w, h, c, rc = 4, end, i, len; + char buf[256]; + char* start; + strcpy(buf, p->song->path); + len = strlen(buf); + while (len > 0 && buf[len] != '/') { + buf[len] = 0; + len--; + } + end = sizeof tt / sizeof *tt; + for (i = 0; i < end; i++) { + start = buf + len; + strcat(start, tt[i]); + pixels = stbi_load( + buf, + &w, + &h, + &c, + rc + ); + if (pixels) { + conv_art(p, pixels, w, h); + stbi_image_free(pixels); + return; + } + buf[len + 1] = 0; + } +} + void play_song(Player* p, Song* song) { drflac* f; int sr, channels; - f = drflac_open_file( + p->has_art = 0; + f = drflac_open_file_with_metadata( song->path, + art_meta, + p, 0 ); /* todo errors and stuff */ if (!f) return; + if (p->f) + drflac_close(p->f); + stop_audio(); + p->song = song; channels = f->channels; sr = f->sampleRate; p->f = f; p->channels = channels; - stop_audio(); + p->play = 1; + p->cs = 0; + p->ms = f->totalPCMFrameCount; + if (!p->has_art) + try_load_art(p); init_audio( p, sr, @@ -183,5 +309,27 @@ void play_song(Player* p, Song* song) { ); } +void play_seek(Player* p, float v) { + unsigned long long fi; + if (!p->song) return; + /* probably want to protect against overflow/error */ + fi = (unsigned long long)((float)p->ms * v); + lock_audio(); + p->cs = fi; + drflac_seek_to_pcm_frame(p->f, fi); + unlock_audio(); +} + +void deinit_player(Player* p) { + if (p->song) { + stop_audio(); + wait_audio(); + drflac_close(p->f); + } +} + #define DR_FLAC_IMPLEMENTATION #include "dr_flac.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" diff --git a/library.h b/library.h index 1397c15..81ce96b 100644 --- a/library.h +++ b/library.h @@ -23,8 +23,11 @@ typedef struct { seek, samples, channels, - play + play, + has_art ; + unsigned long long cs, ms; + unsigned cover[album_cover_w * album_cover_h]; } Player; void build_library( @@ -35,6 +38,8 @@ void build_library( int get_song_meta(const char* path, Song* s); void play_song(Player* p, Song* song); +void play_seek(Player* p, float v); void init_player(Player* p); +void deinit_player(Player* p); #endif diff --git a/main.c b/main.c index 94f78b5..2b0e763 100644 --- a/main.c +++ b/main.c @@ -6,14 +6,44 @@ Library lib; Player pl; +UIButton* play_button; +UISlider* seek_slider; +UIImageDisplay* cover_image; + +void ui_update_play(void) { + UI_FREE(play_button->label); + play_button->label = UIStringCopy( + pl.play? "Pause": "Play", + -1 + ); + UIElementRefresh((UIElement*)play_button); + UIImageDisplaySetContent( + cover_image, + pl.cover, + album_cover_w, + album_cover_h, + album_cover_w * 4 + ); + UIElementRepaint((UIElement*)cover_image, 0); +} + +void ui_update_seek(void) { + float p; + if (!pl.song) return; + p = (float)pl.cs / (float)pl.ms; + seek_slider->position = p; + UIElementRefresh((UIElement*)seek_slider); +} int libtab_msg( - UIElement *el, + UIElement* el, UIMessage msg, int di, - void *dp + void* dp ) { Song* song; + (void)di; + (void)dp; if (msg == UI_MSG_TABLE_GET_ITEM) { UITableGetItem *m = (UITableGetItem*)dp; song = &lib.songs[lib.indices[m->index]]; @@ -56,18 +86,88 @@ int libtab_msg( el->window->cursorY ); song = &lib.songs[lib.indices[hit]]; - init_player(&pl); play_song(&pl, song); + ui_update_play(); + } + return 0; +} + +int playpause_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + (void)el; + (void)di; + (void)dp; + if (msg == UI_MSG_CLICKED) { + if (!pl.song) return 0; + pl.play = !pl.play; + ui_update_play(); + } + return 0; +} + +int start_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + (void)el; + (void)di; + (void)dp; + if (msg == UI_MSG_CLICKED) { + if (!pl.song) return 0; + play_seek(&pl, 0.0f); + } + return 0; +} + +int end_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + (void)el; + (void)di; + (void)dp; + if (msg == UI_MSG_CLICKED) { + if (!pl.song) return 0; + play_seek(&pl, 1.0f); } return 0; } +int seek_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + UISlider* sl; + (void)el; + (void)di; + (void)dp; + if (msg == UI_MSG_VALUE_CHANGED) { + sl = (UISlider*)el; + play_seek(&pl, sl->position); + } else if (msg == UI_MSG_ANIMATE) { + ui_update_seek(); + } + + return 0; +} + int prog_main(void* mem) { Arena liba; UIWindow* wi; UISplitPane* split1, * split2, * split3; UIPanel* plib, * pctrl, * plist, * pqueue; UITable* libtab; + int r; memory_init(mem); init_arena( &liba, @@ -75,6 +175,7 @@ int prog_main(void* mem) { library_memory_size ); build_library(&liba, &lib, library_path); + init_player(&pl); UIInitialise(); wi = UIWindowCreate( 0, @@ -102,6 +203,39 @@ int prog_main(void* mem) { pctrl = UIPanelCreate(&split3->e, UI_PANEL_GRAY); pctrl->gap = 5; pctrl->border = UI_RECT_1(5); + cover_image = UIImageDisplayCreate( + &pctrl->e, + 0, + (unsigned*)pl.cover, + album_cover_w, + album_cover_h, + album_cover_w + ); + play_button = UIButtonCreate( + &pctrl->e, + 0, + "Play", + -1 + ); + play_button->e.messageUser = playpause_msg; + UIButtonCreate( + &pctrl->e, + 0, + "Start", + -1 + )->e.messageUser = start_msg; + UIButtonCreate( + &pctrl->e, + 0, + "End", + -1 + )->e.messageUser = end_msg; + seek_slider = UISliderCreate( + &pctrl->e, + UI_ELEMENT_H_FILL + ); + UIElementAnimate(&seek_slider->e, false); + seek_slider->e.messageUser = seek_msg; pqueue = UIPanelCreate(&split3->e, UI_PANEL_GRAY); pqueue->gap = 5; @@ -140,5 +274,7 @@ int prog_main(void* mem) { libtab->e.messageUser = libtab_msg; UITableResizeColumns(libtab); - return UIMessageLoop(); + r = UIMessageLoop(); + deinit_player(&pl); + return r; } diff --git a/plat.c b/plat.c index 66dc50b..a70fe52 100644 --- a/plat.c +++ b/plat.c @@ -117,9 +117,11 @@ void* audio_worker(void* arg) { lock_audio(); if (!audio.r) c = 0; as = sound_mix(arg, buf, s); - if (!as) audio.r = c = 0; unlock_audio(); - pa_simple_write(dev, buf, as, 0); + if (as) + pa_simple_write(dev, buf, as, 0); + else + usleep(25000); /* avoid hammering the thread */ } return 0; } @@ -136,9 +138,9 @@ void wait_audio(void) { } void init_audio(void* uptr, int sample, int channels) { - int r; if (audio.dev) { wait_audio(); + pa_simple_free(audio.dev); gfree(audio.buf); } audio.buf = galloc(audio_buffer_size * channels); @@ -164,7 +166,7 @@ void init_audio(void* uptr, int sample, int channels) { } audio.r = 1; audio.chan = channels; - r = pthread_create( + pthread_create( &audio_thread, 0, audio_worker, @@ -173,14 +175,22 @@ void init_audio(void* uptr, int sample, int channels) { pthread_mutex_init(&audio_mutex, 0); } -void lock_audio() { +void lock_audio(void) { pthread_mutex_lock(&audio_mutex); } -void unlock_audio() { +void unlock_audio(void) { pthread_mutex_unlock(&audio_mutex); } +int audio_done(void) { + int r; + lock_audio(); + r = !audio.r; + unlock_audio(); + return r; +} + extern int prog_main(void*); int main() { @@ -189,6 +199,12 @@ int main() { audio.dev = 0; mem = malloc(memory_size); r = prog_main(mem); + if (audio.dev) { + stop_audio(); + wait_audio(); + pa_simple_free(audio.dev); + gfree(audio.buf); + } free(mem); return r; } diff --git a/plat.h b/plat.h index 3119543..ae9cff2 100644 --- a/plat.h +++ b/plat.h @@ -16,5 +16,6 @@ void lock_audio(void); void wait_audio(void); void unlock_audio(void); void stop_audio(void); +int audio_done(void); #endif -- cgit v1.2.3-54-g00ecf