aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sfx/mixer/soft.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/sfx/mixer/soft.cpp')
-rw-r--r--engines/sci/sfx/mixer/soft.cpp988
1 files changed, 988 insertions, 0 deletions
diff --git a/engines/sci/sfx/mixer/soft.cpp b/engines/sci/sfx/mixer/soft.cpp
new file mode 100644
index 0000000000..d71567a209
--- /dev/null
+++ b/engines/sci/sfx/mixer/soft.cpp
@@ -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/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
+};