/*
     _____________________________________________________________
    /                                                             \ 
    | Source:       SOUND CLASS
    \_____________________________________________________________/

    Description:    digital sound playing class

    Code for:       AMIGA

    Refers to:      -

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 940202 0.90000 JTH created & working on Commodore AMIGA
 940205 0.91        overlayed constructors removed.
                    Never call one constructor out of another one!
 940205             IFF sound file loading support
                    playstereo(), intensive parent->child communication
 940219 0.92        fixes: playstereo(): delete others by hand if DiePastPlay
                    stop(): players.remove(this) etc. only if playstate SET!
                    sys_serv[] table changed to scalar (1 ptr per instance)
                    wherever stop() called: if(DiePastPlay) delete ... !
 940317 0.93        Disable() / Enable() protection for all players-list
                    reading & manipulating code
 940319 1.28        SEVERE BUG FIX: play(): on child sound creation,
                    parent FORGOT to enter itself in 'child->parent'!
 940320             complete re-work of Sound class. All interrupt
                    handling now done by real C-functions dedicated to
                    serve only the hardware sound channels.
                    IFFspeed() now gives IFF file speed (if any).
 941023             play() extended by 'options' parameter.
 941201             ~Sound/WIN16/unloaded freeing bug fixed.
 941209             see .h, ~Sound<->stop() bug fix.
 941222             Sound::load: raw file default speed now 32000 with WIN16.
 950311             Sound::Resample: blow and shrink
 950916             Sound::Resample: now, all mappings between 11,22 and 32 khz
                    source and target are supported. targdropevery parm added.
*/

#   include "expdef.h"

#ifdef  WIN16
#   include <windows.h>
#   include <mmsystem.h>
#endif

#   include "explib.h"
#   include "list.h"
#   include "misc.h"
#   include "sound.h"

#ifdef  AMIGA

#   include <exec/types.h>
#   include <exec/memory.h>
#   include <exec/interrupts.h>

#endif

//  ModInit(sound_o);

#define local   static

#ifdef  AMIGA

//  ---------------------------------------------------------
//  --- 'C' Sound services for AMIGA interrupt management ---
//  ---------------------------------------------------------

volatile ulong *const AUD0LCH = (ulong*)0xdff0a0;
volatile uword *const AUD0LEN = (uword*)0xdff0a4;
volatile uword *const AUD0PER = (uword*)0xdff0a6;
volatile uword *const AUD0VOL = (uword*)0xdff0a8;
volatile uword *const AUD0DAT = (uword*)0xdff0aa;

volatile ulong *const AUD1LCH = (ulong*)0xdff0b0;
volatile ulong *const AUD2LCH = (ulong*)0xdff0c0;
volatile ulong *const AUD3LCH = (ulong*)0xdff0d0;

volatile uword *const DMACON  = (uword*)0xdff096;

volatile uword *const INTREQ  = (uword*)0xdff09c;
volatile uword *const INTREQR = (uword*)0xdff01e;
volatile uword *const INTENA  = (uword*)0xdff09a;
volatile uword *const INTENAR = (uword*)0xdff01c;

struct  Channel {

    DCL_DESTR;  // compiler bug fix

    ushort  number;     // this channel's number (0..3)

    static  ushort numcnt;  // used only in constructor

    int     playcnt;    // how often currently played
    int     playmax;    // how often should we play it
    // 0 : play until C_stop() is called

    bool    playstate;  // still playing?

    ulong   seqno;      // playbegin sequence counter
    // 0 == invalid counter entry! counting starts at 1.

    static  ulong seqcnt;   // seqno = ++seqcnt

    // when redirecting an interrupt, remember system's server here:
    Interrupt*  old_serv;

    Interrupt   IRQctl; // IRQ control struc for this channel

    Sound*  src;        // this started this channel's playing

 public:

    Channel();  // this installs interrupt server
    ~Channel(); // this removes  interrupt server

    };

ushort  Channel::numcnt = 0;
ulong   Channel::seqcnt = 1;

local struct Channel chtab[4];  // four sound channels.

local void ChanStop(ushort chan);

//|| C_IRQserv 

