/////////////////////////////////////////////////////////////////////////////
//
// spucore - Emulates a single SPU CORE
//
/////////////////////////////////////////////////////////////////////////////

#ifndef EMU_COMPILE
#error "Hi I forgot to set EMU_COMPILE"
#endif

#include "spucore.h"

////////////////////////////////////////////////////////////////////////////////
/*
** Key-on defer
**
** Bit of a hack I did to stop the clicking in Final Fantasy Tactics
** If there's a key-on while the envelope is still active, the key-on is
** deferred by this many samples while the existing channel is microramped
** down to zero (works best if it's a power of 2)
*/

//#define KEYON_DEFER_SAMPLES (64)
#define KEYON_DEFER_SAMPLES (64)

//
// Render max samples 
//
#define RENDERMAX (200)

////////////////////////////////////////////////////////////////////////////////
/*
** Static information
*/
static const sint16 gauss_shuffled_reverse_table[1024] = {
 0x12c7, 0x59b3, 0x1307, 0xffff, 0x1288, 0x59b2, 0x1347, 0xffff, 0x1249, 0x59b0, 0x1388, 0xffff, 0x120b, 0x59ad, 0x13c9, 0xffff,
 0x11cd, 0x59a9, 0x140b, 0xffff, 0x118f, 0x59a4, 0x144d, 0xffff, 0x1153, 0x599e, 0x1490, 0xffff, 0x1116, 0x5997, 0x14d4, 0xffff,
 0x10db, 0x598f, 0x1517, 0xffff, 0x109f, 0x5986, 0x155c, 0xffff, 0x1065, 0x597c, 0x15a0, 0xffff, 0x102a, 0x5971, 0x15e6, 0xffff,
 0x0ff1, 0x5965, 0x162c, 0xffff, 0x0fb7, 0x5958, 0x1672, 0xffff, 0x0f7f, 0x5949, 0x16b9, 0xffff, 0x0f46, 0x593a, 0x1700, 0xffff,
 0x0f0f, 0x592a, 0x1747, 0x0000, 0x0ed7, 0x5919, 0x1790, 0x0000, 0x0ea1, 0x5907, 0x17d8, 0x0000, 0x0e6b, 0x58f4, 0x1821, 0x0000,
 0x0e35, 0x58e0, 0x186b, 0x0000, 0x0e00, 0x58cb, 0x18b5, 0x0000, 0x0dcb, 0x58b5, 0x1900, 0x0000, 0x0d97, 0x589e, 0x194b, 0x0001,
 0x0d63, 0x5886, 0x1996, 0x0001, 0x0d30, 0x586d, 0x19e2, 0x0001, 0x0cfd, 0x5853, 0x1a2e, 0x0001, 0x0ccb, 0x5838, 0x1a7b, 0x0002,
 0x0c99, 0x581c, 0x1ac8, 0x0002, 0x0c68, 0x57ff, 0x1b16, 0x0002, 0x0c38, 0x57e2, 0x1b64, 0x0003, 0x0c07, 0x57c3, 0x1bb3, 0x0003,
 0x0bd8, 0x57a3, 0x1c02, 0x0003, 0x0ba9, 0x5782, 0x1c51, 0x0004, 0x0b7a, 0x5761, 0x1ca1, 0x0004, 0x0b4c, 0x573e, 0x1cf1, 0x0005,
 0x0b1e, 0x571b, 0x1d42, 0x0005, 0x0af1, 0x56f6, 0x1d93, 0x0006, 0x0ac4, 0x56d1, 0x1de5, 0x0007, 0x0a98, 0x56ab, 0x1e37, 0x0007,
 0x0a6c, 0x5684, 0x1e89, 0x0008, 0x0a40, 0x565b, 0x1edc, 0x0009, 0x0a16, 0x5632, 0x1f2f, 0x0009, 0x09eb, 0x5609, 0x1f82, 0x000a,
 0x09c1, 0x55de, 0x1fd6, 0x000b, 0x0998, 0x55b2, 0x202a, 0x000c, 0x096f, 0x5585, 0x207f, 0x000d, 0x0946, 0x5558, 0x20d4, 0x000e,
 0x091e, 0x5529, 0x2129, 0x000f, 0x08f7, 0x54fa, 0x217f, 0x0010, 0x08d0, 0x54ca, 0x21d5, 0x0011, 0x08a9, 0x5499, 0x222c, 0x0012,
 0x0883, 0x5467, 0x2282, 0x0013, 0x085d, 0x5434, 0x22da, 0x0015, 0x0838, 0x5401, 0x2331, 0x0016, 0x0813, 0x53cc, 0x2389, 0x0018,
 0x07ef, 0x5397, 0x23e1, 0x0019, 0x07cb, 0x5361, 0x2439, 0x001b, 0x07a7, 0x532a, 0x2492, 0x001c, 0x0784, 0x52f3, 0x24eb, 0x001e,
 0x0762, 0x52ba, 0x2545, 0x0020, 0x0740, 0x5281, 0x259e, 0x0021, 0x071e, 0x5247, 0x25f8, 0x0023, 0x06fd, 0x520c, 0x2653, 0x0025,
 0x06dc, 0x51d0, 0x26ad, 0x0027, 0x06bb, 0x5194, 0x2708, 0x0029, 0x069b, 0x5156, 0x2763, 0x002c, 0x067c, 0x5118, 0x27be, 0x002e,
 0x065c, 0x50da, 0x281a, 0x0030, 0x063e, 0x509a, 0x2876, 0x0033, 0x061f, 0x505a, 0x28d2, 0x0035, 0x0601, 0x5019, 0x292e, 0x0038,
 0x05e4, 0x4fd7, 0x298b, 0x003a, 0x05c7, 0x4f95, 0x29e7, 0x003d, 0x05aa, 0x4f52, 0x2a44, 0x0040, 0x058e, 0x4f0e, 0x2aa1, 0x0043,
 0x0572, 0x4ec9, 0x2aff, 0x0046, 0x0556, 0x4e84, 0x2b5c, 0x0049, 0x053b, 0x4e3e, 0x2bba, 0x004d, 0x0520, 0x4df7, 0x2c18, 0x0050,
 0x0506, 0x4db0, 0x2c76, 0x0054, 0x04ec, 0x4d68, 0x2cd4, 0x0057, 0x04d2, 0x4d20, 0x2d33, 0x005b, 0x04b9, 0x4cd7, 0x2d91, 0x005f,
 0x04a0, 0x4c8d, 0x2df0, 0x0063, 0x0488, 0x4c42, 0x2e4f, 0x0067, 0x0470, 0x4bf7, 0x2eae, 0x006b, 0x0458, 0x4bac, 0x2f0d, 0x006f,
 0x0441, 0x4b5f, 0x2f6c, 0x0074, 0x042a, 0x4b13, 0x2fcc, 0x0078, 0x0413, 0x4ac5, 0x302b, 0x007d, 0x03fc, 0x4a77, 0x308b, 0x0082,
 0x03e7, 0x4a29, 0x30ea, 0x0087, 0x03d1, 0x49d9, 0x314a, 0x008c, 0x03bc, 0x498a, 0x31aa, 0x0091, 0x03a7, 0x493a, 0x3209, 0x0096,
 0x0392, 0x48e9, 0x3269, 0x009c, 0x037e, 0x4898, 0x32c9, 0x00a1, 0x036a, 0x4846, 0x3329, 0x00a7, 0x0356, 0x47f4, 0x3389, 0x00ad,
 0x0343, 0x47a1, 0x33e9, 0x00b3, 0x0330, 0x474e, 0x3449, 0x00ba, 0x031d, 0x46fa, 0x34a9, 0x00c0, 0x030b, 0x46a6, 0x3509, 0x00c7,
 0x02f9, 0x4651, 0x3569, 0x00cd, 0x02e7, 0x45fc, 0x35c9, 0x00d4, 0x02d6, 0x45a6, 0x3629, 0x00db, 0x02c4, 0x4550, 0x3689, 0x00e3,
 0x02b4, 0x44fa, 0x36e8, 0x00ea, 0x02a3, 0x44a3, 0x3748, 0x00f2, 0x0293, 0x444c, 0x37a8, 0x00fa, 0x0283, 0x43f4, 0x3807, 0x0101,
 0x0273, 0x439c, 0x3867, 0x010a, 0x0264, 0x4344, 0x38c6, 0x0112, 0x0255, 0x42eb, 0x3926, 0x011b, 0x0246, 0x4292, 0x3985, 0x0123,
 0x0237, 0x4239, 0x39e4, 0x012c, 0x0229, 0x41df, 0x3a43, 0x0135, 0x021b, 0x4185, 0x3aa2, 0x013f, 0x020d, 0x412a, 0x3b00, 0x0148,
 0x0200, 0x40d0, 0x3b5f, 0x0152, 0x01f2, 0x4074, 0x3bbd, 0x015c, 0x01e5, 0x4019, 0x3c1b, 0x0166, 0x01d9, 0x3fbd, 0x3c79, 0x0171,
 0x01cc, 0x3f62, 0x3cd7, 0x017b, 0x01c0, 0x3f05, 0x3d35, 0x0186, 0x01b4, 0x3ea9, 0x3d92, 0x0191, 0x01a8, 0x3e4c, 0x3def, 0x019c,
 0x019c, 0x3def, 0x3e4c, 0x01a8, 0x0191, 0x3d92, 0x3ea9, 0x01b4, 0x0186, 0x3d35, 0x3f05, 0x01c0, 0x017b, 0x3cd7, 0x3f62, 0x01cc,
 0x0171, 0x3c79, 0x3fbd, 0x01d9, 0x0166, 0x3c1b, 0x4019, 0x01e5, 0x015c, 0x3bbd, 0x4074, 0x01f2, 0x0152, 0x3b5f, 0x40d0, 0x0200,
 0x0148, 0x3b00, 0x412a, 0x020d, 0x013f, 0x3aa2, 0x4185, 0x021b, 0x0135, 0x3a43, 0x41df, 0x0229, 0x012c, 0x39e4, 0x4239, 0x0237,
 0x0123, 0x3985, 0x4292, 0x0246, 0x011b, 0x3926, 0x42eb, 0x0255, 0x0112, 0x38c6, 0x4344, 0x0264, 0x010a, 0x3867, 0x439c, 0x0273,
 0x0101, 0x3807, 0x43f4, 0x0283, 0x00fa, 0x37a8, 0x444c, 0x0293, 0x00f2, 0x3748, 0x44a3, 0x02a3, 0x00ea, 0x36e8, 0x44fa, 0x02b4,
 0x00e3, 0x3689, 0x4550, 0x02c4, 0x00db, 0x3629, 0x45a6, 0x02d6, 0x00d4, 0x35c9, 0x45fc, 0x02e7, 0x00cd, 0x3569, 0x4651, 0x02f9,
 0x00c7, 0x3509, 0x46a6, 0x030b, 0x00c0, 0x34a9, 0x46fa, 0x031d, 0x00ba, 0x3449, 0x474e, 0x0330, 0x00b3, 0x33e9, 0x47a1, 0x0343,
 0x00ad, 0x3389, 0x47f4, 0x0356, 0x00a7, 0x3329, 0x4846, 0x036a, 0x00a1, 0x32c9, 0x4898, 0x037e, 0x009c, 0x3269, 0x48e9, 0x0392,
 0x0096, 0x3209, 0x493a, 0x03a7, 0x0091, 0x31aa, 0x498a, 0x03bc, 0x008c, 0x314a, 0x49d9, 0x03d1, 0x0087, 0x30ea, 0x4a29, 0x03e7,
 0x0082, 0x308b, 0x4a77, 0x03fc, 0x007d, 0x302b, 0x4ac5, 0x0413, 0x0078, 0x2fcc, 0x4b13, 0x042a, 0x0074, 0x2f6c, 0x4b5f, 0x0441,
 0x006f, 0x2f0d, 0x4bac, 0x0458, 0x006b, 0x2eae, 0x4bf7, 0x0470, 0x0067, 0x2e4f, 0x4c42, 0x0488, 0x0063, 0x2df0, 0x4c8d, 0x04a0,
 0x005f, 0x2d91, 0x4cd7, 0x04b9, 0x005b, 0x2d33, 0x4d20, 0x04d2, 0x0057, 0x2cd4, 0x4d68, 0x04ec, 0x0054, 0x2c76, 0x4db0, 0x0506,
 0x0050, 0x2c18, 0x4df7, 0x0520, 0x004d, 0x2bba, 0x4e3e, 0x053b, 0x0049, 0x2b5c, 0x4e84, 0x0556, 0x0046, 0x2aff, 0x4ec9, 0x0572,
 0x0043, 0x2aa1, 0x4f0e, 0x058e, 0x0040, 0x2a44, 0x4f52, 0x05aa, 0x003d, 0x29e7, 0x4f95, 0x05c7, 0x003a, 0x298b, 0x4fd7, 0x05e4,
 0x0038, 0x292e, 0x5019, 0x0601, 0x0035, 0x28d2, 0x505a, 0x061f, 0x0033, 0x2876, 0x509a, 0x063e, 0x0030, 0x281a, 0x50da, 0x065c,
 0x002e, 0x27be, 0x5118, 0x067c, 0x002c, 0x2763, 0x5156, 0x069b, 0x0029, 0x2708, 0x5194, 0x06bb, 0x0027, 0x26ad, 0x51d0, 0x06dc,
 0x0025, 0x2653, 0x520c, 0x06fd, 0x0023, 0x25f8, 0x5247, 0x071e, 0x0021, 0x259e, 0x5281, 0x0740, 0x0020, 0x2545, 0x52ba, 0x0762,
 0x001e, 0x24eb, 0x52f3, 0x0784, 0x001c, 0x2492, 0x532a, 0x07a7, 0x001b, 0x2439, 0x5361, 0x07cb, 0x0019, 0x23e1, 0x5397, 0x07ef,
 0x0018, 0x2389, 0x53cc, 0x0813, 0x0016, 0x2331, 0x5401, 0x0838, 0x0015, 0x22da, 0x5434, 0x085d, 0x0013, 0x2282, 0x5467, 0x0883,
 0x0012, 0x222c, 0x5499, 0x08a9, 0x0011, 0x21d5, 0x54ca, 0x08d0, 0x0010, 0x217f, 0x54fa, 0x08f7, 0x000f, 0x2129, 0x5529, 0x091e,
 0x000e, 0x20d4, 0x5558, 0x0946, 0x000d, 0x207f, 0x5585, 0x096f, 0x000c, 0x202a, 0x55b2, 0x0998, 0x000b, 0x1fd6, 0x55de, 0x09c1,
 0x000a, 0x1f82, 0x5609, 0x09eb, 0x0009, 0x1f2f, 0x5632, 0x0a16, 0x0009, 0x1edc, 0x565b, 0x0a40, 0x0008, 0x1e89, 0x5684, 0x0a6c,
 0x0007, 0x1e37, 0x56ab, 0x0a98, 0x0007, 0x1de5, 0x56d1, 0x0ac4, 0x0006, 0x1d93, 0x56f6, 0x0af1, 0x0005, 0x1d42, 0x571b, 0x0b1e,
 0x0005, 0x1cf1, 0x573e, 0x0b4c, 0x0004, 0x1ca1, 0x5761, 0x0b7a, 0x0004, 0x1c51, 0x5782, 0x0ba9, 0x0003, 0x1c02, 0x57a3, 0x0bd8,
 0x0003, 0x1bb3, 0x57c3, 0x0c07, 0x0003, 0x1b64, 0x57e2, 0x0c38, 0x0002, 0x1b16, 0x57ff, 0x0c68, 0x0002, 0x1ac8, 0x581c, 0x0c99,
 0x0002, 0x1a7b, 0x5838, 0x0ccb, 0x0001, 0x1a2e, 0x5853, 0x0cfd, 0x0001, 0x19e2, 0x586d, 0x0d30, 0x0001, 0x1996, 0x5886, 0x0d63,
 0x0001, 0x194b, 0x589e, 0x0d97, 0x0000, 0x1900, 0x58b5, 0x0dcb, 0x0000, 0x18b5, 0x58cb, 0x0e00, 0x0000, 0x186b, 0x58e0, 0x0e35,
 0x0000, 0x1821, 0x58f4, 0x0e6b, 0x0000, 0x17d8, 0x5907, 0x0ea1, 0x0000, 0x1790, 0x5919, 0x0ed7, 0x0000, 0x1747, 0x592a, 0x0f0f,
 0xffff, 0x1700, 0x593a, 0x0f46, 0xffff, 0x16b9, 0x5949, 0x0f7f, 0xffff, 0x1672, 0x5958, 0x0fb7, 0xffff, 0x162c, 0x5965, 0x0ff1,
 0xffff, 0x15e6, 0x5971, 0x102a, 0xffff, 0x15a0, 0x597c, 0x1065, 0xffff, 0x155c, 0x5986, 0x109f, 0xffff, 0x1517, 0x598f, 0x10db,
 0xffff, 0x14d4, 0x5997, 0x1116, 0xffff, 0x1490, 0x599e, 0x1153, 0xffff, 0x144d, 0x59a4, 0x118f, 0xffff, 0x140b, 0x59a9, 0x11cd,
 0xffff, 0x13c9, 0x59ad, 0x120b, 0xffff, 0x1388, 0x59b0, 0x1249, 0xffff, 0x1347, 0x59b2, 0x1288, 0xffff, 0x1307, 0x59b3, 0x12c7,
};

