diff options
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/playit.c | 209 | ||||
-rw-r--r-- | src/playit.h | 13 |
3 files changed, 226 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index ba32c52..7da07f1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,9 @@ AUTOMAKE_OPTIONS = foreign dist-bzip2 no-dist-gzip lib_LIBRARIES = libplayit.a -include_HEADERS = \ +include_HEADERS = playit.h + +noinst_HEADERS = \ include/cmixer.h \ include/disko.h \ include/dmoz.h \ @@ -91,6 +93,7 @@ endif ## aaaaaaaaahhhhhhhhhhhhhhhhhhh!!!!!!!1 libplayit_a_SOURCES = \ + playit.c \ fmt/compression.c \ fmt/it.c \ util/util.c \ diff --git a/src/playit.c b/src/playit.c new file mode 100644 index 0000000..da3d642 --- /dev/null +++ b/src/playit.c @@ -0,0 +1,209 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#include "playit.h" +#include "sndfile.h" +#include "fmt.h" + +#define BYTES_PER_FRAME 4 /* simply fixed at 16-bit+stereo: 4 bytes per frame. */ + +typedef struct playit_prerendered_t { + void *buf; + unsigned num_frames; +} playit_prerendered_t; + +typedef struct playit_t { + song_t *song; + unsigned flags; + playit_prerendered_t prerendered; + unsigned current_frame; +} playit_t; + +static song_t * song_open_file(const char *file) +{ + slurp_t *s; + song_t *song; + + assert(file); + + s = slurp(file, NULL, 0); + if (!s) { + fprintf(stderr, "Failed to open \"%s\"\n", file); + goto _fail; + } + + song = csf_allocate(); + if (!song) { + fprintf(stderr, "Failed to allocate song_t\n"); + goto _fail_slurp; + } + + if (fmt_it_load_song(song, s, 0) != LOAD_SUCCESS) + goto _fail_song; + + unslurp(s); + + return song; + +_fail_song: + csf_free(song); +_fail_slurp: + unslurp(s); +_fail: + return NULL; +} + + +/* prerender the opened song into playit->prerendered.buf */ +static int playit_prerender(playit_t *playit) +{ + unsigned alloc_size = 8192, rendered_size = 0; + void *buf = NULL, *new; + + assert(playit); + assert(playit->song); + + while (!(playit->song->flags & SONG_ENDREACHED)) { + unsigned ret; + + alloc_size *= 2; + new = realloc(buf, alloc_size); + if (!new) { + fprintf(stderr, "Failed to realloc %u bytes\n", alloc_size); + goto _fail; + } else + buf = new; + + ret = csf_read(playit->song, buf + rendered_size, alloc_size - rendered_size); + rendered_size += ret * BYTES_PER_FRAME; + } + + playit->prerendered.buf = realloc(buf, rendered_size); + if (!playit->prerendered.buf) { + fprintf(stderr, "Failed to realloc to rendered %u bytes\n", rendered_size); + goto _fail; + } + + playit->prerendered.num_frames = rendered_size / BYTES_PER_FRAME; + + + return 1; + +_fail: + free(buf); + + return 0; +} + + +playit_t * playit_open_file(const char *file, unsigned flags) +{ + playit_t *playit; + song_t *song; + + assert(file); + + playit = calloc(1, sizeof(playit_t)); + if (!playit) { + fprintf(stderr, "Failed to allocate playit_t\n"); + goto _fail; + } + + playit->song = song = song_open_file(file); + if (!playit->song) + goto _fail_playit; + + /* TODO: it seems kind of silly that we're dicking directly with the song_t members, + * there should really be a schism api for doing this properly. + */ + csf_set_current_order(song, 0); + song->repeat_count = -1; /* -1 to stop vs. looping */ + song->buffer_count = 0; + song->stop_at_order = -1; + song->stop_at_row = -1; + song->flags &= ~(SONG_PAUSED | SONG_PATTERNLOOP | SONG_ENDREACHED); + csf_reset_playmarks(song); + csf_set_resampling_mode(song, SRCMODE_LINEAR); + csf_set_wave_config(song, 44100, 16, 2); + + /* If seekable render the whole song into an in-memory buffer for + * precise seekability. This is useful for demo development where + * jumping around the demo within a tool like GNU Rocket is desirable. + * Presumably you'd turn this off for the release and mix realtime, + * unless you need the CPU time and have RAM to spare. + */ + if ((flags & PLAYIT_FLAG_SEEKABLE) && !playit_prerender(playit)) + goto _fail_playit; + + playit->flags = flags; + + return playit; + +_fail_song: + csf_free(playit->song); +_fail_playit: + free(playit); +_fail: + return NULL; +} + + +/* set song frame to an arbitrary offset, returns new offset */ +unsigned playit_seek(playit_t *playit, unsigned frame) +{ + assert(playit); + assert((playit->flags & PLAYIT_FLAG_SEEKABLE)); + + if (frame > playit->prerendered.num_frames) + frame = playit->prerendered.num_frames; + + return playit->current_frame = frame; +} + + +/* Populate buf with up to len bytes of rendered song data starting from the current frame offset. */ +/* The number of frames written into buf is returned (not a byte count!). */ +/* When the end of the song is reached, 0 is returned. */ +int playit_update(playit_t *playit, void *buf, int len, unsigned *res_frame) +{ + int ret = 0; + + assert(playit); + assert(buf); + + if (!(playit->flags & PLAYIT_FLAG_SEEKABLE)) { + if (!(playit->song->flags & SONG_ENDREACHED)) + ret = csf_read(playit->song, buf, len); + } else { + unsigned frames_to_copy = len / BYTES_PER_FRAME; + playit_prerendered_t *pre = &playit->prerendered; + + frames_to_copy = MIN(frames_to_copy, pre->num_frames - playit->current_frame); + + if (frames_to_copy) { + void *src = pre->buf + playit->current_frame * BYTES_PER_FRAME; + + memcpy(buf, src, frames_to_copy * BYTES_PER_FRAME); + } + + ret = frames_to_copy; + } + + playit->current_frame += ret; + + if (res_frame) + *res_frame = playit->current_frame; + + return ret; +} + + +void playit_destroy(playit_t *playit) +{ + assert(playit); + assert(playit->song); + + csf_free(playit->song); + free(playit); +} diff --git a/src/playit.h b/src/playit.h new file mode 100644 index 0000000..6fd9ddf --- /dev/null +++ b/src/playit.h @@ -0,0 +1,13 @@ +#ifndef _PLAYIT_H +#define _PLAYIT_H + +#define PLAYIT_FLAG_SEEKABLE 1 + +typedef struct playit_t playit_t; + +playit_t * playit_open_file(const char *file, unsigned flags); +unsigned playit_seek(playit_t *playit, unsigned frame); +int playit_update(playit_t *playit, void *buf, int len, unsigned *res_frame); +void playit_destroy(playit_t *playit); + +#endif |