/*

 Name:  VIRTCH.C

 Description:
 All-c sample mixing routines, using a 32 bits mixing buffer

 C Mixer Portability:
 All Systems -- All compilers.

 Assembly Mixer Portability:
  
 MSDOS:  BC(?)   Watcom(y)   DJGPP(y)
 Win95:  ?
 Os2:    ?
 Linux:  y

 PowerPC Portability:
 
 BeOS:   y
 MacOS:  y (hopefully)
 AIX:    n
 Linux:  n

(y) - yes
(n) - no (not possible or not useful)
(?) - may be possible, but not tested

*/

#include <stddef.h>
#include <string.h>
#include "mikmod.h"


#ifdef __GNUC__
#define __cdecl
#endif

#ifdef __WATCOMC__
#define   inline
#endif

// for PC-assembly mixing
// ======================
//
// Uncomment both lines below for assembly mixing under WATCOM or GCC for
// Linux.  Note that there is no 16 bit mixers for assembly yet (C only).

//#define __MIX8BITS__
//#define __ASSEMBLY__

#define FRACBITS 11
#define FRACMASK ((1L<<FRACBITS)-1)

#define TICKLSIZE 3600
#define TICKWSIZE (TICKLSIZE*2)
#define TICKBSIZE (TICKWSIZE*2)

#ifndef MIN
#define MIN(a,b) (((a)<(b)) ? (a) : (b))
#endif

#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif

#ifdef __POWERPC__
#pragma toc_data on // The assembler functions expect this
#endif

typedef struct
{   UBYTE  kick;                  // =1 -> sample has to be restarted
    UBYTE  active;                // =1 -> sample is playing
    UWORD  flags;                 // 16/8 bits looping/one-shot
    SWORD  handle;                // identifies the sample
    ULONG  start;                 // start index
    ULONG  size;                  // samplesize
    ULONG  reppos;                // loop start
    ULONG  repend;                // loop end
    ULONG  frq;                   // current frequency
    UWORD  vol;                   // current volume
    UWORD  pan;                   // current panning position
    SLONG  current;               // current index in the sample
    SLONG  increment;             // fixed-point increment value
} VINFO;


#ifdef __MIX8BITS__
static SBYTE *Samples[MAXSAMPLEHANDLES];
#else
static SWORD *Samples[MAXSAMPLEHANDLES];
#endif

// Volume table for 8 bit sample mixing
#ifdef __MIX8BITS__
static SLONG  **voltab;
#endif

static VINFO  *vinf = NULL;
static VINFO  *vnf;

static UWORD  samplesthatfit;

// Reverb and Stereo Delay control variables
static int     DELAYSIZE;
static int     REVERBSIZE;
static SLONG  *gReverbBuf = NULL;
static SLONG  *gStereoDelayBuf = NULL;
static UWORD   gSDReadPos, gSDWritePos;
static UWORD   gStereoDelay;
static UWORD   gRVReadPos, gRVWritePos;
static UWORD   gReverb;

static int     vc_memory;
static SLONG   idxsize,idxlpos,idxlend;
static SLONG   TICKLEFT;
static SLONG   VC_TICKBUF[TICKLSIZE];

#ifdef __cplusplus
extern "C" {
#endif

// Amplification shift (amount to decrease 32 bit mixing buffer by!)
static int      bitshift;

// Volume Table values for use by 8 bit assembly mixers
#ifdef __MIX8BITS__
static SLONG  *lvoltab,*rvoltab;
#else
static SLONG  lvolsel, rvolsel;
#endif

#ifdef __cplusplus
}
#endif

#ifdef __ASSEMBLY__

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __GNUC__
#define __cdecl
#endif

void __cdecl AsmStereoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,ULONG todo);
void __cdecl AsmMonoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,ULONG todo);
void __cdecl AsmMix32To16Surround(SWORD *dest, SLONG *srce, ULONG count);
void __cdecl AsmMix32To8Surround(SBYTE *dest, SLONG *srce, ULONG count);

#ifdef __cplusplus
}
#endif

#else

#ifdef __MIX8BITS__


// ==============================================================
//  8 bit sample mixers!

static SLONG MixStereoNormal(
        register SBYTE *srce,                         //      r3
        register SLONG *dest,                         //      r4
        register SLONG index,                         //      r5
        register SLONG increment,                     //      r6
        register ULONG todo)                          //      r7
{
    register SBYTE  sample;
    
    for(dest--; todo; todo--)
    {
        sample = srce[index >> FRACBITS];
        index += increment;
        
        *(++dest) += lvoltab[(UBYTE)sample];
        *(++dest) += rvoltab[(UBYTE)sample];
    }
    return index;
}