static sint32 ratelogtable[32+128];

/*
// v1.10 coefs - normalized from v1.04
static const sint32 reverb_lowpass_coefs[8] = {
  (int)((-0.036346113709214548)*(2048.0)),
  (int)(( 0.044484956332843419)*(2048.0)),
  (int)(( 0.183815456609675380)*(2048.0)),
  (int)(( 0.308045700766695740)*(2048.0)),
  (int)(( 0.308045700766695740)*(2048.0)),
  (int)(( 0.183815456609675380)*(2048.0)),
  (int)(( 0.044484956332843419)*(2048.0)),
  (int)((-0.036346113709214548)*(2048.0))
};
*/

/*
// test coefs - ganked from LAME's blackman function
// (11-point filter, but only a few points were nonzero)
static const sint32 reverb_new_lowpass_coefs[3] = {
  (int)((-0.02134438446523164)*(2048.0)),
// skip one
  (int)(( 0.27085135668587779)*(2048.0)),
  (int)(( 0.50098605555870768)*(2048.0))
// rest are symmetrical
};
*/

// PS1 reverb downsampling coefficients(as best as I could extract them at the moment, some of the even(non-zero/non-16384) ones *might* be off by 1.
// -------------
#if 1
static const sint32 reverb_psx_lowpass_coefs[11] = {
   -1,
//  0,
    2,
//  0,
  -10,
//  0,
   35,
//  0,
 -103,
//  0,
  266,
//  0,
 -616,
//  0,
 1332,
//  0,
-2960,
//  0,
10246,
16384
};
#else
static const sint32 reverb_psx_lowpass_coefs[48] = {
   -1,
    0,
    2,
    0,
  -10,
    0,
   35,
    0,
 -103,
    0,
  266,
    0,
 -616,
    0,
 1332,
    0,
-2960,
    0,
10246,
16384,
10246,
    0,
-2960,
    0,
 1332,
    0,
 -616,
    0,
  266,
    0,
 -103,
    0,
   35,
    0,
  -10,
    0,
    2,
    0,
   -1,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
};
#endif


static const int noisetable[] = {
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1
};

/*
** Static init
*/
sint32 EMU_CALL spucore_init(void) {
  sint32 i;

  memset(ratelogtable, 0, sizeof(ratelogtable));
  ratelogtable[32-8] = 1;
  ratelogtable[32-7] = 1;
  ratelogtable[32-6] = 1;
  ratelogtable[32-5] = 1;
  ratelogtable[32-4] = 2;
  ratelogtable[32-3] = 2;
  ratelogtable[32-2] = 3;
  ratelogtable[32-1] = 3;
  ratelogtable[32+0] = 4;
  ratelogtable[32+1] = 5;
  ratelogtable[32+2] = 6;
  ratelogtable[32+3] = 7;
  for(i = 4; i < 128; i++) {
    uint32 n = 2*ratelogtable[32+i-4];
    if(n > 0x20000000) n = 0x20000000;
    ratelogtable[32+i] = n;
  }

  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/*
** State information
*/
#define SPUCORESTATE ((struct SPUCORE_STATE*)(state))

#define SAMPLE_STATE_OFF    (0)
#define SAMPLE_STATE_ENDING (1)
#define SAMPLE_STATE_ON     (2)

struct SPUCORE_SAMPLE {
  uint8 state;
  uint8 array_cleared;
  sint32 array[32];
  uint32 phase;

  uint32 block_addr;
  uint32 start_block_addr;
  //uint32 start_loop_block_addr;
  uint32 loop_block_addr;
};

#define ENVELOPE_STATE_OFF     (0)
#define ENVELOPE_STATE_ATTACK  (1)
#define ENVELOPE_STATE_DECAY   (2)
#define ENVELOPE_STATE_SUSTAIN (3)
#define ENVELOPE_STATE_RELEASE (4)

struct SPUCORE_ENVELOPE {
  uint32 reg_ad;
  uint32 reg_sr;
  sint32 level;
  sint32 delta;
  int    state;
  sint32 cachemax;
};

////////////////////////////////////////////////////////////////////////////////
/*
** Volumes with increase/decrease modes
*/
struct SPUCORE_VOLUME {
  uint16 mode;
  sint32 level;
};

static EMU_INLINE void EMU_CALL volume_setmode(struct SPUCORE_VOLUME *vol, uint16 mode) {
  vol->mode = mode;
  if(mode & 0x8000) {
  } else {
    vol->level = mode;
    vol->level <<= 17;
    vol->level >>= 1;
  }
}

static EMU_INLINE uint16 EMU_CALL volume_getmode(struct SPUCORE_VOLUME *vol) {
  return vol->mode;
}

static EMU_INLINE sint32 EMU_CALL volume_getlevel(struct SPUCORE_VOLUME *vol) {
  return (vol->level) >> 15;
}

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

struct SPUCORE_CHAN {
  struct SPUCORE_VOLUME vol[2];
  uint32 voice_pitch;
  struct SPUCORE_SAMPLE   sample;
  struct SPUCORE_ENVELOPE env;
  int samples_until_pending_keyon;
};

/*
** Reverb resample state
*/
struct SPUCORE_REVERB_RESAMPLER {
  sint32  in_queue_l[64];
  sint32  in_queue_r[64];
  sint32 out_queue_l[16];
  sint32 out_queue_r[16];
  int queue_index;
};

struct SPUCORE_REVERB {
  uint32 FB_SRC_A   ;
  uint32 FB_SRC_B   ;
  uint16 IIR_ALPHA  ;
  uint16 ACC_COEF_A ;
  uint16 ACC_COEF_B ;
  uint16 ACC_COEF_C ;
  uint16 ACC_COEF_D ;
  uint16 IIR_COEF   ;
  uint16 FB_ALPHA   ;
  uint16 FB_X       ;
  uint32 IIR_DEST_A0;
  uint32 IIR_DEST_A1;
  uint32 ACC_SRC_A0 ;
  uint32 ACC_SRC_A1 ;
  uint32 ACC_SRC_B0 ;
  uint32 ACC_SRC_B1 ;
  uint32 IIR_SRC_A0 ;
  uint32 IIR_SRC_A1 ;
  uint32 IIR_DEST_B0;
  uint32 IIR_DEST_B1;
  uint32 ACC_SRC_C0 ;
  uint32 ACC_SRC_C1 ;
  uint32 ACC_SRC_D0 ;
  uint32 ACC_SRC_D1 ;
  uint32 IIR_SRC_B1 ;
  uint32 IIR_SRC_B0 ;
  uint32 MIX_DEST_A0;
  uint32 MIX_DEST_A1;
  uint32 MIX_DEST_B0;
  uint32 MIX_DEST_B1;
  uint16 IN_COEF_L  ;
  uint16 IN_COEF_R  ;

  uint32 start_address;
  uint32 end_address;

  sint32 current_address;

  sint32 safe_start_address;
  sint32 safe_end_address;
  sint32 safe_size;

  struct SPUCORE_REVERB_RESAMPLER resampler;
};

struct SPUCORE_STATE {
  uint32 flags;
  sint32 memsize;
  struct SPUCORE_CHAN chan[24];
  struct SPUCORE_REVERB reverb;
  struct SPUCORE_VOLUME mvol[2];
  sint16 evol[2];
  sint16 avol[2];
  sint16 bvol[2];
  uint32 kon;
  uint32 koff;
  uint32 fm;
  uint32 noise;
  uint32 vmix[2];
  uint32 vmixe[2];
  uint32 irq_address;
  uint32 noiseclock;
  uint32 noisecounter;
  sint32 noiseval;
  uint32 irq_decoder_clock;
  uint32 irq_triggered_cycle;
};

struct SPUCORE_IRQ_STATE {
  uint32 offset;
  uint32 triggered_cycle;
};

uint32 EMU_CALL spucore_get_state_size(void) {
  return(sizeof(struct SPUCORE_STATE));
}

/*
** Initialize SPU CORE state
*/
void EMU_CALL spucore_clear_state(void *state) {
  /*
  ** Clear to zero
  */
  memset(state, 0, sizeof(struct SPUCORE_STATE));
  /*
  ** Set other defaults
  */
  SPUCORESTATE->memsize = 0x80000;
  spucore_setreg(state, SPUREG_EEA, 0x7FFFF, 0xFFFFFFFF);
  spucore_setreg(state, SPUREG_VMIX, 0x00FFFFFF, 0xFFFFFFFF);
  spucore_setflag(state, SPUREG_FLAG_MSNDL , 1);
  spucore_setflag(state, SPUREG_FLAG_MSNDR , 1);
  spucore_setflag(state, SPUREG_FLAG_MSNDEL, 1);
  spucore_setflag(state, SPUREG_FLAG_MSNDER, 1);
  spucore_setflag(state, SPUREG_FLAG_SINL, 1);
  spucore_setflag(state, SPUREG_FLAG_SINR, 1);
  SPUCORESTATE->irq_triggered_cycle = 0xFFFFFFFF;
}

void EMU_CALL spucore_set_mem_size(void *state, uint32 size) {
  SPUCORESTATE->memsize = (sint32)size;
  spucore_setreg(state, SPUREG_EEA, size-1, 0xFFFFFFFF);
}

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

static void EMU_CALL make_safe_reverb_addresses(struct SPUCORE_STATE *state) {
  struct SPUCORE_REVERB *r = &(state->reverb);
  sint32 sa = r->start_address;
  sint32 ea = r->end_address;

//EMUTRACE2("[sa,ea=%X,%X]\n",sa,ea);

  ea += 0x20000;
  ea &= (~0x1FFFF);
  sa &= (~1);

  if(ea > state->memsize) ea = state->memsize;
  if(ea < 0x20000) ea = 0x20000;
  if(sa > ea) {
    sa &= 0x1FFFE;
    sa += ea;
    sa -= 0x20000;
  }

  r->safe_start_address = sa;
  r->safe_end_address   = ea;
  r->safe_size          = ea-sa;

  r->current_address &= (~1);
  if(
    (r->current_address < r->safe_start_address) ||
    (r->current_address >= r->safe_end_address)
  ) {
    r->current_address = r->safe_start_address;
  }
}

////////////////////////////////////////////////////////////////////////////////
/*
** ADPCM sample decoding
*/
/*
#define SPUCORE_PREDICT_SKEL(prednum,coef1,coef2) \
static void EMU_CALL spucore_predict_##prednum(uint8 *src, sint32 *dest, uint32 shift) {  \
  uint32 i;                                                                               \
  sint32 p_a = dest[-2];                                                                  \
  sint32 p_b = dest[-1];                                                                  \
  shift += 16;                                                                            \
  for(i = 0; i < 14; i++) {                                                               \
    sint32 a = src[i^(EMU_ENDIAN_XOR(1))];                                                \
    sint32 b = (a&0xF0)<<24;                                                              \
    a = a << 28; b >>= shift; a >>= shift;                                                \
    a += ( ( ((coef1)*p_b) + ((coef2)*p_a) )+32)>>6;                                      \
    if(a > 32767) { a = 32767; } if(a < (-32768)) { a = (-32768); }                       \
    *dest++ = a;                                                                          \
    b += ( ( ((coef1)* a ) + ((coef2)*p_b) )+32)>>6;                                      \
    if(b > 32767) { b = 32767; } if(b < (-32768)) { b = (-32768); }                       \
    *dest++ = b;                                                                          \
    p_a = a; p_b = b;                                                                     \
  }                                                                                       \
}
*/

#define SPUCORE_PREDICT_SKEL(prednum,coef1,coef2) \
static void EMU_CALL spucore_predict_##prednum(uint16 *src, sint32 *dest, uint32 shift) { \
  uint32 i;                                                                               \
  sint32 p_a = dest[-2];                                                                  \
  sint32 p_b = dest[-1];                                                                  \
  shift += 16;                                                                            \
  for(i = 0; i < 7; i++) {                                                                \
    sint32 a = *src++;                                                                    \
    sint32 b = (a&0x00F0)<<24;                                                            \
    sint32 c = (a&0x0F00)<<20;                                                            \
    sint32 d = (a&0xF000)<<16;                                                            \
    a <<= 28;                                                                             \
    a >>= shift;                                                                          \
    b >>= shift;                                                                          \
    c >>= shift;                                                                          \
    d >>= shift;                                                                          \
    a += ( ( ((coef1)*p_b) + ((coef2)*p_a) )+32)>>6;                                      \
    if(a > 32767) { a = 32767; } if(a < (-32768)) { a = (-32768); }                       \
    *dest++ = a;                                                                          \
    b += ( ( ((coef1)* a ) + ((coef2)*p_b) )+32)>>6;                                      \
    if(b > 32767) { b = 32767; } if(b < (-32768)) { b = (-32768); }                       \
    *dest++ = b;                                                                          \
    c += ( ( ((coef1)* b ) + ((coef2)* a ) )+32)>>6;                                      \
    if(b > 32767) { b = 32767; } if(b < (-32768)) { b = (-32768); }                       \
    *dest++ = c;                                                                          \
    d += ( ( ((coef1)* c ) + ((coef2)* b ) )+32)>>6;                                      \
    if(b > 32767) { b = 32767; } if(b < (-32768)) { b = (-32768); }                       \
    *dest++ = d;                                                                          \
    p_a = c; p_b = d;                                                                     \
  }                                                                                       \
}

