/* Extended Module Player
 * Copyright (C) 1997-1999 Claudio Matsuoka and Hipolito Carraro Jr
 *
 * This file is part of the Extended Module Player and is distributed
 * under the terms of the GNU General Public License. See doc/COPYING
 * for more information.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "mixer.h"

#define OUT_MAXLEN	(5 * 2 * SMIX_MAXRAT * SMIX_RESMAX / SMIX_MINBPM / 3)

#define FLAG_ITPT	0x01
#define FLAG_16_BITS	0x02
#define FLAG_STEREO	0x04
#define FLAG_BACKWARD	0x08
#define FLAG_ACTIVE	0x10
#define FIDX_FLAGMASK	(FLAG_ITPT | FLAG_16_BITS | FLAG_STEREO)

#define LIM_FT		 12
#define LIM8_HI		 127
#define LIM8_LO		-127
#define LIM12_HI	 4095
#define LIM12_LO	-4096
#define LIM16_HI	 32767
#define LIM16_LO	-32768

#define TURN_OFF	0
#define TURN_ON		1

static char** smix_buffer = NULL;	/* array of output buffers */
static int* smix_buf32b = NULL;		/* temp buffer for 32 bit samples */
static int smix_numvoc;			/* default softmixer voices number */
static int smix_numbuf;			/* number of buffer to output */
static int smix_mode;			/* mode = 0:OFF, 1:MONO, 2:STEREO */
static int smix_resol;			/* resolution output 0:8bit, 1:16bit */
static int smix_ticksize;

static int smix_dtright;		/* anticlick control, right channel */
static int smix_dtleft;			/* anticlick control, left channel */

static int echo_msg;

void    smix_mn8norm     (int, char*, int, int);
void    smix_mn8itpt     (int, char*, int, int);
void    smix_mn16norm    (int, int16*, int, int);
void    smix_mn16itpt    (int, int16*, int, int);
void    smix_st8norm     (int, char*, int, int);
void    smix_st8itpt     (int, char*, int, int);
void    smix_st16norm    (int, int16*, int, int);
void    smix_st16itpt    (int, int16*, int, int);

static void     out_su8norm     (char*, int*, int, int);
static void     out_su16norm    (int16*, int*, int, int);
static void     out_u8ulaw      (char*, int*, int, int);

/* Array index:
 *
 * bit 0: 0=non-interpolated, 1=interpolated
 * bit 1: 0=8 bit, 1=16 bit
 * bit 2: 0=mono, 1=stereo
 */

static void (*mix_fn[])() = {
    smix_mn8norm,
    smix_mn8itpt,
    smix_mn16norm,
    smix_mn16itpt,
    smix_st8norm,
    smix_st8itpt,
    smix_st16norm,
    smix_st16itpt
};

#define OUT_U8ULAW	0
#define OUT_SU8NORM	1
#define OUT_SU16NORM	2

static void (*out_fn[])() = {
    out_u8ulaw,
    out_su8norm,
    out_su16norm
};


/* Handler 32bit samples to 8bit, signed or unsigned, mono or stereo output */
static void out_su8norm (char *dt, int *og, int num, int cod)
{
    int smp, lhi, llo, left, mask;

    mask = 0;
    left = LIM_FT + 8;
    if (cod & XMP_FMT_UNS)
	mask = 0x80;

    lhi = LIM8_HI;
    llo = LIM8_LO;

    for (; num--; ++og, ++dt) {
	smp = *og >> left;
	smp = smp > lhi ? lhi + mask : smp < llo ? llo + mask : smp + mask;
	*dt = smp;
    }
}


/* Handler 32bit samples to 16bit, signed or unsigned, mono or stereo output */
static void out_su16norm (int16 *dt, int *og, int num, int cod)
{
    int smp, lhi, llo, left, mask;

    mask = 0;
    left = LIM_FT;
    if (cod & XMP_FMT_UNS)
	mask = 0x8000;

    lhi = LIM16_HI;
    llo = LIM16_LO;

    for (; num--; ++og, ++dt) {
	smp = *og >> left;
	smp = smp > lhi ? lhi + mask : smp < llo ? llo + mask : smp + mask;
	*dt = smp;
    }
}


