text: /*
  Copyright (C) 1996-1997 Id Software, Inc.
  
  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  
  See the GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  
  */
  // snd_mix.c -- portable code to mix sounds for snd_dma.c
  
  #include "quakedef.h"
  
  #ifdef _WIN32
  #include "winquake.h"
  #else
  #define DWORD	unsigned long
  #endif
  
  #define	PAINTBUFFER_SIZE	512
  portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
  int		snd_scaletable[32][256];
  int* snd_p, snd_linear_count, snd_vol;
  short* snd_out;
  
  void Snd_WriteLinearBlastStereo16(void);
  
  #if	!id386
  void Snd_WriteLinearBlastStereo16(void)
  {
     int		i;
     int		val;
  
     for (i = 0; i < snd_linear_count; i += 2)
     {
        val = (snd_p[i] * snd_vol) >> 8;
        if (val > 0x7fff)
           snd_out[i] = 0x7fff;
        else if (val < (short)0x8000)
           snd_out[i] = (short)0x8000;
        else
           snd_out[i] = val;
  
        val = (snd_p[i + 1] * snd_vol) >> 8;
        if (val > 0x7fff)
           snd_out[i + 1] = 0x7fff;
        else if (val < (short)0x8000)
           snd_out[i + 1] = (short)0x8000;
        else
           snd_out[i + 1] = val;
     }
  }
  #endif
  
  void S_TransferStereo16(int endtime)
  {
     int		lpos;
     int		lpaintedtime;
     DWORD* pbuf;
  #ifdef _WIN32
     int		reps;
     DWORD	dwSize, dwSize2;
     DWORD* pbuf2;
     HRESULT	hresult;
  #endif
  
     snd_vol = volume.value * 256;
  
     snd_p = (int*)paintbuffer;
     lpaintedtime = paintedtime;
  
  #ifdef _WIN32
     if (pDSBuf)
     {
        reps = 0;
  
        while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &pbuf, &dwSize,
           &pbuf2, &dwSize2, 0)) != DS_OK)
        {
           if (hresult != DSERR_BUFFERLOST)
           {
              Con_Printf("S_TransferStereo16: DS::Lock Sound Buffer Failed\n");
              S_Shutdown();
              S_Startup();
              return;
           }
  
           if (++reps > 10000)
           {
              Con_Printf("S_TransferStereo16: DS: couldn't restore buffer\n");
              S_Shutdown();
              S_Startup();
              return;
           }
        }
     }
     else
  #endif
     {
        pbuf = (DWORD*)shm->buffer;
     }
  
     while (lpaintedtime < endtime)
     {
        // handle recirculating buffer issues
        lpos = lpaintedtime & ((shm->samples >> 1) - 1);
  
        snd_out = (short*)pbuf + (lpos << 1);
  
        snd_linear_count = (shm->samples >> 1) - lpos;
        if (lpaintedtime + snd_linear_count > endtime)
           snd_linear_count = endtime - lpaintedtime;
  
        snd_linear_count <<= 1;
  
        // write a linear blast of samples
        Snd_WriteLinearBlastStereo16();
  
        snd_p += snd_linear_count;
        lpaintedtime += (snd_linear_count >> 1);
     }
  
  #ifdef _WIN32
     if (pDSBuf)
        pDSBuf->lpVtbl->Unlock(pDSBuf, pbuf, dwSize, NULL, 0);
  #endif
  }
  
  void S_TransferPaintBuffer(int endtime)
  {
     int 	out_idx;
     int 	count;
     int 	out_mask;
     int* p;
     int 	step;
     int		val;
     int		snd_vol;
     DWORD* pbuf;
  #ifdef _WIN32
     int		reps;
     DWORD	dwSize, dwSize2;
     DWORD* pbuf2;
     HRESULT	hresult;
  #endif
  
     if (shm->samplebits == 16 && shm->channels == 2)
     {
        S_TransferStereo16(endtime);
        return;
     }
  
     p = (int*)paintbuffer;
     count = (endtime - paintedtime) * shm->channels;
     out_mask = shm->samples - 1;
     out_idx = paintedtime * shm->channels & out_mask;
     step = 3 - shm->channels;
     snd_vol = volume.value * 256;
  
  #ifdef _WIN32
     if (pDSBuf)
     {
        reps = 0;
  
        while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &pbuf, &dwSize,
           &pbuf2, &dwSize2, 0)) != DS_OK)
        {
           if (hresult != DSERR_BUFFERLOST)
           {
              Con_Printf("S_TransferPaintBuffer: DS::Lock Sound Buffer Failed\n");
              S_Shutdown();
              S_Startup();
              return;
           }
  
           if (++reps > 10000)
           {
              Con_Printf("S_TransferPaintBuffer: DS: couldn't restore buffer\n");
              S_Shutdown();
              S_Startup();
              return;
           }
        }
     }
     else
  #endif
     {
        pbuf = (DWORD*)shm->buffer;
     }
  
     if (shm->samplebits == 16)
     {
        short* out = (short*)pbuf;
        while (count--)
        {
           val = (*p * snd_vol) >> 8;
           p += step;
           if (val > 0x7fff)
              val = 0x7fff;
           else if (val < (short)0x8000)
              val = (short)0x8000;
           out[out_idx] = val;
           out_idx = (out_idx + 1) & out_mask;
        }
     }
     else if (shm->samplebits == 8)
     {
        unsigned char* out = (unsigned char*)pbuf;
        while (count--)
        {
           val = (*p * snd_vol) >> 8;
           p += step;
           if (val > 0x7fff)
              val = 0x7fff;
           else if (val < (short)0x8000)
              val = (short)0x8000;
           out[out_idx] = (val >> 8) + 128;
           out_idx = (out_idx + 1) & out_mask;
        }
     }
  
  #ifdef _WIN32
     if (pDSBuf) {
        DWORD dwNewpos, dwWrite;
        int il = paintedtime;
        int ir = endtime - paintedtime;
  
        ir += il;
  
        pDSBuf->lpVtbl->Unlock(pDSBuf, pbuf, dwSize, NULL, 0);
  
        pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &dwNewpos, &dwWrite);
  
        //		if ((dwNewpos >= il) && (dwNewpos <= ir))
        //			Con_Printf("%d-%d p %d c\n", il, ir, dwNewpos);
     }
  #endif
  }
  
  
  /*
  ===============================================================================
  
  CHANNEL MIXING
  
  ===============================================================================
  */
  
  void SND_PaintChannelFrom8(channel_t* ch, sfxcache_t* sc, int endtime);
  void SND_PaintChannelFrom16(channel_t* ch, sfxcache_t* sc, int endtime);
  
  void S_PaintChannels(int endtime)
  {
     int 	i;
     int 	end;
     channel_t* ch;
     sfxcache_t* sc;
     int		ltime, count;
  
     while (paintedtime < endtime)
     {
        // if paintbuffer is smaller than DMA buffer
        end = endtime;
        if (endtime - paintedtime > PAINTBUFFER_SIZE)
           end = paintedtime + PAINTBUFFER_SIZE;
  
        // clear the paint buffer
        Q_memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));
  
        // paint in the channels.
        ch = channels;
        for (i = 0; i < total_channels; i++, ch++)
        {
           if (!ch->sfx)
              continue;
           if (!ch->leftvol && !ch->rightvol)
              continue;
           sc = S_LoadSound(ch->sfx);
           if (!sc)
              continue;
  
           ltime = paintedtime;
  
           while (ltime < end)
           {	// paint up to end
              if (ch->end < end)
                 count = ch->end - ltime;
              else
                 count = end - ltime;
  
              if (count > 0)
              {
                 if (sc->width == 1)
                    SND_PaintChannelFrom8(ch, sc, count);
                 else
                    SND_PaintChannelFrom16(ch, sc, count);
  
                 ltime += count;
              }
  
              // if at end of loop, restart
              if (ltime >= ch->end)
              {
                 if (sc->loopstart >= 0)
                 {
                    ch->pos = sc->loopstart;
                    ch->end = ltime + sc->length - ch->pos;
                 }
                 else
                 {	// channel just stopped
                    ch->sfx = NULL;
                    break;
                 }
              }
           }
  
        }
  
        // transfer out according to DMA format
        S_TransferPaintBuffer(end);
        paintedtime = end;
     }
  }
  
  void SND_InitScaletable(void)
  {
     int		i, j;
  
     for (i = 0; i < 32; i++)
        for (j = 0; j < 256; j++)
           snd_scaletable[i][j] = ((signed char)j) * i * 8;
  }
  
  
  #if	!id386
  
  void SND_PaintChannelFrom8(channel_t* ch, sfxcache_t* sc, int count)
  {
     int 	data;
     int* lscale, * rscale;
     unsigned char* sfx;
     int		i;
  
     if (ch->leftvol > 255)
        ch->leftvol = 255;
     if (ch->rightvol > 255)
        ch->rightvol = 255;
  
     lscale = snd_scaletable[ch->leftvol >> 3];
     rscale = snd_scaletable[ch->rightvol >> 3];
     sfx = (signed char*)sc->data + ch->pos;
  
     for (i = 0; i < count; i++)
     {
        data = sfx[i];
        paintbuffer[i].left += lscale[data];
        paintbuffer[i].right += rscale[data];
     }
  
     ch->pos += count;
  }
  
  #endif	// !id386
  
  
  void SND_PaintChannelFrom16(channel_t* ch, sfxcache_t* sc, int count)
  {
     int data;
     int left, right;
     int leftvol, rightvol;
     signed short* sfx;
     int	i;
  
     leftvol = ch->leftvol;
     rightvol = ch->rightvol;
     sfx = (signed short*)sc->data + ch->pos;
  
     for (i = 0; i < count; i++)
     {
        data = sfx[i];
        left = (data * leftvol) >> 8;
        right = (data * rightvol) >> 8;
        paintbuffer[i].left += left;
        paintbuffer[i].right += right;
     }
  
     ch->pos += count;
  }
  
  