SPUCORE_PREDICT_SKEL(0,0,0)
SPUCORE_PREDICT_SKEL(1,60,0)
SPUCORE_PREDICT_SKEL(2,115,-52)
SPUCORE_PREDICT_SKEL(3,98,-55)
SPUCORE_PREDICT_SKEL(4,122,-60)

typedef void (EMU_CALL * spucore_predict_callback_t)(uint16*, sint32*, uint32);

static spucore_predict_callback_t spucore_predict[8] = {
  spucore_predict_0, spucore_predict_1, spucore_predict_2, spucore_predict_3,
  spucore_predict_4, spucore_predict_1, spucore_predict_2, spucore_predict_3
};

static void EMU_CALL decode_sample_block(
  uint16 *ram,
  uint32 memmax,
  struct SPUCORE_SAMPLE *sample,
  int skip
) {
//  uint32 i;
  if(sample->state != SAMPLE_STATE_ON) {
    if(!sample->array_cleared) {
      memset(sample->array, 0, sizeof(sample->array));
      sample->array_cleared = 1;
    }
    sample->state = SAMPLE_STATE_OFF;
  } else {
    /* temporary hack to avoid memory problems */
    sample->block_addr &= (memmax-1);
    if((sample->block_addr + 0x10) > memmax) sample->block_addr -= 0x10;
    ram += sample->block_addr >> 1;
    /* decode */
    if(skip) {
      if(!sample->array_cleared) {
        memset(sample->array, 0, sizeof(sample->array));
        sample->array_cleared = 1;
      }
    } else {
      sample->array[0] = sample->array[28];
      sample->array[1] = sample->array[29];
      sample->array[2] = sample->array[30];
      sample->array[3] = sample->array[31];
      (spucore_predict[(ram[0]>>4)&7])(
        ram + 1,
        sample->array + 4,
        ram[0] & 0xF
      );
    }
    /* set loop start address if necessary */
    if(ram[0]&0x0400) {
      sample->loop_block_addr = sample->block_addr;
    }
    /* sample end? */
    if(ram[0]&0x0100) {
      /* loop? */
      if(ram[0]&0x0200) {
        sample->block_addr = sample->loop_block_addr;
      /* no loop, just end */
      } else {
        sample->state = SAMPLE_STATE_ENDING;
      }
    } else {
      /* advance to next block */
      sample->block_addr += 16;
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// Returns the number of samples actually generated
// If dest is null, it means "fast forward" - don't render anything
//
//
// output of resampler() is guaranteed clipped, as long as the output of
// decode_sample_block() is
//
static uint32 EMU_CALL resampler(
  uint16 *ram,
  uint32 memmax,
  struct SPUCORE_SAMPLE *sample,
  sint32 *dest,
  uint32 n,
  uint32 phase_inc,
  struct SPUCORE_IRQ_STATE *irq_state
) {
  uint32 irq_triggered_cycle = 0xFFFFFFFF;
  uint32 s;
  uint32 ph; //, phl;
  ph = sample->phase;
  if(!dest) {
    ph += phase_inc * n;
    s = 0;
    while(ph >= 0x1C000) {
      if(sample->state == SAMPLE_STATE_OFF) break;
      if(irq_state && irq_state->offset - sample->block_addr < 16 && irq_triggered_cycle == 0xFFFFFFFF) {
        irq_triggered_cycle = s;
      }
      decode_sample_block(ram, memmax, sample, 1);
      s += phase_inc * 28;
      ph -= 0x1C000;
    }
    s = n;
  } else {
    uint32 t = 0;
    for(s = 0; s < n; s++) {
      sint32 *source_signal;
      sint16 *mygauss;
      sint32 sum;
      if(ph >= 0x1C000) {
        if(sample->state == SAMPLE_STATE_OFF) break;
        if(irq_state && irq_state->offset - sample->block_addr < 16 && irq_triggered_cycle == 0xFFFFFFFF) {
          irq_triggered_cycle = t;
        }
        decode_sample_block(ram, memmax, sample, 0);
        ph -= 0x1C000;
      }
      source_signal = sample->array + (ph >> 12);
      mygauss = (sint16*) (((uint8*)gauss_shuffled_reverse_table) + ((ph & 0xFF0) >> 1));

      { sum =
          (source_signal[0] * mygauss[0]) +
          (source_signal[1] * mygauss[1]) +
          (source_signal[2] * mygauss[2]) +
          (source_signal[3] * mygauss[3]);
      }
      sum >>= 15;

      *dest++ = sum;
      ph += phase_inc;
      t += phase_inc;
    }
  }

  if(irq_state && irq_triggered_cycle != 0xFFFFFFFF) {
    irq_triggered_cycle = (irq_triggered_cycle * 768) >> 12;
    if(irq_triggered_cycle < irq_state->triggered_cycle) irq_state->triggered_cycle = irq_triggered_cycle;
  }

  sample->phase = ph;

  return s;
}

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

static uint32 EMU_CALL resampler_modulated(
  uint16 *ram,
  uint32 memmax,
  struct SPUCORE_SAMPLE *sample,
  sint32 *dest,
  uint32 n,
  uint32 phase_inc,
  sint32 *fmbuf,
  struct SPUCORE_IRQ_STATE *irq_state
) {
  uint32 irq_triggered_cycle = 0xFFFFFFFF;
  uint32 s, t = 0;
  uint32 ph; //, phl;
  uint32 pimod;
  ph = sample->phase;
  if(!dest) {
    for(s = 0; s < n; s++) {
      pimod = ((*fmbuf++ + 32768) * phase_inc) / 32768;
      if(pimod > 0x3FFF) pimod = 0x3FFF;
      else if(pimod < 1) pimod = 1;
      ph += pimod;
      while(ph >= 0x1C000) {
        if(sample->state == SAMPLE_STATE_OFF) break;
        if(irq_state && irq_state->offset - sample->block_addr < 16 && irq_triggered_cycle == 0xFFFFFFFF) {
          irq_triggered_cycle = t;
        }
        decode_sample_block(ram, memmax, sample, 1);
        ph -= 0x1C000;
      }
      t += pimod;
    }
  } else {
    for(s = 0; s < n; s++) {
      sint32 *source_signal;
      sint16 *mygauss;
      sint32 sum;
      if(ph >= 0x1C000) {
        if(sample->state == SAMPLE_STATE_OFF) break;
        if(irq_state && irq_state->offset - sample->block_addr < 16 && irq_triggered_cycle == 0xFFFFFFFF) {
          irq_triggered_cycle = t;
        }
        decode_sample_block(ram, memmax, sample, 0);
        ph -= 0x1C000;
      }
      source_signal = sample->array + (ph >> 12);
      mygauss = (sint16*) (((uint8*)gauss_shuffled_reverse_table) + ((ph & 0xFF0) >> 1));

      { sum =
          (source_signal[0] * mygauss[0]) +
          (source_signal[1] * mygauss[1]) +
          (source_signal[2] * mygauss[2]) +
          (source_signal[3] * mygauss[3]);
      }
      sum >>= 15;

      *dest++ = sum;
      pimod = ((*fmbuf++ + 32768) * phase_inc) / 32768;
      if(pimod > 0x3FFF) pimod = 0x3FFF;
      else if(pimod < 1) pimod = 1;
      ph += pimod;
      t += pimod;
    }
  }

  if(irq_state && irq_triggered_cycle != 0xFFFFFFFF) {
    irq_triggered_cycle = (irq_triggered_cycle * 768) >> 12;
    if(irq_triggered_cycle < irq_state->triggered_cycle) irq_state->triggered_cycle = irq_triggered_cycle;
  }

  sample->phase = ph;

  return s;
}

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

#define MY_AM (((env->reg_ad)>>15)&0x01)
#define MY_AR (((env->reg_ad)>> 8)&0x7F)
#define MY_DR (((env->reg_ad)>> 4)&0x0F)
#define MY_SL (((env->reg_ad)>> 0)&0x0F)
#define MY_SM (((env->reg_sr)>>15)&0x01)
#define MY_SD (((env->reg_sr)>>14)&0x01)
#define MY_SR (((env->reg_sr)>> 6)&0x7F)
#define MY_RM (((env->reg_sr)>> 5)&0x01)
#define MY_RR (((env->reg_sr)>> 0)&0x1F)

/*
** - Sets the current envelope slope
** - Returns the max number of samples that can be processed at the current
**   slope
*/
static EMU_INLINE sint32 EMU_CALL envelope_do(struct SPUCORE_ENVELOPE *env) {
  sint32 target = 0;
  /*
  ** Clip envelope value in case it wrapped around
  */
  switch(((env->level) >> 30) & 3) {
  case 2: env->level = 0x7FFFFFFF; break;
  case 3: env->level = 0x00000000; break;
  }
  switch(env->state) {
  case ENVELOPE_STATE_ATTACK : goto attack;
  case ENVELOPE_STATE_DECAY  : goto decay;
  case ENVELOPE_STATE_SUSTAIN: goto sustain;
  case ENVELOPE_STATE_RELEASE: goto release;
  default:
    env->state = ENVELOPE_STATE_OFF;
    env->level = 0;
    env->delta = 0;
    return 1;
  }
attack:
  if(env->level == 0x7FFFFFFF) {
    env->state = ENVELOPE_STATE_DECAY;
    goto decay;
  }
  /* log */
  if(MY_AM) {
    if(env->level < 0x60000000) {
      target = 0x60000000;
      env->delta = ratelogtable[32+(MY_AR^0x7F)-0x10];
    } else {
      target = 0x7FFFFFFF;
      env->delta = ratelogtable[32+(MY_AR^0x7F)-0x18];
    }
  /* linear */
  } else {
    target = 0x7FFFFFFF;
    env->delta = ratelogtable[32+(MY_AR^0x7F)-0x10];
  }
  goto domax;

decay:
  if((((env->level)>>27)&0xF) <= ((sint32)(MY_SL))) {
    env->state = ENVELOPE_STATE_SUSTAIN;
    goto sustain;
  }
  target = env->level & (~0x07FFFFFF);
  switch(((env->level)>>28)&0x7) {
  case 0: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+0];  break;
  case 1: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+4];  break;
  case 2: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+6];  break;
  case 3: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+8];  break;
  case 4: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+9];  break;
  case 5: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+10]; break;
  case 6: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+11]; break;
  case 7: env->delta = -ratelogtable[32+(4*(MY_DR^0x1F))-0x18+12]; break;
  }
  goto domax;