/* Handler 32bit samples to 8bit, unsigned ulaw, mono or stereo output */
static void out_u8ulaw (char *dt, int *og, int num, int cod)
{
    int smp, lhi, llo, left;

    lhi = LIM12_HI;
    llo = LIM12_LO;
    left = LIM_FT + 4;

    for (; num--; ++og, ++dt) {
	smp = *og >> left;
	*dt = smp > lhi ? ulaw_encode (lhi) :
	      smp < llo ? ulaw_encode (llo) : ulaw_encode (smp);
    }
}


/* Prepare the mixer for the next tick */
inline static void smix_resetvar ()
{
/*
    smix_ticksize = xmp_ctl->freq * xmp_ctl->rrate * 33 / xmp_bpm / 100;
    smix_ticksize /= xmp_ctl->fetch & XMP_CTL_MEDBPM ? 125 : 33;
*/
    smix_ticksize = xmp_ctl->fetch & XMP_CTL_MEDBPM ?
	xmp_ctl->freq * xmp_ctl->rrate * 33 / xmp_bpm / 12500 :
    	xmp_ctl->freq * xmp_ctl->rrate / xmp_bpm / 100;

    if (smix_buf32b) {
	smix_dtright = smix_dtleft = TURN_OFF;
	memset (smix_buf32b, 0, smix_ticksize * smix_mode * sizeof (int));
    }
}


/* Softmixer anticlick, make one linear rampdown ... Funny? */
static void smix_rampdown (int voc, int* buf, int cnt)
{
    int mod;
    int smp_l, smp_r;
    int dec_l, dec_r;

    if (voc < 0) {
	smp_r = smix_dtright;
	smp_l = smix_dtleft;
    }
    else {
	smp_r = voice_array[voc].sright;
	smp_l = voice_array[voc].sleft;
	voice_array[voc].sright = voice_array[voc].sleft = TURN_OFF;
    }

    if (!(smp_l || smp_r))
	return;

    if (!buf) {
	buf = smix_buf32b;
	cnt = smix_ticksize;
    }
    if (!cnt)
	return;

    mod = !(xmp_ctl->outfmt & XMP_FMT_MONO);
    dec_r = smp_r / cnt;
    dec_l = smp_l / cnt;

    while ((smp_r || smp_l) && cnt--) {
	if (dec_r > 0)
	    *(buf++) += smp_r > dec_r ? (smp_r -= dec_r):(smp_r = 0);
	else
	    *(buf++) += smp_r < dec_r ? (smp_r -= dec_r):(smp_r = 0);

	if (dec_l > 0)
	    *(buf++) += smp_l > dec_l ? (smp_r -= dec_r):(smp_r = 0);
	else
	    *(buf++) += smp_l < dec_l ? (smp_r -= dec_r):(smp_r = 0);
    }
}


/* Ok it's messy, but it does anticlick :-) Hipolito */
static void smix_anticlick (int voc, int vol, int pan, int* buf, int cnt)
{
    int oldvol, newvol;
    struct voice_info *vi = &voice_array[voc];

    if (extern_drv)
	return;			/* Anticlick is useful for softmixer only */

    if (vi->vol) {
	oldvol = vi->vol * (0x80 - vi->pan);
	newvol = vol * (0x80 - pan);
	vi->sright -= vi->sright / oldvol * newvol;

	oldvol = vi->vol * (0x80 + vi->pan);
	newvol = vol * (0x80 + pan);
	vi->sleft -= vi->sleft / oldvol * newvol;
    }

    if (!buf) {
	smix_dtright += vi->sright;
	smix_dtleft += vi->sleft;
	vi->sright = vi->sleft = TURN_OFF;
    }
    else
	smix_rampdown (voc, buf, cnt);
}


/* Fill the output buffer calling one of the handlers. The buffer contains
 * sound for one tick (a PAL frame or 1/50s for standard vblank-timed mods)
 */