static SLONG MixMonoNormal(
        register SBYTE *srce,                                           //      r3
        register SLONG *dest,                                           //      r4
        register SLONG index,                                           //      r5
        register SLONG increment,                                       //      r6
        register SLONG todo)                                            //      r7
{
    register int  sample;
    
    for (dest--; todo>0; todo--)
    {
        sample = srce[index >> FRACBITS];
        index += increment;
        
        *(++dest) += lvoltab[(UBYTE)sample];
    }
    
    return index;
}

#else   // not __MIX8BITS__

// ==============================================================
//  16 bit sample mixers!

#ifndef __POWERPC__

static SLONG MixStereoNormal(
        register SWORD *srce,               //      r3
        register SLONG *dest,               //      r4
        register SLONG index,               //      r5
        register SLONG increment,           //      r6
        register ULONG todo)                //      r7
{
    register SWORD  sample;

    for (dest--; todo>0; todo--)
    {
        sample = srce[index >> FRACBITS];
        index += increment;

        *(++dest) += lvolsel * sample;
        *(++dest) += rvolsel * sample;
    }
    
    return index;
}


static SLONG MixMonoNormal(
        register SWORD *srce,                                           //      r3
        register SLONG *dest,                                           //      r4
        register SLONG index,                                           //      r5
        register SLONG increment,                                       //      r6
        register SLONG todo)                                            //      r7
{
    register SWORD  sample;
    
    for (dest--;todo > 0;todo--)
    {
        sample = srce[index >> FRACBITS];
        index += increment;

        *(++dest) += lvolsel * sample;
    }

    return index;
}

#else // __POWERPC__

static asm SLONG MixStereoNormal(
		register SWORD *srce,
		register SLONG *dest,
		register SLONG index,
		register SLONG increment, 
		register ULONG todo
		)
{	
	lwz     r9,lvolsel(RTOC)
	lwz     r10,rvolsel(RTOC)

	mtctr	todo		
@loop
	rlwinm	r0,index,32-(FRACBITS-1),FRACBITS,30	
	add		index,index,increment
	lhax	r8,srce,r0

	mullw	r11,r9,r8	
	lwz		r0,0(dest)	
	add		r0,r0,r11	
	stw		r0,0(dest)

	mullw	r11,r10,r8	
	lwz		r0,4(dest)
	add		r0,r0,r11
	stw		r0,4(dest)	
	addi	dest,dest,8 // faster on 604, instead of using updating
	
	bdnz	@loop

	// horrible, but mwcc does not want mr r3,index
	addi	r3,index,0	
	blr
}

static asm SLONG MixMonoNormal(
		register SWORD *srce,
		register SLONG *dest,
		register SLONG index,
		register SLONG increment, 
		register SLONG todo
		)
{	
	lwz     r9,lvolsel(RTOC)

	addi	dest,dest,-4
	mtctr	todo
	
@loop
	rlwinm	r0,index,32-(FRACBITS-1),FRACBITS,30
	add		index,index,increment
	lhax	r8,srce,r0

	mullw	r11,r9,r8 	
	lwz 	r0,4(dest)  // stwu  will be faster here, 
	add		r0,r0,r11   // since we anyway have to 				
	stwu	r0,4(dest)  // update two registers
		
	bdnz	@loop

	addi	r3,index,0
	blr
}
#endif // __POWERPC__

#endif   // __MIX8BITS__

static void (*Mix32to16)(register SWORD *dste, register SLONG *srce, register SLONG count);
static void (*Mix32to8)(register SBYTE *dste, register SLONG *srce, register SLONG count);

#ifndef __POWERPC__

static void Mix32To16_Normal(
        register SWORD *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
    register SLONG lbitshift = bitshift;
    
    for (;count--;)
    {   x = *srce++>>lbitshift;
        x = (x > 32767) ? 32767 : (x < -32768) ? -32768 : x;
        *dste++ = x;
    }
}

#else // __POWERPC__