sustain:
  if(!MY_SD) {
    if(env->level == 0x7FFFFFFF) {
      env->delta = 0;
      return 0x7FFFFFFF;
    }
    /* log */
    if(MY_SM) {
      if(env->level < 0x60000000) {
        target = 0x60000000;
        env->delta = ratelogtable[32+(MY_SR^0x7F)-0x10];
      } else {
        target = 0x7FFFFFFF;
        env->delta = ratelogtable[32+(MY_SR^0x7F)-0x18];
      }
    /* linear */
    } else {
      target = 0x7FFFFFFF;
      env->delta = ratelogtable[32+(MY_SR^0x7F)-0x10];
    }
  } else {
    if(env->level == 0x00000000) {
      env->delta = 0;
      return 0x7FFFFFFF;
    }
    /* log */
    if(MY_SM) {
      target = ((env->level)&(~0x0FFFFFFF));
      switch(((env->level)>>28)&0x7) {
      case 0: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+0];  break;
      case 1: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+4];  break;
      case 2: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+6];  break;
      case 3: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+8];  break;
      case 4: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+9];  break;
      case 5: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+10]; break;
      case 6: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+11]; break;
      case 7: env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x1B+12]; break;
      }
    /* linear */
    } else {
      target = 0x00000000;
      env->delta = -ratelogtable[32+(MY_SR^0x7F)-0x0F];
    }
  }
  goto domax;

release:
  if(env->level == 0) {
    env->delta = 0;
    env->state = ENVELOPE_STATE_OFF;
    return 1;
  }
  /* log */
  if(MY_RM) {
    target = ((env->level) & (~0x0FFFFFFF));
    switch(((env->level)>>28)&0x7) {
    case 0: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+0];  break;
    case 1: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+4];  break;
    case 2: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+6];  break;
    case 3: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+8];  break;
    case 4: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+9];  break;
    case 5: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+10]; break;
    case 6: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+11]; break;
    case 7: env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x18+12]; break;
    }
  /* linear */
  } else {
    target = 0;
    env->delta = -ratelogtable[32+(4*(MY_RR^0x1F))-0x0C];
  }
  goto domax;

domax:
  { sint32 max;
    if(env->delta) {
      max = (target - (env->level)) / (env->delta);
    } else {
      max = 0x7FFFFFFF;
    }
    max--;
    if(max < 1) max = 1;
    return max;
  }
}

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

static void EMU_CALL envelope_prime(struct SPUCORE_ENVELOPE *env) {
//  if(env->state != ENVELOPE_STATE_OFF) {
//if(env->state!=ENVELOPE_STATE_RELEASE){
//    OutputDebugString("envelope re-prime\n");
//}
//  }

//EMUTRACE2("[eprime %04X %04X]",env->reg_ad_x,env->reg_sr_x);

  env->level = 1;
  env->state = ENVELOPE_STATE_ATTACK;
  env->delta = 1;

  env->cachemax = 0;
}

static void EMU_CALL envelope_release(struct SPUCORE_ENVELOPE *env) {
  if(env->state != ENVELOPE_STATE_OFF) {
    env->state = ENVELOPE_STATE_RELEASE;
  }
  env->cachemax = 0;
}

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

static void EMU_CALL sample_prime(struct SPUCORE_SAMPLE *sample) {
  sample->state = SAMPLE_STATE_ON;
  memset(sample->array, 0, sizeof(sample->array));
  sample->array_cleared = 1;
  sample->phase = 0x1C000; // ensure it grabs the first block right away
//EMUTRACE2("[sprime %08X %08X]", sample->start_block_addr, sample->start_loop_block_addr);

  sample->block_addr = sample->start_block_addr;
  //sample->loop_block_addr = sample->start_loop_block_addr;
}

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

static void EMU_CALL voice_on(struct SPUCORE_CHAN *c) {

  // FOR DEBUGGING PURPOSES ONLY.
//  if(c->sample.state == SAMPLE_STATE_ON)return;
//  if(c->env.state != ENVELOPE_STATE_OFF)return;
  /*
  ** Defer if already on
  */
  if(c->env.state != ENVELOPE_STATE_OFF) {
    //EMUTRACE0("alreadyon:");
    if(!(c->samples_until_pending_keyon)) {
      //EMUTRACE0("defer");
      c->samples_until_pending_keyon = KEYON_DEFER_SAMPLES;
    } else {
      //EMUTRACE0("was already deferred");
    }
  } else {
//    EMUTRACE0("prime");
    sample_prime(&(c->sample));
    envelope_prime(&(c->env));
  }
//  EMUTRACE0("\n");
}

static void EMU_CALL voice_off(struct SPUCORE_CHAN *c) {
  //EMUTRACE0("release");
  envelope_release(&(c->env));
  //EMUTRACE0("\n");
}

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

static void EMU_CALL voices_on(struct SPUCORE_STATE *state, uint32 bits) {
  int a;
  for(a = 0; a < 24; a++) {
    if(bits & 1) {

//      EMUTRACE1("voiceon  %2d:",a);

//EMUTRACE2("[vol %08X %08X]",state->chan[a].vol[0].level,state->chan[a].vol[1].level);
//EMUTRACE2("[mvol %08X %08X]",state->mvol[0].level,state->mvol[1].level);
////EMUTRACE2("[avol %08X %08X]",state->avol[0],state->avol[1]);
//EMUTRACE2("[evol %08X %08X]",state->evol[0],state->evol[1]);

//    sint32 extvol_l = ((sint16)(state->avol[0]));
//    sint32 extvol_r = ((sint16)(state->avol[1]));

      voice_on(state->chan + a);
    }
    bits >>= 1;
  }
}

static void EMU_CALL voices_off(struct SPUCORE_STATE *state, uint32 bits) {
  int a;
  for(a = 0; a < 24; a++) {
    if(bits & 1) {
      //EMUTRACE1("voiceoff %2d:",a);
      voice_off(state->chan + a);
    }
    bits >>= 1;
  }
}

////////////////////////////////////////////////////////////////////////////////
/*
** - Scales samples in a buffer using an envelope
** - Returns the actual number of samples modified
**
** All UNMODIFIED samples must then be discarded
** (they're supposed to be zero)
*/
static int EMU_CALL enveloper(
  struct SPUCORE_ENVELOPE *env,
  sint32 *buf,
  int samples
) {
  int i = 0;
  while(i < samples) {
    sint32 e, d, max;
    if(env->state == ENVELOPE_STATE_OFF) break;
    max = env->cachemax;
    if(!max) {
      max = envelope_do(env);
      env->cachemax = max;
    }
    e = env->level;
    d = env->delta;
    if(max < 1) max = 1;
    if(max > (samples - i)) { max = samples - i; }
    env->cachemax -= max;
    if(!buf) {
      i += max;
      e += max * d;
    } else {
      while(max--) {
        sint32 b = buf[i];
        b *= (e >> 16);
        b >>= 15;
        buf[i] = b;
        e += d;
        i++;
      }
    }
    env->level = e;
    env->delta = d;
  }
  return i;
}

////////////////////////////////////////////////////////////////////////////////
//
// - Calls the resampler and enveloper
// - Returns the number of samples actually generated
//   (can be 0 if the channel is silent)
//
// If buf is null, it means "fast forward" - don't render anything
//
//
// output of render_channel_raw() is guaranteed clipped as long as
// resampler() and enveloper() are also
//
static int EMU_CALL render_channel_raw(
  uint16 *ram,
  uint32 memmax,
  struct SPUCORE_CHAN *c,
  sint32 *buf,
  sint32 *fmbuf,
  sint32 *nbuf,
  int samples,
  struct SPUCORE_IRQ_STATE *irq_state
) {
  int r = samples;
  /* If the envelope is dead, don't bother anyway */
  if((c->env.state) == ENVELOPE_STATE_OFF) return 0;
  /* Do resampling */
  if (fmbuf) r = resampler_modulated(ram, memmax, &(c->sample), buf, r, c->voice_pitch, fmbuf, irq_state);
  else r = resampler(ram, memmax, &(c->sample), buf, r, c->voice_pitch, irq_state);
  if(nbuf) {
    r = samples;
    if(buf) memcpy(buf, nbuf, 4 * r);
  }
  /* Do enveloping */
  r = enveloper(&(c->env), buf, r);
  /* If we were cut short by _either_, then the envelope state must be set
  ** to OFF */
  if(r < samples) c->env.state = ENVELOPE_STATE_OFF;
  return r;
}

//
// This is the new render_channel which processes deferred key-ons
//
//
// output of render_channel_mono() is guaranteed clipped as long as
// render_channel_raw() is also
//
static int EMU_CALL render_channel_mono(
  uint16 *ram,
  uint32 memmax,
  struct SPUCORE_CHAN *c,
  sint32 *buf,
  sint32 *fmbuf,
  sint32 *nbuf,
  sint32 samples,
  struct SPUCORE_IRQ_STATE *irq_state
) {
  sint32 n;
  sint32 r, r2;
  sint32 defer_remaining;
  struct SPUCORE_IRQ_STATE spare_state;

//top:
  n = c->samples_until_pending_keyon;

  if(!n) {
    return render_channel_raw(ram, memmax, c, buf, fmbuf, nbuf, samples, irq_state);
  }

  //
  // Don't defer key-on if fast-forwarding
  // this caused a STUCK-NOTE BUG. should not use.
  //
//  if(!buf) {
//    c->samples_until_pending_keyon = 0;
//    goto top;
//  }

  if(n > samples) { n = samples; }

  /*
  ** r = how many samples we actually will process
  */
  r = render_channel_raw(ram, memmax, c, buf, fmbuf, nbuf, n, irq_state);

  defer_remaining = c->samples_until_pending_keyon;
  if(buf) {
    sint32 i;
    for(i = 0; i < r; i++) {
      (*buf) = ((*buf)*defer_remaining) / KEYON_DEFER_SAMPLES;
      buf++;
      defer_remaining--;
    }
  } else {
    defer_remaining -= r;
  }
  if(fmbuf) fmbuf += r;
  if(nbuf) nbuf += r;
  /*
  ** if render_channel_raw got cut short, then we're done anyway.
  */
  if(r < n) defer_remaining = 0;

  c->samples_until_pending_keyon = defer_remaining;
  /*
  ** Process the key-on if necessary
  */
  if(!defer_remaining) {
    sample_prime(&(c->sample));
    envelope_prime(&(c->env));
  }

  /*
  ** we already handled r samples
  */
  samples -= r;

  /*
  ** if there are any remaining samples to render, do them here
  */
  r2 = 0;
  if(samples) {
    struct SPUCORE_IRQ_STATE *s = NULL;
    if(irq_state) {
      s = &spare_state;
      spare_state.offset = irq_state->offset;
    }
    r2 = render_channel_raw(ram, memmax, c, buf, fmbuf, nbuf, samples, s);
	if(irq_state && irq_state->triggered_cycle == 0xFFFFFFFF && spare_state.triggered_cycle != 0xFFFFFFFF) irq_state->triggered_cycle = spare_state.triggered_cycle + r * 768;
  }

  return r + r2;

}

////////////////////////////////////////////////////////////////////////////////
//
// Generates a buffer worth of noise data, ganked from Eternal SPU
//

static void EMU_CALL render_noise(
  struct SPUCORE_STATE *state,
  sint32 *buf,
  sint32 samples
) {
  int n;
  uint32 noisecounter = state->noisecounter;
  sint32 noiseval = state->noiseval;
  uint32 noiseinc = (uint16)(0x8000 >> (state->noiseclock << 6));
  for(n = 0; n < samples; n++) {
    noisecounter += noiseinc;
    noiseval += noisecounter + noisecounter + noisetable[(noisecounter >> 10) & 63];
	if (noiseval < -32767) noiseval = -32767;
	else if (noiseval > 32767) noiseval = 32767;
    if (buf) *buf++ = noiseval;
  }
  state->noisecounter = noisecounter;
  state->noiseval = noiseval;
}

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