local void C_IRQserv(ushort num)
{
 Channel* ch = &chtab[num];

    // reset acknowledge flag
    *INTREQ = 1<<(7+num);

    _p(num, ch->number, ch->playcnt, ch->playmax);

    ch->playcnt++;

    if(!ch->playmax)    return;

    if( ch->playcnt < ch->playmax)  return;

    ChanStop(num);  // counter reached playmax
}

local void C_IRQentry0()    {   C_IRQserv(0);   }
local void C_IRQentry1()    {   C_IRQserv(1);   }
local void C_IRQentry2()    {   C_IRQserv(2);   }
local void C_IRQentry3()    {   C_IRQserv(3);   }

Channel::Channel()
{_
 void (*c_stub)();

    number  = numcnt++; // auto-setup channel number on construction
    // it is expected that constructors of chtab[0] to [3] are called
    // in ascending sequence!

    // SETUP INTERRUPT SERVER:

    memset(&IRQctl, 0, sizeof(IRQctl));

    // determine C function stub to pre-serve irq:

    switch(number)
    {
        case 0 : c_stub = C_IRQentry0; break;
        case 1 : c_stub = C_IRQentry1; break;
        case 2 : c_stub = C_IRQentry2; break;
        case 3 : c_stub = C_IRQentry3; break;

        default : SysErr(202941055); return;
    }

    IRQctl.is_Node.ln_Type  = NT_INTERRUPT;
    IRQctl.is_Node.ln_Pri   = 4;
    IRQctl.is_Node.ln_Name  = "audio interrupt";
    IRQctl.is_Data  = c_stub;
    IRQctl.is_Code  = c_stub;

    old_serv = SetIntVector(

        7 + number, // 'audio data given out' IRQ (->Intern I / 432)

        &IRQctl

        );

    playstate   = 0;
    seqno       = 0;    // 0 == invalid seqno entry
    src         = 0;
}

Channel::~Channel()
{_
    CHK_DESTR;  // compiler bug fix

    if(playstate)   // if currently playing something

        ChanStop(number);   // stop it immediately

    // REMOVE INTERRUPT SERVER:

    if(IRQctl.is_Code)  // if successfully constructed

        SetIntVector(7 + number, old_serv);
}

//  the following should be called only
//  in DISABLED INTERRUPT state!

//|| ChanPlay  

local void ChanPlay(
    ushort chan, Sound* dat, int howoften, int speed, int volume
    )
{
 Channel *ch = &chtab[chan];

    _p(chan, (ulong)ch, (ulong)dat, howoften);

    ch->playcnt     = 0;
    ch->playmax     = howoften ? (howoften+1) : 0;
    ch->playstate   = 1;

    ch->seqno       = ch->seqcnt++;
    ch->src         = dat;  // remember who started us

    // START INTERRUPT PLAYING:

 int    woffs = chan * 0x8; // word pointer offset
 int    loffs = chan * 0x4; // long pointer offset
 uword  speriod;

    // 'speed' determines number of sample bytes per second.
    // calc sample_period value from it:

    speriod = (uword)(10000000.0 / (speed * 2.79365));

    *DMACON = 1<<chan;  // the case DMA still plays, stop it

    // start interrupt:

    //Disable();

    // unmask audio output end interrupt:
    *INTENA = (1<<15)|(1<<(7+chan));

    // reset 'irq occured' flag to avoid immediate irq:
    *INTREQ = 1<<(7+chan);

    // setup playback parms:

    *(AUD0LCH+loffs) = (ulong)dat->sdata;
    *(AUD0LEN+woffs) = dat->ssize / 2;  // length in WORDS!
    *(AUD0PER+woffs) = speriod;
    *(AUD0VOL+woffs) = volume;

    //Enable();

    // start audio DMA: this will start playing the sound!
    *DMACON = (1<<chan) + 0x8200;
    // NOTE: if there was a sound playing on this channel,
    //       and the time that DMA was switched off was very short
    //       so that DMA didn't try to read another word,
    //       then on reactivation of DMA the OLD sound continues to play
    //       it's remaining sample words before playing new sound.
}

//  the following should be called only
//  in DISABLED INTERRUPT state!

//|| ChanStop 

