/*
   Copyright (C) 2006 yopyop
   Copyright (C) 2006 Theo Berkau
   Copyright (C) 2008-2017 DeSmuME team

   Ideas borrowed from Stephane Dallongeville's SCSP core

   This file 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 file 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 the this software.  If not, see <http://www.gnu.org/licenses/>.
   */

//#include "XSFCommon.h"
#include "../spu/samplecache.h"
#include "../spu/interpolator.h"

#define _USE_MATH_DEFINES
#include <math.h>
#ifndef M_PI
#define M_PI 3.1415926535897932386
#endif

#include <stdlib.h>
#include <string.h>
#include <queue>
#include <vector>

#include "MMU.h"
#include "SPU.h"
#include "mem.h"
#include "readwrite.h"
#include "armcpu.h"
#include "NDSSystem.h"
#include "emufile.h"
#include "matrix.h"
#include "bits.h"

static inline s16 read16(u32 addr) { return (s16)_MMU_read16<ARMCPU_ARM7,MMU_AT_DEBUG>(addr); }
static inline u8 read08(u32 addr) { return _MMU_read08<ARMCPU_ARM7,MMU_AT_DEBUG>(addr); }

#define K_ADPCM_LOOPING_RECOVERY_INDEX 99999
#define COSINE_INTERPOLATION_RESOLUTION 8192

SPU_struct *SPU_core = 0;
int SPU_currentCoreNum = SNDCORE_DUMMY;
static int volume = 100;
SampleCache spuSampleCache;

static size_t buffersize = 0;
static ESynchMode synchmode = ESynchMode_Synchronous;
static ESynchMethod synchmethod = ESynchMethod_0;
static ISynchronizingAudioBuffer* synchronizer = metaspu_construct(synchmethod);

static int SNDCoreId=-1;
static SoundInterface_struct *SNDCore=NULL;
extern SoundInterface_struct *SNDCoreList[];

static const int format_shift[] = { 2, 1, 3, 0 };
static const u8 volume_shift[] = { 0, 1, 2, 4 };