#define MAKE_SINT32_COEF(x)   ((sint32)((sint16)(state->reverb.x)))
#define MAKE_REVERB_OFFSET(x) (state->reverb.x)
#define NORMALIZE_REVERB_OFFSET(x) {while(x>=state->reverb.safe_end_address){x-=state->reverb.safe_size;}while(x<state->reverb.safe_start_address){x+=state->reverb.safe_size;}}

#define RAM_PCM_SAMPLE(x) (*((sint16*)(((uint8*)ram) + (x))))
#define RAM_SINT32_SAMPLE(x) ((sint32)(RAM_PCM_SAMPLE(x)))
/*
** Multiplying -32768 by -32768 and scaling by 15 bits is not safe
** (the sign would get flipped)
** so we should clip to -32767 instead
*/
#define CLIP_PCM_1(x) {if(x>32767){x=32767;}else if(x<(-32767)){x=(-32767);}}
#define CLIP_PCM_2(a,b) {CLIP_PCM_1(a);CLIP_PCM_1(b);}
#define CLIP_PCM_4(a,b,c,d) {CLIP_PCM_1(a);CLIP_PCM_1(b);CLIP_PCM_1(c);CLIP_PCM_1(d);}

//#define CLIP_PCMDBL_1(x) {if(x>(32767*2)){x=(32767*2);}else if(x<(-(32767*2))){x=(-(32767*2));}}
//#define CLIP_PCMDBL_2(a,b) {CLIP_PCMDBL_1(a);CLIP_PCMDBL_1(b);}
//#define CLIP_PCMDBL_4(a,b,c,d) {CLIP_PCMDBL_1(a);CLIP_PCMDBL_1(b);CLIP_PCMDBL_1(c);CLIP_PCMDBL_1(d);}

////////////////////////////////////////////////////////////////////////////////
/*
** 22KHz reverb steady state step
*/
static void EMU_CALL reverb_steadystate22(struct SPUCORE_STATE *state, uint16 *ram, sint32 input_l, sint32 input_r) {
  /*
  ** Current reverb offset
  */
  sint32 current     = state->reverb.current_address;
  /*
  ** Reverb registers
  */
  sint32 fb_src_a    = MAKE_REVERB_OFFSET(FB_SRC_A);
  sint32 fb_src_b    = MAKE_REVERB_OFFSET(FB_SRC_B);
  sint32 iir_alpha   = MAKE_SINT32_COEF(IIR_ALPHA);
  sint32 acc_coef_a  = MAKE_SINT32_COEF(ACC_COEF_A);
  sint32 acc_coef_b  = MAKE_SINT32_COEF(ACC_COEF_B);
  sint32 acc_coef_c  = MAKE_SINT32_COEF(ACC_COEF_C);
  sint32 acc_coef_d  = MAKE_SINT32_COEF(ACC_COEF_D);
  sint32 iir_coef    = MAKE_SINT32_COEF(IIR_COEF);
  sint32 fb_alpha    = MAKE_SINT32_COEF(FB_ALPHA);
  sint32 fb_x        = MAKE_SINT32_COEF(FB_X);
  sint32 iir_dest_a0 = MAKE_REVERB_OFFSET(IIR_DEST_A0);
  sint32 iir_dest_a1 = MAKE_REVERB_OFFSET(IIR_DEST_A1);
  sint32 acc_src_a0  = MAKE_REVERB_OFFSET(ACC_SRC_A0);
  sint32 acc_src_a1  = MAKE_REVERB_OFFSET(ACC_SRC_A1);
  sint32 acc_src_b0  = MAKE_REVERB_OFFSET(ACC_SRC_B0);
  sint32 acc_src_b1  = MAKE_REVERB_OFFSET(ACC_SRC_B1);
  sint32 iir_src_a0  = MAKE_REVERB_OFFSET(IIR_SRC_A0);
  sint32 iir_src_a1  = MAKE_REVERB_OFFSET(IIR_SRC_A1);
  sint32 iir_dest_b0 = MAKE_REVERB_OFFSET(IIR_DEST_B0);
  sint32 iir_dest_b1 = MAKE_REVERB_OFFSET(IIR_DEST_B1);
  sint32 acc_src_c0  = MAKE_REVERB_OFFSET(ACC_SRC_C0);
  sint32 acc_src_c1  = MAKE_REVERB_OFFSET(ACC_SRC_C1);
  sint32 acc_src_d0  = MAKE_REVERB_OFFSET(ACC_SRC_D0);
  sint32 acc_src_d1  = MAKE_REVERB_OFFSET(ACC_SRC_D1);
  sint32 iir_src_b1  = MAKE_REVERB_OFFSET(IIR_SRC_B1);
  sint32 iir_src_b0  = MAKE_REVERB_OFFSET(IIR_SRC_B0);
  sint32 mix_dest_a0 = MAKE_REVERB_OFFSET(MIX_DEST_A0);
  sint32 mix_dest_a1 = MAKE_REVERB_OFFSET(MIX_DEST_A1);
  sint32 mix_dest_b0 = MAKE_REVERB_OFFSET(MIX_DEST_B0);
  sint32 mix_dest_b1 = MAKE_REVERB_OFFSET(MIX_DEST_B1);
  sint32 in_coef_l   = MAKE_SINT32_COEF(IN_COEF_L);
  sint32 in_coef_r   = MAKE_SINT32_COEF(IN_COEF_R);
  /*
  ** Alternate buffer positions
  */
  sint32 fb_src_a0;
  sint32 fb_src_a1;
  sint32 fb_src_b0;
  sint32 fb_src_b1;
  sint32 iir_dest_a0_plus;
  sint32 iir_dest_a1_plus;
  sint32 iir_dest_b0_plus;
  sint32 iir_dest_b1_plus;
  /*
  ** Intermediate results
  */
  sint32 acc0;
  sint32 acc1;
  sint32 iir_input_a0;
  sint32 iir_input_a1;
  sint32 iir_input_b0;
  sint32 iir_input_b1;
  sint32 iir_a0;
  sint32 iir_a1;
  sint32 iir_b0;
  sint32 iir_b1;
  sint32 fb_a0;
  sint32 fb_a1;
  sint32 fb_b0;
  sint32 fb_b1;
  sint32 mix_a0;
  sint32 mix_a1;
  sint32 mix_b0;
  sint32 mix_b1;

  /*
  ** Offsets
  */
  mix_dest_a0 += current; NORMALIZE_REVERB_OFFSET(mix_dest_a0);
  mix_dest_a1 += current; NORMALIZE_REVERB_OFFSET(mix_dest_a1);
  mix_dest_b0 += current; NORMALIZE_REVERB_OFFSET(mix_dest_b0);
  mix_dest_b1 += current; NORMALIZE_REVERB_OFFSET(mix_dest_b1);
  fb_src_a0 = mix_dest_a0 - fb_src_a; NORMALIZE_REVERB_OFFSET(fb_src_a0);
  fb_src_a1 = mix_dest_a1 - fb_src_a; NORMALIZE_REVERB_OFFSET(fb_src_a1);
  fb_src_b0 = mix_dest_b0 - fb_src_b; NORMALIZE_REVERB_OFFSET(fb_src_b0);
  fb_src_b1 = mix_dest_b1 - fb_src_b; NORMALIZE_REVERB_OFFSET(fb_src_b1);
  acc_src_a0 += current; NORMALIZE_REVERB_OFFSET(acc_src_a0);
  acc_src_a1 += current; NORMALIZE_REVERB_OFFSET(acc_src_a1);
  acc_src_b0 += current; NORMALIZE_REVERB_OFFSET(acc_src_b0);
  acc_src_b1 += current; NORMALIZE_REVERB_OFFSET(acc_src_b1);
  acc_src_c0 += current; NORMALIZE_REVERB_OFFSET(acc_src_c0);
  acc_src_c1 += current; NORMALIZE_REVERB_OFFSET(acc_src_c1);
  acc_src_d0 += current; NORMALIZE_REVERB_OFFSET(acc_src_d0);
  acc_src_d1 += current; NORMALIZE_REVERB_OFFSET(acc_src_d1);
  iir_src_a0 += current; NORMALIZE_REVERB_OFFSET(iir_src_a0);
  iir_src_a1 += current; NORMALIZE_REVERB_OFFSET(iir_src_a1);
  iir_src_b0 += current; NORMALIZE_REVERB_OFFSET(iir_src_b0);
  iir_src_b1 += current; NORMALIZE_REVERB_OFFSET(iir_src_b1);
  iir_dest_a0 += current; NORMALIZE_REVERB_OFFSET(iir_dest_a0);
  iir_dest_a1 += current; NORMALIZE_REVERB_OFFSET(iir_dest_a1);
  iir_dest_b0 += current; NORMALIZE_REVERB_OFFSET(iir_dest_b0);
  iir_dest_b1 += current; NORMALIZE_REVERB_OFFSET(iir_dest_b1);
  iir_dest_a0_plus = iir_dest_a0 + 2; NORMALIZE_REVERB_OFFSET(iir_dest_a0_plus);
  iir_dest_a1_plus = iir_dest_a1 + 2; NORMALIZE_REVERB_OFFSET(iir_dest_a1_plus);
  iir_dest_b0_plus = iir_dest_b0 + 2; NORMALIZE_REVERB_OFFSET(iir_dest_b0_plus);
  iir_dest_b1_plus = iir_dest_b1 + 2; NORMALIZE_REVERB_OFFSET(iir_dest_b1_plus);

  /*
  ** IIR
  */
  CLIP_PCM_2(input_l,input_r);
  input_l *= in_coef_l;
  input_r *= in_coef_r;
#define OPPOSITE_IIR_ALPHA (32768-iir_alpha)
  iir_input_a0 = ((RAM_SINT32_SAMPLE(iir_src_a0) * iir_coef) + input_l) >> 15;
  iir_input_a1 = ((RAM_SINT32_SAMPLE(iir_src_a1) * iir_coef) + input_r) >> 15;
  iir_input_b0 = ((RAM_SINT32_SAMPLE(iir_src_b0) * iir_coef) + input_l) >> 15;
  iir_input_b1 = ((RAM_SINT32_SAMPLE(iir_src_b1) * iir_coef) + input_r) >> 15;
  CLIP_PCM_4(iir_input_a0,iir_input_a1,iir_input_b0,iir_input_b1);
  iir_a0 = ((iir_input_a0 * iir_alpha) + (RAM_SINT32_SAMPLE(iir_dest_a0) * (OPPOSITE_IIR_ALPHA))) >> 15;
  iir_a1 = ((iir_input_a1 * iir_alpha) + (RAM_SINT32_SAMPLE(iir_dest_a1) * (OPPOSITE_IIR_ALPHA))) >> 15;
  iir_b0 = ((iir_input_b0 * iir_alpha) + (RAM_SINT32_SAMPLE(iir_dest_b0) * (OPPOSITE_IIR_ALPHA))) >> 15;
  iir_b1 = ((iir_input_b1 * iir_alpha) + (RAM_SINT32_SAMPLE(iir_dest_b1) * (OPPOSITE_IIR_ALPHA))) >> 15;
  CLIP_PCM_4(iir_a0,iir_a1,iir_b0,iir_b1);

  RAM_PCM_SAMPLE(iir_dest_a0_plus) = iir_a0;
  RAM_PCM_SAMPLE(iir_dest_a1_plus) = iir_a1;
  RAM_PCM_SAMPLE(iir_dest_b0_plus) = iir_b0;
  RAM_PCM_SAMPLE(iir_dest_b1_plus) = iir_b1;

  /*
  ** Accumulators
  */
  acc0 =
    ((RAM_SINT32_SAMPLE(acc_src_a0) * acc_coef_a) >> 15) +
    ((RAM_SINT32_SAMPLE(acc_src_b0) * acc_coef_b) >> 15) +
    ((RAM_SINT32_SAMPLE(acc_src_c0) * acc_coef_c) >> 15) +
    ((RAM_SINT32_SAMPLE(acc_src_d0) * acc_coef_d) >> 15);
  acc1 =
    ((RAM_SINT32_SAMPLE(acc_src_a1) * acc_coef_a) >> 15) +
    ((RAM_SINT32_SAMPLE(acc_src_b1) * acc_coef_b) >> 15) +
    ((RAM_SINT32_SAMPLE(acc_src_c1) * acc_coef_c) >> 15) +
    ((RAM_SINT32_SAMPLE(acc_src_d1) * acc_coef_d) >> 15);
  CLIP_PCM_2(acc0,acc1);

  /*
  ** Feedback
  */
  fb_a0 = RAM_SINT32_SAMPLE(fb_src_a0);
  fb_a1 = RAM_SINT32_SAMPLE(fb_src_a1);
  fb_b0 = RAM_SINT32_SAMPLE(fb_src_b0);
  fb_b1 = RAM_SINT32_SAMPLE(fb_src_b1);

  mix_a0 = acc0 - ((fb_a0*fb_alpha)>>15);
  mix_a1 = acc1 - ((fb_a1*fb_alpha)>>15);
  mix_b0 = fb_alpha*acc0;
  mix_b1 = fb_alpha*acc1;
  fb_alpha = ((sint32)((sint16)(((sint16)fb_alpha)^0x8000)));
  mix_b0 -= fb_a0*fb_alpha;
  mix_b1 -= fb_a1*fb_alpha;
  mix_b0 -= fb_b0*fb_x;
  mix_b1 -= fb_b1*fb_x;
  mix_b0>>=15;
  mix_b1>>=15;

  CLIP_PCM_4(mix_a0,mix_a1,mix_b0,mix_b1);
  RAM_PCM_SAMPLE(mix_dest_a0) = mix_a0;
  RAM_PCM_SAMPLE(mix_dest_a1) = mix_a1;
  RAM_PCM_SAMPLE(mix_dest_b0) = mix_b0;
  RAM_PCM_SAMPLE(mix_dest_b1) = mix_b1;

}