local void ChanStop(ushort chan)
{
 Channel *ch = &chtab[chan];

    _p(chan, ch->number, ch->playstate, (ulong)ch->src);

    if(!ch->playstate)  return; // nothing to stop

    ch->playstate   = 0;    // clear flag
    ch->src         = 0;    // forget who started us

    // STOP INTERRUPT PLAYING:

 int    woffs = chan * 0x8; // word pointer offset
 int    loffs = chan * 0x4; // long pointer offset

    //Disable();

    // stop audio DMA:

    *DMACON = 1<<chan;

    // mask audio output end interrupt

    *INTENA = 1<<(7+chan);

    // if DMA is re-started too soon so that it hasn't read another
    // word meanwhile, it will continue playing old sample!
    // so try to enforce channel's total DMA termination by this:
    //*(AUD0DAT+woffs) = 0; // write two dummy samples (==1 word) to channel
    // ^doesn't work
    // NOTE that if not Disable()'d, this would cause an audio interrupt.

    // clear playback parms:

    *(AUD0LCH+loffs) = 0;
    *(AUD0LEN+woffs) = 0;
    *(AUD0PER+woffs) = 0;
    *(AUD0VOL+woffs) = 0;

    //Enable();
}

#endif  // AMIGA

ulong Sound::instcnt = 0;   // Sound instance counter

//|| Snd::Snd  

Sound::Sound(char* xname, void* adr, size_t size)
{_
    instcnt++;

    // set default values:
    ssize       = 0;
    sdata       = 0;
    sdata_static= 0;        // dynamic

    IFF_speed   = 0;        // no IFF speed yet available

    if(xname)
        strncpy(name, xname, MAX_NAME); // might be 0 (default)
    else
        name[0] = '\0';

    if(size)    // taking existing RAM sound data
    {
        ssize   = size;
        sdata   = (uchar*)adr;
        sdata_static = 1;   // of course, do NOT free sdata in ~Sound !
    }
    else
     if(name[0]) load(name);    // loading sound data from file
}

bool Sound::valid()
{_
    if(sdata)   return 1;

    return  0;
}

void ul_swap(ulong& x)
{
    //  swap byte order:

    ulong ux = x;

    x   =       ((ux>>24)&0xFFUL)
            |   (((ux>>16)&0xFFUL)<<8 )
            |   (((ux>> 8)&0xFFUL)<<16)
            |   (((ux    )&0xFFUL)<<24) ;
}

bool ul_match(ulong x, char* str)
{_
 uchar* cp1 = (uchar*)&x;
 uchar* cp2 = (uchar*)str;

#ifdef  WIN16
    ul_swap(x);
#endif

//  dmsg("ul_match %c%c%c%c %s",
//      *cp1,*(cp1+1),*(cp1+2),*(cp1+3),str);

    if(     *cp1++ == *cp2++
        &&  *cp1++ == *cp2++
        &&  *cp1++ == *cp2++
        &&  *cp1   == *cp2  )

        return 1;
    else
        return 0;
}

size_t round_chunk_size(size_t csize)
{_
    if(csize & 0x1) csize++;

    return csize;
}

uchar* read_chunk(FILE *f, size_t &csize)
{_
 ulong ul[2]={0,0};
 ulong ux;
 uchar* chunk;

    fread(ul, 0x4, 0x1, f);     // read chunk's size
#ifdef  WIN16
    ul_swap(ul[0]);
#endif

    csize = round_chunk_size(ul[0]);

//  dmsg("reading chunk sized %lu", csize);

    ifn(chunk = new uchar[csize+4])
    {   MemErr(502942129); return 0;    }

    fread(chunk, 0x1, csize, f);    // read chunk data

    return chunk;
}

