/*
 * FILE: mix.c
 * PROGRAM: RAT/Mixer
 * AUTHOR: Isidor Kouvelas + Colin Perkins
 *
 * $Revision: 1.1.1.1 $
 * $Date: 1998/08/15 19:16:45 $
 *
 * Copyright (c) 1995,1996 University College London
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted, for non-commercial use only, provided
 * that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Science
 *      Department at University College London
 * 4. Neither the name of the University nor of the Department may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * Use of this software for commercial purposes is explicitly forbidden
 * unless prior written permission is obtained from the authors.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "bat_include.h"

#define ROUND_UP(x, y)  (x) % (y) > 0 ? (x) - (x) % (y) : (x)

/*
 * Initialise the circular buffer that is used in mixing.
 * The buffer length should be as big as the largest possible
 * device coushion used (and maybe some more).
 * We allocate space three time the requested one so that we
 * dont have to copy everything when we hit the boundaries..
 */
mix_struct *
init_mix(session_struct *sp, int buffer_length)
{
	mix_struct	*ms;

	ms = (mix_struct *) xmalloc(sizeof(mix_struct));
	memset(ms, 0 , sizeof(mix_struct));
	ms->buf_len = buffer_length;
	ms->mix_buffer = (sample *)xmalloc(3 * buffer_length * BYTES_PER_SAMPLE);
	audio_zero(ms->mix_buffer, 3*buffer_length, DEV_L16);
	ms->mix_buffer += buffer_length;
	return (ms);
}

static void
mix_add(mix_struct *ms, sample *buf, int offset, int len)
{
	assert(buf    != NULL);
	assert(offset >= 0);
	assert(offset <= ms->buf_len);
	assert(len    <= ms->buf_len);
	
	xmemchk();
	if (offset + len > ms->buf_len) {
		assert((offset + len - ms->buf_len) < ms->buf_len);
		mix_audio(ms->mix_buffer + offset, buf, ms->buf_len - offset);
		mix_audio(ms->mix_buffer, buf, offset + len - ms->buf_len);
	} else {
		mix_audio(ms->mix_buffer + offset, buf, len);
	}
	xmemchk();
}

static void
mix_zero(mix_struct *ms, int offset, int len)
{
	xmemchk();
	if (offset + len > ms->buf_len) {
		assert((offset + len - ms->buf_len) <= ms->buf_len);
		audio_zero(ms->mix_buffer + offset, ms->buf_len - offset, DEV_L16);
		audio_zero(ms->mix_buffer, offset + len-ms->buf_len, DEV_L16);
	} else {
		assert((offset + len) <= ms->buf_len);
		audio_zero(ms->mix_buffer + offset, len, DEV_L16);
	}
	xmemchk();
}

/*
 * This function assumes that packets come in order without
 * missing or duplicates! Starts of talkspurts should allways
 * be signaled.
 */
void
mix_do_one_chunk(mix_struct *ms, rx_queue_element_struct *el)
{
        u_int32	playout, diff;
        int	pos, i;

	assert((ms->head + ms->buf_len - ms->tail) % ms->buf_len == ms->dist);
	playout = el->playoutpt;

	/* If it is too late... */
	if (ts_gt(ms->tail_time, playout)) {
#ifdef DEBUG
	    	printf("Unit arrived late in mix %ld\n", ms->tail_time - playout);
#endif
	    	return;
	}

#ifdef DEBUG
	/* This should never really happen but you can't be too careful :-) */
	if (ts_gt(el->dbe_source[0]->last_mixed_playout + SAMPLES_PER_UNIT, playout)) {
		printf("New unit overlaps with previous by %ld samples\n", el->dbe_source[0]->last_mixed_playout + SAMPLES_PER_UNIT - playout);
	}
	if (ts_gt(playout, el->dbe_source[0]->last_mixed_playout + SAMPLES_PER_UNIT)) {
		printf("Gap between units %ld samples\n", playout - el->dbe_source[0]->last_mixed_playout - SAMPLES_PER_UNIT);
	}
#endif

	for (i=0; i < el->dbe_source_count; i++) {
		if (el->dbe_source[i] != NULL) {
			el->dbe_source[i]->last_mixed_playout = playout;
		}
	}

	if (el->dbe_source[0]->mute)
		return;

	/* Convert playout to position in buffer */
	pos = (playout - ms->head_time + ms->head) % ms->buf_len;
	assert(pos >= 0);

	/* Should clear buffer before advancing...
	 * Or better only mix if something there otherwise copy...
	 */

	/*
	 * If we have not mixed this far (normal case)
	 * we mast clear the buffer ahead (or copy)
	 */
	if (ts_gt(playout + SAMPLES_PER_UNIT, ms->head_time)) {
		diff = playout + SAMPLES_PER_UNIT - ms->head_time;
		mix_zero(ms, ms->head, diff);
		ms->dist += diff;
		ms->head += diff;
		ms->head %= ms->buf_len;
		ms->head_time += diff;
	}
	assert((ms->head + ms->buf_len - ms->tail) % ms->buf_len == ms->dist);

	mix_add(ms, el->decomp_data, pos, SAMPLES_PER_UNIT);
}

