summaryrefslogtreecommitdiff
path: root/src/playit.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/playit.c')
-rw-r--r--src/playit.c209
1 files changed, 209 insertions, 0 deletions
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);
+}
© All Rights Reserved