static int softmixer ()
{
    int* buf_pos;
    struct voice_info* vi;
    struct patch_info* pi;
    int smp_cnt, tic_cnt, lpsta, lpend;
    int vol_l, vol_r, itp_inc, voc;
    int prv_l, prv_r;

    if (!extern_drv)
	smix_rampdown (-1, NULL, TURN_OFF);	/* Anti-click */

    for (voc = numvoc; voc--; ) {
	vi = &voice_array[voc];
	if (vi->chn < 0)
	    continue;
	if (vi->period < 1) {
	    drv_resetvoice (voc, TURN_ON);
	    continue;
	}
	pi = patch_array[vi->smp];

	buf_pos = smix_buf32b;
	vol_r = (vi->vol * (0x80 - vi->pan)) >> SMIX_VOL_FT;
	vol_l = (vi->vol * (0x80 + vi->pan)) >> SMIX_VOL_FT;
	itp_inc = ((long long) vi->pbase << SMIX_SFT_FT) / vi->period;

	/* This is for bidirectional sample loops */
	if (vi->fidx & FLAG_BACKWARD)
	    itp_inc = -itp_inc;

	/* Sample loop processing. Offsets in samples, not bytes */
	lpsta = pi->loop_start >> !!(vi->fidx & FLAG_16_BITS);
	lpend = pi->loop_end >> !!(vi->fidx & FLAG_16_BITS);

	for (tic_cnt = smix_ticksize; tic_cnt; ) {
	    /* How many samples we can write before the loop break or
	     * sample end... */
	    smp_cnt = 1 + (((long long) (vi->end - vi->pos) << SMIX_SFT_FT)
		- vi->itpt) / itp_inc;

	    if (itp_inc > 0) {
		if (vi->end < vi->pos)
		    smp_cnt = 0;
	    } else
		if (vi->end > vi->pos)
		    smp_cnt = 0;

	    /* ...inside the tick boundaries */
	    if (smp_cnt > tic_cnt)
		smp_cnt = tic_cnt;

	    if (vi->vol) {
		/* Something for Hipolito's anticlick routine */
		prv_r = buf_pos[smix_mode * smp_cnt - 2];
		prv_l = buf_pos[smix_mode * smp_cnt - 1];

		/* Call the output handler */
		mix_fn[vi->fidx & FIDX_FLAGMASK] (buf_pos, vi->sptr,
		    smp_cnt, vol_l, vol_r, vi->pos - 1,
		    vi->itpt + (1 << SMIX_SFT_FT), itp_inc);

		buf_pos += smix_mode * smp_cnt;

		/* More stuff for Hipolito's anticlick routine */
		vi->sright = buf_pos[-2] - prv_r;
		vi->sleft = buf_pos[-1] - prv_l;
	    }

	    vi->itpt += itp_inc * smp_cnt;
	    vi->pos += vi->itpt >> SMIX_SFT_FT;
	    vi->itpt &= SMIX_AND_FT;

	    /* No more samples in this tick */
	    if (!(tic_cnt -= smp_cnt))
		continue;

	    /* Single shot sample */
            if (!(vi->fidx ^= vi->fxor) || lpsta >= lpend) {
		smix_anticlick (voc, TURN_OFF, TURN_OFF, buf_pos, tic_cnt);
		drv_resetvoice (voc, TURN_OFF);
		tic_cnt = 0;
		continue;
	    }

	    if (!(vi->fidx & FLAG_BACKWARD || vi->fxor)) {
		vi->pos -= lpend - lpsta;
	    } else {
		vi->itpt += (itp_inc = -itp_inc);
		vi->pos += vi->itpt >> SMIX_SFT_FT;
		vi->itpt &= SMIX_AND_FT;
		vi->end = itp_inc > 0 ? lpend : lpsta;
	    }
	}
    }
    return smix_ticksize * smix_mode * smix_resol;
}


static void smix_voicepos (int voc, int pos, int itp)
{
    struct voice_info *vi = &voice_array[voc];
    struct patch_info *pi = patch_array[vi->smp];
    int lpend, res, mde;

    res = !!(pi->mode & WAVE_16_BITS);
    mde = (pi->mode & WAVE_LOOPING) && !(pi->mode & WAVE_BIDIR_LOOP);
    mde = (mde << res) + res + 1;	/* Ugh, look for xmp_cvt_anticlick! */

    lpend = pi->len - mde;
    if (pi->mode & WAVE_LOOPING)
	lpend =  lpend > pi->loop_end ? pi->loop_end : lpend;
    lpend >>= res;

    if (pos < lpend) {
	vi->pos = pos;
	vi->itpt = itp;
	vi->end = lpend;
	if (vi->fidx & FLAG_BACKWARD)
	    vi->fidx ^= vi->fxor;
    } else
	drv_resetvoice (voc, TURN_ON);		/* Bad data, reset voice */
}


