diff options
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | config.h | 3 | ||||
| -rw-r--r-- | library.c | 152 | ||||
| -rw-r--r-- | library.h | 7 | ||||
| -rw-r--r-- | main.c | 144 | ||||
| -rw-r--r-- | plat.c | 28 | ||||
| -rw-r--r-- | plat.h | 1 | 
7 files changed, 324 insertions, 14 deletions
| @@ -10,7 +10,8 @@ defines = \  	-Dplat_pulse \  	-Dis64bit=1 \  	-Dplat_x86 \ -	-DDEBUG +	-DDEBUG \ +	-DUI_LINUX   luigi = luigi.o  sources = \ @@ -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) @@ -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" @@ -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 @@ -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;  } @@ -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;  } @@ -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 |