static asm void Mix32To16_Normal(
		register SWORD *dest,
		register SLONG *srce,
		register SLONG count)
{
	lwz		r11,bitshift(RTOC)	
	addi	dest,dest,-2
	
	mtctr	count
@loop
	lwz		r10,0(srce)
	sraw	r10,r10,r11
	cmpwi	r10,0x7FFF
	addi	srce,srce,4
	ble		@skip1
	li		r10,0x7FFF
@skip1
	cmpwi	r10,-0x8000
	bge		@skip2
	li		r10,-0x8000
@skip2
	sthu	r10,2(dest)
	bdnz	@loop
	blr
}

#endif // __POWERPC__

#ifndef __POWERPC__

static void Mix32To8_Normal(
        register SBYTE *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
    register SLONG lbitshift = bitshift;
   
    for (;count--;)
    {   x = *srce++>>lbitshift;
        x = (x > 127) ? 127 : (x < -128) ? -128 : x;
        *dste++  = x+128;
    }
}

#else // __POWERPC__

static asm void Mix32To8_Normal(
		register SBYTE *dest,
		register SLONG *srce,
		register SLONG count)
{
	lwz		r11,bitshift(RTOC)
	addi	dest,dest,-1
	
	mtctr	count
@loop
	lwz		r10,0(srce)
	sraw	r10,r10,r11
	cmpwi	r10,0x7F
	addi	srce,srce,4
	ble		@skip1
	li		r10,0x7F
@skip1
	cmpwi	r10,-0x80
	bge		@skip2
	li		r10,-0x80
@skip2
	stbu	r10,1(dest)
	bdnz	@loop
	blr
}

#endif // __POWERPC__

static void Mix32To16_Reverb(
        register SWORD *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
    // This enhances the compiled code on PPC
 	register SLONG *lgReverbBuf			= gReverbBuf;
	register SLONG  lgRVWritePos		= gRVWritePos;
	register SLONG  lgRVReadPos			= gRVReadPos;
	register SLONG  lgReverb			= gReverb;
    register SLONG	lbitshift			= bitshift;
   
    for (;count--;)
    {   x = *srce++>>lbitshift;
        
        lgReverbBuf[lgRVWritePos] = x>>1;
        lgRVWritePos = (lgRVWritePos + 1) & lgReverb;

        x = (x-(x>>3)) + lgReverbBuf[lgRVReadPos];
        lgRVReadPos = (lgRVReadPos + 1) & lgReverb;

        x = (x > 32767) ? 32767 : (x < -32768) ? -32768 : x;

        *dste++  = x;
    }

	gRVWritePos		= lgRVWritePos;
	gRVReadPos		= lgRVReadPos;
}


static void Mix32To8_Reverb(
        register SBYTE *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
	register SLONG *lgReverbBuf			= gReverbBuf;
	register SLONG  lgRVWritePos		= gRVWritePos;
	register SLONG  lgRVReadPos			= gRVReadPos;
	register SLONG  lgReverb			= gReverb;
    register SLONG	lbitshift			= bitshift;
    
    for (;count--;)
    {   x = *srce++>>lbitshift;

        lgReverbBuf[lgRVWritePos] = x>>1;
        lgRVWritePos = (lgRVWritePos + 1) & lgReverb;

        x = (x-(x>>3)) + lgReverbBuf[lgRVReadPos];
        lgRVReadPos = (lgRVReadPos + 1) & lgReverb;

        x = (x > 127) ? 127 : (x < -128) ? -128 : x;

        *dste++  = x+128;
    }

	gRVWritePos		= lgRVWritePos;
	gRVReadPos		= lgRVReadPos;
}


static void Mix32To16_Delay(
        register SWORD *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
    register SLONG *lgStereoDelayBuf 	= gStereoDelayBuf;
    register SLONG  lgSDWritePos		= gSDWritePos;
    register SLONG  lgSDReadPos			= gSDReadPos;
    register SLONG	lgStereoDelay		= gStereoDelay;
    register SLONG	lbitshift			= bitshift;
    
    for (;count--;)
    {   x = *srce++>>lbitshift;
        x = (x > 32767) ? 32767 : (x < -32768) ? -32768 : x;

        if(count & 1)
        {   lgStereoDelayBuf[lgSDWritePos] = x;
            lgSDWritePos = (lgSDWritePos + 1) & lgStereoDelay;

            *dste++     = lgStereoDelayBuf[lgSDReadPos];
            lgSDReadPos  = (lgSDReadPos + 1) & lgStereoDelay;
        } else
            *dste++  = x;
    }

    gSDWritePos		= lgSDWritePos;
    gSDReadPos		= lgSDReadPos;
}