rcode Sound::load(char* xname)
{_
 FILE   *f;
 ulong  ul[3]={0,0};
 bool   is_iff=0;

    strncpy(name, xname, MAX_NAME);

#ifdef  WIN16
    //  map Amiga filename to WIN16 filename:

    if(name[0])
    {
     bool point=0;

        for(short l=0; l<MAX_NAME-6; l++)
        {
            ifn(name[l])
            {
                if(point)   break;

                //  no ending -> .iff

                strncpy(&name[l], ".iff", 5);

                break;
            }

            if(name[l] == '/')  name[l] = '\\';

            if(name[l] == '.')  point   = 1;
        }

        dmsg("sound %s", name); // ***
    }
#endif

    // first check if it's an IFF sound file.

    ifn(f=fopen(name,"rb"))
    {
#ifdef  WIN16
        // no file with that name. maybe we should map
        // the filetype?

        char* typstr = strstr(name, ".iff");

        if(typstr)
        {
            strcpy(typstr, ".pcm"); // map .iff -> .pcm

            dmsg("trying to open %s", name);    // *** 

            ifn(f=fopen(name,"rb")) return FILEOPEN;
        }
        else
            return FILEOPEN;
#else
        return FILEOPEN;
#endif
    }

    fread(ul, 0x4, 0x2, f); // read two long words
#ifdef  WIN16
    ul_swap(ul[0]);
    ul_swap(ul[1]);
#endif

    if(ul_match(ul[0], "FORM"))
    {
     uchar* chunk;  // a data chunk
     size_t csize;  // chunksize
     uword* uwp;

        is_iff  = 1;

        // IFF Sound File.

        /*
            FORM (FileSize-8)
            8SVX VHDR (HDR chunk size (without '8SVXVHDR' txt))
            NAME (NameSize)
            ANNO (AnnoSize)
            BODY (BodySize)
            [Raw Sound Data] ... 'til EOF.
        */

        // Get 8SVX chunk:

        fread(ul, 0x4, 0x2, f);
#ifdef  WIN16
        ul_swap(ul[0]);
        ul_swap(ul[1]);
#endif
        ifn(ul_match(ul[0], "8SVX") && ul_match(ul[1], "VHDR"))
            {fclose(f); return INDATA;}

        ifn(chunk = read_chunk(f, csize))   {fclose(f); return MEMORY;}

        // 6th word is the sample rate:

        uwp         = (uword*)chunk;
        IFF_speed   = *(uwp + 6);   // file's sample rate
#ifdef  WIN16
        IFF_speed   = (IFF_speed>>8)|(IFF_speed<<8);    // swap bytes
        winfo.speed = IFF_speed;    // copy into wave info
#endif
        delete [] chunk;

        // Skip all following chunks which aren't BODY:

        while(!feof(f))
        {
            fread(ul, 0x4, 0x2, f);
#ifdef  WIN16
            ul_swap(ul[0]);
            ul_swap(ul[1]);
#endif
            ifn(ul_match(ul[0], "BODY"))
            {
                // skip chunk data

                // dmsg("chunk %4.4s size %ld", &ul[0], ul[1]);

                if((long)ul[1] < 0)
                {
                    perr("%s : illegal chunksize, load aborted",name);
                    return  FAILED;
                }

                fseek(f, round_chunk_size(ul[1]), SEEK_CUR);
            }
            else
                break;
        }

        ifn(ul_match(ul[0], "BODY"))    {fclose(f); return NOTAVAIL;}

        ssize   = ul[1];    // get body size (don't round)

        // dmsg("body size %lu", ssize);

        // Body read follows below.
    }
    else
    {
        // Load RAW data file:

        fclose(f);

        ifn(ssize = filesize(name)) return FILEOPEN;

        ifn(f=fopen(name,"rb")) return FILEOPEN;

#ifdef  WIN16
        IFF_speed   = 32000;        // PCM raw file default speed
        winfo.speed = IFF_speed;    // copy into wave info
#endif
    }

    //  Load RAW data, either whole file or from BODY chunk:

#ifdef  AMIGA
    //  alloc CHIP memory for sound data

    sdata = (uchar*)AllocMem(ssize+2, MEMF_CHIP);
#endif

#ifdef  WIN16
    //  WIN16: allocate a 'memory handle'
    ifn(hdata = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, ssize+2))
    {   MemErr(102941725); return MEMORY;   }

    //  WIN16: create adress from 'memory handle'
    sdata = GlobalLock(hdata);
#endif

    if(!sdata)  {   MemErr(1609941030); return MEMORY;  }

    //  load sound data

#ifdef  WIN16
    farfread(sdata, 0x1, ssize, f);
#else
       fread(sdata, 0x1, ssize, f);
#endif

    fclose(f);

