diff options
Diffstat (limited to 'engines/sci/sfx/mixer/dc.c')
-rw-r--r-- | engines/sci/sfx/mixer/dc.c | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/engines/sci/sfx/mixer/dc.c b/engines/sci/sfx/mixer/dc.c new file mode 100644 index 0000000000..52b3ab5cbb --- /dev/null +++ b/engines/sci/sfx/mixer/dc.c @@ -0,0 +1,329 @@ +/*************************************************************************** + dc.c Copyright (C) 2005 Walter van Niftrik + + + This program may be modified and copied freely according to the terms of + the GNU general public license (GPL), as long as the above copyright + notice and the licensing information contained herein are preserved. + + Please refer to www.gnu.org for licensing details. + + This work is provided AS IS, without warranty of any kind, expressed or + implied, including but not limited to the warranties of merchantibility, + noninfringement, and fitness for a specific purpose. The author will not + be held liable for any damage caused by this work or derivatives of it. + + By using this source code, you agree to the licensing terms as stated + above. + + + Please contact the maintainer for bug reports or inquiries. + + Current Maintainer: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#include "../mixer.h" +#include <sci_memory.h> +#include <dc/sound/sound.h> +#include <stream.h> +#include <sys/queue.h> + +#define FEED_MODE_ALIVE 0 +#define FEED_MODE_IDLE 1 +#define FEED_MODE_DIEING 2 +#define FEED_MODE_DEAD 3 +#define FEED_MODE_RESTART 4 + +typedef struct feed_state { + /* Queue entry. */ + TAILQ_ENTRY(feed_state) entry; + + /* Whether feed is alive or dead. */ + int mode; + + /* Blank gap in frames. */ + int gap; + + /* Stream handle. */ + snd_stream_hnd_t handle; + + /* Feed. */ + sfx_pcm_feed_t *feed; + + /* Timestamp of next frame requested by stream driver. */ + sfx_timestamp_t time; +} feed_state_t; + +TAILQ_HEAD(feed_list, feed_state) feeds; + +/* Buffer size in samples. */ +#define BUF_SIZE 0x4000 + +static char buf[BUF_SIZE * 2]; + +static feed_state_t * +find_feed_state(snd_stream_hnd_t hnd) +{ + feed_state_t *state; + TAILQ_FOREACH(state, &feeds, entry) { + if (state->handle == hnd) + return state; + } + + return NULL; +} + +static void +query_timestamp(feed_state_t *state) +{ + sfx_pcm_feed_t *feed = state->feed; + + if (feed->get_timestamp) { + sfx_timestamp_t stamp; + int val = feed->get_timestamp(feed, &stamp); + + switch (val) { + case PCM_FEED_TIMESTAMP: + state->gap = sfx_timestamp_frame_diff(stamp, state->time); + + if (state->gap >= 0) + state->mode = FEED_MODE_ALIVE; + else { + long secs, usecs; + + state->mode = FEED_MODE_RESTART; + sci_gettime(&secs, &usecs); + state->time = sfx_new_timestamp(secs, usecs, feed->conf.rate); + state->gap = sfx_timestamp_frame_diff(stamp, state->time); + + if (state->gap < 0) + state->gap = 0; + } + break; + case PCM_FEED_IDLE: + state->mode = FEED_MODE_IDLE; + break; + case PCM_FEED_EMPTY: + state->mode = FEED_MODE_DIEING; + state->gap = BUF_SIZE; + } + } else { + state->mode = FEED_MODE_DIEING; + state->gap = BUF_SIZE; + } +} + +void +U8_to_S16(char *buf, int frames, int stereo) +{ + int samples = frames * (stereo ? 2 : 1); + int i; + + for (i = samples - 1; i >= 0; i--) { + buf[i * 2 + 1] = (unsigned char) buf[i] - 128; + buf[i * 2] = 0; + } +} + +static void * +callback(snd_stream_hnd_t hnd, sfx_timestamp_t timestamp, int bytes_req, int *bytes_recv) +{ + feed_state_t *state = find_feed_state(hnd); + sfx_pcm_feed_t *feed; + int channels, frames_req; + int frames_recv = 0; + + assert(state); + + state->time = timestamp; + feed = state->feed; + channels = feed->conf.stereo == SFX_PCM_MONO ? 1 : 2; + frames_req = bytes_req / 2 / channels; + + while (frames_req != frames_recv) { + int frames_left = frames_req - frames_recv; + char *buf_pos = buf + frames_recv * channels * 2; + + if (state->mode == FEED_MODE_IDLE) + query_timestamp(state); + + if (state->mode == FEED_MODE_IDLE) { + memset(buf_pos, 0, frames_left * channels * 2); + + state->time = sfx_timestamp_add(state->time, frames_left); + break; + } + + if (state->gap) { + int frames = state->gap; + + if (frames > frames_left) + frames = frames_left; + + memset(buf_pos, 0, frames * channels * 2); + + state->gap -= frames; + frames_recv += frames; + state->time = sfx_timestamp_add(state->time, frames); + if (!state->gap && state->mode == FEED_MODE_DIEING) { + state->mode = FEED_MODE_DEAD; + break; + } + } else { + int frames = feed->poll(feed, buf_pos, frames_left); + + if (feed->conf.format == SFX_PCM_FORMAT_U8) + U8_to_S16(buf_pos, frames, feed->conf.stereo != SFX_PCM_MONO); + + frames_recv += frames; + state->time = sfx_timestamp_add(state->time, frames); + + if (frames < frames_left) + query_timestamp(state); + } + } + + *bytes_recv = bytes_req; + return buf; +} + +static int +mix_init(sfx_pcm_mixer_t *self, sfx_pcm_device_t *device) +{ + if (snd_stream_init() < 0) { + fprintf(stderr, "[dc-mixer] Failed to initialize streaming sound driver\n"); + return SFX_ERROR; + } + + TAILQ_INIT(&feeds); + + return SFX_OK; +} + +static void +mix_subscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed) +{ + feed_state_t *state = sci_malloc(sizeof(feed_state_t)); + long secs, usecs; + + if ((feed->conf.format != SFX_PCM_FORMAT_S16_LE) && + (feed->conf.format != SFX_PCM_FORMAT_U8)) { + fprintf(stderr, "[dc-mixer] Unsupported feed format\n"); + feed->destroy(feed); + return; + } + + state->handle = snd_stream_alloc(callback, BUF_SIZE); + + if (state->handle == SND_STREAM_INVALID) { + fprintf(stderr, "[dc-mixer] Failed to allocate stream handle\n"); + feed->destroy(feed); + return; + } + + feed->frame_size = SFX_PCM_FRAME_SIZE(feed->conf); + state->mode = FEED_MODE_ALIVE; + state->feed = feed; + state->gap = 0; + + TAILQ_INSERT_TAIL(&feeds, state, entry); + + sci_gettime(&secs, &usecs); + state->time = sfx_new_timestamp(secs, usecs, feed->conf.rate); + snd_stream_start(state->handle, feed->conf.rate, + feed->conf.stereo != SFX_PCM_MONO); +} + +static void +mix_exit(sfx_pcm_mixer_t *self) +{ + snd_stream_shutdown(); +} + +static int +mix_process(sfx_pcm_mixer_t *self) +{ + feed_state_t *state, *state_next; + + TAILQ_FOREACH(state, &feeds, entry) { + snd_stream_poll(state->handle); + } + + state = TAILQ_FIRST(&feeds); + while (state) { + state_next = TAILQ_NEXT(state, entry); + if (state->mode == FEED_MODE_DEAD) { + snd_stream_stop(state->handle); + snd_stream_destroy(state->handle); + state->feed->destroy(state->feed); + TAILQ_REMOVE(&feeds, state, entry); + } + else if (state->mode == FEED_MODE_RESTART) { + snd_stream_stop(state->handle); + snd_stream_start(state->handle, state->feed->conf.rate, + state->feed->conf.stereo != SFX_PCM_MONO); + state->mode = FEED_MODE_ALIVE; + } + state = state_next; + } + + return SFX_OK; +} + +static void +mix_pause(sfx_pcm_mixer_t *self) +{ +} + +static void +mix_resume(sfx_pcm_mixer_t *self) +{ +} + +static int +pcm_init(sfx_pcm_device_t *self) +{ + return SFX_OK; +} + +static void +pcm_exit(sfx_pcm_device_t *self) +{ +} + +sfx_pcm_device_t sfx_pcm_driver_dc = { + "dc", + "0.1", + + pcm_init, + pcm_exit, + NULL, + NULL, + NULL, + + {0, 0, 0}, + 0, + NULL, + NULL +}; + +sfx_pcm_mixer_t sfx_pcm_mixer_dc = { + "dc", + "0.1", + + mix_init, + mix_exit, + mix_subscribe, + mix_pause, + mix_resume, + mix_process, + + 0, + 0, + NULL, + NULL, + NULL +}; |