static void Mix32To8_Delay(
        register SBYTE *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
    register SLONG *lgStereoDelayBuf 	= gStereoDelayBuf;
    register SLONG  lgSDWritePos		= gSDWritePos;
    register SLONG  lgSDReadPos			= gSDReadPos;
    register SLONG	lgStereoDelay		= gStereoDelay;
    register SLONG	lbitshift			= bitshift;
    
    for (;count--;)
    {   x = *srce++>>lbitshift;
        x = (x > 127) ? 127 : (x < -128) ? -128 : x;
                
        if(count & 1)
        {   lgStereoDelayBuf[lgSDWritePos] = x;
            lgSDWritePos = (lgSDWritePos + 1) & lgStereoDelay;

            *dste++     = lgStereoDelayBuf[lgSDReadPos]+128;
            lgSDReadPos  = (lgSDReadPos + 1) & lgStereoDelay;                                   
        } else
            *dste++  = x+128;
    }

    gSDWritePos		= lgSDWritePos;
    gSDReadPos		= lgSDReadPos;
}

static void Mix32To16_Reverb_Delay(
        register SWORD *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
    register SLONG *lgReverbBuf			= gReverbBuf;
	register SLONG  lgRVWritePos		= gRVWritePos;
	register SLONG  lgRVReadPos			= gRVReadPos;
	register SLONG  lgStereoDelay		= gStereoDelay;
    register SLONG *lgStereoDelayBuf 	= gStereoDelayBuf;
    register SLONG  lgSDWritePos		= gSDWritePos;
    register SLONG  lgSDReadPos			= gSDReadPos;
    register SLONG	lgReverb			= gReverb;
    register SLONG	lbitshift			= bitshift;
    
    for (;count--;)
    {   x = *srce++>>lbitshift;
        
        lgReverbBuf[lgRVWritePos] = x>>1;
        lgRVWritePos = (lgRVWritePos + 1) & lgReverb;

        x = (x-(x>>3)) + lgReverbBuf[lgRVReadPos];
        lgRVReadPos = (lgRVReadPos + 1) & lgReverb;

        x = (x > 32767) ? 32767 : (x < -32768) ? -32768 : x;

        if(count & 1)
        {   lgStereoDelayBuf[lgSDWritePos] = x;
            lgSDWritePos = (lgSDWritePos + 1) & lgStereoDelay;

            *dste++     = lgStereoDelayBuf[lgSDReadPos];
            lgSDReadPos  = (lgSDReadPos + 1) & lgStereoDelay;
        } else
            *dste++  = x;
    }

	gRVWritePos		= lgRVWritePos;
	gRVReadPos		= lgRVReadPos;
    gSDWritePos		= lgSDWritePos;
    gSDReadPos		= lgSDReadPos;
}

static void Mix32To8_Reverb_Delay(
        register SBYTE *dste,
        register SLONG *srce,
        register SLONG count)
{
    register SLONG x;
	register SLONG *lgReverbBuf			= gReverbBuf;
	register SLONG  lgRVWritePos		= gRVWritePos;
	register SLONG  lgRVReadPos			= gRVReadPos;
	register SLONG  lgReverb			= gReverb;
    register SLONG *lgStereoDelayBuf 	= gStereoDelayBuf;
    register SLONG  lgSDWritePos		= gSDWritePos;
    register SLONG  lgSDReadPos			= gSDReadPos;
    register SLONG	lgStereoDelay		= gStereoDelay;
    register SLONG	lbitshift			= bitshift;
    
    for (;count--;)
    {   x = *srce++>>lbitshift;

        lgReverbBuf[lgRVWritePos] = x>>1;
        lgRVWritePos = (lgRVWritePos + 1) & lgReverb;

        x = (x-(x>>3)) + lgReverbBuf[lgRVReadPos];
        lgRVReadPos = (lgRVReadPos + 1) & lgReverb;

        x = (x > 127) ? 127 : (x < -128) ? -128 : x;
                
        if(count & 1)
        {   lgStereoDelayBuf[lgSDWritePos] = x;
            lgSDWritePos = (lgSDWritePos + 1) & lgStereoDelay;

            *dste++     = lgStereoDelayBuf[lgSDReadPos]+128;
            lgSDReadPos  = (lgSDReadPos + 1) & lgStereoDelay;                                   
        } else
            *dste++  = x+128;
    }

	gRVWritePos		= lgRVWritePos;
	gRVReadPos		= lgRVReadPos;
    gSDWritePos		= lgSDWritePos;
    gSDReadPos		= lgSDReadPos;
}