#ifdef  WIN16
    //  if it's Amiga IFF, convert 8 bit signed data to 8 bit unsigned

    if(is_iff)
    {
        uchar HUGE *bp = sdata + ssize - 1;

        while(bp >= sdata)  *bp-- = (uchar)((char)*bp + 128);
    }
#endif

#ifdef  WIN16
    winfo.ssize = ssize;
    winfo.sdata = sdata;        
#endif

    return OK;
}

class ByteRing
{
    enum    RingSize {SIZE=(1U<<5), IDXMASK=(SIZE-1)};
    uchar   ring[SIZE];
    ushort  iput;
    ushort  iget;
    ushort  nEntries;

 public:

    ByteRing()
        {   iput=0; iget=0; nEntries=0; }

    rcode   put(uchar toAdd)
        {
            if(nEntries >= SIZE)    SysErr(1103951134);
            ring[iput]  = toAdd;
            iput        = (iput+1) & IDXMASK;
            nEntries++;
            return      OK;
        }

    uchar   get(void)
        {
            uchar   val;
            if(!nEntries)   SysErr(1103951126);
            val         = ring[iget];
            iget        = (iget+1) & IDXMASK;
            nEntries--;
            return      val;
        }

    ushort  numEntries()
        {   return nEntries;    }
};

rcode Sound::ResampleTo(ushort khzto)
{_
 ushort kdst = khzto;
 ushort ksrc = winfo.speed;

    dmsg("ksrc %d kdst %d",ksrc,kdst);

    if(ksrc < 20000)    ksrc = 1;
    else
    if(ksrc >= 20000 && ksrc < 30000)   ksrc = 2;
    else
    if(ksrc >= 30000)   ksrc = 3;

    if(kdst < 20000)    kdst = 1;
    else
    if(kdst >= 20000 && kdst < 30000)   kdst = 2;
    else
    if(kdst >= 30000)   kdst = 3;

    dmsg("2nd, ksrc %d kdst %d",ksrc,kdst);

    ushort b=1,s=1;

    switch(ksrc)
    {
        case 1:
            switch(kdst)
            {
                case 1:  return OK;         // 11 -> 11
                case 2:  b=2; s=1; break;   // 11 -> 22
                case 3:  b=3; s=1; break;   // 11 -> 32
                default: return FAILED;
            }
            break;

        case 2:
            switch(kdst)
            {
                case 1:  b=1; s=2; break;   // 22 -> 11
                case 2:  return OK;         // 22 -> 22
                case 3:  b=3; s=2; break;   // 22 -> 32
                default: return FAILED;
            }
            break;

        case 3:
            switch(kdst)
            {
                case 1:  b=1; s=3; break;   // 32 -> 11
                case 2:  b=2; s=3; break;   // 32 -> 22
                case 3:  return OK;         // 32 -> 32
                default: return FAILED;
            }
            break;
    }

    if(b==1 && s==1)
        return FAILED;

    ushort targdropevery = 0;

    long   ktargint = (long)winfo.speed * (long)b / (long)s;
    //  e.g. 22050 * 3 / 2 = 33075

    if(ktargint > (long)khzto)  // e.g. 33075 > 32000 
    {
        //  calc decimal-places correction of target shrinking:
        long ktargdif = ktargint - (long)khzto;         // e.g. 33075-32000 = 1075
        targdropevery = (ushort)(ktargint / ktargdif);  // e.g. 33075/1075=30
    }

    dmsg("call Resample(%d,%d,%d)",b,s,targdropevery);

    rcode rc = Resample(b,s,targdropevery);

    if(abs((short)khzto - (short)winfo.speed) < 100)
    {
        //  very little difference to target speed:
        //  take exact target speed (e.g. 32000 i/o 31972),
        //  just to make sure the wavedriver is tested with exact 32kHz
        IFF_speed   = khzto;
        winfo.speed = khzto;
    }

    return rc;
}