/*
 * This function was modified so that it returns the amount of
 * silence at the end of the buffer returned so that the cushion
 * adjustment functions can use it to decrease the cushion.
 */
int
mix_get_audio(mix_struct *ms, int amount, sample **bufp)
{
	int	silence;

	if (amount > ms->dist) {
		/*
 		 * If we dont have enough to give one of two things
		 * must have happened.
		 * a) There was silence :-)
		 * b) There wasn't enough time to decode the stuff...
		 * In either case we will have to return silence for
		 * now so zero the rest of the buffer and move the head.
		 */
		silence = amount - ms->dist;
		xmemchk();
		if (ms->head + silence > ms->buf_len) {
#ifdef DEBUG
			printf("Insufficient audio: zeroing end of mix buffer %d %d\n", ms->buf_len - ms->head, silence + ms->head - ms->buf_len);
#endif
			audio_zero(ms->mix_buffer + ms->head, ms->buf_len - ms->head, DEV_L16);
			audio_zero(ms->mix_buffer, silence + ms->head - ms->buf_len, DEV_L16);
		} else {
			audio_zero(ms->mix_buffer + ms->head, silence, DEV_L16);
		}
		xmemchk();
		ms->head      += silence;
		ms->head      %= ms->buf_len;
		ms->head_time += silence;
		ms->dist      = amount;
		assert((ms->head + ms->buf_len - ms->tail) % ms->buf_len == ms->dist);
	} else {
		silence = 0;
	}

	if (ms->tail + amount > ms->buf_len) {
		/*
		 * We have run into the end of the buffer so we will
		 * have to copy stuff before we return it.
		 * The space after the 'end' of the buffer is used
		 * for this purpose as the space before is used to
		 * hold silence that is returned in case the cushion
		 * grows too much.
		 * Of course we could use both here (depending on which
		 * direction involves less copying) and copy actual
		 * voice data in the case a cushion grows into it.
		 * The problem is that in that case we are probably in
		 * trouble and want to avoid doing too much...
		 *
		 * Also if the device is working in similar boundaries
		 * to our chunk sizes and we are a bit careful about the
		 * possible cushion sizes this case can be avoided.
		 */
		xmemchk();
		memcpy(ms->mix_buffer + ms->buf_len, ms->mix_buffer, BYTES_PER_SAMPLE*(ms->tail + amount - ms->buf_len));
		xmemchk();
#ifdef DEBUG
		printf("Copying start of mix len: %d\n", ms->tail + amount - ms->buf_len);
#endif
	}
	*bufp = ms->mix_buffer + ms->tail;
	ms->tail_time += amount;
	ms->tail      += amount;
	ms->tail      %= ms->buf_len;
	ms->dist      -= amount;
	assert((ms->head + ms->buf_len - ms->tail) % ms->buf_len == ms->dist);

	return silence;
}

/*
 * We need the amount of time we went dry so that we can make a time
 * adjustment to keep in sync with the receive buffer etc...
 */
void
mix_get_new_cushion(mix_struct *ms, int last_cushion_size, int new_cushion_size,
			int dry_time, sample **bufp)
{
	int	diff, elapsed_time;

#ifdef DEBUG
	printf("Getting new cushion %d old %d\n", new_cushion_size, last_cushion_size);
#endif

	elapsed_time = last_cushion_size + dry_time;
	diff = abs(new_cushion_size - elapsed_time);

	if (new_cushion_size > elapsed_time) {
		/*
		 * New cushion is larger so move tail back to get
		 * the right amount and end up at the correct time.
		 * The effect of moving the tail is that some old
		 * audio and/or silence will be replayed. We do not
		 * care to much as we are right after an underflow.
		 */
		ms->tail -= diff;
		if (ms->tail < 0) {
			ms->tail += ms->buf_len;
		}
		ms->dist += diff;
		ms->tail_time -= diff;
		assert((ms->head + ms->buf_len - ms->tail) % ms->buf_len == ms->dist);
	} else if (new_cushion_size < elapsed_time) {
		/*
		 * New cushion is smaller so we have to throw away
		 * some audio.
		 */
		ms->tail += diff;
		ms->tail %= ms->buf_len;
		ms->tail_time += diff;
		if (diff > ms->dist) {
			ms->head = ms->tail;
			ms->head_time = ms->tail_time;
			ms->dist = 0;
		} else {
			ms->dist -= diff;
		}
		assert((ms->head + ms->buf_len - ms->tail) % ms->buf_len == ms->dist);
	}
	mix_get_audio(ms, new_cushion_size, bufp);
}