#endif   // not __ASSEMBLY__

static UWORD samples2bytes(UWORD samples)
{
    if(md_mode & DMODE_16BITS) samples<<=1;
    if(md_mode & DMODE_STEREO) samples<<=1;
    return samples;
}


static UWORD bytes2samples(UWORD bytes)
{
    if(md_mode & DMODE_16BITS) bytes>>=1;
    if(md_mode & DMODE_STEREO) bytes>>=1;
    return bytes;
}


/**************************************************
***************************************************
***************************************************
**************************************************/


void VC_SampleUnload(SWORD handle)
{
    void *sampleadr=Samples[handle];

    free(sampleadr);
    Samples[handle]=NULL;
}


/**************************************************
***************************************************
***************************************************
**************************************************/


static void AddChannel(SLONG *ptr, SLONG todo)
{
    register SLONG end;
    register SLONG done;
#ifdef __MIX8BITS__
    register SBYTE *s;
#else
    register SWORD *s;
#endif

    while(todo>0)
    {   // update the 'current' index so the sample loops, or
        // stops playing if it reached the end of the sample

        if(vnf->flags&SF_REVERSE)
        {
            // The sample is playing in reverse

            if(vnf->flags&SF_LOOP && vnf->current<idxlpos)
            {
                // the sample is looping, and it has
                // reached the loopstart index

                if(vnf->flags&SF_BIDI)
                {
                    // sample is doing bidirectional loops, so 'bounce'
                    // the current index against the idxlpos

                    vnf->current=idxlpos+(idxlpos-vnf->current);
                    vnf->flags&=~SF_REVERSE;
                    vnf->increment=-vnf->increment;
                } else
                    // normal backwards looping, so set the
                    // current position to loopend index

                   vnf->current=idxlend-(idxlpos-vnf->current);
            } else
            {
                // the sample is not looping, so check
                // if it reached index 0

                if(vnf->current<0)
                {
                    // playing index reached 0, so stop
                    // playing this sample

                    vnf->current = 0;
                    vnf->active  = 0;
                    break;
                }
            }
        } else
        {
            // The sample is playing forward

            if(vnf->flags&SF_LOOP && vnf->current>idxlend)
            {
                 // the sample is looping, so check if
                 // it reached the loopend index

                 if(vnf->flags&SF_BIDI)
                 {
                     // sample is doing bidirectional loops, so 'bounce'
                     //  the current index against the idxlend

                     vnf->flags   |=SF_REVERSE;
                     vnf->increment=-vnf->increment;
                     vnf->current  =idxlend-(vnf->current-idxlend);
                 } else
                     // normal backwards looping, so set the
                     // current position to loopend index

                     vnf->current = idxlpos+(vnf->current-idxlend);
            } else
            {
                // sample is not looping, so check
                // if it reached the last position

                if(vnf->current>idxsize)
                {
                    // yes, so stop playing this sample

                    vnf->current = 0;
                    vnf->active  = 0;
                    break;
                }
            }
        }

        if(!(s=Samples[vnf->handle]))
        {   vnf->current = 0;
            vnf->active  = 0;
            break;
        }

        end = (vnf->flags & SF_REVERSE) ? 
                (vnf->flags & SF_LOOP) ? idxlpos : 0 :
                (vnf->flags & SF_LOOP) ? idxlend : idxsize;

        done = MIN((end - vnf->current) / vnf->increment + 1, todo);

        if(!done)
        {   vnf->active = 0;
            break;
        }


        if(vnf->vol)
        {
#ifdef __ASSEMBLY__
            if(md_mode & DMODE_STEREO)
                AsmStereoNormal(s,ptr,vnf->current,vnf->increment,done);
            else
                AsmMonoNormal(s,ptr,vnf->current,vnf->increment,done);
            vnf->current += (vnf->increment*done);
#else
            if(md_mode & DMODE_STEREO)
                vnf->current = MixStereoNormal(s,ptr,vnf->current,vnf->increment,done);
            else
                vnf->current = MixMonoNormal(s,ptr,vnf->current,vnf->increment,done);
#endif
        }
        todo -= done;
        ptr  += (md_mode & DMODE_STEREO) ? (done<<1) : done;
    }
}


