summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--config.h3
-rw-r--r--library.c152
-rw-r--r--library.h7
-rw-r--r--main.c144
-rw-r--r--plat.c28
-rw-r--r--plat.h1
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 <stdio.h>
#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