From 9dad4016ef52ae71c0325ea9d078e499c484d51f Mon Sep 17 00:00:00 2001 From: quou Date: Thu, 13 Jun 2024 20:23:37 +1000 Subject: playlists and stuff --- Makefile | 3 +- config.h | 3 +- library.c | 84 +++++++++++- library.h | 11 +- main.c | 447 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- plat.c | 27 +++- plat.h | 3 + playlist.c | 123 +++++++++++++++++ playlist.h | 22 +++ 9 files changed, 702 insertions(+), 21 deletions(-) create mode 100644 playlist.c create mode 100644 playlist.h diff --git a/Makefile b/Makefile index 21c1210..25fb8bc 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ sources = \ library.c \ main.c \ memory.c \ - plat.c + plat.c \ + playlist.c objects = $(sources:%.c=%.o) diff --git a/config.h b/config.h index 47fb153..b8c2bd0 100644 --- a/config.h +++ b/config.h @@ -9,13 +9,14 @@ #define album_cover_h 600 /* For each channel. */ -#define audio_buffer_size (1024 * 4) +#define audio_buffer_size (1024 * 64) #define memory_heap_size (1024 * 1024 * 512) #define memory_size (memory_heap_size + (1024 * 1024 * 8)) #define library_memory_size (1024 * 1024 * 256) #define library_path "/home/quou/music/" +#define playlist_max 2048 #if defined(is64bit) && is64bit #define allocation_default_alignment 8 diff --git a/library.c b/library.c index 350789a..3d5aaa4 100644 --- a/library.c +++ b/library.c @@ -29,6 +29,14 @@ Song* find_song(Library* l, const char* path) { return 0; } +void init_song(Song* s) { + s->track = 0; + s->path[0] = 0; + s->name[0] = 0; + s->artist[0] = 0; + s->album[0] = 0; +} + static void cap_iter( void* uptr, const char* path @@ -45,6 +53,7 @@ static void adder_iter( Song* s; Library* lib = uptr; s = find_song(lib, path); + init_song(s); if (get_song_meta(path, s)) { strcpy(s->path, path); lib->indices[lib->cnt++] = s - lib->songs; @@ -57,7 +66,7 @@ void build_library( const char* path ) { int i; - lib->cnt = lib->cap = 0; + lib->cnt = lib->cap = lib->fcnt = 0; iter_dir(path, cap_iter, lib); lib->cap += 16; lib->songs = arena_alloc( @@ -68,20 +77,76 @@ void build_library( a, lib->cap * sizeof *lib->indices ); + lib->filtered = arena_alloc( + a, + lib->cap * sizeof *lib->filtered + ); for (i = 0; i < lib->cap; i++) lib->songs[i].path[0] = 0; iter_dir(path, adder_iter, lib); } +char upper(char c) { + return c >= 'a' && c <= 'z'? c - ('a' - 'A'): c; +} + +int string_eq( + const char* a, + const char* b +) { + for (; *a && *b; a++, b++) + if (upper(*a) != upper(*b)) return 0; + return *b == 0; +} + +int string_match( + const char* h, + const char* n +) { + const char* c; + for (c = h; *c; c++) { + if (string_eq(c, n)) return 1; + } + return 0; +} + +int song_match( + const Song* s, + const char* str +) { +#define check(n) \ + (s->n[0] && string_match(s->n, str)) + return s->path[0] && ( + check(path) || + check(name) || + check(artist) || + check(album) + ); +#undef check +} + +void filter_library( + Library* l, + const char* str +) { + int i; + Song* s; + l->fcnt = 0; + for (i = 0; i < l->cap; i++) { + s = &l->songs[i]; + if (song_match(s, str)) + l->filtered[l->fcnt++] = i; + } +} + void parse_vorbis_comment( Song* s, const char* com, int len ) { /* this is not safe xDDDD idrc */ - if (len > 64) { + if (len > 64) len = 64; - } if (!memcmp(com, "ARTIST=", 7)) { len -= 7; memcpy(s->artist, com + 7, len); @@ -94,6 +159,9 @@ void parse_vorbis_comment( len -= 6; memcpy(s->album, com + 6, len); s->album[len] = 0; + } else if (!memcmp(com, "TRACKNUMBER=", 12)) { + len -= 6; + s->track = (int)strtol(com + 12, 0, 10); } } @@ -200,6 +268,8 @@ int get_song_meta(const char* path, Song* s) { return 0; } +void ui_update_seek(void); + int sound_mix( void* uptr, unsigned char* buf, @@ -219,7 +289,8 @@ int sound_mix( ); if (!r) p->play = 0; p->cs += r; - ui_update_seek(p); + /* todo fix race condition here */ + ui_update_seek(); return r * p->channels * 4; } @@ -278,7 +349,7 @@ void try_load_art(Player* p) { } } -void play_song(Player* p, Song* song) { +int play_song(Player* p, Song* song) { drflac* f; int sr, channels; p->has_art = 0; @@ -289,7 +360,7 @@ void play_song(Player* p, Song* song) { 0 ); /* todo errors and stuff */ - if (!f) return; + if (!f) return 0; if (p->f) drflac_close(p->f); stop_audio(); @@ -308,6 +379,7 @@ void play_song(Player* p, Song* song) { sr, channels ); + return 1; } void play_seek(Player* p, float v) { diff --git a/library.h b/library.h index 81ce96b..cbb8df6 100644 --- a/library.h +++ b/library.h @@ -4,6 +4,7 @@ #include "memory.h" typedef struct { + int track; char path[256]; char name[64]; char artist[64]; @@ -13,7 +14,8 @@ typedef struct { typedef struct { Song* songs; int* indices; - int cap, cnt; + int* filtered; + int cap, cnt, fcnt; } Library; typedef struct { @@ -30,14 +32,19 @@ typedef struct { unsigned cover[album_cover_w * album_cover_h]; } Player; +Song* find_song(Library* l, const char* path); void build_library( Arena* a, Library* lib, const char* path ); +void filter_library( + Library* l, + const char* str +); int get_song_meta(const char* path, Song* s); -void play_song(Player* p, Song* song); +int play_song(Player* p, Song* song); void play_seek(Player* p, float v); void init_player(Player* p); void deinit_player(Player* p); diff --git a/main.c b/main.c index 0e6d281..9ba07d3 100644 --- a/main.c +++ b/main.c @@ -3,15 +3,66 @@ #include "library.h" #include "luigi.h" #include "memory.h" +#include "plat.h" +#include "playlist.h" + +typedef struct { + UITable* tab; + Playlist* p; +} Playlist_UI; + +typedef struct { + int idx; + UIMenu* menu; +} PS_Ctx; Library lib; Player pl; +Playlist queue, playlist; +Playlist_UI queue_ui, playlist_ui; UIButton* play_button; UISlider* seek_slider; UIImageDisplay* cover_image; UILabel* title_text; UILabel* album_text; UILabel* artist_text; +UIButton* playlist_select; +UITable* libtab; +char filter_text[64]; +char playlist_names[64][64]; + +void ui_update_playlist(void) { + playlist_ui.tab->itemCount = playlist.cnt; + UITableResizeColumns(playlist_ui.tab); + UIElementRefresh(&playlist_ui.tab->e); + playlist_select->label = UIStringCopy( + playlist.name, + -1 + ); + playlist_select->labelBytes = strlen(playlist.name); + UIElementRefresh(playlist_select->e.parent); +} + +void ui_update_queue(void) { + queue_ui.tab->itemCount = queue.cnt; + UITableResizeColumns(queue_ui.tab); + UIElementRefresh(&queue_ui.tab->e); +} + +void ui_add_to_playlist(void* cp) { + playlist_add(&playlist, cp); + ui_update_playlist(); +} + +void ui_add_to_queue(void* cp) { + playlist_add(&queue, cp); + ui_update_queue(); +} + +void ui_queue_next(void* cp) { + playlist_add_first(&queue, cp); + ui_update_queue(); +} void ui_update_play(void) { UI_FREE(play_button->label); @@ -52,6 +103,162 @@ void ui_update_seek(void) { UIElementRefresh((UIElement*)seek_slider); } +int filter_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + UITextbox* tb = (UITextbox*)el; + (void)di; + (void)dp; + int s = sizeof filter_text - 1; + if (msg == UI_MSG_VALUE_CHANGED) { + if (!tb->bytes) { + filter_text[0] = 0; + libtab->itemCount = lib.cnt; + UIElementRefresh((UIElement*)libtab); + return 0; + } + if (s > tb->bytes) + s = tb->bytes - 1; + memcpy(filter_text, tb->string, s); + filter_text[tb->bytes] = 0; + filter_library(&lib, filter_text); + libtab->itemCount = lib.fcnt; + UIElementRefresh((UIElement*)libtab); + } + return 0; +} + +int new_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + char* buf; + const char* r; + int e; + (void)di; + (void)dp; + if (msg == UI_MSG_CLICKED) { + r = UIDialogShow( + el->window, + 0, + "%s%t\n%b%b", + "Name", + &buf, + "Okay", + "Cancel" + ); + if (!strcmp(r, "Okay")) { + init_playlist(&playlist); + memcpy(playlist.name, buf, sizeof playlist.name); + e = sizeof playlist.name - 1; + if (playlist.name[e]) playlist.name[e] = 0; + ui_update_playlist(); + } + } + return 0; +} + +int save_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + char* buf; + const char* r; + int e; + (void)di; + (void)dp; + if (msg == UI_MSG_CLICKED) { + if (!playlist.name[0]) { + r = UIDialogShow( + el->window, + 0, + "%s%t\n%b%b", + "Name", + &buf, + "Okay", + "Cancel" + ); + if (!strcmp(r, "Okay")) { + memcpy(playlist.name, buf, sizeof playlist.name); + e = sizeof playlist.name - 1; + if (playlist.name[e]) playlist.name[e] = 0; + ui_update_playlist(); + } else { + return 0; + } + } + playlist_save(&playlist); + } + return 0; +} + +void ps_select(void* cp) { + int idx; + const char* n; + idx = (int)(uintptr_t)cp; + n = playlist_names[idx]; + if (playlist_load(&lib, &playlist, n)) + ui_update_playlist(); +} + +void ps_iter(void* u, const char* path) { + PS_Ctx* ctx = u; + UIMenu* menu; + const char* name; + int nl; + menu = ctx->menu; + name = path + strlen(path); + for (; name != path && *name != '/'; name--); + for (nl = 0; name[nl] && name[nl] != '.'; nl++); + name++; /* this fails if library path is bad xDDD */ + nl--; /* ... or if the path doesn't have an extension */ + memcpy(playlist_names[ctx->idx], name, nl); + playlist_names[ctx->idx][nl] = 0; + UIMenuAddItem( + menu, + 0, + name, + nl, + ps_select, + (void*)(uintptr_t)ctx->idx + ); + ctx->idx++; +} + +int ps_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + (void)di; + (void)dp; + if (msg == UI_MSG_CLICKED) { + UIMenu* menu; + char buf[256]; + PS_Ctx ctx; + get_playlist_dir(buf); + if (dir_exist(buf)) { + menu = UIMenuCreate( + &el->window->e, + 0 + ); + ctx.menu = menu; + ctx.idx = 0; + iter_dir(buf, ps_iter, &ctx); + UIMenuShow(menu); + } + } + return 0; +} + int libtab_msg( UIElement* el, UIMessage msg, @@ -59,35 +266,44 @@ int libtab_msg( void* dp ) { Song* song; + int* ind; (void)di; (void)dp; + ind = filter_text[0]? lib.filtered: lib.indices; if (msg == UI_MSG_TABLE_GET_ITEM) { UITableGetItem *m = (UITableGetItem*)dp; - song = &lib.songs[lib.indices[m->index]]; + song = &lib.songs[ind[m->index]]; m->isSelected = 0; switch (m->column) { case 0: + return snprintf( + m->buffer, + m->bufferBytes, + "%d", + song->track + ); + case 1: return snprintf( m->buffer, m->bufferBytes, "%s", song->name ); - case 1: + case 2: return snprintf( m->buffer, m->bufferBytes, "%s", song->artist ); - case 2: + case 3: return snprintf( m->buffer, m->bufferBytes, "%s", song->album ); - case 3: + case 4: return snprintf( m->buffer, m->bufferBytes, @@ -102,13 +318,139 @@ int libtab_msg( el->window->cursorX, el->window->cursorY ); - song = &lib.songs[lib.indices[hit]]; + song = &lib.songs[ind[hit]]; play_song(&pl, song); ui_update_play(); + } else if (msg == UI_MSG_RIGHT_DOWN) { + UIMenu* menu; + int hit = UITableHitTest( + (UITable*)el, + el->window->cursorX, + el->window->cursorY + ); + song = &lib.songs[ind[hit]]; + menu = UIMenuCreate( + &el->window->e, + UI_MENU_NO_SCROLL + ); + UIMenuAddItem( + menu, + 0, + "Add to Playlist", + -1, + ui_add_to_playlist, + song + ); + UIMenuAddItem( + menu, + 0, + "Add to Queue", + -1, + ui_add_to_queue, + song + ); + UIMenuAddItem( + menu, + 0, + "Queue Next", + -1, + ui_queue_next, + song + ); + UIMenuShow(menu); } return 0; } +int playlist_tab_msg( + UIElement* el, + UIMessage msg, + int di, + void* dp +) { + Playlist* p = el->cp; + Song* song; + int* ind; + (void)di; + ind = filter_text[0]? lib.filtered: lib.indices; + if (msg == UI_MSG_TABLE_GET_ITEM) { + UITableGetItem *m = (UITableGetItem*)dp; + song = p->songs[m->index]; + switch (m->column) { + case 0: + return snprintf( + m->buffer, + m->bufferBytes, + "%d", + song->track + ); + case 1: + return snprintf( + m->buffer, + m->bufferBytes, + "%s", + song->name + ); + case 2: + return snprintf( + m->buffer, + m->bufferBytes, + "%s", + song->artist + ); + case 3: + return snprintf( + m->buffer, + m->bufferBytes, + "%s", + song->album + ); + default: return 0; + } + } else if (msg == UI_MSG_RIGHT_DOWN) { + if (p == &playlist) { + UIMenu* menu; + int hit = UITableHitTest( + (UITable*)el, + el->window->cursorX, + el->window->cursorY + ); + song = playlist.songs[hit]; + menu = UIMenuCreate( + &el->window->e, + UI_MENU_NO_SCROLL + ); + UIMenuAddItem( + menu, + 0, + "Add to Queue", + -1, + ui_add_to_queue, + song + ); + UIMenuAddItem( + menu, + 0, + "Queue Next", + -1, + ui_queue_next, + song + ); + UIMenuShow(menu); + } + } else if (msg == UI_MSG_LEFT_UP) { + int hit = UITableHitTest( + (UITable*)el, + el->window->cursorX, + el->window->cursorY + ); + song = &lib.songs[ind[hit]]; + if (play_song(&pl, song)) + ui_update_play(); + } + return 0; +} + int playpause_msg( UIElement* el, UIMessage msg, @@ -178,13 +520,32 @@ int seek_msg( return 0; } +void ui_create_playlist( + UIElement* pa, + Playlist_UI* pu, + Playlist* p +) { + UITable* tab; + tab = UITableCreate( + pa, + UI_ELEMENT_H_FILL | UI_ELEMENT_V_FILL, + "Track\tTitle\tArtist\tAlbum" + ); + tab->itemCount = p->cnt; + tab->e.messageUser = playlist_tab_msg; + tab->e.cp = p; + UITableResizeColumns(tab); + pu->tab = tab; + pu->p = p; +} + int prog_main(void* mem) { Arena liba; UIWindow* wi; UISplitPane* split1, * split2, * split3; - UIPanel* plib, * pctrl, * plist, * pqueue; - UITable* libtab; + UIPanel* plib, * pctrl, * plist, * pqueue, * row; int r; + srand(time(0)); memory_init(mem); init_arena( &liba, @@ -193,6 +554,8 @@ int prog_main(void* mem) { ); build_library(&liba, &lib, library_path); init_player(&pl); + init_playlist(&queue); + init_playlist(&playlist); UIInitialise(); wi = UIWindowCreate( 0, @@ -280,6 +643,7 @@ int prog_main(void* mem) { "Queue", 5 ); + ui_create_playlist(&pqueue->e, &queue_ui, &queue); plist = UIPanelCreate(&split2->e, UI_PANEL_GRAY); plist->gap = 5; @@ -290,6 +654,57 @@ int prog_main(void* mem) { "Playlist", 8 ); + playlist_select = UIButtonCreate( + &plist->e, + 0, + "", + 0 + ); + playlist_select->e.messageUser = ps_msg; + row = UIPanelCreate( + &plist->e, + UI_PANEL_HORIZONTAL | + UI_ELEMENT_H_FILL + ); + UIButtonCreate( + &row->e, + 0, + "New", + -1 + )->e.messageUser = new_msg; + UIButtonCreate( + &row->e, + 0, + "Save", + -1 + )->e.messageUser = save_msg; + UISpacerCreate(&row->e, 0, 10, 0); + UIButtonCreate( + &row->e, + 0, + "Play", + -1 + ); + UIButtonCreate( + &row->e, + 0, + "Play Shuffled", + -1 + ); + UISpacerCreate(&row->e, 0, 10, 0); + UIButtonCreate( + &row->e, + 0, + "Enqueue", + -1 + ); + UIButtonCreate( + &row->e, + 0, + "Enqueue Shuffled", + -1 + ); + ui_create_playlist(&plist->e, &playlist_ui, &playlist); plib = UIPanelCreate(&split2->e, UI_PANEL_GRAY); plib->gap = 5; plib->border = UI_RECT_1(5); @@ -299,10 +714,26 @@ int prog_main(void* mem) { "Library", 7 ); + row = UIPanelCreate( + &plib->e, + UI_PANEL_HORIZONTAL | + UI_ELEMENT_H_FILL + ); + UILabelCreate( + &row->e, + 0, + "Filter", + -1 + ); + UISpacerCreate(&row->e, 0, 10, 0); + UITextboxCreate( + &row->e, + UI_ELEMENT_H_FILL + )->e.messageUser = filter_msg; libtab = UITableCreate( &plib->e, UI_ELEMENT_H_FILL | UI_ELEMENT_V_FILL, - "Track\tArtist\tAlbum\tFilename" + "Track\tTitle\tArtist\tAlbum\tFilename" ); libtab->itemCount = lib.cnt; libtab->e.messageUser = libtab_msg; diff --git a/plat.c b/plat.c index a70fe52..bb8b4db 100644 --- a/plat.c +++ b/plat.c @@ -5,13 +5,14 @@ #ifdef plat_posix #define _POSIX_SOURCE #define _GNU_SOURCE +#include +#include #include #include -#include #include -#include #include -#include +#include +#include extern int fileno(FILE*); @@ -93,6 +94,26 @@ void iter_dir(const char* path, Dir_Iter fn, void* u) { closedir(di); } +int dir_exist(const char* p) { + DIR* di; + di = opendir(p); + if (di) { + closedir(di); + return 1; + } + return 0; +} + +int make_dir(const char* p) { + return mkdir( + p, + S_IRWXU | + S_IRWXG | + S_IROTH | + S_IXOTH + ) == 0; +} + pthread_t audio_thread; pthread_mutex_t audio_mutex; diff --git a/plat.h b/plat.h index ae9cff2..a49418d 100644 --- a/plat.h +++ b/plat.h @@ -18,4 +18,7 @@ void unlock_audio(void); void stop_audio(void); int audio_done(void); +int dir_exist(const char* p); +int make_dir(const char* p); + #endif diff --git a/playlist.c b/playlist.c new file mode 100644 index 0000000..8dda913 --- /dev/null +++ b/playlist.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include "plat.h" +#include "playlist.h" + +void init_playlist(Playlist* p) { + p->name[0] = 0; + p->cnt = 0; +} + +void playlist_rm(Playlist* p, int i) { + int j; + for (j = i + 1; j < p->cnt; j++) { + p->songs[j - 1] = p->songs[j]; + } + p->cnt--; +} + +void playlist_add(Playlist* p, Song* s) { + if (p->cnt >= playlist_max) + return; + p->songs[p->cnt++] = s; +} + +void playlist_add_first(Playlist* p, Song* s) { + int i; + if (p->cnt >= playlist_max) + return; + for (i = p->cnt - 1; i >= 0; i--) { + p->songs[i + 1] = p->songs[i]; + } + p->songs[0] = s; + p->cnt++; +} + +void playlist_shuffle(Playlist* p) { + int ci, ri; + Song* t; + for (ci = p->cnt; ci;) { + ri = rand() % ci; + ci--; + t = p->songs[ci]; + p->songs[ci] = p->songs[ri]; + p->songs[ri] = t; + } +} + +void get_playlist_dir(char* buf) { + int llen; + llen = strlen(library_path); + strcpy(buf, library_path); + if (buf[llen - 1] != '/') + strcat(buf, "/"); + strcat(buf, "playlists/"); +} + +void playlist_save(const Playlist* p) { + int i, plen, llen; + const char* path, * ws; + FILE* f; + char wp[256]; + llen = strlen(library_path); + get_playlist_dir(wp); + if (!dir_exist(wp)) + if (!make_dir(wp)) return; + strcat(wp, p->name); + strcat(wp, ".m3u"); + f = fopen(wp, "w"); + if (!f) return; + for (i = 0; i < p->cnt; i++) { + path = p->songs[i]->path; + plen = strlen(path); + if ( + plen >= llen && + !memcmp(path, library_path, llen) + ) + ws = path + llen; + else + ws = path; + fputs(ws, f); + fputs("\n", f); + } + fclose(f); +} + +int playlist_load(Library* l, Playlist* p, const char* name) { + char path[256]; + char line[256]; + const char* sp; + FILE* f; + Song* s; + int ll, lpl; + lpl = strlen(library_path); + get_playlist_dir(path); + strcat(path, name); + strcat(path, ".m3u"); + f = fopen(path, "r"); + if (!f) return 0; + strcpy(p->name, name); + p->cnt = 0; + while (fgets(line, sizeof line, f)) { + ll = strlen(line); + for ( + ; + ll > 1 && line[ll - 1] == '\n'; + ll--, line[ll] = 0 + ); + if (strstr(line, library_path) != line) { + strcpy(path, library_path); + if (path[lpl - 1] != '/') + strcat(path, "/"); + strcat(path, line); + sp = path; + } else { + sp = line; + } + s = find_song(l, sp); + if (s && s->path[0]) + playlist_add(p, s); + } + return 1; +} diff --git a/playlist.h b/playlist.h new file mode 100644 index 0000000..83d532f --- /dev/null +++ b/playlist.h @@ -0,0 +1,22 @@ +#ifndef playlist_h +#define playlist_h + +#include "config.h" +#include "library.h" + +typedef struct { + char name[32]; + Song* songs[playlist_max]; + int cnt; +} Playlist; + +void init_playlist(Playlist* p); +void playlist_rm(Playlist* p, int i); +void playlist_add(Playlist* p, Song* s); +void playlist_add_first(Playlist* p, Song* s); +void playlist_shuffle(Playlist* p); +void playlist_save(const Playlist* p); +int playlist_load(Library* l, Playlist* p, const char* name); +void get_playlist_dir(char* buf); + +#endif -- cgit v1.2.3-54-g00ecf