void VC_WriteSamples(SBYTE *buf, UWORD todo)
{
    register SLONG  left, portion = 0, count;
    register SBYTE  *buffer, *samplebuf;
    register UBYTE  t;
    register SLONG  pan, vol;
    register UWORD  sampletodo;

    samplebuf  = buf;
    sampletodo = todo;

    while (todo)
    {   if(TICKLEFT==0)
        {   md_player();
            TICKLEFT = (md_mixfreq*125L)/(md_bpm*50L);
        }

        left = MIN(TICKLEFT, todo);
        
        buffer = buf;
        TICKLEFT-=left;
        todo-=left;

        buf += samples2bytes(left);

        while (left)
        {   portion = MIN(left, samplesthatfit);
            count   = (md_mode & DMODE_STEREO) ? (portion<<1) : portion;
            
            memset(VC_TICKBUF, 0, count<<2);
            
            for(t=0; t<md_numchn; t++)
            {   vnf = &vinf[t];

                if(vnf->kick)
                {   vnf->current = vnf->start<<FRACBITS;
                    vnf->kick    = 0;
                    vnf->active  = 1;
                }
                
                if (vnf->frq == 0) vnf->active = 0;
                
                if (vnf->active)
                {   vnf->increment = (vnf->frq<<FRACBITS)/md_mixfreq;  //fraction2long(vnf->frq,md_mixfreq);
                    if(vnf->flags & SF_REVERSE) vnf->increment=-vnf->increment;

                    vol = vnf->vol;  pan = vnf->pan;

                    if (md_mode & DMODE_STEREO)
                    {
                        #ifdef __MIX8BITS__
                        lvoltab = voltab[( vol * ((pan<128) ? 128 : (255-pan))) / 512];
                        rvoltab = voltab[( vol * ((pan>128) ? 128 : (pan))) / 512];
                        #else
                        lvolsel = (vol * pan) >> 8;
                        rvolsel = (vol * (255-pan)) >> 8;
                        #endif
                    } else
                    {
                        #ifdef __MIX8BITS__
                        lvoltab = voltab[vol];
                        #else
                        lvolsel = vol;
                        #endif
                    }
                    idxsize = (vnf->size)   ? (vnf->size<<FRACBITS)-1 : 0;
                    idxlend = (vnf->repend) ? (vnf->repend<<FRACBITS)-1 : 0;                    
                    idxlpos = vnf->reppos<<FRACBITS;
                    AddChannel(VC_TICKBUF, portion);
                }
            }

            #ifdef __ASSEMBLY__
            if(md_mode & DMODE_16BITS)
                AsmMix32To16Surround((SWORD *) buffer, VC_TICKBUF, count);
            else
                AsmMix32To8Surround((SBYTE *) buffer, VC_TICKBUF, count);
            #else
            if(md_mode & DMODE_16BITS)
                Mix32to16((SWORD *) buffer, VC_TICKBUF, count);
            else
                Mix32to8((SBYTE *) buffer, VC_TICKBUF, count);
            #endif

            buffer += samples2bytes(portion);
            left   -= portion;
        }
    }
}


UWORD VC_WriteBytes(SBYTE *buf,UWORD todo)
//  Writes 'todo' mixed SBYTES (!!) to 'buf'. It returns the number of
//  SBYTES actually written to 'buf' (which is rounded to number of samples
//  that fit into 'todo' bytes).
{
    todo = bytes2samples(todo);
    VC_WriteSamples(buf,todo);
    return samples2bytes(todo);
}


void VC_SilenceBytes(SBYTE *buf,UWORD todo)
//  Fill the buffer with 'todo' bytes of silence (it depends on the mixing
//  mode how the buffer is filled)
{
    // clear the buffer to zero (16 bits
    // signed ) or 0x80 (8 bits unsigned)

    if(md_mode & DMODE_16BITS)
        memset(buf,0,todo);
    else
        memset(buf,0x80,todo);
}


static UBYTE log2(ULONG x)
{
    UBYTE result = 0;
    while (x>>=1) result++;

    return result;
}