rcode Sound::Resample(  ushort blow,            // 1 to 3
                        ushort shrink,          // 1 or more
                        ushort targdropevery    // shrink factor, decimal places
                        // \ e.g. drop every 1000th byte of target.
                        //   if zero, nothing's dropped.
                     )
{_
 // the newsize does NOT take targdropevery into account.
 ulong  nsize   = (ssize * (ulong)blow / (ulong)shrink);
#ifdef  WIN16
 HANDLE hndata=0;       // a memory handle for GlobalAlloc'ed sound data
#endif
 uchar  HUGE *ndata=0;  // sound data - if 0, sound instance is dead

    if(!sdata)      return  INDATA;
    if(!ssize)      return  INDATA;

    if(playing())   stop();

    //  alloc new memory area:

#   ifdef   AMIGA
    ndata   = (uchar*)AllocMem(nsize + 2, MEMF_CHIP);
#   endif

#   ifdef   WIN16
    ifn(hndata  = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, nsize + 2))
    {   MemErr(912941006); return MEMORY;   }

    ndata   = GlobalLock(hndata);
#   endif

    if(!ndata)  {   MemErr(912941007);  return MEMORY;  }

    //  re-sample sound:

    uchar HUGE *src = sdata;
    uchar HUGE *dst = ndata;

    ulong   i;
    ushort  k, samp, nxsamp;
    short   sisamp, nisamp, diff;

    long    dremain = (long)nsize;  // targ size w/o targdropevery subtracted
    ushort  trelcnt = 0;

    //  STANDARDS:
    //      - before writing one byte to target, dremain must be checked!
    //      - when writing one byte to target, dremain must be down-counted!
    //      - after conversion, remaining space in target is silenced,
    //        therefore dremain MUST contain the exact number of bytes remaining!

    if(blow == 2 && shrink == 3)
    {
        ByteRing    rBlow;

        for(i=0; i<ssize-1; i++)    // all except last sample
        {
            samp    = (ushort)(*src++);
            nxsamp  = (ushort)(*src);

            rBlow.put((uchar)samp);
            rBlow.put((uchar)((samp + nxsamp)>>1));

            if(rBlow.numEntries() >= 3)
            {
                samp  = (ushort)rBlow.get();
                samp += (ushort)rBlow.get();
                samp += (ushort)rBlow.get();

                if(targdropevery && (++trelcnt >= targdropevery))
                {
                    trelcnt = 0;
                    continue;       // 'drop' a target byte to adjust rate
                }

                if(dremain-- <= 0) { SysErr(1103951150); break; }
                *dst++  = samp / 3; // calc average, write
            }
        }
    }
    else
    if(blow == 3 && shrink == 2)
    {
        ByteRing    rBlow;

        for(i=0; i<ssize-1; i++)    // all except last sample
        {
            sisamp   = (short)((ushort)(*src++));
            nisamp   = (short)((ushort)(*src));
            diff     = nisamp - sisamp;
            diff    /= 3;

            rBlow.put((uchar)sisamp);
            rBlow.put((uchar)sisamp + diff);
            rBlow.put((uchar)sisamp + diff * 2);

            while(rBlow.numEntries() >= 2)
            {
                samp     = (ushort)rBlow.get();
                samp    += (ushort)rBlow.get();

                if(targdropevery && (++trelcnt >= targdropevery))
                {
                    trelcnt = 0;
                    continue;   // 'drop' a target byte to adjust rate
                }

                if(dremain-- <= 0) { SysErr(1609951109); break; }
                *dst++   = samp >> 1;   // calc average, shrink
            }
        }
    }
    else
    if(blow == 2 && shrink == 1)
    {
        for(i=0; i<ssize-1; i++)    // all except last sample
        {
            samp     = (ushort)(*src++);
            nxsamp   = (ushort)(*src);

            if(targdropevery && (++trelcnt >= targdropevery))
                trelcnt = 0;
            else
            {
                if(dremain-- <= 0) { SysErr(1609951301); break; }
                *dst++  = ((uchar)samp);
            }

            if(targdropevery && (++trelcnt >= targdropevery))
                trelcnt = 0;
            else
            {
                if(dremain-- <= 0) { SysErr(1609951302); break; }
                *dst++  = ((uchar)((samp + nxsamp)>>1));
            }
        }
    }
    else
    if(blow == 3 && shrink == 1)
    {
        for(i=0; i<ssize-1; i++)    // all except last sample
        {
            sisamp   = (short)((ushort)(*src++));
            nisamp   = (short)((ushort)(*src));
            diff     = nisamp - sisamp;
            diff    /= 3;

            if(targdropevery && (++trelcnt >= targdropevery))
                trelcnt = 0;
            else
            {
                if(dremain-- <= 0) { SysErr(1609951258); break; }
                *dst++   = ((uchar)sisamp);
            }

            if(targdropevery && (++trelcnt >= targdropevery))
                trelcnt = 0;
            else
            {
                if(dremain-- <= 0) { SysErr(1609951259); break; }
                *dst++   = ((uchar)sisamp + diff);
            }

            if(targdropevery && (++trelcnt >= targdropevery))
                trelcnt = 0;
            else
            {
                if(dremain-- <= 0) { SysErr(1609951300); break; }
                *dst++   = ((uchar)sisamp + diff * 2);
            }
        }
    }
    else    //  just shrink
    {
        for(i=0; i<nsize; i++)
        {
            samp  = 0;
            for(k = 0; k<shrink; k++)
                samp += (ushort)(*src++);

            if(targdropevery && (++trelcnt >= targdropevery))
                trelcnt = 0;
            else
            {
                if(dremain-- <= 0) { SysErr(1609951255); break; }
                *dst++  = samp / shrink;
            }
        }
    }

    while(dremain-- > 0)    // fill rest of target
        *dst++  = 0x80;     // with silence (0x80)

    //  free old memory:

    if(!sdata_static)
    {
#       ifdef   AMIGA
        FreeMem(sdata, ssize+2);
#       endif

#       ifdef   WIN16
        if(GlobalUnlock(hdata))
        {   perr("sound %s : data still locked", name); return MEMORY;  }

        if(GlobalFree(hdata))
        {   perr("sound %s : free failed", name); return MEMORY;    }
#       endif
    }

    //  use new one:

    ssize   = nsize;