////////////////////////////////////////////////////////////////////////////////
/*
** 22KHz reverb engine
*/
static void EMU_CALL reverb_engine22(struct SPUCORE_STATE *state, uint16 *ram, sint32 *l, sint32 *r) {
  sint32 input_l = *l;
  sint32 input_r = *r;
  sint32 output_l;
  sint32 output_r;
  sint32 mix_dest_a0 = state->reverb.current_address + MAKE_REVERB_OFFSET(MIX_DEST_A0);
  sint32 mix_dest_a1 = state->reverb.current_address + MAKE_REVERB_OFFSET(MIX_DEST_A1);
  sint32 mix_dest_b0 = state->reverb.current_address + MAKE_REVERB_OFFSET(MIX_DEST_B0);
  sint32 mix_dest_b1 = state->reverb.current_address + MAKE_REVERB_OFFSET(MIX_DEST_B1);
  NORMALIZE_REVERB_OFFSET(mix_dest_a0);
  NORMALIZE_REVERB_OFFSET(mix_dest_a1);
  NORMALIZE_REVERB_OFFSET(mix_dest_b0);
  NORMALIZE_REVERB_OFFSET(mix_dest_b1);

  /*
  ** (Scale these down for now - avoids some clipping)
  */
  input_l *= 2;
  input_r *= 2;
  input_l /= 3;
  input_r /= 3;

  /*
  ** Execute steady state step if necessary
  */
  if(state->flags & SPUREG_FLAG_REVERB_ENABLE) {
    reverb_steadystate22(state, ram, input_l, input_r);
  }

  /*
  ** Retrieve wet out L/R
  ** (pretty certain this is done AFTER the steady state step)
  */
  {
    int al = RAM_SINT32_SAMPLE(mix_dest_a0);
    int ar = RAM_SINT32_SAMPLE(mix_dest_a1);
    int bl = RAM_SINT32_SAMPLE(mix_dest_b0);
    int br = RAM_SINT32_SAMPLE(mix_dest_b1);

    output_l = al + bl;
    output_r = ar + br;
  }

  *l = output_l;
  *r = output_r;

  /*
  ** Advance reverb buffer position
  */
  state->reverb.current_address += 2;
  if(state->reverb.current_address >= state->reverb.safe_end_address) {
    state->reverb.current_address = state->reverb.safe_start_address;
  }
}

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

static void EMU_CALL reverb_process(struct SPUCORE_STATE *state, uint16 *ram, sint32 *buf, int samples) {
  int q = state->reverb.resampler.queue_index;
  /*
  ** Sample loop
  */
  while(samples--) {
    /*
    ** Get an input sample
    */
    sint32 l = buf[0];
    sint32 r = buf[1];
    /*
    ** Put it in the input queue
    */
    state->reverb.resampler.in_queue_l[q & 63] = l;
    state->reverb.resampler.in_queue_r[q & 63] = r;
    /*
    ** If we're ready to create another output sample...
    */
    if(q & 1) {
      /*
      ** Lowpass/downsample
      */
#if 1
      l =
        (state->reverb.resampler.in_queue_l[(q - 38) & 63]) * reverb_psx_lowpass_coefs[0] +
        (state->reverb.resampler.in_queue_l[(q - 36) & 63]) * reverb_psx_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_l[(q - 34) & 63]) * reverb_psx_lowpass_coefs[2] +
        (state->reverb.resampler.in_queue_l[(q - 32) & 63]) * reverb_psx_lowpass_coefs[3] +
        (state->reverb.resampler.in_queue_l[(q - 30) & 63]) * reverb_psx_lowpass_coefs[4] +
        (state->reverb.resampler.in_queue_l[(q - 28) & 63]) * reverb_psx_lowpass_coefs[5] +
        (state->reverb.resampler.in_queue_l[(q - 26) & 63]) * reverb_psx_lowpass_coefs[6] +
        (state->reverb.resampler.in_queue_l[(q - 24) & 63]) * reverb_psx_lowpass_coefs[7] +
        (state->reverb.resampler.in_queue_l[(q - 22) & 63]) * reverb_psx_lowpass_coefs[8] +
        (state->reverb.resampler.in_queue_l[(q - 20) & 63]) * reverb_psx_lowpass_coefs[9] +
        (state->reverb.resampler.in_queue_l[(q - 19) & 63]) * reverb_psx_lowpass_coefs[10] +
        (state->reverb.resampler.in_queue_l[(q - 18) & 63]) * reverb_psx_lowpass_coefs[9] +
        (state->reverb.resampler.in_queue_l[(q - 16) & 63]) * reverb_psx_lowpass_coefs[8] +
        (state->reverb.resampler.in_queue_l[(q - 14) & 63]) * reverb_psx_lowpass_coefs[7] +
        (state->reverb.resampler.in_queue_l[(q - 12) & 63]) * reverb_psx_lowpass_coefs[6] +
        (state->reverb.resampler.in_queue_l[(q - 10) & 63]) * reverb_psx_lowpass_coefs[5] +
        (state->reverb.resampler.in_queue_l[(q - 8) & 63]) * reverb_psx_lowpass_coefs[4] +
        (state->reverb.resampler.in_queue_l[(q - 6) & 63]) * reverb_psx_lowpass_coefs[3] +
        (state->reverb.resampler.in_queue_l[(q - 4) & 63]) * reverb_psx_lowpass_coefs[2] +
        (state->reverb.resampler.in_queue_l[(q - 2) & 63]) * reverb_psx_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_l[(q - 0) & 63]) * reverb_psx_lowpass_coefs[0];
      r =
        (state->reverb.resampler.in_queue_r[(q - 38) & 63]) * reverb_psx_lowpass_coefs[0] +
        (state->reverb.resampler.in_queue_r[(q - 36) & 63]) * reverb_psx_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_r[(q - 34) & 63]) * reverb_psx_lowpass_coefs[2] +
        (state->reverb.resampler.in_queue_r[(q - 32) & 63]) * reverb_psx_lowpass_coefs[3] +
        (state->reverb.resampler.in_queue_r[(q - 30) & 63]) * reverb_psx_lowpass_coefs[4] +
        (state->reverb.resampler.in_queue_r[(q - 28) & 63]) * reverb_psx_lowpass_coefs[5] +
        (state->reverb.resampler.in_queue_r[(q - 26) & 63]) * reverb_psx_lowpass_coefs[6] +
        (state->reverb.resampler.in_queue_r[(q - 24) & 63]) * reverb_psx_lowpass_coefs[7] +
        (state->reverb.resampler.in_queue_r[(q - 22) & 63]) * reverb_psx_lowpass_coefs[8] +
        (state->reverb.resampler.in_queue_r[(q - 20) & 63]) * reverb_psx_lowpass_coefs[9] +
        (state->reverb.resampler.in_queue_r[(q - 19) & 63]) * reverb_psx_lowpass_coefs[10] +
        (state->reverb.resampler.in_queue_r[(q - 18) & 63]) * reverb_psx_lowpass_coefs[9] +
        (state->reverb.resampler.in_queue_r[(q - 16) & 63]) * reverb_psx_lowpass_coefs[8] +
        (state->reverb.resampler.in_queue_r[(q - 14) & 63]) * reverb_psx_lowpass_coefs[7] +
        (state->reverb.resampler.in_queue_r[(q - 12) & 63]) * reverb_psx_lowpass_coefs[6] +
        (state->reverb.resampler.in_queue_r[(q - 10) & 63]) * reverb_psx_lowpass_coefs[5] +
        (state->reverb.resampler.in_queue_r[(q - 8) & 63]) * reverb_psx_lowpass_coefs[4] +
        (state->reverb.resampler.in_queue_r[(q - 6) & 63]) * reverb_psx_lowpass_coefs[3] +
        (state->reverb.resampler.in_queue_r[(q - 4) & 63]) * reverb_psx_lowpass_coefs[2] +
        (state->reverb.resampler.in_queue_r[(q - 2) & 63]) * reverb_psx_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_r[(q - 0) & 63]) * reverb_psx_lowpass_coefs[0];
#else
      l = 0;
      r = 0;

      for (n = 47; n >= 0; n -= 8) {
        l +=
          (state->reverb.resampler.in_queue_l[(q - n + 0) & 63]) * reverb_psx_lowpass_coefs[n - 0] +
          (state->reverb.resampler.in_queue_l[(q - n + 1) & 63]) * reverb_psx_lowpass_coefs[n - 1] +
          (state->reverb.resampler.in_queue_l[(q - n + 2) & 63]) * reverb_psx_lowpass_coefs[n - 2] +
          (state->reverb.resampler.in_queue_l[(q - n + 3) & 63]) * reverb_psx_lowpass_coefs[n - 3] +
          (state->reverb.resampler.in_queue_l[(q - n + 4) & 63]) * reverb_psx_lowpass_coefs[n - 4] +
          (state->reverb.resampler.in_queue_l[(q - n + 5) & 63]) * reverb_psx_lowpass_coefs[n - 5] +
          (state->reverb.resampler.in_queue_l[(q - n + 6) & 63]) * reverb_psx_lowpass_coefs[n - 6] +
          (state->reverb.resampler.in_queue_l[(q - n + 7) & 63]) * reverb_psx_lowpass_coefs[n - 7];
        r +=
          (state->reverb.resampler.in_queue_r[(q - n + 0) & 63]) * reverb_psx_lowpass_coefs[n - 0] +
          (state->reverb.resampler.in_queue_r[(q - n + 1) & 63]) * reverb_psx_lowpass_coefs[n - 1] +
          (state->reverb.resampler.in_queue_r[(q - n + 2) & 63]) * reverb_psx_lowpass_coefs[n - 2] +
          (state->reverb.resampler.in_queue_r[(q - n + 3) & 63]) * reverb_psx_lowpass_coefs[n - 3] +
          (state->reverb.resampler.in_queue_r[(q - n + 4) & 63]) * reverb_psx_lowpass_coefs[n - 4] +
          (state->reverb.resampler.in_queue_r[(q - n + 5) & 63]) * reverb_psx_lowpass_coefs[n - 5] +
          (state->reverb.resampler.in_queue_r[(q - n + 6) & 63]) * reverb_psx_lowpass_coefs[n - 6] +
          (state->reverb.resampler.in_queue_r[(q - n + 7) & 63]) * reverb_psx_lowpass_coefs[n - 7];
      }
#endif

      l >>= 15;
      r >>= 15;

/*
      l =
        (state->reverb.resampler.in_queue_l[(q - 6) & 7]) * reverb_new_lowpass_coefs[0] +
        (state->reverb.resampler.in_queue_l[(q - 4) & 7]) * reverb_new_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_l[(q - 3) & 7]) * reverb_new_lowpass_coefs[2] +
        (state->reverb.resampler.in_queue_l[(q - 2) & 7]) * reverb_new_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_l[(q - 0) & 7]) * reverb_new_lowpass_coefs[0];
      l >>= 11;
      r =
        (state->reverb.resampler.in_queue_r[(q - 6) & 7]) * reverb_new_lowpass_coefs[0] +
        (state->reverb.resampler.in_queue_r[(q - 4) & 7]) * reverb_new_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_r[(q - 3) & 7]) * reverb_new_lowpass_coefs[2] +
        (state->reverb.resampler.in_queue_r[(q - 2) & 7]) * reverb_new_lowpass_coefs[1] +
        (state->reverb.resampler.in_queue_r[(q - 0) & 7]) * reverb_new_lowpass_coefs[0];
      r >>= 11;
*/
//l = state->reverb.resampler.in_queue_l[q & 7];
//r = state->reverb.resampler.in_queue_r[q & 7];

      /*
      ** Run the reverb engine
      */
      reverb_engine22(state, ram, &l, &r);
      /*
      ** Put the new stuff into the output queue
      */
      state->reverb.resampler.out_queue_l[q & 15] = l;
      state->reverb.resampler.out_queue_r[q & 15] = r;
    }
    /*
    ** Upsample
    */
    /*
    ** Upsample using the same technique as for ADPCM samples
    ** (may or may not be right, sounds okay though)
    */