static const s16 wavedutytbl[8][8] = {
  { -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF },
  { -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF },
  { -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
  { -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
  { -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
  { -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
  { -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
  { -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF }
};

static const double ARM7_CLOCK = 33513982;

double DESMUME_SAMPLE_RATE = 48000;
static double samples_per_hline = (DESMUME_SAMPLE_RATE / 59.8261f) / 263.0f;
static double sampleLength = DESMUME_SAMPLE_RATE / 32728.498;

void SetDesmumeSampleRate(double rate) {
  DESMUME_SAMPLE_RATE = rate;
  sampleLength = DESMUME_SAMPLE_RATE / 32728.498;
  samples_per_hline = (DESMUME_SAMPLE_RATE / 59.8261f) / 263.0f;
}

static double samples = 0;

template<typename T>
static FORCEINLINE T MinMax(T val, T min, T max)
{
  if (val < min)
    return min;
  else if (val > max)
    return max;

  return val;
}

//--------------external spu interface---------------

int SPU_ChangeSoundCore(int coreid, int buffersize)
{
  int i;

  ::buffersize = buffersize;

  // Make sure the old core is freed
  if (SNDCore)
    SNDCore->DeInit();

  // So which core do we want?
  if (coreid == SNDCORE_DEFAULT)
    coreid = 0; // Assume we want the first one

  SPU_currentCoreNum = coreid;

  // Go through core list and find the id
  for (i = 0; SNDCoreList[i] != NULL; i++)
  {
    if (SNDCoreList[i]->id == coreid)
    {
      // Set to current core
      SNDCore = SNDCoreList[i];
      break;
    }
  }

  SNDCoreId = coreid;

  //If the user picked the dummy core, disable the user spu
  if(SNDCore == &SNDDummy)
    return 0;

  //If the core wasnt found in the list for some reason, disable the user spu
  if (SNDCore == NULL)
    return -1;

  // Since it failed, instead of it being fatal, disable the user spu
  if (SNDCore->Init(buffersize * 2) == -1)
  {
    SNDCore = 0;
    return -1;
  }

  SNDCore->SetVolume(volume);

  SPU_SetSynchMode(synchmode,synchmethod);

  return 0;
}

SoundInterface_struct *SPU_SoundCore()
{
  return SNDCore;
}

void SPU_ReInit(bool fakeBoot)
{
  SPU_Init(SNDCoreId, buffersize);

  // Firmware set BIAS to 0x200
  if (fakeBoot)
    SPU_WriteWord(0x04000504, 0x0200);
}

int SPU_Init(int coreid, int buffersize)
{
  SPU_core = new SPU_struct((int)ceil(samples_per_hline));
  SPU_Reset();

  SPU_SetSynchMode(synchmode, synchmethod);

  return SPU_ChangeSoundCore(coreid, buffersize);
}

void SPU_Pause(int pause)
{
  if (SNDCore == NULL) return;

  if(pause)
    SNDCore->MuteAudio();
  else
    SNDCore->UnMuteAudio();
}

void SPU_SetSynchMode(int mode, int method)
{
  synchmode = (ESynchMode)mode;
  if(synchmethod != (ESynchMethod)method)
  {
    synchmethod = (ESynchMethod)method;
    delete synchronizer;
    //grr does this need to be locked? spu might need a lock method
    // or maybe not, maybe the platform-specific code that calls this function can deal with it.
    synchronizer = metaspu_construct(synchmethod);
  }
}

void SPU_ClearOutputBuffer()
{
  if(SNDCore && SNDCore->ClearBuffer)
    SNDCore->ClearBuffer();
}

void SPU_SetVolume(int volume)
{
  ::volume = volume;
  if (SNDCore)
    SNDCore->SetVolume(volume);
}


void SPU_Reset(void)
{
  int i;

  SPU_core->reset();

  //zero - 09-apr-2010: this concerns me, regarding savestate synch.
  //After 0.9.6, lets experiment with removing it and just properly zapping the spu instead
  // Reset Registers
  for (i = 0x400; i < 0x51D; i++)
    T1WriteByte(MMU.ARM7_REG, i, 0);

  samples = 0;
}

//------------------------------------------

void SPU_struct::reset()
{
  memset(sndbuf,0,bufsize*2*4);
  memset(outbuf,0,bufsize*2*2);

  memset((void *)channels, 0, sizeof(channel_struct) * 16);

  reconstruct(&regs);

  for(int i = 0; i < 16; i++)
  {
    channels[i].num = i;
  }
}

SPU_struct::SPU_struct(int buffersize)
  : bufpos(0)
  , buflength(0)
  , sndbuf(0)
  , outbuf(0)
    , bufsize(buffersize)
{
  sndbuf = new s32[buffersize*2];
  outbuf = new s16[buffersize*2];
  reset();
}

SPU_struct::~SPU_struct()
{
  if(sndbuf) delete[] sndbuf;
  if(outbuf) delete[] outbuf;
}

void SPU_DeInit(void)
{
  if(SNDCore)
    SNDCore->DeInit();
  SNDCore = 0;

  delete SPU_core; SPU_core=0;
}

//////////////////////////////////////////////////////////////////////////////

void SPU_struct::ShutUp()
{
  for(int i=0;i<16;i++)
    channels[i].status = CHANSTAT_STOPPED;
}

static FORCEINLINE void adjust_channel_timer(channel_struct *chan)
{
  chan->sampinc = (((double)ARM7_CLOCK) / (DESMUME_SAMPLE_RATE * 2)) / (double)(0x10000 - chan->timer);
}

void SPU_struct::KeyProbe(int chan_num)
{
  channel_struct &thischan = channels[chan_num];
  if(thischan.status == CHANSTAT_STOPPED)
  {
    if(thischan.keyon && regs.masteren)
      KeyOn(chan_num);
  }
  else if(thischan.status == CHANSTAT_PLAY)
  {
    if(!thischan.keyon || !regs.masteren)
      KeyOff(chan_num);
  }
}

void SPU_struct::KeyOff(int channel)
{
  channel_struct &thischan = channels[channel];
  thischan.status = CHANSTAT_STOPPED;
}

void SPU_struct::KeyOn(int channel)
{
  channel_struct &thischan = channels[channel];
  thischan.status = CHANSTAT_PLAY;

  thischan.totlength = thischan.length + thischan.loopstart;
  adjust_channel_timer(&thischan);

  switch(thischan.format)
  {
    case 0: // 8-bit
      thischan.sampcnt = -3;
      break;
    case 1: // 16-bit
      thischan.sampcnt = -3;
      break;
    case 2: // ADPCM
      {
        thischan.pcm16b = (s16)read16(thischan.addr);
        thischan.pcm16b_last = thischan.pcm16b;
        thischan.index = read08(thischan.addr + 2) & 0x7F;;
        thischan.lastsampcnt = 7;
        thischan.sampcnt = -3;
        thischan.loop_index = K_ADPCM_LOOPING_RECOVERY_INDEX;
        break;
      }
    case 3: // PSG
      {
        thischan.sampcnt = -1;
        thischan.x = 0x7FFF;
        break;
      }
    default: break;
  }

  thischan.double_totlength_shifted = (double)(thischan.totlength << format_shift[thischan.format]);

  if(thischan.format != 3)
  {
    if(thischan.double_totlength_shifted == 0)
    {
      thischan.status = CHANSTAT_STOPPED;
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

u8 SPU_struct::ReadByte(u32 addr)
{
  //individual channel regs
  if ((addr & 0x0F00) == 0x0400)
  {
    u32 chan_num = (addr >> 4) & 0xF;
    const channel_struct& thischan = channels[chan_num];

    switch (addr & 0xF)
    {
      case 0x0: return thischan.vol;
      case 0x1: return (thischan.volumeDiv | (thischan.hold << 7));
      case 0x2: return thischan.pan;
      case 0x3: return (	thischan.waveduty
                    | (thischan.repeat << 3)
                    | (thischan.format << 5)
                    | ((thischan.status == CHANSTAT_PLAY)?0x80:0)
                    );
      case 0x8: return thischan.timer >> 0;
      case 0x9: return thischan.timer >> 8;
      case 0xA: return thischan.loopstart >> 0;
      case 0xB: return thischan.loopstart >> 8;
    }
    return 0;
  }

  switch(addr)
  {
    //SOUNDCNT
    case 0x500: return regs.mastervol;
    case 0x501: return (regs.ctl_left
                    | (regs.ctl_right << 2)
                    | (regs.ctl_ch1bypass << 4)
                    | (regs.ctl_ch3bypass << 5)
                    | (regs.masteren << 7)
                    );

                //SOUNDBIAS
    case 0x504: return regs.soundbias >> 0;
    case 0x505: return regs.soundbias >> 8;

                //SNDCAP0CNT/SNDCAP1CNT
    case 0x508:
    case 0x509:
                {
                  u32 which = (addr - 0x508);
                  return regs.cap[which].add
                    | (regs.cap[which].source << 1)
                    | (regs.cap[which].oneshot << 2)
                    | (regs.cap[which].bits8 << 3)
                    | (regs.cap[which].runtime.running << 7);
                }

                //SNDCAP0DAD
    case 0x510: return regs.cap[0].dad >> 0;
    case 0x511: return regs.cap[0].dad >> 8;
    case 0x512: return regs.cap[0].dad >> 16;
    case 0x513: return regs.cap[0].dad >> 24;

                //SNDCAP0LEN
    case 0x514: return regs.cap[0].len >> 0;
    case 0x515: return regs.cap[0].len >> 8;

                //SNDCAP1DAD
    case 0x518: return regs.cap[1].dad >> 0;
    case 0x519: return regs.cap[1].dad >> 8;
    case 0x51A: return regs.cap[1].dad >> 16;
    case 0x51B: return regs.cap[1].dad >> 24;

                //SNDCAP1LEN
    case 0x51C: return regs.cap[1].len >> 0;
    case 0x51D: return regs.cap[1].len >> 8;
  } //switch on address

  return 0;
}

u16 SPU_struct::ReadWord(u32 addr)
{
  //individual channel regs
  if ((addr & 0x0F00) == 0x0400)
  {
    u32 chan_num = (addr >> 4) & 0xF;
    const channel_struct& thischan = channels[chan_num];

    switch (addr & 0xF)
    {
      case 0x0: return	(thischan.vol
                    | (thischan.volumeDiv << 8)
                    | (thischan.hold << 15)
                    );
      case 0x2: return	(thischan.pan
                    | (thischan.waveduty << 8)
                    | (thischan.repeat << 11)
                    | (thischan.format << 13)
                    | ((thischan.status == CHANSTAT_PLAY)?(1 << 15):0)
                    );
      case 0x8: return thischan.timer;
      case 0xA: return thischan.loopstart;
    } //switch on individual channel regs
    return 0;
  }

  switch(addr)
  {
    //SOUNDCNT
    case 0x500: return	(regs.mastervol
                    | (regs.ctl_left << 8)
                    | (regs.ctl_right << 10)
                    | (regs.ctl_ch1bypass << 12)
                    | (regs.ctl_ch3bypass << 13)
                    | (regs.masteren << 15)
                    );

                //SOUNDBIAS
    case 0x504: return regs.soundbias;

                //SNDCAP0CNT/SNDCAP1CNT
    case 0x508:
                {
                  u8 val0 =	regs.cap[0].add
                    | (regs.cap[0].source << 1)
                    | (regs.cap[0].oneshot << 2)
                    | (regs.cap[0].bits8 << 3)
                    | (regs.cap[0].runtime.running << 7);
                  u8 val1 =	regs.cap[1].add
                    | (regs.cap[1].source << 1)
                    | (regs.cap[1].oneshot << 2)
                    | (regs.cap[1].bits8 << 3)
                    | (regs.cap[1].runtime.running << 7);
                  return (u16)(val0 | (val1 << 8));
                }

                //SNDCAP0DAD
    case 0x510: return regs.cap[0].dad >> 0;
    case 0x512: return regs.cap[0].dad >> 16;

                //SNDCAP0LEN
    case 0x514: return regs.cap[0].len;

                //SNDCAP1DAD
    case 0x518: return regs.cap[1].dad >> 0;
    case 0x51A: return regs.cap[1].dad >> 16;

                //SNDCAP1LEN
    case 0x51C: return regs.cap[1].len;
  } //switch on address

  return 0;
}

u32 SPU_struct::ReadLong(u32 addr)
{
  //individual channel regs
  if ((addr & 0x0F00) == 0x0400)
  {
    u32 chan_num = (addr >> 4) & 0xF;
    channel_struct &thischan=channels[chan_num];

    switch (addr & 0xF)
    {
      case 0x0: return	(thischan.vol
                    | (thischan.volumeDiv << 8)
                    | (thischan.hold << 15)
                    | (thischan.pan << 16)
                    | (thischan.waveduty << 24)
                    | (thischan.repeat << 27)
                    | (thischan.format << 29)
                    | ((thischan.status == CHANSTAT_PLAY)?(1 << 31):0)
                    );
      case 0x8: return (thischan.timer | (thischan.loopstart << 16));
    } //switch on individual channel regs
    return 0;
  }

  switch(addr)
  {
    //SOUNDCNT
    case 0x500: return	(regs.mastervol
                    | (regs.ctl_left << 8)
                    | (regs.ctl_right << 10)
                    | (regs.ctl_ch1bypass << 12)
                    | (regs.ctl_ch3bypass << 13)
                    | (regs.masteren << 15)
                    );

                //SOUNDBIAS
    case 0x504: return (u32)regs.soundbias;

                //SNDCAP0CNT/SNDCAP1CNT
    case 0x508:
                {
                  u8 val0 =	regs.cap[0].add
                    | (regs.cap[0].source << 1)
                    | (regs.cap[0].oneshot << 2)
                    | (regs.cap[0].bits8 << 3)
                    | (regs.cap[0].runtime.running << 7);
                  u8 val1 =	regs.cap[1].add
                    | (regs.cap[1].source << 1)
                    | (regs.cap[1].oneshot << 2)
                    | (regs.cap[1].bits8 << 3)
                    | (regs.cap[1].runtime.running << 7);
                  return (u32)(val0 | (val1 << 8));
                }

                //SNDCAP0DAD
    case 0x510: return regs.cap[0].dad;

                //SNDCAP0LEN
    case 0x514: return (u32)regs.cap[0].len;

                //SNDCAP1DAD
    case 0x518: return regs.cap[1].dad;

                //SNDCAP1LEN
    case 0x51C: return (u32)regs.cap[1].len;
  } //switch on address

  return 0;
}

SPUFifo::SPUFifo()
{
  reset();
}

void SPUFifo::reset()
{
  head = tail = size = 0;
}

void SPUFifo::enqueue(s16 val)
{
  if(size==16) return;
  buffer[tail] = val;
  tail++;
  tail &= 15;
  size++;
}

s16 SPUFifo::dequeue()
{
  if(size==0) return 0;
  head++;
  head &= 15;
  s16 ret = buffer[head];
  size--;
  return ret;
}

void SPU_struct::ProbeCapture(int which)
{
  //VERY UNTESTED -- HOW MUCH OF THIS RESETS, AND WHEN?

  if(!regs.cap[which].active)
  {
    regs.cap[which].runtime.running = 0;
    return;
  }

  REGS::CAP &cap = regs.cap[which];
  cap.runtime.running = 1;
  cap.runtime.curdad = cap.dad;
  u32 len = cap.len;
  if(len==0) len=1;
  cap.runtime.maxdad = cap.dad + len*4;
  cap.runtime.sampcnt = 0;
  cap.runtime.fifo.reset();
}

void SPU_struct::WriteByte(u32 addr, u8 val)
{
  //individual channel regs
  if ((addr & 0x0F00) == 0x0400)
  {
    u8 chan_num = (addr >> 4) & 0xF;
    channel_struct &thischan = channels[chan_num];

    switch (addr & 0x000F)
    {
      case 0x0: thischan.vol = (val & 0x7F); break;
      case 0x1:
                thischan.volumeDiv = (val & 0x03);
                thischan.hold = (val >> 7) & 0x01;
                break;
      case 0x2: thischan.pan = (val & 0x7F); break;
      case 0x3:
                thischan.waveduty = (val & 0x07);
                thischan.repeat = (val >> 3) & 0x03;
                thischan.format = (val >> 5) & 0x03;
                thischan.keyon = (val >> 7) & 0x01;
                KeyProbe(chan_num);
                break;
      case 0x4: thischan.addr &= 0xFFFFFF00; thischan.addr |= (val & 0xFC); break;
      case 0x5: thischan.addr &= 0xFFFF00FF; thischan.addr |= (val << 8); break;
      case 0x6: thischan.addr &= 0xFF00FFFF; thischan.addr |= (val << 16); break;
      case 0x7: thischan.addr &= 0x00FFFFFF; thischan.addr |= ((val&7) << 24); break; //only 27 bits of this register are used
      case 0x8: thischan.timer &= 0xFF00; thischan.timer |= (val << 0); adjust_channel_timer(&thischan); break;
      case 0x9: thischan.timer &= 0x00FF; thischan.timer |= (val << 8); adjust_channel_timer(&thischan); break;

      case 0xA: thischan.loopstart &= 0xFF00; thischan.loopstart |= (val << 0); break;
      case 0xB: thischan.loopstart &= 0x00FF; thischan.loopstart |= (val << 8); break;
      case 0xC: thischan.length &= 0xFFFFFF00; thischan.length |= (val << 0); break;
      case 0xD: thischan.length &= 0xFFFF00FF; thischan.length |= (val << 8); break;
      case 0xE: thischan.length &= 0xFF00FFFF; thischan.length |= ((val & 0x3F) << 16); //only 22 bits of this register are used
      case 0xF: break;

    } //switch on individual channel regs

    return;
  }

  switch(addr)
  {
    //SOUNDCNT
    case 0x500: regs.mastervol = (val & 0x7F); break;
    case 0x501:
                regs.ctl_left  = (val >> 0) & 3;
                regs.ctl_right = (val >> 2) & 3;
                regs.ctl_ch1bypass = (val >> 4) & 1;
                regs.ctl_ch3bypass = (val >> 5) & 1;
                regs.masteren = (val >> 7) & 1;
                break;

                //SOUNDBIAS
    case 0x504: regs.soundbias &= 0xFF00; regs.soundbias |= (val << 0); break;
    case 0x505: regs.soundbias &= 0x00FF; regs.soundbias |= ((val&3) << 8); break;

                //SNDCAP0CNT/SNDCAP1CNT
    case 0x508:
    case 0x509:
                {
                  u32 which = (addr - 0x508);
                  regs.cap[which].add = BIT0(val);
                  regs.cap[which].source = BIT1(val);
                  regs.cap[which].oneshot = BIT2(val);
                  regs.cap[which].bits8 = BIT3(val);
                  regs.cap[which].active = BIT7(val);
                  ProbeCapture(which);
                  break;
                }

                //SNDCAP0DAD
    case 0x510: regs.cap[0].dad &= 0xFFFFFF00; regs.cap[0].dad |= (val & 0xFC); break;
    case 0x511: regs.cap[0].dad &= 0xFFFF00FF; regs.cap[0].dad |= (val << 8); break;
    case 0x512: regs.cap[0].dad &= 0xFF00FFFF; regs.cap[0].dad |= (val << 16); break;
    case 0x513: regs.cap[0].dad &= 0x00FFFFFF; regs.cap[0].dad |= ((val&7) << 24); break;

                //SNDCAP0LEN
    case 0x514: regs.cap[0].len &= 0xFF00; regs.cap[0].len |= (val << 0); break;
    case 0x515: regs.cap[0].len &= 0x00FF; regs.cap[0].len |= (val << 8); break;

                //SNDCAP1DAD
    case 0x518: regs.cap[1].dad &= 0xFFFFFF00; regs.cap[1].dad |= (val & 0xFC); break;
    case 0x519: regs.cap[1].dad &= 0xFFFF00FF; regs.cap[1].dad |= (val << 8); break;
    case 0x51A: regs.cap[1].dad &= 0xFF00FFFF; regs.cap[1].dad |= (val << 16); break;
    case 0x51B: regs.cap[1].dad &= 0xFF000000; regs.cap[1].dad |= ((val&7) << 24); break;

                //SNDCAP1LEN
    case 0x51C: regs.cap[1].len &= 0xFF00; regs.cap[1].len |= (val << 0); break;
    case 0x51D: regs.cap[1].len &= 0x00FF; regs.cap[1].len |= (val << 8); break;
  } //switch on address
}

void SPU_struct::WriteWord(u32 addr, u16 val)
{
  //individual channel regs
  if ((addr & 0x0F00) == 0x0400)
  {
    u32 chan_num = (addr >> 4) & 0xF;
    channel_struct &thischan=channels[chan_num];

    switch (addr & 0xF)
    {
      case 0x0:
        thischan.vol = (val & 0x7F);
        thischan.volumeDiv = (val >> 8) & 0x3;
        thischan.hold = (val >> 15) & 0x1;
        break;
      case 0x2:
        thischan.pan = (val & 0x7F);
        thischan.waveduty = (val >> 8) & 0x7;
        thischan.repeat = (val >> 11) & 0x3;
        thischan.format = (val >> 13) & 0x3;
        thischan.keyon = (val >> 15) & 0x1;
        KeyProbe(chan_num);
        break;
      case 0x4: thischan.addr &= 0xFFFF0000; thischan.addr |= (val & 0xFFFC); break;
      case 0x6: thischan.addr &= 0x0000FFFF; thischan.addr |= ((val & 0x07FF) << 16); break;
      case 0x8: thischan.timer = val; adjust_channel_timer(&thischan); break;
      case 0xA: thischan.loopstart = val; break;
      case 0xC: thischan.length &= 0xFFFF0000; thischan.length |= (val << 0); break;
      case 0xE: thischan.length &= 0x0000FFFF; thischan.length |= ((val & 0x003F) << 16); break;
    } //switch on individual channel regs
    return;
  }

  switch (addr)
  {
    //SOUNDCNT
    case 0x500:
      regs.mastervol = (val & 0x7F);
      regs.ctl_left  = (val >> 8) & 0x03;
      regs.ctl_right = (val >> 10) & 0x03;
      regs.ctl_ch1bypass = (val >> 12) & 0x01;
      regs.ctl_ch3bypass = (val >> 13) & 0x01;
      regs.masteren = (val >> 15) & 0x01;
      for(u8 i=0; i<16; i++)
        KeyProbe(i);
      break;

      //SOUNDBIAS
    case 0x504: regs.soundbias = (val & 0x3FF); break;

                //SNDCAP0CNT/SNDCAP1CNT
    case 0x508:
                {
                  regs.cap[0].add = BIT0(val);
                  regs.cap[0].source = BIT1(val);
                  regs.cap[0].oneshot = BIT2(val);
                  regs.cap[0].bits8 = BIT3(val);
                  regs.cap[0].active = BIT7(val);
                  ProbeCapture(0);

                  regs.cap[1].add = BIT8(val);
                  regs.cap[1].source = BIT9(val);
                  regs.cap[1].oneshot = BIT10(val);
                  regs.cap[1].bits8 = BIT11(val);
                  regs.cap[1].active = BIT15(val);
                  ProbeCapture(1);
                  break;
                }

                //SNDCAP0DAD
    case 0x510: regs.cap[0].dad &= 0xFFFF0000; regs.cap[0].dad |= (val & 0xFFFC); break;
    case 0x512: regs.cap[0].dad &= 0x0000FFFF; regs.cap[0].dad |= ((val & 0x07FF) << 16); break;

                //SNDCAP0LEN
    case 0x514: regs.cap[0].len = val; break;

                //SNDCAP1DAD
    case 0x518: regs.cap[1].dad &= 0xFFFF0000; regs.cap[1].dad |= (val & 0xFFFC); break;
    case 0x51A: regs.cap[1].dad &= 0x0000FFFF; regs.cap[1].dad |= ((val & 0x07FF) << 16); break;

                //SNDCAP1LEN
    case 0x51C: regs.cap[1].len = val; break;
  } //switch on address
}

void SPU_struct::WriteLong(u32 addr, u32 val)
{
  //individual channel regs
  if ((addr & 0x0F00) == 0x0400)
  {
    u32 chan_num = (addr >> 4) & 0xF;
    channel_struct &thischan=channels[chan_num];

    switch (addr & 0xF)
    {
      case 0x0:
        thischan.vol = val & 0x7F;
        thischan.volumeDiv = (val >> 8) & 0x3;
        thischan.hold = (val >> 15) & 0x1;
        thischan.pan = (val >> 16) & 0x7F;
        thischan.waveduty = (val >> 24) & 0x7;
        thischan.repeat = (val >> 27) & 0x3;
        thischan.format = (val >> 29) & 0x3;
        thischan.keyon = (val >> 31) & 0x1;
        KeyProbe(chan_num);
        break;

      case 0x4: thischan.addr = (val & 0x07FFFFFC); break;
      case 0x8:
                thischan.timer = (val & 0xFFFF);
                thischan.loopstart = ((val >> 16) & 0xFFFF);
                adjust_channel_timer(&thischan);
                break;

      case 0xC: thischan.length = (val & 0x003FFFFF); break; //only 22 bits of this register are used
    } //switch on individual channel regs
    return;
  }

  switch(addr)
  {
    //SOUNDCNT
    case 0x500:
      regs.mastervol = (val & 0x7F);
      regs.ctl_left  = ((val >> 8) & 3);
      regs.ctl_right = ((val>>10) & 3);
      regs.ctl_ch1bypass = ((val >> 12) & 1);
      regs.ctl_ch3bypass = ((val >> 13) & 1);
      regs.masteren = ((val >> 15) & 1);
      for(u8 i=0; i<16; i++)
        KeyProbe(i);
      break;

      //SOUNDBIAS
    case 0x504: regs.soundbias = (val & 0x3FF);

                //SNDCAP0CNT/SNDCAP1CNT
    case 0x508:
                regs.cap[0].add = BIT0(val);
                regs.cap[0].source = BIT1(val);
                regs.cap[0].oneshot = BIT2(val);
                regs.cap[0].bits8 = BIT3(val);
                regs.cap[0].active = BIT7(val);
                ProbeCapture(0);

                regs.cap[1].add = BIT8(val);
                regs.cap[1].source = BIT9(val);
                regs.cap[1].oneshot = BIT10(val);
                regs.cap[1].bits8 = BIT11(val);
                regs.cap[1].active = BIT15(val);
                ProbeCapture(1);
                break;

                //SNDCAP0DAD
    case 0x510: regs.cap[0].dad = (val & 0x07FFFFFC); break;

                //SNDCAP0LEN
    case 0x514: regs.cap[0].len = (val & 0xFFFF); break;

                //SNDCAP1DAD
    case 0x518: regs.cap[1].dad = (val & 0x07FFFFFC); break;

                //SNDCAP1LEN
    case 0x51C: regs.cap[1].len = (val & 0xFFFF); break;
  } //switch on address
}

//////////////////////////////////////////////////////////////////////////////

static FORCEINLINE void FetchPSGData(channel_struct *chan, s32 *data)
{
  if (chan->sampcnt < 0)
  {
    *data = 0;
    return;
  }

  if(chan->num < 8)
  {
    *data = 0;
  }
  else if(chan->num < 14)
  {
    *data = (s32)wavedutytbl[chan->waveduty][(sputrunc(chan->sampcnt)) & 0x7];
  }
  else
  {
    if(chan->lastsampcnt == sputrunc(chan->sampcnt))
    {
      *data = (s32)chan->psgnoise_last;
      return;
    }

    u32 max = sputrunc(chan->sampcnt);
    for(u32 i = chan->lastsampcnt; i < max; i++)
    {
      if(chan->x & 0x1)
      {
        chan->x = (chan->x >> 1) ^ 0x6000;
        chan->psgnoise_last = -0x7FFF;
      }
      else
      {
        chan->x >>= 1;
        chan->psgnoise_last = 0x7FFF;
      }
    }

    chan->lastsampcnt = sputrunc(chan->sampcnt);

    *data = (s32)chan->psgnoise_last;
  }
}

//////////////////////////////////////////////////////////////////////////////

static FORCEINLINE void MixL(SPU_struct* SPU, channel_struct *chan, s32 data)
{
  data = spumuldiv7(data, chan->vol) >> volume_shift[chan->volumeDiv];
  SPU->sndbuf[SPU->bufpos<<1] += data;
}

static FORCEINLINE void MixR(SPU_struct* SPU, channel_struct *chan, s32 data)
{
  data = spumuldiv7(data, chan->vol) >> volume_shift[chan->volumeDiv];
  SPU->sndbuf[(SPU->bufpos<<1)+1] += data;
}

static FORCEINLINE void MixLR(SPU_struct* SPU, channel_struct *chan, s32 data)
{
  data = spumuldiv7(data, chan->vol) >> volume_shift[chan->volumeDiv];
  SPU->sndbuf[SPU->bufpos<<1] += spumuldiv7(data, 127 - chan->pan);
  SPU->sndbuf[(SPU->bufpos<<1)+1] += spumuldiv7(data, chan->pan);
}

//////////////////////////////////////////////////////////////////////////////

template<int FORMAT> static FORCEINLINE void TestForLoop(SPU_struct *SPU, channel_struct *chan)
{
  const int shift = (FORMAT == 0 ? 2 : 1);

  chan->sampcnt += chan->sampinc;

  if (chan->sampcnt > chan->double_totlength_shifted)
  {
    // Do we loop? Or are we done?
    if (chan->repeat == 1)
    {
      while (chan->sampcnt > chan->double_totlength_shifted) {
        chan->sampcnt -= chan->double_totlength_shifted - (double)(chan->loopstart << shift);
      }
    }
    else
    {
      SPU->KeyOff(chan->num);
      SPU->bufpos = SPU->buflength;
    }
  }
}

static FORCEINLINE void TestForLoop2(SPU_struct *SPU, channel_struct *chan)
{
  // Minimum length (the sum of PNT+LEN) is 4 words (16 bytes),
  // smaller values (0..3 words) are causing hang-ups
  // (busy bit remains set infinite, but no sound output occurs).
  // fix: 7th Dragon (JP) - http://sourceforge.net/p/desmume/bugs/1357/
  if (chan->totlength < 4) return;

  chan->sampcnt += chan->sampinc;

  if (chan->sampcnt > chan->double_totlength_shifted)
  {
    // Do we loop? Or are we done?
    if (chan->repeat == 1)
    {
      double step = (chan->double_totlength_shifted - (double)(chan->loopstart << 3));

      while (chan->sampcnt > chan->double_totlength_shifted) chan->sampcnt -= step;

      if(chan->loop_index == K_ADPCM_LOOPING_RECOVERY_INDEX)
      {
        chan->pcm16b = (s16)read16(chan->addr);
        chan->index = read08(chan->addr+2) & 0x7F;
        chan->lastsampcnt = 7;
      }
      else
      {
        chan->pcm16b = chan->loop_pcm16b;
        chan->index = chan->loop_index;
        chan->lastsampcnt = (chan->loopstart << 3);
      }
    }
    else
    {
      chan->status = CHANSTAT_STOPPED;
      SPU->KeyOff(chan->num);
      SPU->bufpos = SPU->buflength;
    }
  }
}

template<int CHANNELS> FORCEINLINE static void SPU_Mix(SPU_struct* SPU, channel_struct *chan, s32 data)
{
  switch(CHANNELS)
  {
    case -1: break; // not reached
    case 0: MixL(SPU, chan, data); break;
    case 1: MixLR(SPU, chan, data); break;
    case 2: MixR(SPU, chan, data); break;
  }
  SPU->lastdata = data;
}

//WORK
  template<int FORMAT, int CHANNELS>
FORCEINLINE static void ____SPU_ChanUpdate(SPU_struct* const SPU, channel_struct* const chan)
{
  for (; SPU->bufpos < SPU->buflength; SPU->bufpos++)
  {
    if(CHANNELS != -1)
    {
      s32 data;
      if (chan->sampcnt < 0) {
        data = 0;
      } else if (FORMAT == 3) {
        FetchPSGData(chan, &data);
      } else {
        const SampleData& sample = spuSampleCache.getSample(chan->addr, chan->loopstart, chan->length, SampleData::Format(FORMAT));
        data = sample.sampleAt(chan->sampcnt, IInterpolator::allInterpolators[CommonSettings.spuInterpolationMode]);
      }
      SPU_Mix<CHANNELS>(SPU, chan, data);
    }

    switch(FORMAT) {
      case 0: case 1: TestForLoop<FORMAT>(SPU, chan); break;
      case 2: TestForLoop2(SPU, chan); break;
      case 3: chan->sampcnt += chan->sampinc; break;
    }
  }
}

template<int FORMAT>
FORCEINLINE static void ___SPU_ChanUpdate(const bool actuallyMix, SPU_struct* const SPU, channel_struct* const chan)
{
  if(!actuallyMix)
    ____SPU_ChanUpdate<FORMAT,-1>(SPU,chan);
  else if (chan->pan == 0)
    ____SPU_ChanUpdate<FORMAT,0>(SPU,chan);
  else if (chan->pan == 127)
    ____SPU_ChanUpdate<FORMAT,2>(SPU,chan);
  else
    ____SPU_ChanUpdate<FORMAT,1>(SPU,chan);
}

FORCEINLINE static void _SPU_ChanUpdate(const bool actuallyMix, SPU_struct* const SPU, channel_struct* const chan)
{
  switch(chan->format)
  {
    case 0: ___SPU_ChanUpdate<0>(actuallyMix, SPU, chan); break;
    case 1: ___SPU_ChanUpdate<1>(actuallyMix, SPU, chan); break;
    case 2: ___SPU_ChanUpdate<2>(actuallyMix, SPU, chan); break;
    case 3: ___SPU_ChanUpdate<3>(actuallyMix, SPU, chan); break;
    default: assert(false);
  }
}

//ENTERNEW
static void SPU_MixAudio_Advanced(bool actuallyMix, SPU_struct *SPU, int length)
{
  //the advanced spu function correctly handles all sound control mixing options, as well as capture
  //this code is not entirely optimal, as it relies on sort of manhandling the core mixing functions
  //in order to get the results it needs.

  //THIS IS MAX HACKS!!!!
  //AND NEEDS TO BE REWRITTEN ALONG WITH THE DEEPEST PARTS OF THE SPU
  //ONCE WE KNOW THAT IT WORKS

  //BIAS gets ignored since our spu is still not bit perfect,
  //and it doesnt matter for purposes of capture

  //-----------DEBUG CODE
  bool skipcap = false;
  //-----------------

  s32 samp0[2] = {0,0};

  //believe it or not, we are going to do this one sample at a time.
  //like i said, it is slower.
  for (int samp = 0; samp < length; samp++)
  {
    SPU->sndbuf[0] = 0;
    SPU->sndbuf[1] = 0;
    SPU->buflength = 1;

    s32 capmix[2] = {0,0};
    s32 mix[2] = {0,0};
    s32 chanout[16];
    s32 submix[32];

    //generate each channel, and helpfully mix it at the same time
    for (int i = 0; i < 16; i++)
    {
      channel_struct *chan = &SPU->channels[i];

      if (chan->status == CHANSTAT_PLAY)
      {
        SPU->bufpos = 0;

        bool bypass = false;
        if (i==1 && SPU->regs.ctl_ch1bypass) bypass=true;
        if (i==3 && SPU->regs.ctl_ch3bypass) bypass=true;


        //output to mixer unless we are bypassed.
        //dont output to mixer if the user muted us
        bool outputToMix = true;
        if (CommonSettings.spu_muteChannels[i]) outputToMix = false;
        if (bypass) outputToMix = false;
        bool outputToCap = outputToMix;
        if (CommonSettings.spu_captureMuted && !bypass) outputToCap = true;

        //channels 1 and 3 should probably always generate their audio
        //internally at least, just in case they get used by the spu output
        bool domix = outputToCap || outputToMix || i==1 || i==3;

        //clear the output buffer since this is where _SPU_ChanUpdate wants to accumulate things
        SPU->sndbuf[0] = SPU->sndbuf[1] = 0;

        //get channel's next output sample.
        _SPU_ChanUpdate(domix, SPU, chan);
        chanout[i] = SPU->lastdata >> volume_shift[chan->volumeDiv];

        //save the panned results
        submix[i*2] = SPU->sndbuf[0];
        submix[i*2+1] = SPU->sndbuf[1];

        //send sample to our capture mix
        if (outputToCap)
        {
          capmix[0] += submix[i*2];
          capmix[1] += submix[i*2+1];
        }

        //send sample to our main mixer
        if (outputToMix)
        {
          mix[0] += submix[i*2];
          mix[1] += submix[i*2+1];
        }
      }
      else
      {
        chanout[i] = 0;
        submix[i*2] = 0;
        submix[i*2+1] = 0;
      }
    } //foreach channel

    s32 mixout[2] = {mix[0],mix[1]};
    s32 capmixout[2] = {capmix[0],capmix[1]};
    s32 sndout[2] = { 0, 0 };
    s32 capout[2];

    //create SPU output
    switch (SPU->regs.ctl_left)
    {
      case SPU_struct::REGS::LOM_LEFT_MIXER: sndout[0] = mixout[0]; break;
      case SPU_struct::REGS::LOM_CH1: sndout[0] = submix[1*2+0]; break;
      case SPU_struct::REGS::LOM_CH3: sndout[0] = submix[3*2+0]; break;
      case SPU_struct::REGS::LOM_CH1_PLUS_CH3: sndout[0] = submix[1*2+0] + submix[3*2+0]; break;
    }
    switch (SPU->regs.ctl_right)
    {
      case SPU_struct::REGS::ROM_RIGHT_MIXER: sndout[1] = mixout[1]; break;
      case SPU_struct::REGS::ROM_CH1: sndout[1] = submix[1*2+1]; break;
      case SPU_struct::REGS::ROM_CH3: sndout[1] = submix[3*2+1]; break;
      case SPU_struct::REGS::ROM_CH1_PLUS_CH3: sndout[1] = submix[1*2+1] + submix[3*2+1]; break;
    }


    //generate capture output ("capture bugs" from gbatek are not emulated)
    if (SPU->regs.cap[0].source == 0)
      capout[0] = capmixout[0]; //cap0 = L-mix
    else if (SPU->regs.cap[0].add)
      capout[0] = chanout[0] + chanout[1]; //cap0 = ch0+ch1
    else capout[0] = chanout[0]; //cap0 = ch0

    if (SPU->regs.cap[1].source == 0)
      capout[1] = capmixout[1]; //cap1 = R-mix
    else if (SPU->regs.cap[1].add)
      capout[1] = chanout[2] + chanout[3]; //cap1 = ch2+ch3
    else capout[1] = chanout[2]; //cap1 = ch2

    capout[0] = MinMax(capout[0],-0x8000,0x7FFF);
    capout[1] = MinMax(capout[1],-0x8000,0x7FFF);

    //write the output sample where it is supposed to go
    if (samp == 0)
    {
      samp0[0] = sndout[0];
      samp0[1] = sndout[1];
    }
    else
    {
      SPU->sndbuf[samp*2+0] = sndout[0];
      SPU->sndbuf[samp*2+1] = sndout[1];
    }

    for (int capchan = 0; capchan < 2; capchan++)
    {
      if (SPU->regs.cap[capchan].runtime.running)
      {
        SPU_struct::REGS::CAP& cap = SPU->regs.cap[capchan];
        u32 last = sputrunc(cap.runtime.sampcnt);
        cap.runtime.sampcnt += SPU->channels[1+2*capchan].sampinc;
        u32 curr = sputrunc(cap.runtime.sampcnt);
        for (u32 j = last; j < curr; j++)
        {
          //so, this is a little strange. why go through a fifo?
          //it seems that some games will set up a reverb effect by capturing
          //to the nearly same address as playback, but ahead by a couple.
          //So, playback will always end up being what was captured a couple of samples ago.
          //This system counts on playback always having read ahead 16 samples.
          //In that case, playback will end up being what was processed at one entire buffer length ago,
          //since the 16 samples would have read ahead before they got captured over

          //It's actually the source channels which should have a fifo, but we are
          //not going to take the hit in speed and complexity. Save it for a future rewrite.
          //Instead, what we do here is delay the capture by 16 samples to create a similar effect.
          //Subjectively, it seems to be working.

          //Don't do anything until the fifo is filled, so as to delay it
          if (cap.runtime.fifo.size < 16)
          {
            cap.runtime.fifo.enqueue(capout[capchan]);
            continue;
          }

          //(actually capture sample from fifo instead of most recently generated)
          u32 multiplier;
          s32 sample = cap.runtime.fifo.dequeue();
          cap.runtime.fifo.enqueue(capout[capchan]);

          if (cap.bits8)
          {
            s8 sample8 = sample >> 8;
            if (skipcap) _MMU_write08<1,MMU_AT_DMA>(cap.runtime.curdad,0);
            else _MMU_write08<1,MMU_AT_DMA>(cap.runtime.curdad,sample8);
            cap.runtime.curdad++;
            multiplier = 4;
          }
          else
          {
            s16 sample16 = sample;
            if (skipcap) _MMU_write16<1,MMU_AT_DMA>(cap.runtime.curdad,0);
            else _MMU_write16<1,MMU_AT_DMA>(cap.runtime.curdad,sample16);
            cap.runtime.curdad+=2;
            multiplier = 2;
          }

          if (cap.runtime.curdad >= cap.runtime.maxdad)
          {
            cap.runtime.curdad = cap.dad;
            cap.runtime.sampcnt -= cap.len*multiplier;
          }
        } //sampinc loop
      } //if capchan running
    } //capchan loop
  } //main sample loop

  SPU->sndbuf[0] = samp0[0];
  SPU->sndbuf[1] = samp0[1];
}

//ENTER
static void SPU_MixAudio(bool actuallyMix, SPU_struct *SPU, int length)
{
  if (actuallyMix)
  {
    memset(SPU->sndbuf, 0, length*4*2);
    memset(SPU->outbuf, 0, length*2*2);
  }

  SPU_MixAudio_Advanced(actuallyMix, SPU, length);

  //we used to bail out if speakers were disabled.
  //this is technically wrong. sound may still be captured, or something.
  //in all likelihood, any game doing this probably master disabled the SPU also
  //so, optimization of this case is probably not necessary.
  //later, we'll just silence the output
  bool speakers = T1ReadWord(MMU.ARM7_REG, 0x304) & 0x01;

  u8 vol = SPU->regs.mastervol;

  // convert from 32-bit->16-bit
  if (actuallyMix && speakers) {
    for (int i = 0; i < length*2; i++)
    {
      // Apply Master Volume
      SPU->sndbuf[i] = spumuldiv7(SPU->sndbuf[i], vol);
      s16 outsample = MinMax(SPU->sndbuf[i],-0x8000,0x7FFF);
      SPU->outbuf[i] = outsample;
    }
  }
}

//////////////////////////////////////////////////////////////////////////////


//emulates one hline of the cpu core.
//this will produce a variable number of samples, calculated to keep a 44100hz output
//in sync with the emulator framerate
int spu_core_samples = 0;
void SPU_Emulate_core()
{
  bool needToMix = true;
  SoundInterface_struct *soundProcessor = SPU_SoundCore();

  samples += samples_per_hline;
  spu_core_samples = (int)(samples);
  samples -= spu_core_samples;

  SPU_MixAudio(needToMix, SPU_core, spu_core_samples);

  if (soundProcessor == NULL)
  {
    return;
  }

  if (soundProcessor->FetchSamples != NULL)
  {
    soundProcessor->FetchSamples(SPU_core->outbuf, spu_core_samples, synchmode, synchronizer);
  }
  else
  {
    SPU_DefaultFetchSamples(SPU_core->outbuf, spu_core_samples, synchmode, synchronizer);
  }
}

void SPU_Emulate_user(bool mix)
{
  static s16 *postProcessBuffer = NULL;
  static size_t postProcessBufferSize = 0;
  size_t freeSampleCount = 0;
  size_t processedSampleCount = 0;
  SoundInterface_struct *soundProcessor = SPU_SoundCore();

  if (soundProcessor == NULL)
  {
    return;
  }

  // Check to see how many free samples are available.
  // If there are some, fill up the output buffer.
  freeSampleCount = soundProcessor->GetAudioSpace();
  if (freeSampleCount == 0)
  {
    return;
  }

  if (freeSampleCount > buffersize)
  {
    freeSampleCount = buffersize;
  }

  // If needed, resize the post-process buffer to guarantee that
  // we can store all the sound data.
  if (postProcessBufferSize < freeSampleCount * 2 * sizeof(s16))
  {
    postProcessBufferSize = freeSampleCount * 2 * sizeof(s16);
    postProcessBuffer = (s16 *)realloc(postProcessBuffer, postProcessBufferSize);
  }

  if (soundProcessor->PostProcessSamples != NULL)
  {
    processedSampleCount = soundProcessor->PostProcessSamples(postProcessBuffer, freeSampleCount, synchmode, synchronizer);
  }
  else
  {
    processedSampleCount = SPU_DefaultPostProcessSamples(postProcessBuffer, freeSampleCount, synchmode, synchronizer);
  }

  soundProcessor->UpdateAudio(postProcessBuffer, processedSampleCount);
}

void SPU_DefaultFetchSamples(s16 *sampleBuffer, size_t sampleCount, ESynchMode synchMode, ISynchronizingAudioBuffer *theSynchronizer)
{
  theSynchronizer->enqueue_samples(sampleBuffer, sampleCount);
}

size_t SPU_DefaultPostProcessSamples(s16 *postProcessBuffer, size_t requestedSampleCount, ESynchMode synchMode, ISynchronizingAudioBuffer *theSynchronizer)
{
  return theSynchronizer->output_samples(postProcessBuffer, requestedSampleCount);
}

//////////////////////////////////////////////////////////////////////////////
// Dummy Sound Interface
//////////////////////////////////////////////////////////////////////////////

int SNDDummyInit(int buffersize);
void SNDDummyDeInit();
void SNDDummyUpdateAudio(s16 *buffer, u32 num_samples);
u32 SNDDummyGetAudioSpace();
void SNDDummyMuteAudio();
void SNDDummyUnMuteAudio();
void SNDDummySetVolume(int volume);
void SNDDummyClearBuffer();
void SNDDummyFetchSamples(s16 *sampleBuffer, size_t sampleCount, ESynchMode synchMode, ISynchronizingAudioBuffer *theSynchronizer);
size_t SNDDummyPostProcessSamples(s16 *postProcessBuffer, size_t requestedSampleCount, ESynchMode synchMode, ISynchronizingAudioBuffer *theSynchronizer);

SoundInterface_struct SNDDummy = {
  SNDCORE_DUMMY,
  "Dummy Sound Interface",
  SNDDummyInit,
  SNDDummyDeInit,
  SNDDummyUpdateAudio,
  SNDDummyGetAudioSpace,
  SNDDummyMuteAudio,
  SNDDummyUnMuteAudio,
  SNDDummySetVolume,
  SNDDummyClearBuffer,
  SNDDummyFetchSamples,
  SNDDummyPostProcessSamples
};

int SNDDummyInit(int buffersize) { return 0; }
void SNDDummyDeInit() {}
void SNDDummyUpdateAudio(s16 *buffer, u32 num_samples) { }
u32 SNDDummyGetAudioSpace() { return DESMUME_SAMPLE_RATE/60 + 5; }
void SNDDummyMuteAudio() {}
void SNDDummyUnMuteAudio() {}
void SNDDummySetVolume(int volume) {}
void SNDDummyClearBuffer() {}
void SNDDummyFetchSamples(s16 *sampleBuffer, size_t sampleCount, ESynchMode synchMode, ISynchronizingAudioBuffer *theSynchronizer) {}
size_t SNDDummyPostProcessSamples(s16 *postProcessBuffer, size_t requestedSampleCount, ESynchMode synchMode, ISynchronizingAudioBuffer *theSynchronizer) { return 0; }

