From 3564bfe8134ce6e1294a59212484e9ee487ab4f5 Mon Sep 17 00:00:00 2001 From: aliaspider Date: Sun, 2 Nov 2014 11:44:50 +0100 Subject: start hooking up blargg's apu implementation to use as a reference. (not yet working) --- source/apu_blargg.c | 3644 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3644 insertions(+) create mode 100644 source/apu_blargg.c (limited to 'source/apu_blargg.c') diff --git a/source/apu_blargg.c b/source/apu_blargg.c new file mode 100644 index 0000000..c2afa9b --- /dev/null +++ b/source/apu_blargg.c @@ -0,0 +1,3644 @@ +/*********************************************************************************** + Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. + + (c) Copyright 1996 - 2002 Gary Henderson (gary.henderson@ntlworld.com), + Jerremy Koot (jkoot@snes9x.com) + + (c) Copyright 2002 - 2004 Matthew Kendora + + (c) Copyright 2002 - 2005 Peter Bortas (peter@bortas.org) + + (c) Copyright 2004 - 2005 Joel Yliluoma (http://iki.fi/bisqwit/) + + (c) Copyright 2001 - 2006 John Weidman (jweidman@slip.net) + + (c) Copyright 2002 - 2006 funkyass (funkyass@spam.shaw.ca), + Kris Bleakley (codeviolation@hotmail.com) + + (c) Copyright 2002 - 2010 Brad Jorsch (anomie@users.sourceforge.net), + Nach (n-a-c-h@users.sourceforge.net), + zones (kasumitokoduck@yahoo.com) + + (c) Copyright 2006 - 2007 nitsuja + + (c) Copyright 2009 - 2010 BearOso, + OV2 + + + BS-X C emulator code + (c) Copyright 2005 - 2006 Dreamer Nom, + zones + + C4 x86 assembler and some C emulation code + (c) Copyright 2000 - 2003 _Demo_ (_demo_@zsnes.com), + Nach, + zsKnight (zsknight@zsnes.com) + + C4 C++ code + (c) Copyright 2003 - 2006 Brad Jorsch, + Nach + + DSP-1 emulator code + (c) Copyright 1998 - 2006 _Demo_, + Andreas Naive (andreasnaive@gmail.com), + Gary Henderson, + Ivar (ivar@snes9x.com), + John Weidman, + Kris Bleakley, + Matthew Kendora, + Nach, + neviksti (neviksti@hotmail.com) + + DSP-2 emulator code + (c) Copyright 2003 John Weidman, + Kris Bleakley, + Lord Nightmare (lord_nightmare@users.sourceforge.net), + Matthew Kendora, + neviksti + + DSP-3 emulator code + (c) Copyright 2003 - 2006 John Weidman, + Kris Bleakley, + Lancer, + z80 gaiden + + DSP-4 emulator code + (c) Copyright 2004 - 2006 Dreamer Nom, + John Weidman, + Kris Bleakley, + Nach, + z80 gaiden + + OBC1 emulator code + (c) Copyright 2001 - 2004 zsKnight, + pagefault (pagefault@zsnes.com), + Kris Bleakley + Ported from x86 assembler to C by sanmaiwashi + + SPC7110 and RTC C++ emulator code used in 1.39-1.51 + (c) Copyright 2002 Matthew Kendora with research by + zsKnight, + John Weidman, + Dark Force + + SPC7110 and RTC C++ emulator code used in 1.52+ + (c) Copyright 2009 byuu, + neviksti + + S-DD1 C emulator code + (c) Copyright 2003 Brad Jorsch with research by + Andreas Naive, + John Weidman + + S-RTC C emulator code + (c) Copyright 2001 - 2006 byuu, + John Weidman + + ST010 C++ emulator code + (c) Copyright 2003 Feather, + John Weidman, + Kris Bleakley, + Matthew Kendora + + Super FX x86 assembler emulator code + (c) Copyright 1998 - 2003 _Demo_, + pagefault, + zsKnight + + Super FX C emulator code + (c) Copyright 1997 - 1999 Ivar, + Gary Henderson, + John Weidman + + Sound emulator code used in 1.5-1.51 + (c) Copyright 1998 - 2003 Brad Martin + (c) Copyright 1998 - 2006 Charles Bilyue' + + Sound emulator code used in 1.52+ + (c) Copyright 2004 - 2007 Shay Green (gblargg@gmail.com) + + SH assembler code partly based on x86 assembler code + (c) Copyright 2002 - 2004 Marcus Comstedt (marcus@mc.pp.se) + + 2xSaI filter + (c) Copyright 1999 - 2001 Derek Liauw Kie Fa + + HQ2x, HQ3x, HQ4x filters + (c) Copyright 2003 Maxim Stepin (maxim@hiend3d.com) + + NTSC filter + (c) Copyright 2006 - 2007 Shay Green + + GTK+ GUI code + (c) Copyright 2004 - 2010 BearOso + + Win32 GUI code + (c) Copyright 2003 - 2006 blip, + funkyass, + Matthew Kendora, + Nach, + nitsuja + (c) Copyright 2009 - 2010 OV2 + + Mac OS GUI code + (c) Copyright 1998 - 2001 John Stiles + (c) Copyright 2001 - 2010 zones + + + Specific ports contains the works of other authors. See headers in + individual files. + + + Snes9x homepage: http://www.snes9x.com/ + + Permission to use, copy, modify and/or distribute Snes9x in both binary + and source form, for non-commercial purposes, is hereby granted without + fee, providing that this license information and copyright notice appear + with all copies and any derived work. + + This software is provided 'as-is', without any express or implied + warranty. In no event shall the authors be held liable for any damages + arising from the use of this software or it's derivatives. + + Snes9x is freeware for PERSONAL USE only. Commercial users should + seek permission of the copyright holders first. Commercial use includes, + but is not limited to, charging money for Snes9x or software derived from + Snes9x, including Snes9x or derivatives in commercial game bundles, and/or + using Snes9x as a promotion for your commercial product. + + The copyright holders request that bug fixes and improvements to the code + should be forwarded to them so everyone can benefit from the modifications + in future versions. + + Super NES and Super Nintendo Entertainment System are trademarks of + Nintendo Co., Limited and its subsidiary companies. + ***********************************************************************************/ +#ifdef USE_BLARGG_APU + +#include +#include +#include +#include +#include + +#include "blargg_endian.h" +#include "apu.h" + +#include "snes9x.h" +#include "snapshot.h" +#include "display.h" + +/*********************************************************************************** + SPC DSP +***********************************************************************************/ + +static dsp_state_t dsp_m; + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#if INT_MAX < 0x7FFFFFFF + #error "Requires that int type have at least 32 bits" +#endif + +/* if ( io < -32768 ) io = -32768; */ +/* if ( io > 32767 ) io = 32767; */ +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +/* Access global DSP register */ +#define REG(n) dsp_m.regs [R_##n] + +/* Access voice DSP register */ +#define VREG(r,n) r [V_##n] + +#define WRITE_SAMPLES( l, r, out ) \ +{\ + out [0] = l;\ + out [1] = r;\ + out += 2;\ + if ( out >= dsp_m.out_end )\ + {\ + out = dsp_m.extra;\ + dsp_m.out_end = &dsp_m.extra [EXTRA_SIZE];\ + }\ +}\ + + +/* Volume registers and efb are signed! Easy to forget int8_t cast. */ +/* Prefixes are to avoid accidental use of locals with same names. */ + +/* Gaussian interpolation */ + +static short gauss [512] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, +}; + +/* Gaussian interpolation */ + +static INLINE int dsp_interpolate( dsp_voice_t *v ) +{ + int offset, out, *in; + short *fwd, *rev; + + /* Make pointers into gaussian based on fractional position between samples */ + offset = v->interp_pos >> 4 & 0xFF; + fwd = gauss + 255 - offset; + rev = gauss + offset; /* mirror left half of gaussian */ + + in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + + CLAMP16( out ); + out &= ~1; + return out; +} + +/* Counters */ + +/* 30720 = 2048 * 5 * 3 */ +#define SIMPLE_COUNTER_RANGE 30720 + +static unsigned const counter_rates [32] = +{ + SIMPLE_COUNTER_RANGE + 1, /* never fires */ + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; + +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 +}; + +#define RUN_COUNTERS() \ + if ( --dsp_m.counter < 0 ) \ + dsp_m.counter = SIMPLE_COUNTER_RANGE - 1; + +#define READ_COUNTER(rate) (((unsigned) dsp_m.counter + counter_offsets [rate]) % counter_rates [rate]) + +/* Envelope */ + +static INLINE void dsp_run_envelope( dsp_voice_t* const v ) +{ + int env, rate, env_data; + + env = v->env; + env_data = v->regs[V_ADSR1]; + + if ( dsp_m.t_adsr0 & 0x80 ) /* 99% ADSR */ + { + if ( v->env_mode >= ENV_DECAY ) /* 99% */ + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == ENV_DECAY ) /* 1% */ + rate = (dsp_m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else /* ENV_ATTACK */ + { + rate = (dsp_m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else /* GAIN */ + { + int mode; + env_data = v->regs[V_GAIN]; + mode = env_data >> 5; + if ( mode < 4 ) /* direct */ + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) /* 4: linear decrease */ + { + env -= 0x20; + } + else if ( mode < 6 ) /* 5: exponential decrease */ + { + env--; + env -= env >> 8; + } + else /* 6,7: linear increase */ + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; /* 7: two-slope linear increase */ + } + } + } + + /* Sustain level */ + if ( (env >> 8) == (env_data >> 5) && v->env_mode == ENV_DECAY ) + v->env_mode = ENV_SUSTAIN; + + v->hidden_env = env; + + /* unsigned cast because linear decrease going negative also triggers this */ + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == ENV_ATTACK ) + v->env_mode = ENV_DECAY; + } + + if (!READ_COUNTER( rate )) + v->env = env; /* nothing else is controlled by the counter */ +} + +/* BRR Decoding */ + +static INLINE void dsp_decode_brr( dsp_voice_t* v ) +{ + int nybbles, *pos, *end, header; + + /* Arrange the four input nybbles in 0xABCD order for easy decoding */ + nybbles = dsp_m.t_brr_byte * 0x100 + dsp_m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + header = dsp_m.t_brr_header; + + /* Write to next four samples in circular buffer */ + pos = &v->buf [v->buf_pos]; + + if ( (v->buf_pos += 4) >= BRR_BUF_SIZE ) + v->buf_pos = 0; + + /* Decode four samples */ + for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) + { + int filter, p1, p2, s, shift; + /* Extract nybble and sign-extend */ + s = (int16_t) nybbles >> 12; + + /* Shift sample based on header */ + shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) /* handle invalid range */ + s = (s >> 25) << 11; /* same as: s = (s < 0 ? -0x800 : 0) */ + + /* Apply IIR filter (8 is the most commonly used) */ + filter = header & 0x0C; + p1 = pos [BRR_BUF_SIZE - 1]; + p2 = pos [BRR_BUF_SIZE - 2] >> 1; + if ( filter >= 8 ) + { + s += p1; + s -= p2; + if ( filter == 8 ) /* s += p1 * 0.953125 - p2 * 0.46875 */ + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else /* s += p1 * 0.8984375 - p2 * 0.40625 */ + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) /* s += p1 * 0.46875 */ + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + /* Adjust and write sample */ + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [BRR_BUF_SIZE] = pos [0] = s; /* second copy simplifies wrap-around */ + } +} + +/* Misc */ + +/* voice 0 doesn't support PMON */ + +#define MISC_27() dsp_m.t_pmon = dsp_m.regs[R_PMON] & 0xFE; + +#define MISC_28() \ + dsp_m.t_non = dsp_m.regs[R_NON]; \ + dsp_m.t_eon = dsp_m.regs[R_EON]; \ + dsp_m.t_dir = dsp_m.regs[R_DIR]; + +#define MISC_29() \ + if ( (dsp_m.every_other_sample ^= 1) != 0 ) \ + dsp_m.new_kon &= ~dsp_m.kon; /* clears KON 63 clocks after it was last read */ + +static INLINE void dsp_misc_30 (void) +{ + if ( dsp_m.every_other_sample ) + { + dsp_m.kon = dsp_m.new_kon; + dsp_m.t_koff = dsp_m.regs[R_KOFF]; + } + + RUN_COUNTERS(); + + /* Noise */ + if ( !READ_COUNTER( dsp_m.regs[R_FLG] & 0x1F ) ) + { + int feedback = (dsp_m.noise << 13) ^ (dsp_m.noise << 14); + dsp_m.noise = (feedback & 0x4000) ^ (dsp_m.noise >> 1); + } +} + +/* Voices */ + +static INLINE void dsp_voice_V1( dsp_voice_t* const v ) +{ + dsp_m.t_dir_addr = dsp_m.t_dir * 0x100 + dsp_m.t_srcn * 4; + dsp_m.t_srcn = v->regs[V_SRCN]; +} + +static INLINE void dsp_voice_V2( dsp_voice_t* const v ) +{ + uint8_t *entry; + + entry = &dsp_m.ram [dsp_m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + + dsp_m.t_brr_next_addr = GET_LE16( entry ); + + dsp_m.t_adsr0 = v->regs [V_ADSR0]; + + + dsp_m.t_pitch = v->regs [V_PITCHL]; +} + +static INLINE void dsp_voice_V3a( dsp_voice_t* const v ) +{ + dsp_m.t_pitch += (v->regs [V_PITCHH] & 0x3F) << 8; +} + +static INLINE void dsp_voice_V3b( dsp_voice_t* const v ) +{ + dsp_m.t_brr_byte = dsp_m.ram [(v->brr_addr + v->brr_offset) & 0xffff]; + dsp_m.t_brr_header = dsp_m.ram [v->brr_addr]; +} + +static void dsp_voice_V3c( dsp_voice_t* const v ) +{ + int output; + + /* Pitch modulation using previous voice's output */ + if ( dsp_m.t_pmon & v->vbit ) + dsp_m.t_pitch += ((dsp_m.t_output >> 5) * dsp_m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + /* Get ready to start BRR decoding on next sample */ + if ( v->kon_delay == 5 ) + { + v->brr_addr = dsp_m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + dsp_m.t_brr_header = 0; /* header is ignored on this sample */ + } + + /* Envelope is never run during KON */ + v->env = 0; + v->hidden_env = 0; + + /* Disable BRR decoding until last three samples */ + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + /* Pitch is never added during KON */ + dsp_m.t_pitch = 0; + } + + output = dsp_interpolate( v ); + + /* Noise */ + if ( dsp_m.t_non & v->vbit ) + output = (int16_t) (dsp_m.noise * 2); + + /* Apply envelope */ + dsp_m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (uint8_t) (v->env >> 4); + + /* Immediate silence due to end of sample or soft reset */ + if ( dsp_m.regs[R_FLG] & 0x80 || (dsp_m.t_brr_header & 3) == 1 ) + { + v->env_mode = ENV_RELEASE; + v->env = 0; + } + + if ( dsp_m.every_other_sample ) + { + /* KOFF */ + if ( dsp_m.t_koff & v->vbit ) + v->env_mode = ENV_RELEASE; + + /* KON */ + if ( dsp_m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = ENV_ATTACK; + } + } + + /* Run envelope for next sample */ + if ( !v->kon_delay ) + { + int env = v->env; + if ( v->env_mode == ENV_RELEASE ) /* 60% */ + { + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; + } + else + { + dsp_run_envelope( v ); + } + } +} + +static INLINE void dsp_voice_output( dsp_voice_t const* v, int ch ) +{ + int amp; + + /* Apply left/right volume */ + amp = (dsp_m.t_output * (int8_t) VREG(v->regs,VOLL + ch)) >> 7; + + /* Add to output total */ + dsp_m.t_main_out [ch] += amp; + CLAMP16( dsp_m.t_main_out [ch] ); + + /* Optionally add to echo total */ + if ( dsp_m.t_eon & v->vbit ) + { + dsp_m.t_echo_out [ch] += amp; + CLAMP16( dsp_m.t_echo_out [ch] ); + } +} + +static INLINE void dsp_voice_V4( dsp_voice_t* const v ) +{ + /* Decode BRR */ + dsp_m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + dsp_decode_brr( v ); + + if ( (v->brr_offset += 2) >= BRR_BLOCK_SIZE ) + { + /* Start decoding next BRR block */ + v->brr_addr = (v->brr_addr + BRR_BLOCK_SIZE) & 0xFFFF; + if ( dsp_m.t_brr_header & 1 ) + { + v->brr_addr = dsp_m.t_brr_next_addr; + dsp_m.t_looped = v->vbit; + } + v->brr_offset = 1; + } + } + + /* Apply pitch */ + v->interp_pos = (v->interp_pos & 0x3FFF) + dsp_m.t_pitch; + + /* Keep from getting too far ahead (when using pitch modulation) */ + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + /* Output left */ + dsp_voice_output( v, 0 ); +} + +static INLINE void dsp_voice_V5( dsp_voice_t* const v ) +{ + int endx_buf; + /* Output right */ + dsp_voice_output( v, 1 ); + + /* ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier */ + endx_buf = dsp_m.regs[R_ENDX] | dsp_m.t_looped; + + /* Clear bit in ENDX if KON just began */ + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + dsp_m.endx_buf = (uint8_t) endx_buf; +} + +static INLINE void dsp_voice_V6( dsp_voice_t* const v ) +{ + (void) v; /* avoid compiler warning about unused v */ + dsp_m.outx_buf = (uint8_t) (dsp_m.t_output >> 8); +} + +static INLINE void dsp_voice_V7( dsp_voice_t* const v ) +{ + /* Update ENDX */ + dsp_m.regs[R_ENDX] = dsp_m.endx_buf; + + dsp_m.envx_buf = v->t_envx_out; +} + +static INLINE void dsp_voice_V8( dsp_voice_t* const v ) +{ + /* Update OUTX */ + v->regs [V_OUTX] = dsp_m.outx_buf; +} + +static INLINE void dsp_voice_V9( dsp_voice_t* const v ) +{ + v->regs [V_ENVX] = dsp_m.envx_buf; +} + +/* Most voices do all these in one clock, so make a handy composite */ + +static INLINE void dsp_voice_V3( dsp_voice_t* const v ) +{ + dsp_voice_V3a( v ); + dsp_voice_V3b( v ); + dsp_voice_V3c( v ); +} + +/* Common combinations of voice steps on different voices. This greatly reduces + code size and allows everything to be inlined in these functions. */ + +static void dsp_voice_V7_V4_V1( dsp_voice_t* const v ) +{ + dsp_voice_V7(v); + dsp_voice_V1(v+3); + dsp_voice_V4(v+1); +} + +static void dsp_voice_V8_V5_V2( dsp_voice_t* const v ) +{ + dsp_voice_V8(v); + dsp_voice_V5(v+1); + dsp_voice_V2(v+2); +} + +static void dsp_voice_V9_V6_V3( dsp_voice_t* const v ) +{ + dsp_voice_V9(v); + dsp_voice_V6(v+1); + dsp_voice_V3(v+2); +} + +/* Echo */ + +/* Current echo buffer pointer for left/right channel */ +#define ECHO_PTR( ch ) (&dsp_m.ram [dsp_m.t_echo_ptr + ch * 2]) + +/* Sample in echo history buffer, where 0 is the oldest */ +#define ECHO_FIR( i ) (dsp_m.echo_hist_pos [i]) + +/* Calculate FIR point for left/right channel */ +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(FIR + i * 0x10)) >> 6) + +#define ECHO_READ(ch) \ +{ \ + int s; \ + if ( dsp_m.t_echo_ptr >= 0xffc0 && dsp_m.rom_enabled ) \ + s = GET_LE16SA( &dsp_m.hi_ram [dsp_m.t_echo_ptr + ch * 2 - 0xffc0] ); \ + else \ + s = GET_LE16SA( ECHO_PTR( ch ) ); \ + /* second copy simplifies wrap-around handling */ \ + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; \ +} + +static INLINE void dsp_echo_22 (void) +{ + int l, r; + + if ( ++dsp_m.echo_hist_pos >= &dsp_m.echo_hist [ECHO_HIST_SIZE] ) + dsp_m.echo_hist_pos = dsp_m.echo_hist; + + dsp_m.t_echo_ptr = (dsp_m.t_esa * 0x100 + dsp_m.echo_offset) & 0xFFFF; + + ECHO_READ(0); + + l = (((dsp_m.echo_hist_pos [0 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 0 * 0x10]) >> 6); + r = (((dsp_m.echo_hist_pos [0 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 0 * 0x10]) >> 6); + + dsp_m.t_echo_in [0] = l; + dsp_m.t_echo_in [1] = r; +} + +static INLINE void dsp_echo_23 (void) +{ + int l, r; + + l = (((dsp_m.echo_hist_pos [1 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 1 * 0x10]) >> 6) + (((dsp_m.echo_hist_pos [2 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 2 * 0x10]) >> 6); + r = (((dsp_m.echo_hist_pos [1 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 1 * 0x10]) >> 6) + (((dsp_m.echo_hist_pos [2 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 2 * 0x10]) >> 6); + + dsp_m.t_echo_in [0] += l; + dsp_m.t_echo_in [1] += r; + + ECHO_READ(1); +} + +static INLINE void dsp_echo_24 (void) +{ + int l, r; + + l = (((dsp_m.echo_hist_pos [3 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 3 * 0x10]) >> 6) + (((dsp_m.echo_hist_pos [4 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 4 * 0x10]) >> 6) + (((dsp_m.echo_hist_pos [5 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 5 * 0x10]) >> 6); + r = (((dsp_m.echo_hist_pos [3 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 3 * 0x10]) >> 6) + (((dsp_m.echo_hist_pos [4 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 4 * 0x10]) >> 6) + (((dsp_m.echo_hist_pos [5 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 5 * 0x10]) >> 6); + + dsp_m.t_echo_in [0] += l; + dsp_m.t_echo_in [1] += r; +} + +static INLINE void dsp_echo_25 (void) +{ + int l = dsp_m.t_echo_in [0] + (((dsp_m.echo_hist_pos [6 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 6 * 0x10]) >> 6); + int r = dsp_m.t_echo_in [1] + (((dsp_m.echo_hist_pos [6 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 6 * 0x10]) >> 6); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) (((dsp_m.echo_hist_pos [7 + 1]) [0] * (int8_t) dsp_m.regs [R_FIR + 7 * 0x10]) >> 6); + r += (int16_t) (((dsp_m.echo_hist_pos [7 + 1]) [1] * (int8_t) dsp_m.regs [R_FIR + 7 * 0x10]) >> 6); + + if ( (int16_t) l != l ) + l = (l >> 31) ^ 0x7FFF; + if ( (int16_t) r != r ) + r = (r >> 31) ^ 0x7FFF; + + dsp_m.t_echo_in [0] = l & ~1; + dsp_m.t_echo_in [1] = r & ~1; +} + +#define ECHO_OUTPUT(var, ch) \ +{ \ + var = (int16_t) ((dsp_m.t_main_out [ch] * (int8_t) REG(MVOLL + ch * 0x10)) >> 7) + (int16_t) ((dsp_m.t_echo_in [ch] * (int8_t) REG(EVOLL + ch * 0x10)) >> 7); \ + CLAMP16( var ); \ +} + +static INLINE void dsp_echo_26 (void) +{ + int l, r; + + ECHO_OUTPUT(dsp_m.t_main_out[0], 0 ); + + l = dsp_m.t_echo_out [0] + (int16_t) ((dsp_m.t_echo_in [0] * (int8_t) dsp_m.regs [R_EFB]) >> 7); + r = dsp_m.t_echo_out [1] + (int16_t) ((dsp_m.t_echo_in [1] * (int8_t) dsp_m.regs [R_EFB]) >> 7); + + if ( (int16_t) l != l ) l = (l >> 31) ^ 0x7FFF; + if ( (int16_t) r != r ) r = (r >> 31) ^ 0x7FFF; + + dsp_m.t_echo_out [0] = l & ~1; + dsp_m.t_echo_out [1] = r & ~1; +} + +static INLINE void dsp_echo_27 (void) +{ + int l, r; + short *out; + + l = dsp_m.t_main_out [0]; + ECHO_OUTPUT(r, 1); + dsp_m.t_main_out [0] = 0; + dsp_m.t_main_out [1] = 0; + + if ( dsp_m.regs [R_FLG] & 0x40 ) + { + l = 0; + r = 0; + } + + out = dsp_m.out; + out [0] = l; + out [1] = r; + out += 2; + if ( out >= dsp_m.out_end ) + { + out = dsp_m.extra; + dsp_m.out_end = &dsp_m.extra [EXTRA_SIZE]; + } + dsp_m.out = out; +} + +#define ECHO_28() dsp_m.t_echo_enabled = dsp_m.regs [R_FLG]; + +#define ECHO_WRITE(ch) \ + if ( !(dsp_m.t_echo_enabled & 0x20) ) \ + { \ + SET_LE16A( ECHO_PTR( ch ), dsp_m.t_echo_out [ch] ); \ + if ( dsp_m.t_echo_ptr >= 0xffc0 ) \ + { \ + SET_LE16A( &dsp_m.hi_ram [dsp_m.t_echo_ptr + ch * 2 - 0xffc0], dsp_m.t_echo_out [ch] ); \ + if ( dsp_m.rom_enabled ) \ + SET_LE16A( ECHO_PTR( ch ), GET_LE16A( &dsp_m.rom [dsp_m.t_echo_ptr + ch * 2 - 0xffc0] ) ); \ + } \ + } \ + dsp_m.t_echo_out [ch] = 0; + +static INLINE void dsp_echo_29 (void) +{ + dsp_m.t_esa = dsp_m.regs [R_ESA]; + + if ( !dsp_m.echo_offset ) + dsp_m.echo_length = (dsp_m.regs [R_EDL] & 0x0F) * 0x800; + + dsp_m.echo_offset += 4; + if ( dsp_m.echo_offset >= dsp_m.echo_length ) + dsp_m.echo_offset = 0; + + + ECHO_WRITE(0); + + dsp_m.t_echo_enabled = dsp_m.regs [R_FLG]; +} + +/* Timing */ + +/* Execute clock for a particular voice */ + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +/* Voice 0 1 2 3 4 5 6 7 */ + +/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks + a pair of samples is be generated. */ + +static void dsp_run( int clocks_remain ) +{ + int phase; + + phase = dsp_m.phase; + dsp_m.phase = (phase + clocks_remain) & 31; + + switch ( phase ) + { +loop: + if ( 0 && !--clocks_remain ) + break; + case 0: + dsp_voice_V5( &dsp_m.voices [0] ); + dsp_voice_V2( &dsp_m.voices [1] ); + if ( 1 && !--clocks_remain ) + break; + case 1: + dsp_voice_V6( &dsp_m.voices [0] ); + dsp_voice_V3( &dsp_m.voices [1] ); + if ( 2 && !--clocks_remain ) + break; + case 2: + dsp_voice_V7_V4_V1( &dsp_m.voices [0] ); + if ( 3 && !--clocks_remain ) + break; + case 3: + dsp_voice_V8_V5_V2( &dsp_m.voices [0] ); + if ( 4 && !--clocks_remain ) + break; + case 4: + dsp_voice_V9_V6_V3( &dsp_m.voices [0] ); + if ( 5 && !--clocks_remain ) + break; + case 5: + dsp_voice_V7_V4_V1( &dsp_m.voices [1] ); + if ( 6 && !--clocks_remain ) + break; + case 6: + dsp_voice_V8_V5_V2( &dsp_m.voices [1] ); + if ( 7 && !--clocks_remain ) + break; + case 7: + dsp_voice_V9_V6_V3( &dsp_m.voices [1] ); + if ( 8 && !--clocks_remain ) + break; + case 8: + dsp_voice_V7_V4_V1( &dsp_m.voices [2] ); + if ( 9 && !--clocks_remain ) + break; + case 9: + dsp_voice_V8_V5_V2( &dsp_m.voices [2] ); + if ( 10 && !--clocks_remain ) + break; + case 10: + dsp_voice_V9_V6_V3( &dsp_m.voices [2] ); + if ( 11 && !--clocks_remain ) + break; + case 11: + dsp_voice_V7_V4_V1( &dsp_m.voices [3] ); + if ( 12 && !--clocks_remain ) + break; + case 12: + dsp_voice_V8_V5_V2( &dsp_m.voices [3] ); + if ( 13 && !--clocks_remain ) + break; + case 13: + dsp_voice_V9_V6_V3( &dsp_m.voices [3] ); + if ( 14 && !--clocks_remain ) + break; + case 14: + dsp_voice_V7_V4_V1( &dsp_m.voices [4] ); + if ( 15 && !--clocks_remain ) + break; + case 15: + dsp_voice_V8_V5_V2( &dsp_m.voices [4] ); + if ( 16 && !--clocks_remain ) + break; + case 16: + dsp_voice_V9_V6_V3( &dsp_m.voices [4] ); + if ( 17 && !--clocks_remain ) + break; + case 17: + dsp_voice_V1( &dsp_m.voices [0] ); + dsp_voice_V7( &dsp_m.voices [5] ); + dsp_voice_V4( &dsp_m.voices [6] ); + if ( 18 && !--clocks_remain ) + break; + case 18: + dsp_voice_V8_V5_V2( &dsp_m.voices [5] ); + if ( 19 && !--clocks_remain ) + break; + case 19: + dsp_voice_V9_V6_V3( &dsp_m.voices [5] ); + if ( 20 && !--clocks_remain ) + break; + case 20: + dsp_voice_V1( &dsp_m.voices [1] ); + dsp_voice_V7( &dsp_m.voices [6] ); + dsp_voice_V4( &dsp_m.voices [7] ); + if ( 21 && !--clocks_remain ) + break; + case 21: + dsp_voice_V8( &dsp_m.voices [6] ); + dsp_voice_V5( &dsp_m.voices [7] ); + dsp_voice_V2( &dsp_m.voices [0] ); + if ( 22 && !--clocks_remain ) + break; + case 22: + dsp_voice_V3a( &dsp_m.voices [0] ); + dsp_voice_V9( &dsp_m.voices [6] ); + dsp_voice_V6( &dsp_m.voices [7] ); + dsp_echo_22(); + if ( 23 && !--clocks_remain ) + break; + case 23: + dsp_voice_V7( &dsp_m.voices [7] ); + dsp_echo_23(); + if ( 24 && !--clocks_remain ) + break; + case 24: + dsp_voice_V8( &dsp_m.voices [7] ); + dsp_echo_24(); + if ( 25 && !--clocks_remain ) + break; + case 25: + dsp_voice_V3b( &dsp_m.voices [0] ); + dsp_voice_V9( &dsp_m.voices [7] ); + dsp_echo_25(); + if ( 26 && !--clocks_remain ) + break; + case 26: + dsp_echo_26(); + if ( 27 && !--clocks_remain ) + break; + case 27: + MISC_27(); + dsp_echo_27(); + if ( 28 && !--clocks_remain ) + break; + case 28: + MISC_28(); + ECHO_28(); + if ( 29 && !--clocks_remain ) + break; + case 29: + MISC_29(); + dsp_echo_29(); + if ( 30 && !--clocks_remain ) + break; + case 30: + dsp_misc_30(); + dsp_voice_V3c( &dsp_m.voices [0] ); + ECHO_WRITE(1); + if ( 31 && !--clocks_remain ) + break; + case 31: + dsp_voice_V4( &dsp_m.voices [0] ); + dsp_voice_V1( &dsp_m.voices [2] ); + + if ( --clocks_remain ) + goto loop; + } +} + +/* Sets destination for output samples. If out is NULL or out_size is 0, + doesn't generate any. */ + +static void dsp_set_output( short * out, int size ) +{ + if ( !out ) + { + out = dsp_m.extra; + size = EXTRA_SIZE; + } + dsp_m.out_begin = out; + dsp_m.out = out; + dsp_m.out_end = out + size; +} + +/* Setup */ + +static void dsp_soft_reset_common (void) +{ + dsp_m.noise = 0x4000; + dsp_m.echo_hist_pos = dsp_m.echo_hist; + dsp_m.every_other_sample = 1; + dsp_m.echo_offset = 0; + dsp_m.phase = 0; + + dsp_m.counter = 0; +} + +/* Resets DSP to power-on state */ + +static void dsp_reset (void) +{ + int i; + + uint8_t const initial_regs [REGISTER_COUNT] = + { + 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, + 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, + 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, + 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, + 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, + 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x00,0x4E,0x7B,0xFF, + 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, + 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF + }; + + /* Resets DSP and uses supplied values to initialize registers */ + + for (i = 0; i < REGISTER_COUNT; i++) + dsp_m.regs[i] = initial_regs[i]; + + /* Internal state */ + for ( i = VOICE_COUNT; --i >= 0; ) + { + dsp_voice_t* v = &dsp_m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &dsp_m.regs [i * 0x10]; + } + dsp_m.new_kon = dsp_m.regs[R_KON]; + dsp_m.t_dir = dsp_m.regs[R_DIR]; + dsp_m.t_esa = dsp_m.regs[R_ESA]; + + dsp_soft_reset_common(); +} + +/* Initializes DSP and has it use the 64K RAM provided */ + +static void dsp_init( void* ram_64k ) +{ + dsp_m.ram = (uint8_t*) ram_64k; + dsp_set_output( 0, 0 ); + dsp_reset(); +} + +/* Emulates pressing reset switch on SNES */ + +static void dsp_soft_reset (void) +{ + dsp_m.regs[R_FLG] = 0xE0; + dsp_soft_reset_common(); +} + + +/* State save/load */ + +#if !SPC_NO_COPY_STATE_FUNCS + +static void spc_copier_copy(spc_state_copy_t * copier, void* state, size_t size ) +{ + copier->func(copier->buf, state, size ); +} + +static int spc_copier_copy_int(spc_state_copy_t * copier, int state, int size ) +{ + uint8_t s [2]; + SET_LE16( s, state ); + copier->func(copier->buf, &s, size ); + return GET_LE16( s ); +} + +static void spc_copier_extra(spc_state_copy_t * copier) +{ + int n = 0; + n = (uint8_t) spc_copier_copy_int(copier, n, sizeof (uint8_t) ); + + if ( n > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof(temp)); + do + { + int size_n = sizeof(temp); + if ( size_n > n ) + size_n = n; + n -= size_n; + copier->func(copier->buf, temp, size_n ); + } + while ( n ); + } +} + +/* Saves/loads exact emulator state */ + +static void dsp_copy_state( unsigned char** io, dsp_copy_func_t copy ) +{ + int i, j; + + spc_state_copy_t copier; + copier.func = copy; + copier.buf = io; + + /* DSP registers */ + spc_copier_copy(&copier, dsp_m.regs, REGISTER_COUNT ); + + /* Internal state */ + + /* Voices */ + for ( i = 0; i < VOICE_COUNT; i++ ) + { + dsp_voice_t* v; + + v = &dsp_m.voices [i]; + + /* BRR buffer */ + for ( j = 0; j < BRR_BUF_SIZE; j++ ) + { + int s; + + s = v->buf [j]; + SPC_COPY( int16_t, s ); + v->buf [j] = v->buf [j + BRR_BUF_SIZE] = s; + } + + SPC_COPY( uint16_t, v->interp_pos ); + SPC_COPY( uint16_t, v->brr_addr ); + SPC_COPY( uint16_t, v->env ); + SPC_COPY( int16_t, v->hidden_env ); + SPC_COPY( uint8_t, v->buf_pos ); + SPC_COPY( uint8_t, v->brr_offset ); + SPC_COPY( uint8_t, v->kon_delay ); + { + int m; + + m = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + spc_copier_extra(&copier); + } + + /* Echo history */ + for ( i = 0; i < ECHO_HIST_SIZE; i++ ) + { + int s, s2; + + s = dsp_m.echo_hist_pos [i] [0]; + s2 = dsp_m.echo_hist_pos [i] [1]; + + SPC_COPY( int16_t, s ); + dsp_m.echo_hist [i] [0] = s; /* write back at offset 0 */ + + SPC_COPY( int16_t, s2 ); + dsp_m.echo_hist [i] [1] = s2; /* write back at offset 0 */ + } + dsp_m.echo_hist_pos = dsp_m.echo_hist; + memcpy( &dsp_m.echo_hist [ECHO_HIST_SIZE], dsp_m.echo_hist, ECHO_HIST_SIZE * sizeof dsp_m.echo_hist [0] ); + + /* Misc */ + SPC_COPY( uint8_t, dsp_m.every_other_sample ); + SPC_COPY( uint8_t, dsp_m.kon ); + + SPC_COPY( uint16_t, dsp_m.noise ); + SPC_COPY( uint16_t, dsp_m.counter ); + SPC_COPY( uint16_t, dsp_m.echo_offset ); + SPC_COPY( uint16_t, dsp_m.echo_length ); + SPC_COPY( uint8_t, dsp_m.phase ); + + SPC_COPY( uint8_t, dsp_m.new_kon ); + SPC_COPY( uint8_t, dsp_m.endx_buf ); + SPC_COPY( uint8_t, dsp_m.envx_buf ); + SPC_COPY( uint8_t, dsp_m.outx_buf ); + + SPC_COPY( uint8_t, dsp_m.t_pmon ); + SPC_COPY( uint8_t, dsp_m.t_non ); + SPC_COPY( uint8_t, dsp_m.t_eon ); + SPC_COPY( uint8_t, dsp_m.t_dir ); + SPC_COPY( uint8_t, dsp_m.t_koff ); + + SPC_COPY( uint16_t, dsp_m.t_brr_next_addr ); + SPC_COPY( uint8_t, dsp_m.t_adsr0 ); + SPC_COPY( uint8_t, dsp_m.t_brr_header ); + SPC_COPY( uint8_t, dsp_m.t_brr_byte ); + SPC_COPY( uint8_t, dsp_m.t_srcn ); + SPC_COPY( uint8_t, dsp_m.t_esa ); + SPC_COPY( uint8_t, dsp_m.t_echo_enabled ); + + SPC_COPY( int16_t, dsp_m.t_main_out [0] ); + SPC_COPY( int16_t, dsp_m.t_main_out [1] ); + SPC_COPY( int16_t, dsp_m.t_echo_out [0] ); + SPC_COPY( int16_t, dsp_m.t_echo_out [1] ); + SPC_COPY( int16_t, dsp_m.t_echo_in [0] ); + SPC_COPY( int16_t, dsp_m.t_echo_in [1] ); + + SPC_COPY( uint16_t, dsp_m.t_dir_addr ); + SPC_COPY( uint16_t, dsp_m.t_pitch ); + SPC_COPY( int16_t, dsp_m.t_output ); + SPC_COPY( uint16_t, dsp_m.t_echo_ptr ); + SPC_COPY( uint8_t, dsp_m.t_looped ); + + spc_copier_extra(&copier); +} +#endif + +/* Core SPC emulation: CPU, timers, SMP registers, memory */ + +/* snes_spc 0.9.0. http://www.slack.net/~ant/ */ + +/*********************************************************************************** + SNES SPC +***********************************************************************************/ + +static spc_state_t m; +static signed char reg_times [256]; +static bool8 allow_time_overflow; + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* (n ? n : 256) */ +#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1) + +/* Timers */ + +#define TIMER_DIV( t, n ) ((n) >> t->prescaler) +#define TIMER_MUL( t, n ) ((n) << t->prescaler) + +static Timer* spc_run_timer_( Timer* t, int time ) +{ + int elapsed; + + elapsed = TIMER_DIV( t, time - t->next_time ) + 1; + t->next_time += TIMER_MUL( t, elapsed ); + + if ( t->enabled ) + { + int remain, divider, over, n; + + remain = IF_0_THEN_256( t->period - t->divider ); + divider = t->divider + elapsed; + over = elapsed - remain; + if ( over >= 0 ) + { + n = over / t->period; + t->counter = (t->counter + 1 + n) & 0x0F; + divider = over - n * t->period; + } + t->divider = (uint8_t) divider; + } + return t; +} + +/* ROM */ + +void spc_enable_rom( int enable ) +{ + if ( m.rom_enabled != enable ) + { + m.rom_enabled = dsp_m.rom_enabled = enable; + if ( enable ) + memcpy( m.hi_ram, &m.ram.ram[ROM_ADDR], sizeof m.hi_ram ); + memcpy( &m.ram.ram[ROM_ADDR], (enable ? m.rom : m.hi_ram), ROM_SIZE ); + /* TODO: ROM can still get overwritten when DSP writes to echo buffer */ + } +} + + +/* DSP */ + +#define MAX_REG_TIME 29 + +#define RUN_DSP( time, offset ) \ + int count = (time) - (offset) - m.dsp_time; \ + if ( count >= 0 ) \ + { \ + int clock_count; \ + clock_count = (count & ~(CLOCKS_PER_SAMPLE - 1)) + CLOCKS_PER_SAMPLE; \ + m.dsp_time += clock_count; \ + dsp_run( clock_count ); \ + } + +static INLINE void spc_dsp_write( int data, int time ) +{ + int addr; + + /* Writes DSP registers. */ + addr = m.smp_regs[0][R_DSPADDR]; + dsp_m.regs [addr] = (uint8_t) data; + switch ( addr & 0x0F ) + { + case V_ENVX: + dsp_m.envx_buf = (uint8_t) data; + break; + + case V_OUTX: + dsp_m.outx_buf = (uint8_t) data; + break; + case 0x0C: + if ( addr == R_KON ) + dsp_m.new_kon = (uint8_t) data; + + if ( addr == R_ENDX ) /* always cleared, regardless of data written */ + { + dsp_m.endx_buf = 0; + dsp_m.regs [R_ENDX] = 0; + } + break; + } + /* dprintf( "SPC wrote to DSP register > $7F\n" ); */ +} + + +/* Memory access extras */ + +/* CPU write */ + +/* divided into multiple functions to keep rarely-used functionality separate + so often-used functionality can be optimized better by compiler */ + +/* If write isn't preceded by read, data has this added to it + int const no_read_before_write = 0x2000; */ + +#define NO_READ_BEFORE_WRITE 8192 +#define NO_READ_BEFORE_WRITE_DIVIDED_BY_TWO 4096 + +static void spc_cpu_write_smp_reg_( int data, int time, int addr ) +{ + switch ( addr ) + { + case R_T0TARGET: + case R_T1TARGET: + case R_T2TARGET: + { + int period; + Timer *t; + + t = &m.timers [addr - R_T0TARGET]; + period = IF_0_THEN_256( data ); + + if ( t->period != period ) + { + if ( time >= t->next_time ) + t = spc_run_timer_( t, time ); + t->period = period; + } + break; + } + case R_T0OUT: + case R_T1OUT: + case R_T2OUT: + /* dprintf( "SPC wrote to counter %d\n", (int) addr - R_T0OUT ); */ + + if ( data < NO_READ_BEFORE_WRITE_DIVIDED_BY_TWO) + { + if ( (time - 1) >= m.timers[addr - R_T0OUT].next_time ) + spc_run_timer_( &m.timers [addr - R_T0OUT], time - 1 )->counter = 0; + else + m.timers[addr - R_T0OUT].counter = 0; + } + break; + + /* Registers that act like RAM */ + case 0x8: + case 0x9: + m.smp_regs[1][addr] = (uint8_t) data; + break; + + case R_TEST: +#if 0 + if ( (uint8_t) data != 0x0A ) + dprintf( "SPC wrote to test register\n" ); +#endif + break; + + case R_CONTROL: + { + int i; + /* port clears */ + if ( data & 0x10 ) + { + m.smp_regs[1][R_CPUIO0] = 0; + m.smp_regs[1][R_CPUIO1] = 0; + } + if ( data & 0x20 ) + { + m.smp_regs[1][R_CPUIO2] = 0; + m.smp_regs[1][R_CPUIO3] = 0; + } + + /* timers */ + { + for ( i = 0; i < TIMER_COUNT; i++ ) + { + Timer* t = &m.timers [i]; + int enabled = data >> i & 1; + if ( t->enabled != enabled ) + { + if ( time >= t->next_time ) + t = spc_run_timer_( t, time ); + t->enabled = enabled; + if ( enabled ) + { + t->divider = 0; + t->counter = 0; + } + } + } + } + spc_enable_rom( data & 0x80 ); + } + break; + } +} + +static int const bits_in_int = CHAR_BIT * sizeof (int); + +static void spc_cpu_write( int data, int addr, int time ) +{ + int reg; + /* RAM */ + m.ram.ram[addr] = (uint8_t) data; + reg = addr - 0xF0; + if ( reg >= 0 ) /* 64% */ + { + /* $F0-$FF */ + if ( reg < REG_COUNT ) /* 87% */ + { + m.smp_regs[0][reg] = (uint8_t) data; + + /* Registers other than $F2 and $F4-$F7 + if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 ) + TODO: this is a bit on the fragile side */ + + if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) /* 36% */ + { + if ( reg == R_DSPDATA ) /* 99% */ + { + RUN_DSP(time, reg_times [m.smp_regs[0][R_DSPADDR]] ); + if (m.smp_regs[0][R_DSPADDR] <= 0x7F ) + spc_dsp_write( data, time ); + } + else + spc_cpu_write_smp_reg_( data, time, reg); + } + } + /* High mem/address wrap-around */ + else + { + reg -= ROM_ADDR - 0xF0; + if ( reg >= 0 ) /* 1% in IPL ROM area or address wrapped around */ + { + if ( reg < ROM_SIZE ) + { + m.hi_ram [reg] = (uint8_t) data; + if ( m.rom_enabled ) + m.ram.ram[reg + ROM_ADDR] = m.rom [reg]; /* restore overwritten ROM */ + } + else + { + *(&(m.ram.ram[0]) + reg + ROM_ADDR) = CPU_PAD_FILL; /* restore overwritten padding */ + spc_cpu_write( data, reg + ROM_ADDR - 0x10000, time ); + } + } + } + } +} + +/* CPU read */ + +static int spc_cpu_read( int addr, int time ) +{ + int result, reg; + + /* RAM */ + result = m.ram.ram[addr]; + reg = addr - 0xF0; + + if ( reg >= 0 ) /* 40% */ + { + reg -= 0x10; + if ( (unsigned) reg >= 0xFF00 ) /* 21% */ + { + reg += 0x10 - R_T0OUT; + + /* Timers */ + if ( (unsigned) reg < TIMER_COUNT ) /* 90% */ + { + Timer* t = &m.timers [reg]; + if ( time >= t->next_time ) + t = spc_run_timer_( t, time ); + result = t->counter; + t->counter = 0; + } + /* Other registers */ + else if ( reg < 0 ) /* 10% */ + { + int reg_tmp; + + reg_tmp = reg + R_T0OUT; + result = m.smp_regs[1][reg_tmp]; + reg_tmp -= R_DSPADDR; + /* DSP addr and data */ + if ( (unsigned) reg_tmp <= 1 ) /* 4% 0xF2 and 0xF3 */ + { + result = m.smp_regs[0][R_DSPADDR]; + if ( (unsigned) reg_tmp == 1 ) + { + RUN_DSP( time, reg_times [m.smp_regs[0][R_DSPADDR] & 0x7F] ); + + result = dsp_m.regs[m.smp_regs[0][R_DSPADDR] & 0x7F]; /* 0xF3 */ + } + } + } + else /* 1% */ + result = spc_cpu_read( reg + (R_T0OUT + 0xF0 - 0x10000), time ); + } + } + + return result; +} + +/*********************************************************************************** + SPC CPU +***********************************************************************************/ + +/* Inclusion here allows static memory access functions and better optimization */ + +/* Timers are by far the most common thing read from dp */ + +#define CPU_READ_TIMER( time, offset, addr_, out )\ +{\ + int adj_time, dp_addr, ti; \ + adj_time = time + offset;\ + dp_addr = addr_;\ + ti = dp_addr - (R_T0OUT + 0xF0);\ + if ( (unsigned) ti < TIMER_COUNT )\ + {\ + Timer* t = &m.timers [ti];\ + if ( adj_time >= t->next_time )\ + t = spc_run_timer_( t, adj_time );\ + out = t->counter;\ + t->counter = 0;\ + }\ + else\ + {\ + int i, reg; \ + out = ram [dp_addr];\ + i = dp_addr - 0xF0;\ + if ( (unsigned) i < 0x10 )\ + { \ + reg = i; \ + out = m.smp_regs[1][reg]; \ + reg -= R_DSPADDR; \ + /* DSP addr and data */ \ + if ( (unsigned) reg <= 1 ) /* 4% 0xF2 and 0xF3 */ \ + { \ + out = m.smp_regs[0][R_DSPADDR]; \ + if ( (unsigned) reg == 1 ) \ + { \ + RUN_DSP( adj_time, reg_times [m.smp_regs[0][R_DSPADDR] & 0x7F] ); \ + out = dsp_m.regs[m.smp_regs[0][R_DSPADDR] & 0x7F ]; /* 0xF3 */ \ + } \ + } \ + } \ + }\ +} + +#define READ_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, time, (addr), out ) +#define SPC_CPU_READ( time, addr ) spc_cpu_read((addr), rel_time + time ) +#define SPC_CPU_WRITE( time, addr, data ) spc_cpu_write((data), (addr), rel_time + time ) + +static unsigned spc_CPU_mem_bit( uint8_t const* pc, int rel_time ) +{ + unsigned addr, t; + + addr = GET_LE16( pc ); + t = SPC_CPU_READ( 0, addr & 0x1FFF ) >> (addr >> 13); + return t << 8 & 0x100; +} + +#define DP_ADDR( addr ) (dp + (addr)) + +#define READ_DP_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, time, DP_ADDR( addr ), out ) +#define READ_DP( time, addr ) SPC_CPU_READ( time, DP_ADDR( addr ) ) +#define WRITE_DP( time, addr, data ) SPC_CPU_WRITE( time, DP_ADDR( addr ), data ) + +#define READ_PROG16( addr ) GET_LE16( ram + (addr) ) + +#define SET_PC( n ) (pc = ram + (n)) +#define GET_PC() (pc - ram) +#define READ_PC( pc ) (*(pc)) + +#define SET_SP( v ) (sp = ram + 0x101 + (v)) +#define GET_SP() (sp - 0x101 - ram) + +#define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) +#define PUSH( v ) (void) (*--sp = (uint8_t) (v)) +#define POP( out ) (void) ((out) = *sp++) + +#define MEM_BIT( rel ) spc_CPU_mem_bit( pc, rel_time + rel ) + +#define GET_PSW( out )\ +{\ + out = psw & ~(N80 | P20 | Z02 | C01);\ + out |= c >> 8 & C01;\ + out |= dp >> 3 & P20;\ + out |= ((nz >> 4) | nz) & N80;\ + if ( !(uint8_t) nz ) out |= Z02;\ +} + +#define SET_PSW( in )\ +{\ + psw = in;\ + c = in << 8;\ + dp = in << 3 & 0x100;\ + nz = (in << 4 & 0x800) | (~in & Z02);\ +} + +static uint8_t* spc_run_until_( int end_time ) +{ + int dp, nz, c, psw, a, x, y; + uint8_t *ram, *pc, *sp; + int rel_time = m.spc_time - end_time; + m.spc_time = end_time; + m.dsp_time += rel_time; + m.timers [0].next_time += rel_time; + m.timers [1].next_time += rel_time; + m.timers [2].next_time += rel_time; + ram = m.ram.ram; + a = m.cpu_regs.a; + x = m.cpu_regs.x; + y = m.cpu_regs.y; + + SET_PC( m.cpu_regs.pc ); + SET_SP( m.cpu_regs.sp ); + SET_PSW( m.cpu_regs.psw ); + + goto loop; + + + /* Main loop */ + +cbranch_taken_loop: + pc += *(int8_t const*) pc; +inc_pc_loop: + pc++; +loop: + { + unsigned opcode, data; + + opcode = *pc; + + if (allow_time_overflow && rel_time >= 0 ) + goto stop; + if ( (rel_time += m.cycle_table [opcode]) > 0 && !allow_time_overflow) + goto out_of_time; + + /* TODO: if PC is at end of memory, this will get wrong operand (very obscure) */ + data = *++pc; + switch ( opcode ) + { + + /* Common instructions */ + +#define BRANCH( cond )\ + {\ + pc++;\ + pc += (int8_t) data;\ + if ( cond )\ + goto loop;\ + pc -= (int8_t) data;\ + rel_time -= 2;\ + goto loop;\ + } + + case 0xF0: /* BEQ */ + BRANCH( !(uint8_t) nz ) /* 89% taken */ + + case 0xD0: /* BNE */ + BRANCH( (uint8_t) nz ) + + case 0x3F: + { /* CALL */ + int old_addr; + old_addr = GET_PC() + 2; + SET_PC( GET_LE16( pc ) ); + PUSH16( old_addr ); + goto loop; + } + case 0x6F: /* RET */ + SET_PC( GET_LE16( sp ) ); + sp += 2; + goto loop; + + case 0xE4: /* MOV a,dp */ + ++pc; + /* 80% from timer */ + READ_DP_TIMER( 0, data, a = nz ); + goto loop; + + case 0xFA:{ /* MOV dp,dp */ + int temp; + READ_DP_TIMER( -2, data, temp ); + data = temp + NO_READ_BEFORE_WRITE ; + } + /* fall through */ + case 0x8F: + { /* MOV dp,#imm */ + int i, temp; + temp = READ_PC( pc + 1 ); + pc += 2; + + i = dp + temp; + ram [i] = (uint8_t) data; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) /* 76% */ + { + m.smp_regs[0][i] = (uint8_t) data; + + /* Registers other than $F2 and $F4-$F7 */ + /* if ( i != 2 && i != 4 && i != 5 && i != 6 && i != 7 ) */ + if ( ((~0x2F00 << (bits_in_int - 16)) << i) < 0 ) /* 12% */ + { + if ( i == R_DSPDATA ) /* 99% */ + { + RUN_DSP(rel_time, reg_times [m.smp_regs[0][R_DSPADDR]] ); + if (m.smp_regs[0][R_DSPADDR] <= 0x7F ) + spc_dsp_write( data, rel_time ); + } + else + spc_cpu_write_smp_reg_( data, rel_time, i); + } + } + goto loop; + } + + case 0xC4: /* MOV dp,a */ + ++pc; + { + int i; + i = dp + data; + ram [i] = (uint8_t) a; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) /* 39% */ + { + unsigned sel; + sel = i - 2; + m.smp_regs[0][i] = (uint8_t) a; + + if ( sel == 1 ) /* 51% $F3 */ + { + RUN_DSP(rel_time, reg_times [m.smp_regs[0][R_DSPADDR]] ); + if (m.smp_regs[0][R_DSPADDR] <= 0x7F ) + spc_dsp_write( a, rel_time ); + } + else if ( sel > 1 ) /* 1% not $F2 or $F3 */ + spc_cpu_write_smp_reg_( a, rel_time, i ); + } + } + goto loop; + +#define CASE( n ) case n: + + /* Define common address modes based on opcode for immediate mode. Execution + ends with data set to the address of the operand. */ +#define ADDR_MODES_( op )\ + CASE( op - 0x02 ) /* (X) */\ + data = x + dp;\ + pc--;\ + goto end_##op;\ + CASE( op + 0x0F ) /* (dp)+Y */\ + data = READ_PROG16( data + dp ) + y;\ + goto end_##op;\ + CASE( op - 0x01 ) /* (dp+X) */\ + data = READ_PROG16( ((uint8_t) (data + x)) + dp );\ + goto end_##op;\ + CASE( op + 0x0E ) /* abs+Y */\ + data += y;\ + goto abs_##op;\ + CASE( op + 0x0D ) /* abs+X */\ + data += x;\ + CASE( op - 0x03 ) /* abs */\ + abs_##op:\ + data += 0x100 * READ_PC( ++pc );\ + goto end_##op;\ + CASE( op + 0x0C ) /* dp+X */\ + data = (uint8_t) (data + x); + +#define ADDR_MODES_NO_DP( op )\ + ADDR_MODES_( op )\ + data += dp;\ + end_##op: + +#define ADDR_MODES( op )\ + ADDR_MODES_( op )\ + CASE( op - 0x04 ) /* dp */\ + data += dp;\ + end_##op: + + /* 1. 8-bit Data Transmission Commands. Group I */ + + ADDR_MODES_NO_DP( 0xE8 ) /* MOV A,addr */ + a = nz = SPC_CPU_READ( 0, data ); + goto inc_pc_loop; + + case 0xBF: + { + /* MOV A,(X)+ */ + int temp; + temp = x + dp; + x = (uint8_t) (x + 1); + a = nz = SPC_CPU_READ( -1, temp ); + goto loop; + } + + case 0xE8: /* MOV A,imm */ + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: /* MOV X,dp+Y */ + data = (uint8_t) (data + y); + case 0xF8: /* MOV X,dp */ + READ_DP_TIMER( 0, data, x = nz ); + goto inc_pc_loop; + + case 0xE9: /* MOV X,abs */ + data = GET_LE16( pc ); + ++pc; + data = SPC_CPU_READ( 0, data ); + case 0xCD: /* MOV X,imm */ + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: /* MOV Y,dp+X */ + data = (uint8_t) (data + x); + case 0xEB: /* MOV Y,dp */ + /* 70% from timer */ + pc++; + READ_DP_TIMER( 0, data, y = nz ); + goto loop; + + case 0xEC: + { /* MOV Y,abs */ + int temp; + temp = GET_LE16( pc ); + pc += 2; + READ_TIMER( 0, temp, y = nz ); + /* y = nz = SPC_CPU_READ( 0, temp ); */ + goto loop; + } + + case 0x8D: /* MOV Y,imm */ + y = data; + nz = data; + goto inc_pc_loop; + + /* 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 */ + + ADDR_MODES_NO_DP( 0xC8 ) /* MOV addr,A */ + SPC_CPU_WRITE( 0, data, a ); + goto inc_pc_loop; + + { + int temp; + case 0xCC: /* MOV abs,Y */ + temp = y; + goto mov_abs_temp; + case 0xC9: /* MOV abs,X */ + temp = x; +mov_abs_temp: + SPC_CPU_WRITE( 0, GET_LE16( pc ), temp ); + pc += 2; + goto loop; + } + + case 0xD9: /* MOV dp+Y,X */ + data = (uint8_t) (data + y); + case 0xD8: /* MOV dp,X */ + SPC_CPU_WRITE( 0, data + dp, x ); + goto inc_pc_loop; + + case 0xDB: /* MOV dp+X,Y */ + data = (uint8_t) (data + x); + case 0xCB: /* MOV dp,Y */ + SPC_CPU_WRITE( 0, data + dp, y ); + goto inc_pc_loop; + + /* 3. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 3. */ + + case 0x7D: /* MOV A,X */ + a = x; + nz = x; + goto loop; + + case 0xDD: /* MOV A,Y */ + a = y; + nz = y; + goto loop; + + case 0x5D: /* MOV X,A */ + x = a; + nz = a; + goto loop; + + case 0xFD: /* MOV Y,A */ + y = a; + nz = a; + goto loop; + + case 0x9D: /* MOV X,SP */ + x = nz = GET_SP(); + goto loop; + + case 0xBD: /* MOV SP,X */ + SET_SP( x ); + goto loop; + + /* case 0xC6: // MOV (X),A (handled by MOV addr,A in group 2) */ + + case 0xAF: /* MOV (X)+,A */ + WRITE_DP( 0, x, a + NO_READ_BEFORE_WRITE ); + x++; + goto loop; + + /* 5. 8-BIT LOGIC OPERATION COMMANDS */ + +#define LOGICAL_OP( op, func )\ + ADDR_MODES( op ) /* addr */\ + data = SPC_CPU_READ( 0, data );\ + case op: /* imm */\ + nz = a func##= data;\ + goto inc_pc_loop;\ + { unsigned addr;\ + case op + 0x11: /* X,Y */\ + data = READ_DP( -2, y );\ + addr = x + dp;\ + goto addr_##op;\ + case op + 0x01: /* dp,dp */\ + data = READ_DP( -3, data );\ + case op + 0x10:{/*dp,imm*/\ + uint8_t const* addr2 = pc + 1;\ + pc += 2;\ + addr = READ_PC( addr2 ) + dp;\ + }\ + addr_##op:\ + nz = data func SPC_CPU_READ( -1, addr );\ + SPC_CPU_WRITE( 0, addr, nz );\ + goto loop;\ + } + + LOGICAL_OP( 0x28, & ); /* AND */ + + LOGICAL_OP( 0x08, | ); /* OR */ + + LOGICAL_OP( 0x48, ^ ); /* EOR */ + + /* 4. 8-BIT ARITHMETIC OPERATION COMMANDS */ + + ADDR_MODES( 0x68 ) /* CMP addr */ + data = SPC_CPU_READ( 0, data ); + case 0x68: /* CMP imm */ + nz = a - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x79: /* CMP (X),(Y) */ + data = READ_DP( -2, y ); + nz = READ_DP( -1, x ) - data; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x69: /* CMP dp,dp */ + data = READ_DP( -3, data ); + case 0x78: /* CMP dp,imm */ + nz = READ_DP( -1, READ_PC( ++pc ) ) - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x3E: /* CMP X,dp */ + data += dp; + goto cmp_x_addr; + case 0x1E: /* CMP X,abs */ + data = GET_LE16( pc ); + pc++; +cmp_x_addr: + data = SPC_CPU_READ( 0, data ); + case 0xC8: /* CMP X,imm */ + nz = x - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x7E: /* CMP Y,dp */ + data += dp; + goto cmp_y_addr; + case 0x5E: /* CMP Y,abs */ + data = GET_LE16( pc ); + pc++; +cmp_y_addr: + data = SPC_CPU_READ( 0, data ); + case 0xAD: /* CMP Y,imm */ + nz = y - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + { + int addr; + case 0xB9: /* SBC (x),(y) */ + case 0x99: /* ADC (x),(y) */ + pc--; /* compensate for inc later */ + data = READ_DP( -2, y ); + addr = x + dp; + goto adc_addr; + case 0xA9: /* SBC dp,dp */ + case 0x89: /* ADC dp,dp */ + data = READ_DP( -3, data ); + case 0xB8: /* SBC dp,imm */ + case 0x98: /* ADC dp,imm */ + addr = READ_PC( ++pc ) + dp; +adc_addr: + nz = SPC_CPU_READ( -1, addr ); + goto adc_data; + + /* catch ADC and SBC together, then decode later based on operand */ +#undef CASE +#define CASE( n ) case n: case (n) + 0x20: + ADDR_MODES( 0x88 ) /* ADC/SBC addr */ + data = SPC_CPU_READ( 0, data ); + case 0xA8: /* SBC imm */ + case 0x88: /* ADC imm */ + addr = -1; /* A */ + nz = a; +adc_data: { + int flags; + if ( opcode >= 0xA0 ) /* SBC */ + data ^= 0xFF; + + flags = data ^ nz; + nz += data + (c >> 8 & 1); + flags ^= nz; + + psw = (psw & ~(V40 | H08)) | + (flags >> 1 & H08) | + ((flags + 0x80) >> 2 & V40); + c = nz; + if ( addr < 0 ) + { + a = (uint8_t) nz; + goto inc_pc_loop; + } + SPC_CPU_WRITE( 0, addr, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + } + + } + + /* 6. ADDITION & SUBTRACTION COMMANDS */ + +#define INC_DEC_REG( reg, op )\ + nz = reg op;\ + reg = (uint8_t) nz;\ + goto loop; + + case 0xBC: INC_DEC_REG( a, + 1 ) /* INC A */ + case 0x3D: INC_DEC_REG( x, + 1 ) /* INC X */ + case 0xFC: INC_DEC_REG( y, + 1 ) /* INC Y */ + + case 0x9C: INC_DEC_REG( a, - 1 ) /* DEC A */ + case 0x1D: INC_DEC_REG( x, - 1 ) /* DEC X */ + case 0xDC: INC_DEC_REG( y, - 1 ) /* DEC Y */ + + case 0x9B: /* DEC dp+X */ + case 0xBB: /* INC dp+X */ + data = (uint8_t) (data + x); + case 0x8B: /* DEC dp */ + case 0xAB: /* INC dp */ + data += dp; + goto inc_abs; + case 0x8C: /* DEC abs */ + case 0xAC: /* INC abs */ + data = GET_LE16( pc ); + pc++; +inc_abs: + nz = (opcode >> 4 & 2) - 1; + nz += SPC_CPU_READ( -1, data ); + SPC_CPU_WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + + /* 7. SHIFT, ROTATION COMMANDS */ + + case 0x5C: /* LSR A */ + c = 0; + case 0x7C:{ /* ROR A */ + nz = (c >> 1 & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto loop; + } + + case 0x1C: /* ASL A */ + c = 0; + case 0x3C: + {/* ROL A */ + int temp; + temp = c >> 8 & 1; + c = a << 1; + nz = c | temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x0B: /* ASL dp */ + c = 0; + data += dp; + goto rol_mem; + case 0x1B: /* ASL dp+X */ + c = 0; + case 0x3B: /* ROL dp+X */ + data = (uint8_t) (data + x); + case 0x2B: /* ROL dp */ + data += dp; + goto rol_mem; + case 0x0C: /* ASL abs */ + c = 0; + case 0x2C: /* ROL abs */ + data = GET_LE16( pc ); + pc++; +rol_mem: + nz = c >> 8 & 1; + nz |= (c = SPC_CPU_READ( -1, data ) << 1); + SPC_CPU_WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + + case 0x4B: /* LSR dp */ + c = 0; + data += dp; + goto ror_mem; + case 0x5B: /* LSR dp+X */ + c = 0; + case 0x7B: /* ROR dp+X */ + data = (uint8_t) (data + x); + case 0x6B: /* ROR dp */ + data += dp; + goto ror_mem; + case 0x4C: /* LSR abs */ + c = 0; + case 0x6C: /* ROR abs */ + data = GET_LE16( pc ); + pc++; +ror_mem: { + int temp = SPC_CPU_READ( -1, data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + SPC_CPU_WRITE( 0, data, nz ); + goto inc_pc_loop; + } + + case 0x9F: /* XCN */ + nz = a = (a >> 4) | (uint8_t) (a << 4); + goto loop; + + /* 8. 16-BIT TRANSMISION COMMANDS */ + + case 0xBA: /* MOVW YA,dp */ + a = READ_DP( -2, data ); + nz = (a & 0x7F) | (a >> 1); + y = READ_DP( 0, (uint8_t) (data + 1) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: /* MOVW dp,YA */ + WRITE_DP( -1, data, a ); + WRITE_DP( 0, (uint8_t) (data + 1), y + NO_READ_BEFORE_WRITE ); + goto inc_pc_loop; + + /* 9. 16-BIT OPERATION COMMANDS */ + + case 0x3A: /* INCW dp */ + case 0x1A:{/* DECW dp */ + int temp; + /* low byte */ + data += dp; + temp = SPC_CPU_READ( -3, data ); + temp += (opcode >> 4 & 2) - 1; /* +1 for INCW, -1 for DECW */ + nz = ((temp >> 1) | temp) & 0x7F; + SPC_CPU_WRITE( -2, data, /*(uint8_t)*/ temp ); + + /* high byte */ + data = (uint8_t) (data + 1) + dp; + temp = (uint8_t) ((temp >> 8) + SPC_CPU_READ( -1, data )); + nz |= temp; + SPC_CPU_WRITE( 0, data, temp ); + + goto inc_pc_loop; + } + + case 0x7A: /* ADDW YA,dp */ + case 0x9A: + {/* SUBW YA,dp */ + int lo, hi, result, flags; + lo = READ_DP( -2, data ); + hi = READ_DP( 0, (uint8_t) (data + 1) ); + + if ( opcode == 0x9A ) /* SUBW */ + { + lo = (lo ^ 0xFF) + 1; + hi ^= 0xFF; + } + + lo += a; + result = y + hi + (lo >> 8); + flags = hi ^ y ^ result; + + psw = (psw & ~(V40 | H08)) | + (flags >> 1 & H08) | + ((flags + 0x80) >> 2 & V40); + c = result; + a = (uint8_t) lo; + result = (uint8_t) result; + y = result; + nz = (((lo >> 1) | lo) & 0x7F) | result; + + goto inc_pc_loop; + } + + case 0x5A: + { /* CMPW YA,dp */ + int temp; + temp = a - READ_DP( -1, data ); + nz = ((temp >> 1) | temp) & 0x7F; + temp = y + (temp >> 8); + temp -= READ_DP( 0, (uint8_t) (data + 1) ); + nz |= temp; + c = ~temp; + nz &= 0xFF; + goto inc_pc_loop; + } + + /* 10. MULTIPLICATION & DIVISON COMMANDS */ + + case 0xCF: { /* MUL YA */ + unsigned temp = y * a; + a = (uint8_t) temp; + nz = ((temp >> 1) | temp) & 0x7F; + y = temp >> 8; + nz |= y; + goto loop; + } + + case 0x9E: /* DIV YA,X */ + { + unsigned ya = y * 0x100 + a; + + psw &= ~(H08 | V40); + + if ( y >= x ) + psw |= V40; + + if ( (y & 15) >= (x & 15) ) + psw |= H08; + + if ( y < x * 2 ) + { + a = ya / x; + y = ya - a * x; + } + else + { + a = 255 - (ya - x * 0x200) / (256 - x); + y = x + (ya - x * 0x200) % (256 - x); + } + + nz = (uint8_t) a; + a = (uint8_t) a; + + goto loop; + } + + /* 11. DECIMAL COMPENSATION COMMANDS */ + + case 0xDF: /* DAA */ + if ( a > 0x99 || c & 0x100 ) + { + a += 0x60; + c = 0x100; + } + + if ( (a & 0x0F) > 9 || psw & H08 ) + a += 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + + case 0xBE: /* DAS */ + if ( a > 0x99 || !(c & 0x100) ) + { + a -= 0x60; + c = 0; + } + + if ( (a & 0x0F) > 9 || !(psw & H08) ) + a -= 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + + /* 12. BRANCHING COMMANDS */ + + case 0x2F: /* BRA rel */ + pc += (int8_t) data; + goto inc_pc_loop; + + case 0x30: /* BMI */ + BRANCH( (nz & NZ_NEG_MASK) ) + + case 0x10: /* BPL */ + BRANCH( !(nz & NZ_NEG_MASK) ) + + case 0xB0: /* BCS */ + BRANCH( c & 0x100 ) + + case 0x90: /* BCC */ + BRANCH( !(c & 0x100) ) + + case 0x70: /* BVS */ + BRANCH( psw & V40 ) + + case 0x50: /* BVC */ + BRANCH( !(psw & V40) ) + +#define CBRANCH( cond )\ + {\ + pc++;\ + if ( cond )\ + goto cbranch_taken_loop;\ + rel_time -= 2;\ + goto inc_pc_loop;\ + } + + case 0x03: /* BBS dp.bit,rel */ + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + CBRANCH( READ_DP( -4, data ) >> (opcode >> 5) & 1 ) + + case 0x13: /* BBC dp.bit,rel */ + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + CBRANCH( !(READ_DP( -4, data ) >> (opcode >> 5) & 1) ) + + case 0xDE: /* CBNE dp+X,rel */ + data = (uint8_t) (data + x); + /* fall through */ + case 0x2E:{ /* CBNE dp,rel */ + int temp; + /* 61% from timer */ + READ_DP_TIMER( -4, data, temp ); + CBRANCH( temp != a ) + } + + case 0x6E: { /* DBNZ dp,rel */ + unsigned temp = READ_DP( -4, data ) - 1; + WRITE_DP( -3, (uint8_t) data, /*(uint8_t)*/ temp + NO_READ_BEFORE_WRITE ); + CBRANCH( temp ) + } + + case 0xFE: /* DBNZ Y,rel */ + y = (uint8_t) (y - 1); + BRANCH( y ) + + case 0x1F: /* JMP [abs+X] */ + SET_PC( GET_LE16( pc ) + x ); + /* fall through */ + case 0x5F: /* JMP abs */ + SET_PC( GET_LE16( pc ) ); + goto loop; + + /* 13. SUB-ROUTINE CALL RETURN COMMANDS */ + + case 0x0F:{/* BRK */ + int temp; + int ret_addr = GET_PC(); + SET_PC( READ_PROG16( 0xFFDE ) ); /* vector address verified */ + PUSH16( ret_addr ); + GET_PSW( temp ); + psw = (psw | B10) & ~I04; + PUSH( temp ); + goto loop; + } + + case 0x4F:{/* PCALL offset */ + int ret_addr = GET_PC() + 1; + SET_PC( 0xFF00 | data ); + PUSH16( ret_addr ); + goto loop; + } + + case 0x01: /* TCALL n */ + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xA1: + case 0xB1: + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: { + int ret_addr = GET_PC(); + SET_PC( READ_PROG16( 0xFFDE - (opcode >> 3) ) ); + PUSH16( ret_addr ); + goto loop; + } + + /* 14. STACK OPERATION COMMANDS */ + + { + int temp; + case 0x7F: /* RET1 */ + temp = *sp; + SET_PC( GET_LE16( sp + 1 ) ); + sp += 3; + goto set_psw; + case 0x8E: /* POP PSW */ + POP( temp ); +set_psw: + SET_PSW( temp ); + goto loop; + } + + case 0x0D: { /* PUSH PSW */ + int temp; + GET_PSW( temp ); + PUSH( temp ); + goto loop; + } + + case 0x2D: /* PUSH A */ + PUSH( a ); + goto loop; + + case 0x4D: /* PUSH X */ + PUSH( x ); + goto loop; + + case 0x6D: /* PUSH Y */ + PUSH( y ); + goto loop; + + case 0xAE: /* POP A */ + POP( a ); + goto loop; + + case 0xCE: /* POP X */ + POP( x ); + goto loop; + + case 0xEE: /* POP Y */ + POP( y ); + goto loop; + + /* 15. BIT OPERATION COMMANDS */ + + case 0x02: /* SET1 */ + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xA2: + case 0xC2: + case 0xE2: + case 0x12: /* CLR1 */ + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xB2: + case 0xD2: + case 0xF2: + { + int bit, mask; + bit = 1 << (opcode >> 5); + mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + data += dp; + SPC_CPU_WRITE( 0, data, (SPC_CPU_READ( -1, data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: /* TSET1 abs */ + case 0x4E: /* TCLR1 abs */ + data = GET_LE16( pc ); + pc += 2; + { + unsigned temp = SPC_CPU_READ( -2, data ); + nz = (uint8_t) (a - temp); + temp &= ~a; + if ( opcode == 0x0E ) + temp |= a; + SPC_CPU_WRITE( 0, data, temp ); + } + goto loop; + + case 0x4A: /* AND1 C,mem.bit */ + c &= MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x6A: /* AND1 C,/mem.bit */ + c &= ~MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x0A: /* OR1 C,mem.bit */ + c |= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x2A: /* OR1 C,/mem.bit */ + c |= ~MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x8A: /* EOR1 C,mem.bit */ + c ^= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0xEA: /* NOT1 mem.bit */ + data = GET_LE16( pc ); + pc += 2; + { + unsigned temp = SPC_CPU_READ( -1, data & 0x1FFF ); + temp ^= 1 << (data >> 13); + SPC_CPU_WRITE( 0, data & 0x1FFF, temp ); + } + goto loop; + + case 0xCA: /* MOV1 mem.bit,C */ + data = GET_LE16( pc ); + pc += 2; + { + unsigned temp, bit; + temp = SPC_CPU_READ( -2, data & 0x1FFF ); + bit = data >> 13; + temp = (temp & ~(1 << bit)) | ((c >> 8 & 1) << bit); + SPC_CPU_WRITE( 0, data & 0x1FFF, temp + NO_READ_BEFORE_WRITE ); + } + goto loop; + + case 0xAA: /* MOV1 C,mem.bit */ + c = MEM_BIT( 0 ); + pc += 2; + goto loop; + + /* 16. PROGRAM PSW FLAG OPERATION COMMANDS */ + + case 0x60: /* CLRC */ + c = 0; + goto loop; + + case 0x80: /* SETC */ + c = ~0; + goto loop; + + case 0xED: /* NOTC */ + c ^= 0x100; + goto loop; + + case 0xE0: /* CLRV */ + psw &= ~(V40 | H08); + goto loop; + + case 0x20: /* CLRP */ + dp = 0; + goto loop; + + case 0x40: /* SETP */ + dp = 0x100; + goto loop; + + case 0xA0: /* EI */ + psw |= I04; + goto loop; + + case 0xC0: /* DI */ + psw &= ~I04; + goto loop; + + /* 17. OTHER COMMANDS */ + + case 0x00: /* NOP */ + goto loop; + + case 0xFF: + { /* STOP */ + /* handle PC wrap-around */ + unsigned addr = GET_PC() - 1; + if ( addr >= 0x10000 ) + { + addr &= 0xFFFF; + SET_PC( addr ); + /* dprintf( "SPC: PC wrapped around\n" ); */ + goto loop; + } + } + /* fall through */ + case 0xEF: /* SLEEP */ + --pc; + rel_time = 0; + goto stop; + } /* switch */ + } +out_of_time: + rel_time -= m.cycle_table [*pc]; /* undo partial execution of opcode */ +stop: + + /* Uncache registers */ +#if 0 + if ( GET_PC() >= 0x10000 ) + dprintf( "SPC: PC wrapped around\n" ); +#endif + m.cpu_regs.pc = (uint16_t) GET_PC(); + m.cpu_regs.sp = ( uint8_t) GET_SP(); + m.cpu_regs.a = ( uint8_t) a; + m.cpu_regs.x = ( uint8_t) x; + m.cpu_regs.y = ( uint8_t) y; + { + int temp; + GET_PSW( temp ); + m.cpu_regs.psw = (uint8_t) temp; + } + m.spc_time += rel_time; + m.dsp_time -= rel_time; + m.timers [0].next_time -= rel_time; + m.timers [1].next_time -= rel_time; + m.timers [2].next_time -= rel_time; + return &m.smp_regs[0][R_CPUIO0]; +} + +/* Runs SPC to end_time and starts a new time frame at 0 */ + +static void spc_end_frame( int end_time ) +{ + int i; + /* Catch CPU up to as close to end as possible. If final instruction + would exceed end, does NOT execute it and leaves m.spc_time < end. */ + + if ( end_time > m.spc_time ) + spc_run_until_( end_time ); + + m.spc_time -= end_time; + m.extra_clocks += end_time; + + /* Catch timers up to CPU */ + for ( i = 0; i < TIMER_COUNT; i++ ) + { + if ( 0 >= m.timers[i].next_time ) + spc_run_timer_( &m.timers [i], 0 ); + } + + /* Catch DSP up to CPU */ + if ( m.dsp_time < 0 ) + { + RUN_DSP( 0, MAX_REG_TIME ); + } + + /* Save any extra samples beyond what should be generated */ + if ( m.buf_begin ) + { + short *main_end, *dsp_end, *out, *in; + /* Get end pointers */ + main_end = m.buf_end; /* end of data written to buf */ + dsp_end = dsp_m.out; /* end of data written to dsp.extra() */ + if ( m.buf_begin <= dsp_end && dsp_end <= main_end ) + { + main_end = dsp_end; + dsp_end = dsp_m.extra; /* nothing in DSP's extra */ + } + + /* Copy any extra samples at these ends into extra_buf */ + out = m.extra_buf; + for ( in = m.buf_begin + SPC_SAMPLE_COUNT(); in < main_end; in++ ) + *out++ = *in; + for ( in = dsp_m.extra; in < dsp_end ; in++ ) + *out++ = *in; + + m.extra_pos = out; + } +} + +/* Support SNES_MEMORY_APURAM */ + +uint8_t * spc_apuram() +{ + return m.ram.ram; +} + +/* Init */ + +static void spc_reset_buffer(void) +{ + short *out; + /* Start with half extra buffer of silence */ + out = m.extra_buf; + while ( out < &m.extra_buf [EXTRA_SIZE_DIV_2] ) + *out++ = 0; + m.extra_pos = out; + m.buf_begin = 0; + dsp_set_output( 0, 0 ); +} + +/* Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. */ + +static void spc_set_tempo( int t ) +{ + int timer2_shift, other_shift; + m.tempo = t; + timer2_shift = 4; /* 64 kHz */ + other_shift = 3; /* 8 kHz */ + + m.timers [2].prescaler = timer2_shift; + m.timers [1].prescaler = timer2_shift + other_shift; + m.timers [0].prescaler = timer2_shift + other_shift; +} + +static void spc_reset_common( int timer_counter_init ) +{ + int i; + for ( i = 0; i < TIMER_COUNT; i++ ) + m.smp_regs[1][R_T0OUT + i] = timer_counter_init; + + /* Run IPL ROM */ + memset( &m.cpu_regs, 0, sizeof(m.cpu_regs)); + m.cpu_regs.pc = ROM_ADDR; + + m.smp_regs[0][R_TEST ] = 0x0A; + m.smp_regs[0][R_CONTROL] = 0xB0; /* ROM enabled, clear ports */ + for ( i = 0; i < PORT_COUNT; i++ ) + m.smp_regs[1][R_CPUIO0 + i] = 0; + + /* reset time registers */ + m.spc_time = 0; + m.dsp_time = 0; + m.dsp_time = CLOCKS_PER_SAMPLE + 1; + + for ( i = 0; i < TIMER_COUNT; i++ ) + { + Timer* t = &m.timers [i]; + t->next_time = 1; + t->divider = 0; + } + + /* Registers were just loaded. Applies these new values. */ + spc_enable_rom( m.smp_regs[0][R_CONTROL] & 0x80 ); + + /* Timer registers have been loaded. Applies these to the timers. Does not + reset timer prescalers or dividers. */ + for ( i = 0; i < TIMER_COUNT; i++ ) + { + Timer* t = &m.timers [i]; + t->period = IF_0_THEN_256( m.smp_regs[0][R_T0TARGET + i] ); + t->enabled = m.smp_regs[0][R_CONTROL] >> i & 1; + t->counter = m.smp_regs[1][R_T0OUT + i] & 0x0F; + } + + spc_set_tempo( m.tempo ); + + m.extra_clocks = 0; + spc_reset_buffer(); +} + +/* Resets SPC to power-on state. This resets your output buffer, so you must + call set_output() after this. */ + +static void spc_reset (void) +{ + m.cpu_regs.pc = 0xFFC0; + m.cpu_regs.a = 0x00; + m.cpu_regs.x = 0x00; + m.cpu_regs.y = 0x00; + m.cpu_regs.psw = 0x02; + m.cpu_regs.sp = 0xEF; + memset( m.ram.ram, 0x00, 0x10000 ); + + /* RAM was just loaded from SPC, with $F0-$FF containing SMP registers + and timer counts. Copies these to proper registers. */ + m.rom_enabled = dsp_m.rom_enabled = 0; + + /* Loads registers from unified 16-byte format */ + memcpy( m.smp_regs[0], &m.ram.ram[0xF0], REG_COUNT ); + memcpy( m.smp_regs[1], m.smp_regs[0], REG_COUNT ); + + /* These always read back as 0 */ + m.smp_regs[1][R_TEST ] = 0; + m.smp_regs[1][R_CONTROL ] = 0; + m.smp_regs[1][R_T0TARGET] = 0; + m.smp_regs[1][R_T1TARGET] = 0; + m.smp_regs[1][R_T2TARGET] = 0; + + /* Put STOP instruction around memory to catch PC underflow/overflow */ + memset( m.ram.padding1, CPU_PAD_FILL, sizeof m.ram.padding1 ); + memset( m.ram.padding2, CPU_PAD_FILL, sizeof m.ram.padding2 ); + + spc_reset_common( 0x0F ); + dsp_reset(); +} + + +/* Emulates pressing reset switch on SNES. This resets your output buffer, so + you must call set_output() after this. */ + +static void spc_soft_reset (void) +{ + spc_reset_common( 0 ); + dsp_soft_reset(); +} + +#if !SPC_NO_COPY_STATE_FUNCS +void spc_copy_state( unsigned char** io, dsp_copy_func_t copy ) +{ + int i; + spc_state_copy_t copier; + copier.func = copy; + copier.buf = io; + + /* Make state data more readable by putting 64K RAM, 16 SMP registers, + then DSP (with its 128 registers) first */ + + /* RAM */ + spc_enable_rom( 0 ); /* will get re-enabled if necessary in regs_loaded() below */ + spc_copier_copy(&copier, m.ram.ram, 0x10000 ); + + { + /* SMP registers */ + uint8_t regs [REG_COUNT], regs_in [REG_COUNT]; + + memcpy( regs, m.smp_regs[0], REG_COUNT ); + memcpy( regs_in, m.smp_regs[1], REG_COUNT ); + + spc_copier_copy(&copier, regs, sizeof(regs)); + spc_copier_copy(&copier, regs_in, sizeof(regs_in)); + + memcpy( m.smp_regs[0], regs, REG_COUNT); + memcpy( m.smp_regs[1], regs_in, REG_COUNT ); + + spc_enable_rom( m.smp_regs[0][R_CONTROL] & 0x80 ); + } + + /* CPU registers */ + SPC_COPY( uint16_t, m.cpu_regs.pc ); + SPC_COPY( uint8_t, m.cpu_regs.a ); + SPC_COPY( uint8_t, m.cpu_regs.x ); + SPC_COPY( uint8_t, m.cpu_regs.y ); + SPC_COPY( uint8_t, m.cpu_regs.psw ); + SPC_COPY( uint8_t, m.cpu_regs.sp ); + spc_copier_extra(&copier); + + SPC_COPY( int16_t, m.spc_time ); + SPC_COPY( int16_t, m.dsp_time ); + + /* DSP */ + dsp_copy_state( io, copy ); + + /* Timers */ + for ( i = 0; i < TIMER_COUNT; i++ ) + { + Timer *t; + + t = &m.timers [i]; + t->period = IF_0_THEN_256( m.smp_regs[0][R_T0TARGET + i] ); + t->enabled = m.smp_regs[0][R_CONTROL] >> i & 1; + SPC_COPY( int16_t, t->next_time ); + SPC_COPY( uint8_t, t->divider ); + SPC_COPY( uint8_t, t->counter ); + spc_copier_extra(&copier); + } + + spc_set_tempo( m.tempo ); + + spc_copier_extra(&copier); +} +#endif + + +/*********************************************************************************** + APU +***********************************************************************************/ + +#define APU_DEFAULT_INPUT_RATE 32000 +#define APU_MINIMUM_SAMPLE_COUNT 512 +#define APU_MINIMUM_SAMPLE_BLOCK 128 +#define APU_NUMERATOR_NTSC 15664 +#define APU_DENOMINATOR_NTSC 328125 +#define APU_NUMERATOR_PAL 34176 +#define APU_DENOMINATOR_PAL 709379 + +static apu_callback sa_callback = NULL; + +static bool8 sound_in_sync = TRUE; + +static int buffer_size; +static int lag_master = 0; +static int lag = 0; + +static short *landing_buffer = NULL; + +static bool8 resampler = FALSE; + +static int32 reference_time; +static uint32 spc_remainder; + +static int timing_hack_denominator = TEMPO_UNIT; +/* Set these to NTSC for now. Will change to PAL in S9xAPUTimingSetSpeedup + if necessary on game load. */ +static uint32 ratio_numerator = APU_NUMERATOR_NTSC; +static uint32 ratio_denominator = APU_DENOMINATOR_NTSC; + +/*********************************************************************************** + RESAMPLER +************************************************************************************/ +static int rb_size; +static int rb_buffer_size; +static int rb_start; +static unsigned char *rb_buffer; +static uint32_t r_step; +static uint32_t r_frac; +static int r_left[4], r_right[4]; + +#define SPACE_EMPTY() (rb_buffer_size - rb_size) +#define SPACE_FILLED() (rb_size) +#define MAX_WRITE() (SPACE_EMPTY() >> 1) +#define AVAIL() (((((uint32_t) rb_size) << 14) - r_frac) / r_step * 2) + +#define RESAMPLER_MIN(a, b) ((a) < (b) ? (a) : (b)) +#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) +#define SHORT_CLAMP(n) ((short) CLAMP((n), -32768, 32767)) + +static INLINE int32_t hermite (int32_t mu1, int32_t a, int32_t b, int32_t c, int32_t d) +{ + int32_t mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = ((mu1 * mu1) >> 15); + mu3 = ((mu2 * mu1) >> 15); + + m0 = (c - a) << 14; + m1 = (d - b) << 14; + + a0 = (((mu3 << 1) - (3 * mu2) + 32768) * b); + a1 = ((mu3 - (mu2 << 1) + mu1) * m0) >> 15; + a2 = ((mu3 - mu2) * m1) >> 15; + a3 = ((3 * mu2 - (mu3 << 1)) * c); + + return ((a0) + (a1) + (a2) + (a3)) >> 15; +} + +static void resampler_clear(void) +{ + rb_start = 0; + rb_size = 0; + memset (rb_buffer, 0, rb_buffer_size); + + r_frac = 65536; + r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0; + r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0; +} + +static void resampler_time_ratio(double ratio) +{ + r_step = 65536 * ratio; + resampler_clear(); +} + +static void resampler_read(short *data, int num_samples) +{ + int i_position, o_position, consumed; + short *internal_buffer; + + i_position = rb_start >> 1; + internal_buffer = (short *)rb_buffer; + o_position = 0; + consumed = 0; + + while (o_position < num_samples && consumed < rb_buffer_size) + { + int s_left, s_right, max_samples; + int hermite_val; + + s_left = internal_buffer[i_position]; + s_right = internal_buffer[i_position + 1]; + max_samples = rb_buffer_size >> 1; + + while (r_frac <= 65536 && o_position < num_samples) + { + hermite_val = hermite(r_frac >> 1, r_left [0], r_left [1], r_left [2], r_left [3]); + data[o_position] = SHORT_CLAMP (hermite_val); + hermite_val = hermite(r_frac >> 1, r_right[0], r_right[1], r_right[2], r_right[3]); + data[o_position + 1] = SHORT_CLAMP (hermite_val); + + o_position += 2; + + r_frac += r_step; + } + + if (r_frac > 65536) + { + r_left [0] = r_left [1]; + r_left [1] = r_left [2]; + r_left [2] = r_left [3]; + r_left [3] = s_left; + + r_right[0] = r_right[1]; + r_right[1] = r_right[2]; + r_right[2] = r_right[3]; + r_right[3] = s_right; + + r_frac -= 65536; + + i_position += 2; + if (i_position >= max_samples) + i_position -= max_samples; + consumed += 2; + } + } + + rb_size -= consumed << 1; + rb_start += consumed << 1; + if (rb_start >= rb_buffer_size) + rb_start -= rb_buffer_size; +} + +static void resampler_new(int num_samples) +{ + int new_size = num_samples << 1; + + rb_buffer_size = new_size; + rb_buffer = (unsigned char*)malloc(rb_buffer_size); + memset (rb_buffer, 0, rb_buffer_size); + + rb_size = 0; + rb_start = 0; + resampler_clear(); +} + +static INLINE bool8 resampler_push(short *src, int num_samples) +{ + int bytes, end, first_write_size; + unsigned char *src_ring; + + bytes = num_samples << 1; + if (MAX_WRITE() < num_samples || SPACE_EMPTY() < bytes) + return FALSE; + + /* Ring buffer push */ + src_ring = (unsigned char*)src; + end = (rb_start + rb_size) % rb_buffer_size; + first_write_size = RESAMPLER_MIN(bytes, rb_buffer_size - end); + + memcpy (rb_buffer + end, src_ring, first_write_size); + + if (bytes > first_write_size) + memcpy (rb_buffer, src_ring + first_write_size, bytes - first_write_size); + + rb_size += bytes; + + return TRUE; +} + +static INLINE void resampler_resize (int num_samples) +{ + /* int size; */ + /* size = num_samples << 1; */ + free(rb_buffer); + rb_buffer_size = rb_size; + rb_buffer = (unsigned char*)malloc(rb_buffer_size); + memset (rb_buffer, 0, rb_buffer_size); + + rb_size = 0; + rb_start = 0; +} + +/*********************************************************************************** + APU + ***********************************************************************************/ + +bool8 S9xMixSamples (short *buffer, unsigned sample_count) +{ + if (AVAIL() >= (sample_count + lag)) + { + resampler_read(buffer, sample_count); + if (lag == lag_master) + lag = 0; + } + else + { + memset(buffer, 0, sample_count << 1); + if (lag == 0) + lag = lag_master; + + return (FALSE); + } + + return (TRUE); +} + +int S9xGetSampleCount (void) +{ + return AVAIL(); +} + +/* Sets destination for output samples */ + +static void spc_set_output( short* out, int size ) +{ + short *out_end, *in; + + out_end = out + size; + m.buf_begin = out; + m.buf_end = out_end; + + /* Copy extra to output */ + in = m.extra_buf; + while ( in < m.extra_pos && out < out_end ) + *out++ = *in++; + + /* Handle output being full already */ + if ( out >= out_end ) + { + /* Have DSP write to remaining extra space */ + out = dsp_m.extra; + out_end = &dsp_m.extra[EXTRA_SIZE]; + + /* Copy any remaining extra samples as if DSP wrote them */ + while ( in < m.extra_pos ) + *out++ = *in++; + } + + dsp_set_output( out, out_end - out ); +} + +void S9xFinalizeSamples (void) +{ + bool8 ret; + + ret = resampler_push(landing_buffer, SPC_SAMPLE_COUNT()); + sound_in_sync = FALSE; + + /* We weren't able to process the entire buffer. Potential overrun. */ + if (!ret && Settings.SoundSync) + return; + + if (!Settings.SoundSync || (SPACE_EMPTY() >= SPACE_FILLED())) + sound_in_sync = TRUE; + + m.extra_clocks &= CLOCKS_PER_SAMPLE - 1; + spc_set_output(landing_buffer, buffer_size); +} + +void S9xClearSamples (void) +{ + resampler_clear(); + lag = lag_master; +} + +bool8 S9xSyncSound (void) +{ + if (!Settings.SoundSync || sound_in_sync) + return TRUE; + + sa_callback(); + + return sound_in_sync; +} + +void S9xSetSamplesAvailableCallback (apu_callback callback) +{ + sa_callback = callback; +} + +static void UpdatePlaybackRate (void) +{ + double time_ratio; + if (Settings.SoundInputRate == 0) + Settings.SoundInputRate = APU_DEFAULT_INPUT_RATE; + + time_ratio = (double) Settings.SoundInputRate * TEMPO_UNIT / (Settings.SoundPlaybackRate * timing_hack_denominator); + resampler_time_ratio(time_ratio); +} + +bool8 S9xInitSound (int buffer_ms, int lag_ms) +{ + /* buffer_ms : buffer size given in millisecond + lag_ms : allowable time-lag given in millisecond */ + int sample_count, lag_sample_count; + + sample_count = buffer_ms * 32000 / 1000; + lag_sample_count = lag_ms * 32000 / 1000; + + lag_master = lag_sample_count; + + lag_master <<= 1; + + lag = lag_master; + + if (sample_count < APU_MINIMUM_SAMPLE_COUNT) + sample_count = APU_MINIMUM_SAMPLE_COUNT; + + buffer_size = sample_count; + buffer_size <<= 1; + buffer_size <<= 1; + + printf("Sound buffer size: %d (%d samples)\n", buffer_size, sample_count); + + if (landing_buffer) + free(landing_buffer); + landing_buffer = (short*)malloc(buffer_size * 2); + if (!landing_buffer) + return (FALSE); + + /* The resampler and spc unit use samples (16-bit short) as + arguments. Use 2x in the resampler for buffer leveling with SoundSync */ + + if (!resampler) + { + resampler_new(buffer_size >> (Settings.SoundSync ? 0 : 1)); + resampler = TRUE; + } + else + resampler_resize(buffer_size >> (Settings.SoundSync ? 0 : 1)); + + m.extra_clocks &= CLOCKS_PER_SAMPLE - 1; + spc_set_output(landing_buffer, buffer_size >> 1); + + UpdatePlaybackRate(); + + return TRUE; +} + +/* Must be called once before using */ +static unsigned char cycle_table [128] = +{/* 01 23 45 67 89 AB CD EF */ + 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, /* 0 */ + 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, /* 1 */ + 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, /* 2 */ + 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, /* 3 */ + 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, /* 4 */ + 0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, /* 5 */ + 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, /* 6 */ + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, /* 7 */ + 0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, /* 8 */ + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, /* 9 */ + 0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, /* A */ + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, /* B */ + 0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, /* C */ + 0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, /* D */ + 0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, /* E */ + 0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, /* F */ +}; + +static signed char const reg_times_ [256] = +{ + -1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22, + 2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23, + 5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23, + 8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24, + 11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24, + 14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24, + 17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25, + 20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25, + + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +}; + +bool8 S9xInitAPU (void) +{ + int i; + + uint8_t APUROM[64] = + { + 0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, + 0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78, + 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4, + 0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, + 0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB, + 0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA, + 0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD, + 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF + }; + + memset( &m, 0, sizeof m ); + dsp_init( m.ram.ram ); + + m.tempo = TEMPO_UNIT; + + /* Most SPC music doesn't need ROM, and almost all the rest only + rely on these two bytes */ + + m.rom [0x3E] = 0xFF; + m.rom [0x3F] = 0xC0; + + + /* unpack cycle table */ + for ( i = 0; i < 128; i++ ) + { + int n; + n = cycle_table [i]; + m.cycle_table [i * 2 + 0] = n >> 4; + m.cycle_table [i * 2 + 1] = n & 0x0F; + } + + allow_time_overflow = FALSE; + + dsp_m.rom = m.rom; + dsp_m.hi_ram = m.hi_ram; + + + memcpy( reg_times, reg_times_, sizeof reg_times ); + + spc_reset(); + + + memcpy( m.rom, APUROM, sizeof m.rom ); + + landing_buffer = NULL; + + return TRUE; +} + +void S9xDeinitAPU (void) +{ + if (resampler) + { + free(rb_buffer); + resampler = FALSE; + } + + if (landing_buffer) + { + free(landing_buffer); + landing_buffer = NULL; + } +} + +#define S9X_APU_GET_CLOCK(cpucycles) ((ratio_numerator * (cpucycles - reference_time) + spc_remainder) / ratio_denominator) +#define S9X_APU_GET_CLOCK_REMAINDER(cpucycles) ((ratio_numerator * (cpucycles - reference_time) + spc_remainder) % ratio_denominator) + +/* Emulated port read at specified time */ + +uint8 S9xAPUReadPort (int port) { return ((uint8) spc_run_until_(S9X_APU_GET_CLOCK(CPU.Cycles))[port]); } + +/* Emulated port write at specified time */ + +void S9xAPUWritePort (int port, uint8 byte) +{ + spc_run_until_( S9X_APU_GET_CLOCK(CPU.Cycles) ) [0x10 + port] = byte; + m.ram.ram [0xF4 + port] = byte; +} + +void S9xAPUSetReferenceTime (int32 cpucycles) +{ + reference_time = cpucycles; +} + +void S9xAPUExecute (void) +{ + /* Accumulate partial APU cycles */ + spc_end_frame(S9X_APU_GET_CLOCK(CPU.Cycles)); + + spc_remainder = S9X_APU_GET_CLOCK_REMAINDER(CPU.Cycles); + reference_time = CPU.Cycles; + + if (SPC_SAMPLE_COUNT() >= APU_MINIMUM_SAMPLE_BLOCK || !sound_in_sync) + sa_callback(); +} + +void S9xAPUTimingSetSpeedup (int ticks) +{ + if (ticks != 0) + printf("APU speedup hack: %d\n", ticks); + + timing_hack_denominator = TEMPO_UNIT - ticks; + spc_set_tempo(timing_hack_denominator); + + ratio_numerator = Settings.PAL ? APU_NUMERATOR_PAL : APU_NUMERATOR_NTSC; + ratio_denominator = Settings.PAL ? APU_DENOMINATOR_PAL : APU_DENOMINATOR_NTSC; + ratio_denominator = ratio_denominator * timing_hack_denominator / TEMPO_UNIT; + + UpdatePlaybackRate(); +} + +void S9xAPUAllowTimeOverflow (bool8 allow) +{ + allow_time_overflow = allow; +} + +void S9xResetAPU (void) +{ + reference_time = 0; + spc_remainder = 0; + spc_reset(); + + m.extra_clocks &= CLOCKS_PER_SAMPLE - 1; + + spc_set_output(landing_buffer, buffer_size >> 1); + + resampler_clear(); +} + +void S9xSoftResetAPU (void) +{ + reference_time = 0; + spc_remainder = 0; + spc_soft_reset(); + + m.extra_clocks &= CLOCKS_PER_SAMPLE - 1; + spc_set_output(landing_buffer, buffer_size >> 1); + + resampler_clear(); +} + +static void from_apu_to_state (uint8 **buf, void *var, size_t size) +{ + memcpy(*buf, var, size); + *buf += size; +} + +static void to_apu_from_state (uint8 **buf, void *var, size_t size) +{ + memcpy(var, *buf, size); + *buf += size; +} + +// work around optimization bug in android GCC +// similar to this: http://jeffq.com/blog/over-aggressive-gcc-optimization-can-cause-sigbus-crash-when-using-memcpy-with-the-android-ndk/ +#if defined(ANDROID) || defined(__QNX__) +void __attribute__((optimize(0))) S9xAPUSaveState (uint8 *block) +#else +void S9xAPUSaveState (uint8 *block) +#endif +{ + uint8 *ptr; + + ptr = block; + + spc_copy_state(&ptr, from_apu_to_state); + + SET_LE32(ptr, reference_time); + ptr += sizeof(int32); + SET_LE32(ptr, spc_remainder); +} + +#if defined(ANDROID) || defined(__QNX__) +void __attribute__((optimize(0))) S9xAPULoadState (uint8 *block) +#else +void S9xAPULoadState (uint8 *block) +#endif +{ + uint8 *ptr; + + ptr = block; + + S9xResetAPU(); + + spc_copy_state(&ptr, to_apu_from_state); + + reference_time = GET_LE32(ptr); + ptr += sizeof(int32); + spc_remainder = GET_LE32(ptr); +} + +#endif -- cgit v1.2.3