#define gauss_table_0x000 gauss_shuffled_reverse_table[0x200]
#define gauss_table_0x100 gauss_shuffled_reverse_table[0x201]
#define gauss_table_0x200 gauss_shuffled_reverse_table[0x202]
#define gauss_table_0x300 gauss_shuffled_reverse_table[0x203]
#define gauss_table_0x080 gauss_shuffled_reverse_table[0x000]
#define gauss_table_0x180 gauss_shuffled_reverse_table[0x001]
#define gauss_table_0x280 gauss_shuffled_reverse_table[0x002]
#define gauss_table_0x380 gauss_shuffled_reverse_table[0x003]
    if(q & 1) {
      l =
        (state->reverb.resampler.out_queue_l[(q - 6) & 15]) * gauss_table_0x080 +
        (state->reverb.resampler.out_queue_l[(q - 4) & 15]) * gauss_table_0x180 +
        (state->reverb.resampler.out_queue_l[(q - 2) & 15]) * gauss_table_0x280 +
        (state->reverb.resampler.out_queue_l[(q - 0) & 15]) * gauss_table_0x380;
      l >>= 15;
      r =
        (state->reverb.resampler.out_queue_r[(q - 6) & 15]) * gauss_table_0x080 +
        (state->reverb.resampler.out_queue_r[(q - 4) & 15]) * gauss_table_0x180 +
        (state->reverb.resampler.out_queue_r[(q - 2) & 15]) * gauss_table_0x280 +
        (state->reverb.resampler.out_queue_r[(q - 0) & 15]) * gauss_table_0x380;
      r >>= 15;
    } else {
      l =
        (state->reverb.resampler.out_queue_l[(q - 7) & 15]) * gauss_table_0x000 +
        (state->reverb.resampler.out_queue_l[(q - 5) & 15]) * gauss_table_0x100 +
        (state->reverb.resampler.out_queue_l[(q - 3) & 15]) * gauss_table_0x200 +
        (state->reverb.resampler.out_queue_l[(q - 1) & 15]) * gauss_table_0x300;
      l >>= 15;
      r =
        (state->reverb.resampler.out_queue_r[(q - 7) & 15]) * gauss_table_0x000 +
        (state->reverb.resampler.out_queue_r[(q - 5) & 15]) * gauss_table_0x100 +
        (state->reverb.resampler.out_queue_r[(q - 3) & 15]) * gauss_table_0x200 +
        (state->reverb.resampler.out_queue_r[(q - 1) & 15]) * gauss_table_0x300;
      r >>= 15;
    }
    /*
    ** Advance queue position, write output, all that good stuff
    */
    q++;
    buf[0] = l;
    buf[1] = r;
    buf += 2;
  }
  state->reverb.resampler.queue_index = q;
}

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

//int spucore_frq[RENDERMAX];