void VC_PlayStart(void)
{
    int   t;
#ifdef __MIX8BITS__
    int  c;
    SLONG  maxvol, volmul;

    maxvol = 16777216L / md_numchn;

    for(t=0; t<65; t++)
    {   volmul = (maxvol*t)/64;
        for(c=-128; c<128; c++)
            voltab[t][(UBYTE)c] = (SLONG)c*volmul;
    }

    bitshift = 17-log2(md_numchn);
#else
    bitshift = (log2(md_numchn)>>3)+9;
#endif

    if (!(md_mode & DMODE_16BITS))
        bitshift+=8;

    gSDReadPos  = 0;
    gSDWritePos = gStereoDelay>>1;
    for(t=0; t<DELAYSIZE; t++) 
        gStereoDelayBuf[t] = 0;

    gRVReadPos  = 0;
    gRVWritePos = gStereoDelay>>1;
    for(t=0; t<REVERBSIZE; t++) 
        gStereoDelayBuf[t] = 0;

    samplesthatfit=TICKLSIZE;
    if(md_mode & DMODE_STEREO) samplesthatfit>>=1;
    TICKLEFT = 0;
}


void VC_PlayStop(void)
{
}

BOOL VC_Init(void)
{

#ifdef __MIX8BITS__
    int t;
    if((voltab = (SLONG **)_mm_calloc(65,sizeof(SLONG *))) == NULL) return 1;
    for(t=0; t<65; t++)
       if((voltab[t] = (SLONG *)_mm_calloc(256,sizeof(SLONG))) == NULL) return 1;
#endif
    if(md_mode & DMODE_INTERP) md_mode&=~DMODE_INTERP;

#ifndef __BEOS__
    DELAYSIZE  = 1<<(md_stereodelay+1);
    REVERBSIZE = 1<<(md_reverb+1);

    if(!(md_mode & DMODE_STEREO))
    {   DELAYSIZE >>= 1;
        REVERBSIZE >>= 1;
    }

    if(DELAYSIZE > 1)
    {   gStereoDelay    = DELAYSIZE-1;
        gStereoDelayBuf = (SLONG *) _mm_calloc(sizeof(SLONG), DELAYSIZE);
    } else DELAYSIZE = 0;

    if(REVERBSIZE > 1)
    {   gStereoDelay    = REVERBSIZE-1;
        gStereoDelayBuf = (SLONG *) _mm_calloc(sizeof(SLONG), REVERBSIZE);
    } else REVERBSIZE = 0;


#ifndef __ASSEMBLY__
    Mix32to16 = Mix32To16_Normal;
    Mix32to8  = Mix32To8_Normal;

    if(REVERBSIZE)
    {   if(DELAYSIZE)
        {   Mix32to16 = Mix32To16_Reverb_Delay;
            Mix32to8 = Mix32To8_Reverb_Delay;
        } else
        {   Mix32to16 = Mix32To16_Reverb;
            Mix32to8 = Mix32To8_Reverb;
        }
    } else if(DELAYSIZE)
    {   Mix32to16 = Mix32To16_Delay;
        Mix32to8 = Mix32To8_Delay;
    }
    
    Mix32to16 = Mix32To16_Reverb_Delay;
    Mix32to8  = Mix32To8_Reverb_Delay;
#endif
#else // __BEOS__
    Mix32to16 = Mix32To16_Normal;
    Mix32to8  = Mix32To8_Normal;
#endif // __BEOS__
    return 0;
}

void VC_Exit(void)
{
#ifdef __MIX8BITS__
    int t;
    if(voltab!=NULL)
    {   for(t=0; t<65; t++) if(voltab[t]!=NULL) free(voltab[t]);
        free(voltab); voltab = NULL;
    }
#endif

    if(vinf!=NULL) free(vinf);
    vinf = NULL;

    if(gStereoDelay!=NULL) free(gStereoDelayBuf);
    if(gStereoDelayBuf!=NULL)   free(gStereoDelayBuf);

    gStereoDelay = NULL;
    gReverbBuf   = NULL;
}


BOOL VC_SetNumChannels(void)
{
    int t;

    if(vinf!=NULL) free(vinf);
    if((vinf = (VINFO *)_mm_calloc(sizeof(VINFO),md_numchn)) == NULL) return 1;
    
    for(t=0; t<md_numchn; t++)
    {   vinf[t].frq = 10000;
        vinf[t].pan = (t&1) ? 0 : 255;
    }
    return 0;
}

void VC_VoiceSetVolume(UBYTE voice, UWORD vol)
{
    vinf[voice].vol = vol;
}


void VC_VoiceSetFrequency(UBYTE voice, ULONG frq)
{
    vinf[voice].frq = frq;
}


