aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sfx/mixer
diff options
context:
space:
mode:
authorJordi Vilalta Prat2009-02-15 06:10:59 +0000
committerJordi Vilalta Prat2009-02-15 06:10:59 +0000
commitfa6e10e9cec163845aa29e7940c86e9c9ab8a2bc (patch)
treece87338830cc8c149e1de545246bcefe4f45da00 /engines/sci/sfx/mixer
parent7c148ddf021c990fa866b7600f979aac9a5b26c9 (diff)
downloadscummvm-rg350-fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc.tar.gz
scummvm-rg350-fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc.tar.bz2
scummvm-rg350-fa6e10e9cec163845aa29e7940c86e9c9ab8a2bc.zip
Import the SCI engine sources from the FreeSCI Glutton branch (it doesn't compile yet)
svn-id: r38192
Diffstat (limited to 'engines/sci/sfx/mixer')
-rw-r--r--engines/sci/sfx/mixer/Makefile.am6
-rw-r--r--engines/sci/sfx/mixer/dc.c329
-rw-r--r--engines/sci/sfx/mixer/mixers.c55
-rw-r--r--engines/sci/sfx/mixer/soft.c988
-rw-r--r--engines/sci/sfx/mixer/test.c351
5 files changed, 1729 insertions, 0 deletions
diff --git a/engines/sci/sfx/mixer/Makefile.am b/engines/sci/sfx/mixer/Makefile.am
new file mode 100644
index 0000000000..684d40a4ba
--- /dev/null
+++ b/engines/sci/sfx/mixer/Makefile.am
@@ -0,0 +1,6 @@
+EXTRA_DIST = dc.c
+noinst_LIBRARIES = libscimixer.a
+INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@
+libscimixer_a_SOURCES = mixers.c soft.c
+test_LDADD = libscimixer.a ../../scicore/libscicore.a
+check_PROGRAMS = test
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
+};
diff --git a/engines/sci/sfx/mixer/mixers.c b/engines/sci/sfx/mixer/mixers.c
new file mode 100644
index 0000000000..1c7e27ab17
--- /dev/null
+++ b/engines/sci/sfx/mixer/mixers.c
@@ -0,0 +1,55 @@
+/***************************************************************************
+ mixers.c Copyright (C) 2003 Christoph Reichenbach
+
+
+ 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:
+
+ Christoph Reichenbach (CR) <jameson@linuxgames.com>
+
+***************************************************************************/
+
+#include "../mixer.h"
+#include <resource.h>
+
+extern sfx_pcm_mixer_t sfx_pcm_mixer_soft_linear;
+
+#ifdef _DREAMCAST
+extern sfx_pcm_mixer_t sfx_pcm_mixer_dc;
+#endif
+
+static sfx_pcm_mixer_t *mixers[] = {
+#ifdef _DREAMCAST
+ &sfx_pcm_mixer_dc,
+#endif
+ &sfx_pcm_mixer_soft_linear,
+ NULL
+};
+
+sfx_pcm_mixer_t *
+sfx_pcm_find_mixer(char *name)
+{
+ int i = 0;
+
+ if (name)
+ while (mixers[i] && strcmp(name, mixers[i]->name))
+ ++i;
+
+ return mixers[i];
+}
diff --git a/engines/sci/sfx/mixer/soft.c b/engines/sci/sfx/mixer/soft.c
new file mode 100644
index 0000000000..9f833017df
--- /dev/null
+++ b/engines/sci/sfx/mixer/soft.c
@@ -0,0 +1,988 @@
+/***************************************************************************
+ mixer.c Copyright (C) 2003 Christoph Reichenbach
+
+
+ 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:
+
+ Christoph Reichenbach (CR) <jameson@linuxgames.com>
+
+***************************************************************************/
+
+#include "../mixer.h"
+#include <sci_memory.h>
+
+/* Max. number of microseconds in difference allowed between independent audio streams */
+#define TIMESTAMP_MAX_ALLOWED_DELTA 2000
+
+/*#define DEBUG 3*/
+/* Set DEBUG to one of the following:
+** anything -- high-level debugging (feed subscriptions/deletions etc.)
+** >= 1 -- rough input and output analysis (once per call)
+** >= 2 -- more detailed input analysis (once per call and feed)
+** >= 3 -- fully detailed input and output analysis (once per frame and feed)
+*/
+
+/*#define DEBUG 0*/
+
+#define MIN_DELTA_OBSERVATIONS 100 /* Number of times the mixer is called before it starts trying to improve latency */
+#define MAX_DELTA_OBSERVATIONS 1000000 /* Number of times the mixer is called before we assume we truly understand timing */
+
+static int diagnosed_too_slow = 0;
+
+static volatile int mixer_lock = 0;
+
+/*#define DEBUG_LOCKS*/
+#ifdef DEBUG_LOCKS
+# define DEBUG_ACQUIRE fprintf(stderr, "[ -LOCK -] ACKQ %d: %d\n", __LINE__, mixer_lock)
+# define DEBUG_WAIT fprintf(stderr, "[ -LOCK -] WAIT %d: %d\n", __LINE__, mixer_lock);
+# define DEBUG_RELEASE ; fprintf(stderr, "[ -LOCK -] REL %d: %d\n", __LINE__, mixer_lock);
+#else
+# define DEBUG_ACQUIRE
+# define DEBUG_WAIT
+# define DEBUG_RELEASE
+#endif
+
+#define ACQUIRE_LOCK() ++mixer_lock; while (mixer_lock != 1) { DEBUG_WAIT sci_sched_yield(); } DEBUG_ACQUIRE
+#define RELEASE_LOCK() --mixer_lock DEBUG_RELEASE
+
+struct mixer_private {
+ byte *outbuf; /* Output buffer to write to the PCM device next time */
+ sfx_timestamp_t outbuf_timestamp; /* Timestamp associated with the output buffer */
+ int have_outbuf_timestamp; /* Whether we really _have_ an associated timestamp */
+ byte *writebuf; /* Buffer we're supposed to write to */
+ gint32 *compbuf_l, *compbuf_r; /* Intermediate buffers for computation */
+ int lastbuf_len; /* Number of frames stored in the last buffer */
+
+ long skew; /* Millisecond relative to which we compute time. This is the millisecond
+ ** part of the first time we emitted sound, to simplify some computations. */
+ long lsec; /* Last point in time we updated buffers, if any (seconds since the epoch) */
+ int played_this_second; /* Number of frames emitted so far in second lsec */
+
+ int max_delta; /* maximum observed time delta (using 'frames' as a metric unit) */
+ int delta_observations; /* Number of times we played; confidence measure for max_delta */
+
+ /* Pause data */
+ int paused;
+};
+
+#define P ((struct mixer_private *)(self->private_bits))
+
+
+static int
+mix_init(sfx_pcm_mixer_t *self, sfx_pcm_device_t *device)
+{
+ self->dev = device;
+ self->private_bits /* = P */ = sci_malloc(sizeof(struct mixer_private));
+ P->outbuf = P->writebuf = NULL;
+ P->lastbuf_len = 0;
+ P->compbuf_l = (gint32*)sci_malloc(sizeof(gint32) * device->buf_size);
+ P->compbuf_r = (gint32*)sci_malloc(sizeof(gint32) * device->buf_size);
+ P->played_this_second = 0;
+ P->paused = 0;
+#ifdef DEBUG
+ sciprintf("[soft-mixer] Initialised device %s v%s (%d Hz, %d/%x)\n",
+ device->name, device->version,
+ device->conf.rate, device->conf.stereo, device->conf.format);
+#endif
+ return SFX_OK;
+}
+
+static inline unsigned int
+gcd(unsigned int a, unsigned int b)
+{
+ if (a == b)
+ return a;
+
+ if (a < b) {
+ unsigned int c = b % a;
+
+ if (!c)
+ return a;
+
+ return gcd(c, a);
+ } else
+ return gcd(b, a);
+}
+
+static sfx_pcm_urat_t
+urat(unsigned int nom, unsigned int denom)
+{
+ sfx_pcm_urat_t rv;
+ unsigned int g;
+
+ rv.val = nom / denom;
+ nom -= rv.val * denom;
+ if (nom == 0)
+ g = 1;
+ else
+ g = gcd(nom, denom);
+
+ rv.nom = nom / g;
+ rv.den = denom / g;
+
+ return rv;
+}
+
+static void
+mix_subscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed)
+{
+ sfx_pcm_feed_state_t *fs;
+ ACQUIRE_LOCK();
+ if (!self->feeds) {
+ self->feeds_allocd = 2;
+ self->feeds = (sfx_pcm_feed_state_t*)sci_malloc(sizeof(sfx_pcm_feed_state_t)
+ * self->feeds_allocd);
+ } else if (self->feeds_allocd == self->feeds_nr) {
+ self->feeds_allocd += 2;
+ self->feeds = (sfx_pcm_feed_state_t*)sci_realloc(self->feeds,
+ sizeof(sfx_pcm_feed_state_t)
+ * self->feeds_allocd);
+ }
+
+ fs = self->feeds + self->feeds_nr++;
+ fs->feed = feed;
+
+ feed->frame_size = SFX_PCM_FRAME_SIZE(feed->conf);
+
+ /* fs->buf_size = (self->dev->buf_size
+ * (feed->conf.rate
+ + self->dev->conf.rate - 1))
+ / self->dev->conf.rate;
+ */
+ /* For the sake of people without 64 bit CPUs: */
+ fs->buf_size = 2 + /* Additional safety */
+ (self->dev->buf_size *
+ (1 + (feed->conf.rate / self->dev->conf.rate)));
+fprintf(stderr, " ---> %d/%d/%d/%d = %d\n",
+ self->dev->buf_size,
+ feed->conf.rate,
+ self->dev->conf.rate,
+ feed->frame_size,
+ fs->buf_size);
+
+ fs->buf = (byte*)sci_malloc(fs->buf_size * feed->frame_size);
+fprintf(stderr, " ---> --> %d for %p at %p\n", fs->buf_size * feed->frame_size, (void *)fs, (void *)fs->buf);
+{int i; for (i = 0; i < fs->buf_size * feed->frame_size; i++)
+fs->buf[i] = 0xa5; }
+ fs->scount = urat(0, 1);
+ fs->spd = urat(feed->conf.rate, self->dev->conf.rate);
+ fs->scount.den = fs->spd.den;
+ fs->ch_old.left = 0;
+ fs->ch_old.right = 0;
+ fs->ch_new.left = 0;
+ fs->ch_new.right = 0;
+ fs->mode = SFX_PCM_FEED_MODE_ALIVE;
+
+ /* If the feed can't provide us with timestamps, we don't need to wait for it to do so */
+ fs->pending_review = (feed->get_timestamp)? 1 : 0;
+
+ fs->frame_bufstart = 0;
+
+#ifdef DEBUG
+ sciprintf("[soft-mixer] Subscribed %s-%x (%d Hz, %d/%x) at %d+%d/%d, buffer size %d\n",
+ feed->debug_name, feed->debug_nr, feed->conf.rate, feed->conf.stereo, feed->conf.format,
+ fs->spd.val, fs->spd.nom, fs->spd.den, fs->buf_size);
+#endif
+ RELEASE_LOCK();
+}
+
+
+static void
+_mix_unsubscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed)
+{
+ int i;
+#ifdef DEBUG
+ sciprintf("[soft-mixer] Unsubscribing %s-%x\n", feed->debug_name, feed->debug_nr);
+#endif
+ for (i = 0; i < self->feeds_nr; i++) {
+ sfx_pcm_feed_state_t *fs = self->feeds + i;
+
+ if (fs->feed == feed) {
+ feed->destroy(feed);
+
+ if (fs->buf)
+ sci_free(fs->buf);
+
+ self->feeds_nr--;
+
+ /* Copy topmost into deleted so that we don't have any holes */
+ if (i != self->feeds_nr)
+ self->feeds[i] = self->feeds[self->feeds_nr];
+
+ if (self->feeds_allocd > 8 && self->feeds_allocd > (self->feeds_nr << 1)) {
+ /* Limit memory waste */
+ self->feeds_allocd >>= 1;
+ self->feeds
+ = (sfx_pcm_feed_state_t*)sci_realloc(self->feeds,
+ sizeof(sfx_pcm_feed_state_t)
+ * self->feeds_allocd);
+ }
+
+ for (i = 0; i < self->feeds_nr; i++)
+ fprintf(stderr, " Feed #%d: %s-%x\n",
+ i, self->feeds[i].feed->debug_name,
+ self->feeds[i].feed->debug_nr);
+
+ return;
+ }
+ }
+
+ fprintf(stderr, "[sfx-mixer] Assertion failed: Deleting invalid feed %p out of %d\n",
+ (void *)feed, self->feeds_nr);
+
+ BREAKPOINT();
+}
+
+static void
+mix_unsubscribe(sfx_pcm_mixer_t *self, sfx_pcm_feed_t *feed)
+{
+ ACQUIRE_LOCK();
+ _mix_unsubscribe(self, feed);
+ RELEASE_LOCK();
+}
+
+
+static void
+mix_exit(sfx_pcm_mixer_t *self)
+{
+ ACQUIRE_LOCK();
+ while (self->feeds_nr)
+ _mix_unsubscribe(self, self->feeds[0].feed);
+
+ if (P->outbuf)
+ sci_free(P->outbuf);
+ if (P->writebuf)
+ sci_free(P->writebuf);
+
+ if (P->compbuf_l)
+ sci_free(P->compbuf_l);
+ if (P->compbuf_l)
+ sci_free(P->compbuf_r);
+
+ sci_free(P);
+ self->private_bits /* = P */ = NULL;
+ RELEASE_LOCK();
+
+#ifdef DEBUG
+ sciprintf("[soft-mixer] Uninitialising mixer\n");
+#endif
+}
+
+
+#define LIMIT_16_BITS(v) \
+ if (v < -32767) \
+ v = -32768; \
+ else if (v > 32766) \
+ v = 32767
+
+static inline void
+mix_compute_output(sfx_pcm_mixer_t *self, int outplen)
+{
+ int frame_i;
+ sfx_pcm_config_t conf = self->dev->conf;
+ int use_16 = conf.format & SFX_PCM_FORMAT_16;
+ int bias = conf.format & ~SFX_PCM_FORMAT_LMASK;
+ byte *lchan, *rchan = NULL;
+ /* Don't see how this could possibly wind up being
+ ** used w/o initialisation, but you never know... */
+ gint32 *lsrc = P->compbuf_l;
+ gint32 *rsrc = P->compbuf_r;
+ int frame_size = SFX_PCM_FRAME_SIZE(conf);
+
+
+ if (!P->writebuf)
+ P->writebuf = (byte*)sci_malloc(self->dev->buf_size * frame_size + 4);
+
+ if (conf.stereo) {
+ if (conf.stereo == SFX_PCM_STEREO_RL) {
+ lchan = P->writebuf + ((use_16)? 2 : 1);
+ rchan = P->writebuf;
+ } else {
+ lchan = P->writebuf;
+ rchan = P->writebuf + ((use_16)? 2 : 1);
+ }
+ } else
+ lchan = P->writebuf;
+
+
+ for (frame_i = 0; frame_i < outplen; frame_i++) {
+ int left = *lsrc++;
+ int right = *rsrc++;
+
+ if (conf.stereo) {
+ LIMIT_16_BITS(left);
+ LIMIT_16_BITS(right);
+
+ if (!use_16) {
+ left >>= 8;
+ right >>= 8;
+ }
+
+ left += bias;
+ right += bias;
+
+ if (use_16) {
+ if (SFX_PCM_FORMAT_LE == (conf.format & SFX_PCM_FORMAT_ENDIANNESS)) {
+ lchan[0] = left & 0xff;
+ lchan[1] = (left >> 8) & 0xff;
+ rchan[0] = right & 0xff;
+ rchan[1] = (right >> 8) & 0xff;
+ } else {
+ lchan[1] = left & 0xff;
+ lchan[0] = (left >> 8) & 0xff;
+ rchan[1] = right & 0xff;
+ rchan[0] = (right >> 8) & 0xff;
+ }
+
+ lchan += 4;
+ rchan += 4;
+ } else {
+ *lchan = left & 0xff;
+ *rchan = right & 0xff;
+
+ lchan += 2;
+ rchan += 2;
+ }
+
+ } else {
+ left += right;
+ left >>= 1;
+ LIMIT_16_BITS(left);
+ if (!use_16)
+ left >>= 8;
+
+ left += bias;
+
+ if (use_16) {
+ if (SFX_PCM_FORMAT_LE == (conf.format & SFX_PCM_FORMAT_ENDIANNESS)) {
+ lchan[0] = left & 0xff;
+ lchan[1] = (left >> 8) & 0xff;
+ } else {
+ lchan[1] = left & 0xff;
+ lchan[0] = (left >> 8) & 0xff;
+ }
+
+ lchan += 2;
+ } else {
+ *lchan = left & 0xff;
+ lchan += 1;
+ }
+ }
+ }
+}
+
+static inline void
+mix_swap_buffers(sfx_pcm_mixer_t *self)
+{ /* Swap buffers */
+ byte *tmp = P->outbuf;
+ P->outbuf = P->writebuf;
+ P->writebuf = tmp;
+}
+
+
+#define FRAME_OFFSET(usecs) \
+ ((usecs >> 7) /* approximate, since uint32 is too small */ \
+ * ((long) self->dev->conf.rate)) \
+ / (1000000L >> 7)
+
+static inline int
+mix_compute_buf_len(sfx_pcm_mixer_t *self, int *skip_frames)
+ /* Computes the number of frames we ought to write. It tries to minimise the number,
+ ** in order to reduce latency. */
+ /* It sets 'skip_frames' to the number of frames to assume lost by latency, effectively
+ ** skipping them. */
+{
+ int free_frames;
+ int played_frames = 0; /* since the last call */
+ long secs, usecs;
+ int frame_pos;
+ int result_frames;
+
+ sci_gettime(&secs, &usecs);
+
+ if (!P->outbuf) {
+ /* Called for the first time ever? */
+ P->skew = usecs;
+ P->lsec = secs;
+ P->max_delta = 0;
+ P->delta_observations = 0;
+ P->played_this_second = 0;
+ *skip_frames = 0;
+ return self->dev->buf_size;
+ }
+
+ /* fprintf(stderr, "[%d:%d]S%d ", secs, usecs, P->skew);*/
+
+ if (P->skew > usecs) {
+ secs--;
+ usecs += (1000000 - P->skew);
+ }
+ else
+ usecs -= P->skew;
+
+ frame_pos = FRAME_OFFSET(usecs);
+
+ played_frames = frame_pos - P->played_this_second
+ + ((secs - P->lsec) * self->dev->conf.rate);
+ /*
+ fprintf(stderr, "%d:%d - %d:%d => %d\n", secs, frame_pos,
+ P->lsec, P->played_this_second, played_frames);
+ */
+
+ if (played_frames > self->dev->buf_size)
+ played_frames = self->dev->buf_size;
+
+ /*
+ fprintf(stderr, "Between %d:? offset=%d and %d:%d offset=%d: Played %d at %d\n", P->lsec, P->played_this_second,
+ secs, usecs, frame_pos, played_frames, self->dev->conf.rate);
+ */
+
+
+ if (played_frames > P->max_delta)
+ P->max_delta = played_frames;
+
+ free_frames = played_frames;
+
+ if (free_frames > self->dev->buf_size) {
+ if (!diagnosed_too_slow) {
+ sciprintf("[sfx-mixer] Your timer is too slow for your PCM output device (%d/%d), free=%d.\n"
+ "[sfx-mixer] You might want to try changing the device, timer, or mixer, if possible.\n",
+ played_frames, self->dev->buf_size, free_frames);
+ }
+ diagnosed_too_slow = 1;
+
+ *skip_frames = free_frames - self->dev->buf_size;
+ free_frames = self->dev->buf_size;
+ } else
+ *skip_frames = 0;
+
+ ++P->delta_observations;
+ if (P->delta_observations > MAX_DELTA_OBSERVATIONS)
+ P->delta_observations = MAX_DELTA_OBSERVATIONS;
+
+/* /\* Disabled, broken *\/ */
+/* if (0 && P->delta_observations > MIN_DELTA_OBSERVATIONS) { /\* Start improving after a while *\/ */
+/* int diff = self->dev->conf.rate - P->max_delta; */
+
+/* /\* log-approximate P->max_delta over time *\/ */
+/* recommended_frames = P->max_delta + */
+/* ((diff * MIN_DELTA_OBSERVATIONS) / P->delta_observations); */
+/* /\* WTF? *\/ */
+/* } else */
+/* recommended_frames = self->dev->buf_size; /\* Initially, keep the buffer full *\/ */
+
+#if (DEBUG >= 1)
+ sciprintf("[soft-mixer] played since last time: %d, recommended: %d, free: %d\n",
+ played_frames, recommended_frames, free_frames);
+#endif
+
+ result_frames = free_frames;
+
+ if (result_frames < 0)
+ result_frames = 0;
+
+ P->played_this_second += result_frames;
+ while (P->played_this_second >= self->dev->conf.rate) {
+ /* Won't normally happen more than once */
+ P->played_this_second -= self->dev->conf.rate;
+ P->lsec++;
+ }
+
+ if (result_frames > self->dev->buf_size) {
+ fprintf(stderr, "[soft-mixer] Internal assertion failed: frames-to-write %d > %d\n",
+ result_frames, self->dev->buf_size);
+ }
+ return result_frames;
+}
+
+
+
+#define READ_NEW_VALUES() \
+ if (frames_left > 0) { \
+ if (bias) { /* unsigned data */ \
+ if (!use_16) { \
+ c_new.left = (*lsrc) << 8; \
+ c_new.right = (*rsrc) << 8; \
+ } else { \
+ if (conf.format & SFX_PCM_FORMAT_LE) { \
+ c_new.left = lsrc[0] | lsrc[1] << 8; \
+ c_new.right = rsrc[0] | rsrc[1] << 8; \
+ } else { \
+ c_new.left = lsrc[1] | lsrc[0] << 8; \
+ c_new.right = rsrc[1] | rsrc[0] << 8; \
+ } \
+ } \
+ } else { /* signed data */ \
+ if (!use_16) { \
+ c_new.left = (*((signed char *)lsrc)) << 8; \
+ c_new.right = (*((signed char *)rsrc)) << 8; \
+ } else { \
+ if (conf.format & SFX_PCM_FORMAT_LE) { \
+ c_new.left = lsrc[0] | ((signed char *)lsrc)[1] << 8; \
+ c_new.right = rsrc[0] | ((signed char *)rsrc)[1] << 8; \
+ } else { \
+ c_new.left = lsrc[1] | ((signed char *)lsrc)[0] << 8; \
+ c_new.right = rsrc[1] | ((signed char *)rsrc)[0] << 8; \
+ } \
+ } \
+ } \
+ \
+ c_new.left -= bias; \
+ c_new.right -= bias; \
+ \
+ lsrc += frame_size; \
+ rsrc += frame_size; \
+ } else { \
+ c_new.left = c_new.right = 0; \
+ break; \
+ }
+
+
+static volatile int xx_offset;
+static volatile int xx_size;
+
+static void
+mix_compute_input_linear(sfx_pcm_mixer_t *self, int add_result,
+ int len, sfx_timestamp_t *ts, sfx_timestamp_t base_ts)
+ /* if add_result is non-zero, P->outbuf should be added to rather than overwritten. */
+ /* base_ts is the timestamp for the first frame */
+{
+ sfx_pcm_feed_state_t *fs = self->feeds + add_result;
+ sfx_pcm_feed_t *f = fs->feed;
+ sfx_pcm_config_t conf = f->conf;
+ int use_16 = conf.format & SFX_PCM_FORMAT_16;
+ gint32 *lchan = P->compbuf_l;
+ gint32 *rchan = P->compbuf_r;
+ int frame_size = f->frame_size;
+ byte *wr_dest = fs->buf + (frame_size * fs->frame_bufstart);
+ byte *lsrc = fs->buf;
+ byte *rsrc = fs->buf;
+ /* Location to write to */
+ int frames_nr;
+ int bias = (conf.format & ~SFX_PCM_FORMAT_LMASK)? 0x8000 : 0;
+ /* We use this only on a 16 bit level here */
+
+ /* The two most extreme source frames we consider for a
+ ** destination frame */
+ struct twochannel_data c_old = fs->ch_old;
+ struct twochannel_data c_new = fs->ch_new;
+
+ int frames_read = 0;
+ int frames_left;
+ int write_offset; /* Iterator for translation */
+ int delay_frames = 0; /* Number of frames (dest buffer) at the beginning we skip */
+
+ /* First, compute the number of frames we want to retreive */
+ frames_nr = fs->spd.val * len;
+ /* A little complicated since we must consider partial frames */
+ frames_nr += (fs->spd.nom * len
+ + (fs->scount.den - fs->scount.nom) /* remember that we may have leftovers */
+ + (fs->spd.den - 1 /* round up */)
+ )
+ / fs->spd.den;
+
+ ts->secs = -1;
+
+ if (frames_nr > fs->buf_size) {
+ fprintf(stderr, "%d (%d*%d + somethign) bytes, but only %d allowed!!!!!\n",
+ frames_nr * f->frame_size,
+ fs->spd.val, len,
+ fs->buf_size);
+ BREAKPOINT();
+ }
+
+ if (fs->pending_review) {
+ int newmode = PCM_FEED_EMPTY; /* empty unless a get_timestamp() tells otherwise */
+
+ RELEASE_LOCK();
+ /* Retrieve timestamp */
+ if (f->get_timestamp)
+ newmode = f->get_timestamp(f, ts);
+ ACQUIRE_LOCK();
+
+ fs = self->feeds + add_result;
+ /* Reset in case of status update */
+
+ switch (newmode) {
+
+ case PCM_FEED_TIMESTAMP: {
+ /* Compute the number of frames the returned timestamp is in the future: */
+ delay_frames =
+ sfx_timestamp_frame_diff(sfx_timestamp_renormalise(*ts, base_ts.frame_rate),
+ base_ts);
+
+ if (delay_frames <= 0)
+ /* Start ASAP, even if it's too late */
+ delay_frames = 0;
+ else
+ if (delay_frames > len)
+ delay_frames = len;
+ fs->pending_review = 0;
+ }
+ break;
+
+ case PCM_FEED_EMPTY:
+ fs->mode = SFX_PCM_FEED_MODE_DEAD;
+
+ /* ...fall through... */
+
+ case PCM_FEED_IDLE:
+ /* Clear audio buffer, if neccessary, and return */
+ if (!add_result) {
+ memset(P->compbuf_l, 0, sizeof(gint32) * len);
+ memset(P->compbuf_r, 0, sizeof(gint32) * len);
+ }
+ return;
+
+ default:
+ fprintf(stderr, "[soft-mixer] Fatal: Invalid mode returned by PCM feed %s-%d's get_timestamp(): %d\n",
+ f->debug_name, f->debug_nr, newmode);
+ exit(1);
+ }
+ }
+
+ RELEASE_LOCK();
+ /* Make sure we have sufficient information */
+ if (frames_nr > delay_frames + fs->frame_bufstart)
+ frames_read =
+ f->poll(f, wr_dest,
+ frames_nr
+ - delay_frames
+ - fs->frame_bufstart);
+
+ ACQUIRE_LOCK();
+ fs = self->feeds + add_result;
+
+ frames_read += fs->frame_bufstart;
+ frames_left = frames_read;
+
+ /* Reset in case of status update */
+
+ /* Skip at the beginning: */
+ if (delay_frames) {
+ if (!add_result) {
+ memset(lchan, 0, sizeof(gint32) * delay_frames);
+ memset(rchan, 0, sizeof(gint32) * delay_frames);
+ }
+ lchan += delay_frames;
+ rchan += delay_frames;
+
+ len -= delay_frames;
+ }
+
+
+#if (DEBUG >= 2)
+ sciprintf("[soft-mixer] Examining %s-%x (frame size %d); read %d/%d/%d, re-using %d frames\n",
+ f->debug_name, f->debug_nr, frame_size, frames_read, frames_nr,
+ fs->buf_size, fs->frame_bufstart);
+#endif
+
+
+ if (conf.stereo == SFX_PCM_STEREO_LR)
+ rsrc += (use_16)? 2 : 1;
+ else if (conf.stereo == SFX_PCM_STEREO_RL)
+ lsrc += (use_16)? 2 : 1;
+ /* Otherwise, we let both point to the same place */
+
+#if (DEBUG >= 2)
+ sciprintf("[soft-mixer] Stretching theoretical %d (physical %d) results to %d\n", frames_nr, frames_left, len);
+#endif
+ for (write_offset = 0; write_offset < len; write_offset++) {
+ int leftsum = 0; /* Sum of any complete frames we used */
+ int rightsum = 0;
+
+ int left; /* Sum of the two most extreme source frames
+ ** we considered, i.e. the oldest and newest
+ ** one corresponding to the output frame we are
+ ** computing */
+ int right;
+
+ int frame_steps = fs->spd.val;
+ int j;
+
+ if (fs->scount.nom >= fs->scount.den) {
+ fs->scount.nom -= fs->scount.den; /* Ensure fractional part < 1 */
+ ++frame_steps;
+ }
+ if (frame_steps)
+ c_old = c_new;
+
+#if 0
+ if (write_offset == 0) {
+ READ_NEW_VALUES();
+ --frames_left;
+#if (DEBUG >= 3)
+ sciprintf("[soft-mixer] Initial read %d:%d\n", c_new.left, c_new.right);
+#endif
+ c_old = c_new;
+ }
+#endif
+
+ for (j = 0; j < frame_steps; j++) {
+ READ_NEW_VALUES();
+ --frames_left;
+#if (DEBUG >= 3)
+ sciprintf("[soft-mixer] Step %d/%d made %d:%d\n", j, frame_steps, c_new.left, c_new.right);
+#endif
+
+ /* The last frame will be subject to the fractional
+ ** part analysis, so we add it to 'left' and 'right'
+ ** later-- all others are added to (leftsum, rightsum).
+ */
+ if (j+1 < frame_steps) {
+ leftsum += c_new.left;
+ rightsum += c_new.right;
+ }
+ }
+
+ left = c_new.left * fs->scount.nom
+ + c_old.left * (fs->scount.den - fs->scount.nom);
+ right = c_new.right * fs->scount.nom
+ + c_old.right * (fs->scount.den - fs->scount.nom);
+
+ /* Normalise */
+ left /= fs->spd.den;
+ right /= fs->spd.den;
+
+
+ leftsum += left;
+ rightsum += right;
+
+
+ /* Make sure to divide by the number of frames we added here */
+ if (frame_steps > 1) {
+ leftsum /= (frame_steps);
+ rightsum /= (frame_steps);
+ }
+
+
+#if (DEBUG >= 3)
+ sciprintf("[soft-mixer] Ultimate result: %d:%d (frac %d:%d)\n", leftsum, rightsum, left, right);
+#endif
+
+ if (add_result) {
+ *(lchan++) += leftsum;
+ *(rchan++) += rightsum;
+ } else {
+ *(lchan++) = leftsum;
+ *(rchan++) = rightsum;
+ }
+
+ fs->scount.nom += fs->spd.nom; /* Count up fractional part */
+ }
+
+ fs->ch_old = c_old;
+ fs->ch_new = c_new;
+
+ /* If neccessary, zero out the rest */
+ if (write_offset < len && !add_result) {
+ memset(lchan, 0, sizeof(gint32) * (len - write_offset));
+ memset(rchan, 0, sizeof(gint32) * (len - write_offset));
+ }
+
+ /* Save whether we have a partial frame still stored */
+ fs->frame_bufstart = frames_left;
+
+ if (frames_left) {
+ xx_offset = ((frames_read - frames_left) * f->frame_size);
+ xx_size = frames_left * f->frame_size;
+ if (xx_offset + xx_size
+ >= fs->buf_size * f->frame_size) {
+ fprintf(stderr, "offset %d >= max %d!\n",
+ (xx_offset + xx_size), fs->buf_size * f->frame_size);
+ BREAKPOINT();
+ }
+
+ memmove(fs->buf,
+ fs->buf + ((frames_read - frames_left) * f->frame_size),
+ frames_left * f->frame_size);
+ }
+#if (DEBUG >= 2)
+ sciprintf("[soft-mixer] Leaving %d over\n", fs->frame_bufstart);
+#endif
+
+ if (frames_read + delay_frames < frames_nr) {
+ if (f->get_timestamp) /* Can resume? */
+ fs->pending_review = 1;
+ else
+ fs->mode = SFX_PCM_FEED_MODE_DEAD; /* Done. */
+ }
+}
+
+static int
+mix_process_linear(sfx_pcm_mixer_t *self)
+{
+ACQUIRE_LOCK();
+{
+ int src_i; /* source feed index counter */
+ int frames_skip; /* Number of frames to discard, rather than to emit */
+ int buflen = mix_compute_buf_len(self, &frames_skip); /* Compute # of frames we must compute and write */
+ int fake_buflen;
+ int timestamp_max_delta = 0;
+ int have_timestamp = 0;
+ sfx_timestamp_t start_timestamp; /* The timestamp at which the first frame will be played */
+ sfx_timestamp_t min_timestamp;
+ sfx_timestamp_t timestamp;
+
+ if (self->dev->get_output_timestamp)
+ start_timestamp = self->dev->get_output_timestamp(self->dev);
+ else {
+ long sec, usec;
+ sci_gettime(&sec, &usec);
+ start_timestamp = sfx_new_timestamp(sec, usec, self->dev->conf.rate);
+ }
+
+ if ((P->outbuf) && (P->lastbuf_len)) {
+ sfx_timestamp_t ts;
+ int rv;
+
+ if (P->have_outbuf_timestamp) {
+ ts = sfx_timestamp_renormalise(P->outbuf_timestamp, self->dev->conf.rate);
+ }
+
+ rv = self->dev->output(self->dev, P->outbuf,
+ P->lastbuf_len,
+ (P->have_outbuf_timestamp)? &ts : NULL);
+
+ if (rv == SFX_ERROR) {
+ RELEASE_LOCK();
+ return rv; /* error */
+ }
+ }
+
+#if (DEBUG >= 1)
+ if (self->feeds_nr)
+ sciprintf("[soft-mixer] Mixing %d output frames on %d input feeds\n", buflen, self->feeds_nr);
+#endif
+ if (self->feeds_nr && !P->paused) {
+ /* Below, we read out all feeds in case we have to skip frames first, then get the
+ ** most current sound. 'fake_buflen' is either the actual buflen (for the last iteration)
+ ** or a fraction of the buf length to discard. */
+ do {
+ if (frames_skip) {
+ if (frames_skip > self->dev->buf_size)
+ fake_buflen = self->dev->buf_size;
+ else
+ fake_buflen = frames_skip;
+
+ frames_skip -= fake_buflen;
+ } else {
+ fake_buflen = buflen;
+ frames_skip = -1; /* Mark us as being completely done */
+ }
+
+ for (src_i = 0; src_i < self->feeds_nr; src_i++) {
+ mix_compute_input_linear(self, src_i,
+ fake_buflen, &timestamp,
+ start_timestamp);
+
+ if (timestamp.secs >= 0) {
+ if (have_timestamp) {
+ int diff = sfx_timestamp_usecs_diff(min_timestamp, timestamp);
+ if (diff > 0) {
+ /* New earlier timestamp */
+ timestamp = min_timestamp;
+ timestamp_max_delta += diff;
+ } else if (diff > timestamp_max_delta)
+ timestamp_max_delta = diff;
+ /* New max delta for timestamp */
+ } else {
+ min_timestamp = timestamp;
+ have_timestamp = 1;
+ }
+ }
+ }
+ /* Destroy all feeds we finished */
+ for (src_i = 0; src_i < self->feeds_nr; src_i++)
+ if (self->feeds[src_i].mode == SFX_PCM_FEED_MODE_DEAD)
+ _mix_unsubscribe(self, self->feeds[src_i].feed);
+ } while (frames_skip >= 0);
+
+ } else { /* Zero it out */
+ memset(P->compbuf_l, 0, sizeof(gint32) * buflen);
+ memset(P->compbuf_r, 0, sizeof(gint32) * buflen);
+ }
+
+#if (DEBUG >= 1)
+ if (self->feeds_nr)
+ sciprintf("[soft-mixer] Done mixing for this session, the result will be our next output buffer\n");
+#endif
+
+#if (DEBUG >= 3)
+ if (self->feeds_nr) {
+ int i;
+ sciprintf("[soft-mixer] Intermediate representation:\n");
+ for (i = 0; i < buflen; i++)
+ sciprintf("[soft-mixer] Offset %d:\t[%04x:%04x]\t%d:%d\n", i,
+ P->compbuf_l[i] & 0xffff, P->compbuf_r[i] & 0xffff,
+ P->compbuf_l[i], P->compbuf_r[i]);
+ }
+#endif
+
+ if (timestamp_max_delta > TIMESTAMP_MAX_ALLOWED_DELTA)
+ sciprintf("[soft-mixer] Warning: Difference in timestamps between audio feeds is %d us\n", timestamp_max_delta);
+
+ mix_compute_output(self, buflen);
+ P->lastbuf_len = buflen;
+
+ /* Finalize */
+ mix_swap_buffers(self);
+ if (have_timestamp)
+ P->outbuf_timestamp = sfx_timestamp_add(min_timestamp,
+ timestamp_max_delta >> 1);
+ P->have_outbuf_timestamp = have_timestamp;
+
+} RELEASE_LOCK();
+ return SFX_OK;
+}
+
+static void
+mix_pause(sfx_pcm_mixer_t *self)
+{
+ ACQUIRE_LOCK();
+ P->paused = 1;
+ RELEASE_LOCK();
+}
+
+static void
+mix_resume(sfx_pcm_mixer_t *self)
+{
+ ACQUIRE_LOCK();
+ P->paused = 0;
+ RELEASE_LOCK();
+}
+
+sfx_pcm_mixer_t sfx_pcm_mixer_soft_linear = {
+ "soft-linear",
+ "0.1",
+
+ mix_init,
+ mix_exit,
+ mix_subscribe,
+ mix_pause,
+ mix_resume,
+ mix_process_linear,
+
+ 0,
+ 0,
+ NULL,
+ NULL,
+ NULL
+};
diff --git a/engines/sci/sfx/mixer/test.c b/engines/sci/sfx/mixer/test.c
new file mode 100644
index 0000000000..20b3e952e1
--- /dev/null
+++ b/engines/sci/sfx/mixer/test.c
@@ -0,0 +1,351 @@
+/***************************************************************************
+ test.c Copyright (C) 2003 Christoph Reichenbach
+
+
+ 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:
+
+ Christoph Reichenbach (CR) <jameson@linuxgames.com>
+
+***************************************************************************/
+/* Mixer inspection/test program */
+
+
+#include "../mixer.h"
+#include <time.h>
+
+#if 0
+sfx_pcm_mixer_t *mix;
+
+int dev_init(sfx_pcm_device_t *self);
+void dev_exit(sfx_pcm_device_t *self);
+int dev_option(sfx_pcm_device_t *self, char *name, char *value);
+int dev_output(sfx_pcm_device_t *self, byte *buf, int count);
+
+#define MIN_OUTPUT 128
+/* Min amount of output to compute */
+
+#define DEVICES_NR 10
+
+sfx_pcm_device_t devices[DEVICES_NR] = {
+ { "test-1", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_U8 }, 1024, NULL },
+#if (DEVICES_NR > 1)
+ { "test-2", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_U8 }, 1024, NULL },
+ { "test-3", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_STEREO_RL, SFX_PCM_FORMAT_U8 }, 1024, NULL },
+ { "test-4", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S8 }, 1024, NULL },
+ { "test-5", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_U16_LE }, 1024, NULL },
+ { "test-6", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_U16_BE }, 1024, NULL },
+ { "test-7", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_LE }, 1024, NULL },
+ { "test-8", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_BE }, 1024, NULL },
+ { "test-9", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_STEREO_RL, SFX_PCM_FORMAT_S16_LE }, 1024, NULL },
+ { "test-10", "0", dev_init, dev_exit, dev_option, dev_output,
+ { 200, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_U16_BE }, 1024, NULL }
+#endif
+};
+
+int output_count;
+
+int dev_init(sfx_pcm_device_t *self)
+{
+ output_count = 0;
+
+ fprintf(stderr, "[DEV] Initialised device %p as follows:\n"
+ "\trate = %d\n"
+ "\tstereo = %s\n"
+ "\tbias = %x\n"
+ "\tbytes/sample = %d\n"
+ "\tendianness = %s\n",
+ self,
+ self->conf.rate,
+ self->conf.stereo? ((self->conf.stereo == SFX_PCM_STEREO_LR)? "Left, Right" : "Right, Left") : "No",
+ self->conf.format & ~SFX_PCM_FORMAT_LMASK,
+ (self->conf.format & SFX_PCM_FORMAT_16)? 2 : 1,
+ ((self->conf.format & SFX_PCM_FORMAT_ENDIANNESS) == SFX_PCM_FORMAT_BE)? "big" : "little");
+ return 0;
+}
+
+void dev_exit(sfx_pcm_device_t *self)
+{
+ fprintf(stderr, "[DEV] Uninitialising device\n");
+}
+
+int dev_option(sfx_pcm_device_t *self, char *name, char *value)
+{
+ fprintf(stderr, "[DEV] Set option '%s' to '%s'\n", name, value);
+ return 0;
+}
+
+int dev_output_enabled = 0;
+
+int dev_output(sfx_pcm_device_t *self, byte *buf, int count)
+{
+ int mono_sample_size = ((self->conf.format & SFX_PCM_FORMAT_16)? 2 : 1);
+ int sample_size = (self->conf.stereo? 2 : 1) * mono_sample_size;
+ int bias = self->conf.format & ~SFX_PCM_FORMAT_LMASK;
+ int is_bigendian = (self->conf.format & SFX_PCM_FORMAT_ENDIANNESS) == SFX_PCM_FORMAT_BE;
+ byte *left_channel = buf;
+ byte *right_channel = buf;
+
+ if (!dev_output_enabled)
+ return 0;
+
+ if (self->conf.format & SFX_PCM_FORMAT_16)
+ bias <<= 8;
+
+ if (self->conf.stereo == SFX_PCM_STEREO_LR)
+ right_channel += mono_sample_size;
+ if (self->conf.stereo == SFX_PCM_STEREO_RL)
+ left_channel += mono_sample_size;
+
+ while (count--) {
+ int right = right_channel[0];
+ int left = left_channel[0];
+ int second_byte = ((self->conf.format & SFX_PCM_FORMAT_16)? 1 : 0);
+
+ if (second_byte) {
+
+ if (is_bigendian) {
+ left = left << 8 | left_channel[1];
+ right = right << 8 | right_channel[1];
+ } else {
+ left = left | left_channel[1] << 8;
+ right = right | right_channel[1] << 8;
+ }
+ }
+
+ left -= bias;
+ right -= bias;
+
+ if (!second_byte) {
+ left <<= 8;
+ right <<= 8;
+ }
+
+ fprintf(stderr, "[DEV] %p play %04x:\t%04x %04x\n", self, output_count++, left & 0xffff, right & 0xffff);
+
+ left_channel += sample_size;
+ right_channel += sample_size;
+ }
+ return 0;
+}
+
+/* Feeds for debugging */
+
+typedef struct {
+ int i;
+} int_struct;
+
+int feed_poll(sfx_pcm_feed_t *self, byte *dest, int size);
+void feed_destroy(sfx_pcm_feed_t *self);
+
+int_struct private_bits[10] = {
+ {0},
+ {0},
+ {0},
+ {0},
+ {0},
+ {0},
+ {0},
+ {0},
+ {0}
+};
+
+
+typedef struct {
+ int start;
+ int samples_nr;
+ byte *data;
+} sample_feed_t;
+
+#define FEEDS_NR 4
+
+sfx_pcm_feed_t feeds[FEEDS_NR] = {
+ { feed_poll, feed_destroy, &(private_bits[0]),
+ { 200, SFX_PCM_MONO, SFX_PCM_FORMAT_S8 }, "test-feed", 0, 0}
+#if FEEDS_NR > 1
+ ,{ feed_poll, feed_destroy, &(private_bits[1]),
+ { 400, SFX_PCM_MONO, SFX_PCM_FORMAT_U8 }, "test-feed", 1, 0}
+#endif
+#if FEEDS_NR > 2
+ ,{ feed_poll, feed_destroy, &(private_bits[2]),
+ { 20, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_LE }, "test-feed", 2, 0}
+#endif
+#if FEEDS_NR > 3
+ ,{ feed_poll, feed_destroy, &(private_bits[3]),
+ { 150, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_S8 }, "test-feed", 3, 0}
+#endif
+ /*
+ ,{ feed_poll, feed_destroy, &(private_bits[4]),
+ {}, "test-feed", 4, 0}
+ ,{ feed_poll, feed_destroy, &(private_bits[5]),
+ {}, "test-feed", 5, 0}
+ */
+};
+
+byte feed_data_0[] = {0xfd, 0xfe, 0xff, 0, 1, 2, 3, 4, 5, 6};
+byte feed_data_1[] = {0x80, 0x90, 0xA0, 0xB0, 0xC0,
+ 0xD0, 0xD0, 0xC0, 0xB0, 0xA0, 0x90, 0x80};
+byte feed_data_2[] = {0x00, 0x00,
+ 0x00, 0x80,
+ 0xe8, 0x03};
+byte feed_data_3[] = {0x00, 0x10,
+ 0x01, 0x20,
+ 0x02, 0x30};
+
+sample_feed_t sample_feeds[FEEDS_NR] = {
+ { 1, 10, feed_data_0 }
+#if FEEDS_NR > 1
+ ,{ 21, 12, feed_data_1 }
+#endif
+#if FEEDS_NR > 2
+ ,{ 0, 3, feed_data_2 }
+#endif
+#if FEEDS_NR > 3
+ ,{ 40, 3, feed_data_3 }
+#endif
+};
+
+void
+feed_destroy(sfx_pcm_feed_t *self)
+{
+ int_struct *s = (int_struct *) self->internal;
+ s->i = 0; /* reset */
+}
+
+
+int
+feed_poll(sfx_pcm_feed_t *self, byte *dest, int size)
+{
+ int_struct *s = (int_struct *) self->internal;
+ int sample_size = self->sample_size;
+ sample_feed_t *data = &(sample_feeds[self->debug_nr]);
+ int bias = self->conf.format & ~SFX_PCM_FORMAT_LMASK;
+ byte neutral[4] = {0, 0, 0, 0};
+ int i;
+fprintf(stderr, "[feed] Asked for %d at %p, ss=%d\n", size, dest, sample_size);
+ if (bias) {
+ byte first = bias >> 8;
+ byte second = bias & 0xff;
+
+ if ((self->conf.format & SFX_PCM_FORMAT_ENDIANNESS) == SFX_PCM_FORMAT_LE) {
+ int t = first;
+ first = second;
+ second = t;
+ }
+
+ if (self->conf.format & SFX_PCM_FORMAT_16) {
+ neutral[0] = first;
+ neutral[1] = second;
+ neutral[2] = first;
+ neutral[3] = second;
+ } else {
+ neutral[0] = bias;
+ neutral[1] = bias;
+ }
+ }
+
+ for (i = 0; i < size; i++) {
+ int t = s->i - data->start;
+
+ if (t >= data->samples_nr)
+ return i;
+
+ if (t >= 0)
+ memcpy(dest, data->data + t * sample_size, sample_size);
+ else
+ memcpy(dest, neutral, sample_size);
+
+ dest += sample_size;
+ s->i++;
+ }
+ return size;
+}
+
+
+
+
+extern FILE *con_file;
+
+#define DELAY usleep((rand() / (RAND_MAX / 250L)))
+
+
+int
+main(int argc, char **argv)
+{
+ int dev_nr;
+
+ mix = sfx_pcm_find_mixer(NULL);
+
+ if (!mix) {
+ fprintf(stderr, "Error: Could not find a mixer!\n");
+ return 1;
+ } else {
+ fprintf(stderr, "Running %s, v%s\n",
+ mix->name, mix->version);
+ }
+ con_file = stderr;
+
+ srand(time(NULL));
+
+ for (dev_nr = 0; dev_nr < DEVICES_NR; dev_nr++) {
+ sfx_pcm_device_t *dev = &(devices[dev_nr++]);
+ int j;
+ dev->init(dev);
+ mix->init(mix, dev);
+
+ dev_output_enabled = 0;
+ /* Prime it to our timing */
+ for (j = 0; j < 250; j++) {
+ DELAY;
+ mix->process(mix);
+ }
+ dev_output_enabled = 1;
+
+ fprintf(stderr, "[test] Subscribing...\n");
+
+ for (j = 0; j < FEEDS_NR; j++)
+ mix->subscribe(mix, &(feeds[j]));
+
+ fprintf(stderr, "[test] Subscribed %d feeds.\n",
+ FEEDS_NR);
+
+ while (output_count < MIN_OUTPUT) {
+ DELAY;
+ mix->process(mix);
+ fprintf(stderr, "<tick>\n");
+ }
+
+ fprintf(stderr, "[test] Preparing finalisation\n");
+ mix->exit(mix);
+ fprintf(stderr, "[test] Mixer uninitialised\n");
+ }
+}
+
+#else
+int main() {}
+#endif