/*
** Renderer
*/
static void EMU_CALL render(struct SPUCORE_STATE *state, uint16 *ram, sint16 *buf, sint16 *extinput, sint32 samples, uint8 mainout, uint8 effectout) {
  uint32 chanbit;
  uint32 maskmain_l;
  uint32 maskmain_r;
  uint32 maskverb_l;
  uint32 maskverb_r;
  uint32 masknoise;
  uint32 maskfm;
  sint32 ibuf   [  RENDERMAX];
  sint32 ibufmix[2*RENDERMAX];
  sint32 ibufrvb[2*RENDERMAX];
  sint32 ibufn  [  RENDERMAX];
  sint32 ibuffm [  RENDERMAX];
  int ch, i;
  sint32 m_v_l;
  sint32 m_v_r;
  sint32 r_v_l;
  sint32 r_v_r;
  struct SPUCORE_IRQ_STATE irq_state;
  struct SPUCORE_IRQ_STATE *irq_state_ptr;

  //spucore_frq[samples]++;

  irq_state_ptr = NULL;
  irq_state.triggered_cycle = 0xFFFFFFFF;
  if (state->flags & SPUREG_FLAG_IRQ_ENABLE) {
    if (state->memsize == 0x80000 && state->irq_address < 0x1000) {
      uint32 irq_address_masked = state->irq_address & 0x3FF;
      uint32 irq_sample_offset = irq_address_masked - state->irq_decoder_clock;
      if(irq_sample_offset > 0x3FF) irq_sample_offset += 0x400;
      if (irq_sample_offset < (uint32)samples) {
        irq_state.triggered_cycle = irq_sample_offset * 768;
      }
    } else {
      irq_state_ptr = &irq_state;
      irq_state.offset = state->irq_address;
    }
  }

  state->irq_decoder_clock = (state->irq_decoder_clock + samples) & 0x3FF;

  memset(ibufmix, 0, 8 * samples);
  if(effectout) {
    memset(ibufrvb, 0, 8 * samples);
  }

  maskmain_l = 0;
  maskmain_r = 0;
  maskverb_l = 0;
  maskverb_r = 0;
  if(state->flags & SPUREG_FLAG_MSNDL) maskmain_l = state->vmix[0] & 0xFFFFFF;
  if(state->flags & SPUREG_FLAG_MSNDR) maskmain_r = state->vmix[1] & 0xFFFFFF;
  if(effectout) {
    if(state->flags & SPUREG_FLAG_MSNDEL) maskverb_l = state->vmixe[0] & 0xFFFFFF;
    if(state->flags & SPUREG_FLAG_MSNDER) maskverb_r = state->vmixe[1] & 0xFFFFFF;
  }
  masknoise = state->noise;
  render_noise(state, masknoise ? ibufn : NULL, samples);
  maskfm = state->fm & 0xFFFFFE;

  if(!mainout) { maskmain_l = 0; maskmain_r = 0; }

  for(ch = 0, chanbit = 1; ch < 24; ch++, chanbit <<= 1) {
    int r;
    sint32 v_l, v_r;
    uint32 main_l = chanbit & maskmain_l;
    uint32 main_r = chanbit & maskmain_r;
    uint32 verb_l = chanbit & maskverb_l;
    uint32 verb_r = chanbit & maskverb_r;
    sint32 *b = buf ? ibuf : NULL;
    sint32 *fm = (chanbit & maskfm) ? ibuffm : NULL;
    sint32 *noise = (chanbit & masknoise) ? ibufn : NULL;
    if(!(main_l | main_r | verb_l | verb_r)) b = NULL;
    r = render_channel_mono(
      ram, state->memsize, state->chan + ch, b, fm, noise, samples, irq_state_ptr
    );
    if(!b) {
      memset(ibuffm, 0, 4 * samples);
      continue;
    }
    memcpy(ibuffm, ibuf, 4 * r);
    if(r < samples) memset(ibuffm + r, 0, 4 * (samples-r));
    v_l = volume_getlevel(state->chan[ch].vol+0);
    v_r = volume_getlevel(state->chan[ch].vol+1);
    for(i = 0; i < r; i++) {
      sint32 q_l = (v_l * ibuf[i]) >> 16;
      sint32 q_r = (v_r * ibuf[i]) >> 16;
      if(main_l) ibufmix[2*i+0] += q_l;
      if(main_r) ibufmix[2*i+1] += q_r;
      if(verb_l) ibufrvb[2*i+0] += q_l;
      if(verb_r) ibufrvb[2*i+1] += q_r;
    }
  }

  state->irq_triggered_cycle = irq_state.triggered_cycle;

  if(!buf) return;

  /*
  ** Mix in external input, if it exists
  */
  if(extinput) {
    sint32 extvol_l = ((sint16)(state->avol[0]));
    sint32 extvol_r = ((sint16)(state->avol[1]));
    if(extvol_l == -0x8000) extvol_l = -0x7FFF;
    if(extvol_r == -0x8000) extvol_r = -0x7FFF;
    for(i = 0; i < samples; i++) {
      sint32 sin_l = extinput[2*i+0];
      sint32 sin_r = extinput[2*i+1];
      sin_l *= extvol_l;
      sin_r *= extvol_r;
      sin_l >>= 15;
      sin_r >>= 15;
      if(state->flags & SPUREG_FLAG_SINL ) ibufmix[2*i+0] += sin_l;
      if(state->flags & SPUREG_FLAG_SINR ) ibufmix[2*i+1] += sin_r;
      if(state->flags & SPUREG_FLAG_SINEL) ibufrvb[2*i+0] += sin_l;
      if(state->flags & SPUREG_FLAG_SINER) ibufrvb[2*i+1] += sin_r;
    }
  }

  /*
  ** Do reverb
  ** This handles both writing into the buffer and retrieving
  ** values out of it, resampling to/from 22KHz, etc.
  */
  if(effectout) {
    reverb_process(state, ram, ibufrvb, samples);
  }
  /*
  **
  */
  m_v_l = volume_getlevel(state->mvol+0);
  m_v_r = volume_getlevel(state->mvol+1);
  r_v_l = ((sint16)(state->evol[0]));
  r_v_r = ((sint16)(state->evol[1]));

  if(!effectout) {
    for(i = 0; i < samples; i++) {
      sint64 q_l = ibufmix[2*i+0];
      sint64 q_r = ibufmix[2*i+1];
      q_l *= (sint64)m_v_l;
      q_r *= (sint64)m_v_r;
      q_l >>= 15;
      q_r >>= 15;
      CLIP_PCM_2(q_l,q_r);
      *buf++ = (sint16)q_l;
      *buf++ = (sint16)q_r;
    }
  } else {
    for(i = 0; i < samples; i++) {
      sint64 q_l = ibufmix[2*i+0];
      sint64 q_r = ibufmix[2*i+1];
      sint64 r_l = ibufrvb[2*i+0];
      sint64 r_r = ibufrvb[2*i+1];
      q_l *= (sint64)m_v_l;
      q_r *= (sint64)m_v_r;
      r_l *= (sint64)r_v_l;
      r_r *= (sint64)r_v_r;
      q_l >>= 15;
      q_r >>= 15;
      r_l >>= 15;
      r_r >>= 15;
      q_l += r_l;
      q_r += r_r;
      CLIP_PCM_2(q_l, q_r);
      *buf++ = (sint16)q_l;
      *buf++ = (sint16)q_r;
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
/*
** Externally-accessible renderer
*/
void EMU_CALL spucore_render(void *state, uint16 *ram, sint16 *buf, sint16 *extinput, uint32 samples, uint8 mainout, uint8 effectout) {
  while(samples > RENDERMAX) {
    samples -= RENDERMAX;
    render(SPUCORESTATE, ram, buf, extinput, RENDERMAX, mainout, effectout);
    if(buf     ) buf      += 2 * RENDERMAX;
    if(extinput) extinput += 2 * RENDERMAX;
  }
  if(samples) render(SPUCORESTATE, ram, buf, extinput, samples, mainout, effectout);
}

////////////////////////////////////////////////////////////////////////////////
/*
** Flag get/set
*/

int EMU_CALL spucore_getflag(void *state, uint32 n) {
  return !!(SPUCORESTATE->flags & n);
}

void EMU_CALL spucore_setflag(void *state, uint32 n, int value) {
  if(value) {
    SPUCORESTATE->flags |= n;
  } else {
    SPUCORESTATE->flags &= ~n;
  }
}

////////////////////////////////////////////////////////////////////////////////
/*
** Register get/set
*/

uint32 EMU_CALL spucore_getreg(void *state, uint32 n) {
  switch(n) {
  case SPUREG_MVOLL:  return volume_getmode (SPUCORESTATE->mvol+0) & 0x0000FFFF;
  case SPUREG_MVOLR:  return volume_getmode (SPUCORESTATE->mvol+1) & 0x0000FFFF;
  case SPUREG_MVOLXL: return volume_getlevel(SPUCORESTATE->mvol+0) & 0x0000FFFF;
  case SPUREG_MVOLXR: return volume_getlevel(SPUCORESTATE->mvol+1) & 0x0000FFFF;
  case SPUREG_EVOLL:  return SPUCORESTATE->evol[0] & 0x0000FFFF;
  case SPUREG_EVOLR:  return SPUCORESTATE->evol[1] & 0x0000FFFF;
  case SPUREG_AVOLL:  return SPUCORESTATE->avol[0] & 0x0000FFFF;
  case SPUREG_AVOLR:  return SPUCORESTATE->avol[1] & 0x0000FFFF;
  case SPUREG_BVOLL:  return SPUCORESTATE->bvol[0] & 0x0000FFFF;
  case SPUREG_BVOLR:  return SPUCORESTATE->bvol[1] & 0x0000FFFF;
  case SPUREG_KON:    return SPUCORESTATE->kon     & 0x00FFFFFF;
  case SPUREG_KOFF:   return SPUCORESTATE->koff    & 0x00FFFFFF;
  case SPUREG_FM:     return SPUCORESTATE->fm      & 0x00FFFFFF;
  case SPUREG_NOISE:  return SPUCORESTATE->noise   & 0x00FFFFFF;
  case SPUREG_VMIXE:  return (SPUCORESTATE->vmixe[0] | SPUCORESTATE->vmixe[1]) & 0xFFFFFF;
  case SPUREG_VMIX:   return (SPUCORESTATE->vmix[0]  | SPUCORESTATE->vmix[1] ) & 0xFFFFFF;
  case SPUREG_VMIXEL: return SPUCORESTATE->vmixe[0] & 0x00FFFFFF;
  case SPUREG_VMIXER: return SPUCORESTATE->vmixe[1] & 0x00FFFFFF;
  case SPUREG_VMIXL:  return SPUCORESTATE->vmix[0]  & 0x00FFFFFF;
  case SPUREG_VMIXR:  return SPUCORESTATE->vmix[1]  & 0x00FFFFFF;
  case SPUREG_ESA:    return SPUCORESTATE->reverb.start_address;
  case SPUREG_EEA:    return SPUCORESTATE->reverb.end_address;
  case SPUREG_EAX:    return SPUCORESTATE->reverb.current_address;
  case SPUREG_IRQA:   return SPUCORESTATE->irq_address;
  case SPUREG_NOISECLOCK: return SPUCORESTATE->noiseclock;
#define SPUCORE_REVERB_GET(x) case SPUREG_REVERB_##x:return SPUCORESTATE->reverb.x;
  SPUCORE_REVERB_GET(FB_SRC_A)
  SPUCORE_REVERB_GET(FB_SRC_B)
  SPUCORE_REVERB_GET(IIR_ALPHA)
  SPUCORE_REVERB_GET(ACC_COEF_A)
  SPUCORE_REVERB_GET(ACC_COEF_B)
  SPUCORE_REVERB_GET(ACC_COEF_C)
  SPUCORE_REVERB_GET(ACC_COEF_D)
  SPUCORE_REVERB_GET(IIR_COEF)
  SPUCORE_REVERB_GET(FB_ALPHA)
  SPUCORE_REVERB_GET(FB_X)
  SPUCORE_REVERB_GET(IIR_DEST_A0)
  SPUCORE_REVERB_GET(IIR_DEST_A1)
  SPUCORE_REVERB_GET(ACC_SRC_A0)
  SPUCORE_REVERB_GET(ACC_SRC_A1)
  SPUCORE_REVERB_GET(ACC_SRC_B0)
  SPUCORE_REVERB_GET(ACC_SRC_B1)
  SPUCORE_REVERB_GET(IIR_SRC_A0)
  SPUCORE_REVERB_GET(IIR_SRC_A1)
  SPUCORE_REVERB_GET(IIR_DEST_B0)
  SPUCORE_REVERB_GET(IIR_DEST_B1)
  SPUCORE_REVERB_GET(ACC_SRC_C0)
  SPUCORE_REVERB_GET(ACC_SRC_C1)
  SPUCORE_REVERB_GET(ACC_SRC_D0)
  SPUCORE_REVERB_GET(ACC_SRC_D1)
  SPUCORE_REVERB_GET(IIR_SRC_B1)
  SPUCORE_REVERB_GET(IIR_SRC_B0)
  SPUCORE_REVERB_GET(MIX_DEST_A0)
  SPUCORE_REVERB_GET(MIX_DEST_A1)
  SPUCORE_REVERB_GET(MIX_DEST_B0)
  SPUCORE_REVERB_GET(MIX_DEST_B1)
  SPUCORE_REVERB_GET(IN_COEF_L)
  SPUCORE_REVERB_GET(IN_COEF_R)
  }
  return 0;
}

void EMU_CALL spucore_setreg(void *state, uint32 n, uint32 value, uint32 mask) {
  value &= mask;
  switch(n) {
  /* TODO: the increase/decrease modes, etc. */
  case SPUREG_MVOLL: volume_setmode(SPUCORESTATE->mvol+0, value); break;
  case SPUREG_MVOLR: volume_setmode(SPUCORESTATE->mvol+1, value); break;
  case SPUREG_EVOLL: SPUCORESTATE->evol[0] = value; break;
  case SPUREG_EVOLR: SPUCORESTATE->evol[1] = value; break;
  case SPUREG_AVOLL: SPUCORESTATE->avol[0] = value; break;
  case SPUREG_AVOLR: SPUCORESTATE->avol[1] = value; break;
  case SPUREG_BVOLL: SPUCORESTATE->bvol[0] = value; break;
  case SPUREG_BVOLR: SPUCORESTATE->bvol[1] = value; break;

  case SPUREG_KON:
    SPUCORESTATE->kon &= ~mask;
    SPUCORESTATE->kon |= value;
    voices_on(state, value);
    break;
  case SPUREG_KOFF:
    SPUCORESTATE->koff &= ~mask;
    SPUCORESTATE->koff |= value;
    voices_off(state, value);
    break;

  case SPUREG_FM:
    SPUCORESTATE->fm &= ~mask;
    SPUCORESTATE->fm |= value;
    break;
  case SPUREG_NOISE:
    SPUCORESTATE->noise &= ~mask;
    SPUCORESTATE->noise |= value;
    break;
  case SPUREG_VMIXE:
    SPUCORESTATE->vmixe[0] &= ~mask;
    SPUCORESTATE->vmixe[0] |= value;
    SPUCORESTATE->vmixe[1] &= ~mask;
    SPUCORESTATE->vmixe[1] |= value;
    break;
  case SPUREG_VMIX:
    SPUCORESTATE->vmix[0] &= ~mask;
    SPUCORESTATE->vmix[0] |= value;
    SPUCORESTATE->vmix[1] &= ~mask;
    SPUCORESTATE->vmix[1] |= value;
    break;
  case SPUREG_VMIXEL:
    SPUCORESTATE->vmixe[0] &= ~mask;
    SPUCORESTATE->vmixe[0] |= value;
    break;
  case SPUREG_VMIXER:
    SPUCORESTATE->vmixe[1] &= ~mask;
    SPUCORESTATE->vmixe[1] |= value;
    break;
  case SPUREG_VMIXL:
    SPUCORESTATE->vmix[0] &= ~mask;
    SPUCORESTATE->vmix[0] |= value;
    break;
  case SPUREG_VMIXR:
    SPUCORESTATE->vmix[1] &= ~mask;
    SPUCORESTATE->vmix[1] |= value;
    break;

  case SPUREG_ESA:
    SPUCORESTATE->reverb.start_address &= ~mask;
    SPUCORESTATE->reverb.start_address |= value;
    make_safe_reverb_addresses(state);
    SPUCORESTATE->reverb.current_address = SPUCORESTATE->reverb.safe_start_address;
    break;
  case SPUREG_EEA:
    SPUCORESTATE->reverb.end_address &= ~mask;
    SPUCORESTATE->reverb.end_address |= value;
    make_safe_reverb_addresses(state);
    break;
  case SPUREG_IRQA:
    /* TODO: actual implementation of IRQs */
    SPUCORESTATE->irq_address &= ~mask;
    SPUCORESTATE->irq_address |= value;
    break;
  case SPUREG_NOISECLOCK:
    SPUCORESTATE->noiseclock = value & 0x3F;
    break;

#define SPUCORE_REVERB_SET(x) case SPUREG_REVERB_##x:SPUCORESTATE->reverb.x&=(~mask);SPUCORESTATE->reverb.x|=value;break;
  SPUCORE_REVERB_SET(FB_SRC_A)
  SPUCORE_REVERB_SET(FB_SRC_B)
  SPUCORE_REVERB_SET(IIR_ALPHA)
  SPUCORE_REVERB_SET(ACC_COEF_A)
  SPUCORE_REVERB_SET(ACC_COEF_B)
  SPUCORE_REVERB_SET(ACC_COEF_C)
  SPUCORE_REVERB_SET(ACC_COEF_D)
  SPUCORE_REVERB_SET(IIR_COEF)
  SPUCORE_REVERB_SET(FB_ALPHA)
  SPUCORE_REVERB_SET(FB_X)
  SPUCORE_REVERB_SET(IIR_DEST_A0)
  SPUCORE_REVERB_SET(IIR_DEST_A1)
  SPUCORE_REVERB_SET(ACC_SRC_A0)
  SPUCORE_REVERB_SET(ACC_SRC_A1)
  SPUCORE_REVERB_SET(ACC_SRC_B0)
  SPUCORE_REVERB_SET(ACC_SRC_B1)
  SPUCORE_REVERB_SET(IIR_SRC_A0)
  SPUCORE_REVERB_SET(IIR_SRC_A1)
  SPUCORE_REVERB_SET(IIR_DEST_B0)
  SPUCORE_REVERB_SET(IIR_DEST_B1)
  SPUCORE_REVERB_SET(ACC_SRC_C0)
  SPUCORE_REVERB_SET(ACC_SRC_C1)
  SPUCORE_REVERB_SET(ACC_SRC_D0)
  SPUCORE_REVERB_SET(ACC_SRC_D1)
  SPUCORE_REVERB_SET(IIR_SRC_B1)
  SPUCORE_REVERB_SET(IIR_SRC_B0)
  SPUCORE_REVERB_SET(MIX_DEST_A0)
  SPUCORE_REVERB_SET(MIX_DEST_A1)
  SPUCORE_REVERB_SET(MIX_DEST_B0)
  SPUCORE_REVERB_SET(MIX_DEST_B1)
  SPUCORE_REVERB_SET(IN_COEF_L)
  SPUCORE_REVERB_SET(IN_COEF_R)
  }
}

////////////////////////////////////////////////////////////////////////////////
/*
** Voice register get/set
*/

uint32 EMU_CALL spucore_getreg_voice(void *state, uint32 voice, uint32 n) {
  switch(n) {
  case SPUREG_VOICE_VOLL : return volume_getmode(SPUCORESTATE->chan[voice].vol+0);
  case SPUREG_VOICE_VOLR : return volume_getmode(SPUCORESTATE->chan[voice].vol+1);
  case SPUREG_VOICE_VOLXL: return volume_getlevel(SPUCORESTATE->chan[voice].vol+0);
  case SPUREG_VOICE_VOLXR: return volume_getlevel(SPUCORESTATE->chan[voice].vol+1);
  case SPUREG_VOICE_PITCH: return SPUCORESTATE->chan[voice].voice_pitch;
  case SPUREG_VOICE_ADSR1: return SPUCORESTATE->chan[voice].env.reg_ad;
  case SPUREG_VOICE_ADSR2: return SPUCORESTATE->chan[voice].env.reg_sr;
  case SPUREG_VOICE_ENVX :
    if(SPUCORESTATE->chan[voice].env.state == ENVELOPE_STATE_OFF) return 0;
    return (SPUCORESTATE->chan[voice].env.level) >> 16;
  case SPUREG_VOICE_SSA  : return SPUCORESTATE->chan[voice].sample.start_block_addr;
  case SPUREG_VOICE_LSAX : return SPUCORESTATE->chan[voice].sample.loop_block_addr;
  case SPUREG_VOICE_NAX  : return SPUCORESTATE->chan[voice].sample.block_addr;
  }
  return 0;
}

void EMU_CALL spucore_setreg_voice(void *state, uint32 voice, uint32 n, uint32 value, uint32 mask) {
  value &= mask;
  switch(n) {
  /* TODO: the increase/decrease modes */
  case SPUREG_VOICE_VOLL : volume_setmode(SPUCORESTATE->chan[voice].vol+0, value); break;
  case SPUREG_VOICE_VOLR : volume_setmode(SPUCORESTATE->chan[voice].vol+1, value); break;
  case SPUREG_VOICE_PITCH: SPUCORESTATE->chan[voice].voice_pitch = value; break;
  case SPUREG_VOICE_ADSR1: SPUCORESTATE->chan[voice].env.reg_ad = value; SPUCORESTATE->chan[voice].env.cachemax = envelope_do(&SPUCORESTATE->chan[voice].env); break;
  case SPUREG_VOICE_ADSR2: SPUCORESTATE->chan[voice].env.reg_sr = value; SPUCORESTATE->chan[voice].env.cachemax = envelope_do(&SPUCORESTATE->chan[voice].env); break;

  case SPUREG_VOICE_SSA:
    SPUCORESTATE->chan[voice].sample.start_block_addr &= ~mask;
    SPUCORESTATE->chan[voice].sample.start_block_addr |= value;
    break;
  case SPUREG_VOICE_LSAX:
    SPUCORESTATE->chan[voice].sample.loop_block_addr &= ~mask;
    SPUCORESTATE->chan[voice].sample.loop_block_addr |= value;
    break;
  }
}

////////////////////////////////////////////////////////////////////////////////
/*
** IRQ checking
*/
uint32 EMU_CALL spucore_cycles_until_interrupt(void *state, uint16 *ram, uint32 samples) {
  uint32 r;
  void *backup;

  if (!(SPUCORESTATE->flags & SPUREG_FLAG_IRQ_ENABLE)) return 0xFFFFFFFF;

  backup = malloc(spucore_get_state_size());
  if (!backup) return 0xFFFFFFFF;
  memcpy(backup, state, spucore_get_state_size());
  state = backup;
  SPUCORESTATE->irq_triggered_cycle = 0xFFFFFFFF;
  r = 0;
  while(samples > RENDERMAX) {
    samples -= RENDERMAX;
    render(SPUCORESTATE, ram, NULL, NULL, RENDERMAX, 0, 0);
	if (SPUCORESTATE->irq_triggered_cycle != 0xFFFFFFFF) break;
	r += RENDERMAX * 768;
  }
  if(samples && SPUCORESTATE->irq_triggered_cycle == 0xFFFFFFFF) render(SPUCORESTATE, ram, NULL, NULL, samples, 0, 0);
  r = (SPUCORESTATE->irq_triggered_cycle == 0xFFFFFFFF) ? 0xFFFFFFFF : SPUCORESTATE->irq_triggered_cycle + r;
  free(backup);
  return r;
}

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