static void smix_setpatch (int voc, int smp, int ramp)
{
    struct voice_info* vi = &voice_array[voc];
    struct patch_info* pi = patch_array[smp];

    if (ramp)				/* Anti-click */
	xmp_smix_setvol (voc, TURN_OFF);

    vi->smp = smp;
    vi->vol = TURN_OFF;
    vi->sptr = extern_drv ? NULL : pi->data;
    vi->freq = (long long) C4_FREQ * pi->base_freq / xmp_ctl->freq;
    vi->fidx = xmp_ctl->fetch & XMP_CTL_ITPT ?
	FLAG_ITPT | FLAG_ACTIVE : FLAG_ACTIVE;

    if (xmp_ctl->outfmt & XMP_FMT_MONO)
	vi->pan = TURN_OFF;
    else {
	vi->pan = pi->panning;
	vi->fidx |= FLAG_STEREO;
    }

    if (pi->mode & WAVE_16_BITS)
	vi->fidx |= FLAG_16_BITS;

    if (pi->mode & WAVE_LOOPING)
	vi->fxor = !!(pi->mode & WAVE_BIDIR_LOOP) * FLAG_BACKWARD;
    else
	vi->fxor = vi->fidx;

    smix_voicepos (voc, 0, 0);
}


static void smix_setnote (int voc, int nte)
{
    struct voice_info *vi = &voice_array[voc];

    vi->period = note_to_period2 (vi->note = nte, 0);
    vi->pbase = SMIX_C4NOTE * vi->freq / patch_array[vi->smp]->base_note;
}


static inline void smix_setbend (int voc, int bend)
{
    voice_array[voc].period =
	note_to_period2 (voice_array[voc].note, bend);
}


void xmp_smix_setvol (int voc, int vol)
{
    smix_anticlick (voc, vol, voice_array[voc].pan, NULL, TURN_OFF);
    voice_array[voc].vol = vol;
}


void xmp_smix_setpan (int voc, int pan)
{
    voice_array[voc].pan = pan;
}


void xmp_smix_echoback (int msg)
{
    xmp_event_callback (echo_msg = msg);
}


int xmp_smix_getmsg ()
{
    return echo_msg;
}


int xmp_smix_numvoices (int num)
{
    return num > smix_numvoc ? smix_numvoc : num;
}


/* WARNING! Output samples must have the same byte order of the host machine!
 * (That's what happens in most cases anyway)
 */
int xmp_smix_writepatch (struct patch_info *patch)
{
    if (patch) {
	if (patch->len == XMP_PATCH_FM) {
	}
	if (patch->len <= 0)
	    return XMP_ERR_TPTCH;

	if (patch->mode & WAVE_UNSIGNED)
	    xmp_cvt_sig2uns (patch->len, patch->mode & WAVE_16_BITS,
		patch->data);
    }

    return XMP_OK;
}


int xmp_smix_on (struct xmp_control *ctl)
{
    int cnt;

    if (smix_numbuf)
	return XMP_OK;

    if (ctl->numbuf < 1)
	ctl->numbuf = 1;
    cnt = smix_numbuf = ctl->numbuf;

    smix_buffer = calloc (sizeof (void *), cnt);
    smix_buf32b = calloc (sizeof (int), OUT_MAXLEN);
    if (!(smix_buffer && smix_buf32b))
	return XMP_ERR_ALLOC;

    while (cnt--)
	if (!(smix_buffer[cnt] = calloc (SMIX_RESMAX, OUT_MAXLEN)))
	    return XMP_ERR_ALLOC;

    smix_numvoc = SMIX_NUMVOC;
    extern_drv = TURN_OFF;

    return XMP_OK;
}


void xmp_smix_off ()
{
    while (smix_numbuf)
	free (smix_buffer[--smix_numbuf]);
    free (smix_buf32b);
    free (smix_buffer);
    smix_buf32b = NULL;
    smix_buffer = NULL;
    extern_drv = TURN_ON;
}


void *xmp_smix_buffer ()
{
    static int outbuf;
    int act;

    if (!xmp_ctl->resol)
	act = OUT_U8ULAW;
    else if (xmp_ctl->resol > 8)
	act = OUT_SU16NORM;
    else
	act = OUT_SU8NORM;

/* The mixer works with multiple buffers -- this is useless when using
 * multi-buffered sound output (e.g. OSS fragments) but can be useful for
 * DMA transfers in DOS.
 */
    if (++outbuf >= smix_numbuf)
	outbuf = 0;

    out_fn[act] (smix_buffer[outbuf], smix_buf32b,
	smix_mode * smix_ticksize, xmp_ctl->outfmt);

    smix_resetvar ();
    return smix_buffer[outbuf]; 
}
