#include #include #include #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->prerendered.buf); free(playit); }