#   ifdef   WIN16
    hdata   = hndata;
#   endif
    sdata   = ndata;

#   ifdef   WIN16
    winfo.ssize = ssize;
    winfo.sdata = sdata;        
#   endif

    sdata_static    = 0;

    //  update further stuff

    IFF_speed   = (ulong)IFF_speed * (ulong)blow / (ulong) shrink;

    if(targdropevery)
        IFF_speed -= (ushort)((long)IFF_speed / (long)targdropevery);
        //  e.g. 33075 -= (33075 / 30 = 1102) -> 31972

    winfo.speed = IFF_speed;

    return  OK;
}

void Sound::unload(void)
{
    if(playing())   stop();

    if(sdata && !sdata_static)
    {
#       ifdef   AMIGA
        FreeMem(sdata, ssize+2);
#       endif

#       ifdef   WIN16
        if(GlobalUnlock(hdata))
        {   perr("sound %s : data still locked", name); return; }

        if(GlobalFree(hdata))
        {   perr("sound %s : free failed", name); return;   }
#       endif
    }

    ssize       = 0;
    sdata       = 0;
    sdata_static= 0;

    winfo.ssize = 0;
    winfo.sdata = 0;
}

Sound::~Sound()
{_p (ssize, (ulong)sdata, -1, -1);

    CHK_DESTR;  // compiler bug fix

    if(playing())   stop();

    if(sdata && !sdata_static)
    {
#       ifdef   AMIGA
        FreeMem(sdata, ssize+2);
#       endif

#       ifdef   WIN16
        if(GlobalUnlock(hdata))
        {   perr("sound %s : data still locked", name); return; }

        if(GlobalFree(hdata))
        {   perr("sound %s : free failed", name); return;   }
#       endif
    }

    instcnt--;
}