void VC_VoiceSetPanning(UBYTE voice, ULONG pan)
{
    if(pan==PAN_SURROUND) pan = 128;
    vinf[voice].pan = pan;
}


void VC_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags)
{
    if(start>=size) return;

    if(flags&SF_LOOP)
        if(repend>size) repend = size;    // repend can't be bigger than size

    vinf[voice].flags    = flags;
    vinf[voice].handle   = handle;
    vinf[voice].start    = start;
    vinf[voice].size     = size;
    vinf[voice].reppos   = reppos;
    vinf[voice].repend   = repend;
    vinf[voice].kick     = 1;
}


void VC_VoiceStop(UBYTE voice)
{
    vinf[voice].active = 0;
}  


BOOL VC_VoiceStopped(UBYTE voice)
{
    return(vinf[voice].active==0);
}


void VC_VoiceReleaseSustain(UBYTE voice)
{

}


SLONG VC_VoiceGetPosition(UBYTE voice)
{
    return(vinf[voice].current>>11);
}


SWORD VC_SampleLoad(SAMPLOAD *sload, FILE *fp)
{
    SAMPLE *s = sload->sample;
    int    handle;
    ULONG  t, length,loopstart,loopend;

    // Find empty slot to put sample address in
    for(handle=0; handle<MAXSAMPLEHANDLES; handle++)
        if(Samples[handle]==NULL) break;

    if(handle==MAXSAMPLEHANDLES)
    {   _mm_error = MDERR_OUT_OF_HANDLES;
        return -1;
    }

    length    = s->length;
    loopstart = s->loopstart;
    loopend   = s->loopend;

    SL_SampleSigned(sload);

#ifdef __MIX8BITS__
    SL_Sample16to8(sload);
    if((Samples[handle]=(SBYTE *)_mm_malloc(length+16))==NULL)
    {   _mm_error = MDERR_SAMPLE_TOO_BIG;
        return -1;
    }
    // read sample into buffer.
    SL_Load(Samples[handle],sload,length);
#else
    SL_Sample8to16(sload);
    if((Samples[handle]=(SWORD *)_mm_malloc((length+16)*2))==NULL)
    {   _mm_error = MDERR_SAMPLE_TOO_BIG;
        return -1;
    }
    // read sample into buffer.
    SL_Load(Samples[handle],sload,length<<1);
#endif


    // Unclick samples:

    for(t=0; t<16; t++)
        Samples[handle][t] = (Samples[handle][t]*t)/16;

    if(s->flags & SF_LOOP)
    {   if(s->flags & SF_BIDI)
            for(t=0; t<16; t++) Samples[handle][loopend+t] = Samples[handle][(loopend-t)-1];
        else
            for(t=0; t<16; t++) Samples[handle][loopend+t] = Samples[handle][t+loopstart];
    } else
        for(t=0; t<16; t++) Samples[handle][t+length] = 0;

    return handle;
}


ULONG VC_SampleSpace(int type)
{
    return vc_memory;
}


ULONG VC_SampleLength(int type, SAMPLE *s)
{
#ifdef __MIX8BITS__
    return s->length + 16;
#else
    return (s->length * ((s->flags&SF_16BITS) ? 2 : 1)) + 16;
#endif
}


ULONG VC_RealVolume(UBYTE voice)
{
    ULONG i,s,size;
    int k,j;
    SWORD *smp16;
    SBYTE *smp8;
    SLONG t;
                    
    t = vinf[voice].current>>FRACBITS;
    if(vinf[voice].active==0) return 0;

    s    = vinf[voice].handle;
    size = vinf[voice].size;

    i=128; t-=64; k=0; j=0;
    if(i>size) i=size;
    if(t<0) t=0; else if(t>size) t = size;

    i &= ~1;  // make sure it's EVEN.

    if(vinf[voice].flags & SF_16BITS)
    {   smp16 = (SWORD *)Samples[s];
        smp16 = &smp16[t];
        for(; i; i-=2, smp16+=2)
        {   if(k<*smp16) k = *smp16;
            if(j>*smp16) j = *smp16;
        }
        k = abs(k-j)>>16;
    } else
    { 
        smp8 = (SBYTE *)(Samples[s]);
        smp8 = &smp8[t];
        for(; i; i-=2, smp8+=2)
        {   if(k<*smp8) k = *smp8;
            if(j>*smp8) j = *smp8;
        }
        k = abs(k-j);
    }
    return k;
}