//|| playstereo 
rcode Sound::playstereo(int howoften, int speed, int volume)
{_

#ifdef  AMIGA

 uword  chanmap=0;
 short  lchan=0,rchan=0;    // left, right channel number
 bool   lforce=0,rforce=0;  // force on l or r chan some sound to stop
 short  i;

    if(!valid())    return NOTAVAIL;

    if(speed == -1)             // if default selected
        ifn(speed = IFF_speed)  // use IFF
            speed = 10000;      // or  default speed

    Disable();  // ==== interrupts ====

    // find free audio channels

    for(i=0; i<4; i++)
    {
        if(chtab[i].playstate)
            chanmap |= 1 << i;  // mark alloc'ed channels
    }

    // channels (0,3)==left (1,2)==right

    ifn(chanmap & 0x1)  lchan = 0;
    else
    ifn(chanmap & 0x8)  lchan = 3;
    else
        lforce = 1; // force a left channel

    ifn(chanmap & 0x2)  rchan = 1;
    else
    ifn(chanmap & 0x4)  rchan = 2;
    else
        rforce = 1; // force a right channel

    if(lforce || rforce)
     // find one or two victims which must stop so we use their channels
     for(i=0; i<4; i++)
     {
        if(lforce && (i == 0 || i == 3))
            {
                lchan   = i;
                ChanStop(i);
                lforce  = 0;
            }
        else
        if(rforce && (i == 1 || i == 2))
            {
                rchan   = i;
                ChanStop(i);
                rforce  = 0;
            }
     }

    // NOTE that 'ChanPlay' must be called with INTERRUPTS DISABLED.
    ChanPlay(lchan, this, howoften, speed, volume);
    ChanPlay(rchan, this, howoften, speed, volume);

    Enable();   // ==== interrupts ====

    return  OK;

#endif

#ifdef  WIN16

    if(ChanManPlay(&winfo, howoften, SND_PLAYLEFT))
    {
/*
        //  play failed, try default method

        bool br=0;

        if(sdata)
            br = sndPlaySound((LPCSTR)sdata, SND_ASYNC|SND_NODEFAULT|SND_MEMORY);

        if(!br)
*/
            return FAILED;
    }
    else
        //  play once more, should go onto other stereo channel
        ChanManPlay(&winfo, howoften, SND_PLAYRIGHT);

    //  play(howoften, speed, volume);

    return  OK;

#endif
}

//|| play      
rcode Sound::play(int howoften, int speed, int volume, ulong opts)
{_

#ifdef  AMIGA

 Channel *ch,*oldchan=0;
 ushort  chanmap=0;
 short   i;

    if(!valid())    return NOTAVAIL;

    if(speed == -1)             // if default selected
        ifn(speed = IFF_speed)  // use IFF
            speed = 10000;      // or  default speed

    Disable();  // ==== interrupts ====

    // find a free audio channel

    for(i=0; i<4; i++)
    {
        ch  = &chtab[i];

        ifn(oldchan)
            oldchan = ch;
        else
        if(ch->seqno < oldchan->seqno)
            oldchan  = ch;      // found new oldest channel

        if(ch->playstate)
            chanmap |= 1 << i;  // mark alloc'ed channels
    }

    if(chanmap == 0xF)
    {
        // ALL channels are currently allocated!
        // So sorry, but we must throw out oldest sound:

        chanmap ^= (1<<oldchan->number);    // free bit in chanmap

        ChanStop(oldchan->number);
    }

    for(i=0; i<4; i++)  ifn(chanmap & (1<<i))   break;

    if(i > 3)   {SysErr(202941347); Enable(); return FAILED;}

    // NOTE that 'ChanPlay' must be called with INTERRUPTS DISABLED.
    ChanPlay(i, this, howoften, speed, volume);

    Enable();   // ==== interrupts ====

#endif

#ifdef  WIN16

    if(ChanManPlay(&winfo, howoften, opts))
    {
/*
        //  play failed, try default method

        bool br=0;

        if(sdata)
            br = sndPlaySound((LPCSTR)sdata, SND_ASYNC|SND_NODEFAULT|SND_MEMORY);

        if(!br)
*/
            return FAILED;
    }

#endif

    return OK;
}

//|| playing() 
bool Sound::playing()
{_
    // if there's ANY channel that plays our data, say 'yes'.

#ifdef  AMIGA

    for(short i=0; i<4; i++)

        if(     chtab[i].src == this
            &&  chtab[i].playstate  )

            return 1;

#endif

    return  0;
}

//|| stop()    
void Sound::stop()
{_
#ifdef  WIN16
    ChanManStop(&winfo);
    return;
#else
    if(!playing())  return;
#endif

#ifdef  AMIGA

    // stop ALL channels that play our data.

    Disable();

    for(short i=0; i<4; i++)

        if(     chtab[i].src == this
            &&  chtab[i].playstate  )

            ChanStop(i);

    Enable();

#endif

}
