rlm@0: // snes_spc 0.9.0. http://www.slack.net/~ant/ rlm@0: rlm@0: #include "SPC_DSP.h" rlm@0: rlm@0: #include "blargg_endian.h" rlm@0: #include rlm@0: rlm@0: /* Copyright (C) 2007 Shay Green. This module is free software; you rlm@0: can redistribute it and/or modify it under the terms of the GNU Lesser rlm@0: General Public License as published by the Free Software Foundation; either rlm@0: version 2.1 of the License, or (at your option) any later version. This rlm@0: module is distributed in the hope that it will be useful, but WITHOUT ANY rlm@0: WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS rlm@0: FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more rlm@0: details. You should have received a copy of the GNU Lesser General Public rlm@0: License along with this module; if not, write to the Free Software Foundation, rlm@0: Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ rlm@0: rlm@0: #include "blargg_source.h" rlm@0: rlm@0: #ifdef BLARGG_ENABLE_OPTIMIZER rlm@0: #include BLARGG_ENABLE_OPTIMIZER rlm@0: #endif rlm@0: rlm@0: #if INT_MAX < 0x7FFFFFFF rlm@0: #error "Requires that int type have at least 32 bits" rlm@0: #endif rlm@0: rlm@0: // TODO: add to blargg_endian.h rlm@0: #define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) rlm@0: #define GET_LE16A( addr ) GET_LE16( addr ) rlm@0: #define SET_LE16A( addr, data ) SET_LE16( addr, data ) rlm@0: rlm@0: static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = rlm@0: { rlm@0: 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, rlm@0: 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, rlm@0: 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, rlm@0: 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, rlm@0: 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, rlm@0: 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, rlm@0: 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, rlm@0: 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF rlm@0: }; rlm@0: rlm@0: // if ( io < -32768 ) io = -32768; rlm@0: // if ( io > 32767 ) io = 32767; rlm@0: #define CLAMP16( io )\ rlm@0: {\ rlm@0: if ( (int16_t) io != io )\ rlm@0: io = (io >> 31) ^ 0x7FFF;\ rlm@0: } rlm@0: rlm@0: // Access global DSP register rlm@0: #define REG(n) m.regs [r_##n] rlm@0: rlm@0: // Access voice DSP register rlm@0: #define VREG(r,n) r [v_##n] rlm@0: rlm@0: #define WRITE_SAMPLES( l, r, out ) \ rlm@0: {\ rlm@0: out [0] = l;\ rlm@0: out [1] = r;\ rlm@0: out += 2;\ rlm@0: if ( out >= m.out_end )\ rlm@0: {\ rlm@0: check( out == m.out_end );\ rlm@0: check( m.out_end != &m.extra [extra_size] || \ rlm@0: (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ rlm@0: out = m.extra;\ rlm@0: m.out_end = &m.extra [extra_size];\ rlm@0: }\ rlm@0: }\ rlm@0: rlm@0: void SPC_DSP::set_output( sample_t* out, int size ) rlm@0: { rlm@0: require( (size & 1) == 0 ); // must be even rlm@0: if ( !out ) rlm@0: { rlm@0: out = m.extra; rlm@0: size = extra_size; rlm@0: } rlm@0: m.out_begin = out; rlm@0: m.out = out; rlm@0: m.out_end = out + size; rlm@0: } rlm@0: rlm@0: // Volume registers and efb are signed! Easy to forget int8_t cast. rlm@0: // Prefixes are to avoid accidental use of locals with same names. rlm@0: rlm@0: // Gaussian interpolation rlm@0: rlm@0: static short const gauss [512] = rlm@0: { rlm@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, rlm@0: 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, rlm@0: 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, rlm@0: 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, rlm@0: 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, rlm@0: 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, rlm@0: 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, rlm@0: 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, rlm@0: 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, rlm@0: 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, rlm@0: 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, rlm@0: 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, rlm@0: 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, rlm@0: 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, rlm@0: 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, rlm@0: 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, rlm@0: 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, rlm@0: 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, rlm@0: 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, rlm@0: 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, rlm@0: 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, rlm@0: 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, rlm@0: 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, rlm@0: 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, rlm@0: 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, rlm@0: 1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, rlm@0: 1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, rlm@0: 1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, rlm@0: 1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, rlm@0: 1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, rlm@0: 1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, rlm@0: 1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, rlm@0: }; rlm@0: rlm@0: inline int SPC_DSP::interpolate( voice_t const* v ) rlm@0: { rlm@0: // Make pointers into gaussian based on fractional position between samples rlm@0: int offset = v->interp_pos >> 4 & 0xFF; rlm@0: short const* fwd = gauss + 255 - offset; rlm@0: short const* rev = gauss + offset; // mirror left half of gaussian rlm@0: rlm@0: int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; rlm@0: int out; rlm@0: out = (fwd [ 0] * in [0]) >> 11; rlm@0: out += (fwd [256] * in [1]) >> 11; rlm@0: out += (rev [256] * in [2]) >> 11; rlm@0: out = (int16_t) out; rlm@0: out += (rev [ 0] * in [3]) >> 11; rlm@0: rlm@0: CLAMP16( out ); rlm@0: out &= ~1; rlm@0: return out; rlm@0: } rlm@0: rlm@0: rlm@0: //// Counters rlm@0: rlm@0: int const simple_counter_range = 2048 * 5 * 3; // 30720 rlm@0: rlm@0: static unsigned const counter_rates [32] = rlm@0: { rlm@0: simple_counter_range + 1, // never fires rlm@0: 2048, 1536, rlm@0: 1280, 1024, 768, rlm@0: 640, 512, 384, rlm@0: 320, 256, 192, rlm@0: 160, 128, 96, rlm@0: 80, 64, 48, rlm@0: 40, 32, 24, rlm@0: 20, 16, 12, rlm@0: 10, 8, 6, rlm@0: 5, 4, 3, rlm@0: 2, rlm@0: 1 rlm@0: }; rlm@0: rlm@0: static unsigned const counter_offsets [32] = rlm@0: { rlm@0: 1, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 536, 0, 1040, rlm@0: 0, rlm@0: 0 rlm@0: }; rlm@0: rlm@0: inline void SPC_DSP::init_counter() rlm@0: { rlm@0: m.counter = 0; rlm@0: } rlm@0: rlm@0: inline void SPC_DSP::run_counters() rlm@0: { rlm@0: if ( --m.counter < 0 ) rlm@0: m.counter = simple_counter_range - 1; rlm@0: } rlm@0: rlm@0: inline unsigned SPC_DSP::read_counter( int rate ) rlm@0: { rlm@0: return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; rlm@0: } rlm@0: rlm@0: rlm@0: //// Envelope rlm@0: rlm@0: inline void SPC_DSP::run_envelope( voice_t* const v ) rlm@0: { rlm@0: int env = v->env; rlm@0: if ( v->env_mode == env_release ) // 60% rlm@0: { rlm@0: if ( (env -= 0x8) < 0 ) rlm@0: env = 0; rlm@0: v->env = env; rlm@0: } rlm@0: else rlm@0: { rlm@0: int rate; rlm@0: int env_data = VREG(v->regs,adsr1); rlm@0: if ( m.t_adsr0 & 0x80 ) // 99% ADSR rlm@0: { rlm@0: if ( v->env_mode >= env_decay ) // 99% rlm@0: { rlm@0: env--; rlm@0: env -= env >> 8; rlm@0: rate = env_data & 0x1F; rlm@0: if ( v->env_mode == env_decay ) // 1% rlm@0: rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; rlm@0: } rlm@0: else // env_attack rlm@0: { rlm@0: rate = (m.t_adsr0 & 0x0F) * 2 + 1; rlm@0: env += rate < 31 ? 0x20 : 0x400; rlm@0: } rlm@0: } rlm@0: else // GAIN rlm@0: { rlm@0: int mode; rlm@0: env_data = VREG(v->regs,gain); rlm@0: mode = env_data >> 5; rlm@0: if ( mode < 4 ) // direct rlm@0: { rlm@0: env = env_data * 0x10; rlm@0: rate = 31; rlm@0: } rlm@0: else rlm@0: { rlm@0: rate = env_data & 0x1F; rlm@0: if ( mode == 4 ) // 4: linear decrease rlm@0: { rlm@0: env -= 0x20; rlm@0: } rlm@0: else if ( mode < 6 ) // 5: exponential decrease rlm@0: { rlm@0: env--; rlm@0: env -= env >> 8; rlm@0: } rlm@0: else // 6,7: linear increase rlm@0: { rlm@0: env += 0x20; rlm@0: if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) rlm@0: env += 0x8 - 0x20; // 7: two-slope linear increase rlm@0: } rlm@0: } rlm@0: } rlm@0: rlm@0: // Sustain level rlm@0: if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) rlm@0: v->env_mode = env_sustain; rlm@0: rlm@0: v->hidden_env = env; rlm@0: rlm@0: // unsigned cast because linear decrease going negative also triggers this rlm@0: if ( (unsigned) env > 0x7FF ) rlm@0: { rlm@0: env = (env < 0 ? 0 : 0x7FF); rlm@0: if ( v->env_mode == env_attack ) rlm@0: v->env_mode = env_decay; rlm@0: } rlm@0: rlm@0: if ( !read_counter( rate ) ) rlm@0: v->env = env; // nothing else is controlled by the counter rlm@0: } rlm@0: } rlm@0: rlm@0: rlm@0: //// BRR Decoding rlm@0: rlm@0: inline void SPC_DSP::decode_brr( voice_t* v ) rlm@0: { rlm@0: // Arrange the four input nybbles in 0xABCD order for easy decoding rlm@0: int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; rlm@0: rlm@0: int const header = m.t_brr_header; rlm@0: rlm@0: // Write to next four samples in circular buffer rlm@0: int* pos = &v->buf [v->buf_pos]; rlm@0: int* end; rlm@0: if ( (v->buf_pos += 4) >= brr_buf_size ) rlm@0: v->buf_pos = 0; rlm@0: rlm@0: // Decode four samples rlm@0: for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) rlm@0: { rlm@0: // Extract nybble and sign-extend rlm@0: int s = (int16_t) nybbles >> 12; rlm@0: rlm@0: // Shift sample based on header rlm@0: int const shift = header >> 4; rlm@0: s = (s << shift) >> 1; rlm@0: if ( shift >= 0xD ) // handle invalid range rlm@0: s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) rlm@0: rlm@0: // Apply IIR filter (8 is the most commonly used) rlm@0: int const filter = header & 0x0C; rlm@0: int const p1 = pos [brr_buf_size - 1]; rlm@0: int const p2 = pos [brr_buf_size - 2] >> 1; rlm@0: if ( filter >= 8 ) rlm@0: { rlm@0: s += p1; rlm@0: s -= p2; rlm@0: if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 rlm@0: { rlm@0: s += p2 >> 4; rlm@0: s += (p1 * -3) >> 6; rlm@0: } rlm@0: else // s += p1 * 0.8984375 - p2 * 0.40625 rlm@0: { rlm@0: s += (p1 * -13) >> 7; rlm@0: s += (p2 * 3) >> 4; rlm@0: } rlm@0: } rlm@0: else if ( filter ) // s += p1 * 0.46875 rlm@0: { rlm@0: s += p1 >> 1; rlm@0: s += (-p1) >> 5; rlm@0: } rlm@0: rlm@0: // Adjust and write sample rlm@0: CLAMP16( s ); rlm@0: s = (int16_t) (s * 2); rlm@0: pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around rlm@0: } rlm@0: } rlm@0: rlm@0: rlm@0: //// Misc rlm@0: rlm@0: #define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() rlm@0: rlm@0: MISC_CLOCK( 27 ) rlm@0: { rlm@0: m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON rlm@0: } rlm@0: MISC_CLOCK( 28 ) rlm@0: { rlm@0: m.t_non = REG(non); rlm@0: m.t_eon = REG(eon); rlm@0: m.t_dir = REG(dir); rlm@0: } rlm@0: MISC_CLOCK( 29 ) rlm@0: { rlm@0: if ( (m.every_other_sample ^= 1) != 0 ) rlm@0: m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read rlm@0: } rlm@0: MISC_CLOCK( 30 ) rlm@0: { rlm@0: if ( m.every_other_sample ) rlm@0: { rlm@0: m.kon = m.new_kon; rlm@0: m.t_koff = REG(koff) | m.mute_mask; rlm@0: } rlm@0: rlm@0: run_counters(); rlm@0: rlm@0: // Noise rlm@0: if ( !read_counter( REG(flg) & 0x1F ) ) rlm@0: { rlm@0: int feedback = (m.noise << 13) ^ (m.noise << 14); rlm@0: m.noise = (feedback & 0x4000) ^ (m.noise >> 1); rlm@0: } rlm@0: } rlm@0: rlm@0: rlm@0: //// Voices rlm@0: rlm@0: #define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) rlm@0: rlm@0: inline VOICE_CLOCK( V1 ) rlm@0: { rlm@0: m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; rlm@0: m.t_srcn = VREG(v->regs,srcn); rlm@0: } rlm@0: inline VOICE_CLOCK( V2 ) rlm@0: { rlm@0: // Read sample pointer (ignored if not needed) rlm@0: uint8_t const* entry = &m.ram [m.t_dir_addr]; rlm@0: if ( !v->kon_delay ) rlm@0: entry += 2; rlm@0: m.t_brr_next_addr = GET_LE16A( entry ); rlm@0: rlm@0: m.t_adsr0 = VREG(v->regs,adsr0); rlm@0: rlm@0: // Read pitch, spread over two clocks rlm@0: m.t_pitch = VREG(v->regs,pitchl); rlm@0: } rlm@0: inline VOICE_CLOCK( V3a ) rlm@0: { rlm@0: m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; rlm@0: } rlm@0: inline VOICE_CLOCK( V3b ) rlm@0: { rlm@0: // Read BRR header and byte rlm@0: m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; rlm@0: m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking rlm@0: } rlm@0: VOICE_CLOCK( V3c ) rlm@0: { rlm@0: // Pitch modulation using previous voice's output rlm@0: if ( m.t_pmon & v->vbit ) rlm@0: m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; rlm@0: rlm@0: if ( v->kon_delay ) rlm@0: { rlm@0: // Get ready to start BRR decoding on next sample rlm@0: if ( v->kon_delay == 5 ) rlm@0: { rlm@0: v->brr_addr = m.t_brr_next_addr; rlm@0: v->brr_offset = 1; rlm@0: v->buf_pos = 0; rlm@0: m.t_brr_header = 0; // header is ignored on this sample rlm@0: m.kon_check = true; rlm@0: } rlm@0: rlm@0: // Envelope is never run during KON rlm@0: v->env = 0; rlm@0: v->hidden_env = 0; rlm@0: rlm@0: // Disable BRR decoding until last three samples rlm@0: v->interp_pos = 0; rlm@0: if ( --v->kon_delay & 3 ) rlm@0: v->interp_pos = 0x4000; rlm@0: rlm@0: // Pitch is never added during KON rlm@0: m.t_pitch = 0; rlm@0: } rlm@0: rlm@0: // Gaussian interpolation rlm@0: { rlm@0: int output = interpolate( v ); rlm@0: rlm@0: // Noise rlm@0: if ( m.t_non & v->vbit ) rlm@0: output = (int16_t) (m.noise * 2); rlm@0: rlm@0: // Apply envelope rlm@0: m.t_output = (output * v->env) >> 11 & ~1; rlm@0: v->t_envx_out = (uint8_t) (v->env >> 4); rlm@0: } rlm@0: rlm@0: // Immediate silence due to end of sample or soft reset rlm@0: if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) rlm@0: { rlm@0: v->env_mode = env_release; rlm@0: v->env = 0; rlm@0: } rlm@0: rlm@0: if ( m.every_other_sample ) rlm@0: { rlm@0: // KOFF rlm@0: if ( m.t_koff & v->vbit ) rlm@0: v->env_mode = env_release; rlm@0: rlm@0: // KON rlm@0: if ( m.kon & v->vbit ) rlm@0: { rlm@0: v->kon_delay = 5; rlm@0: v->env_mode = env_attack; rlm@0: } rlm@0: } rlm@0: rlm@0: // Run envelope for next sample rlm@0: if ( !v->kon_delay ) rlm@0: run_envelope( v ); rlm@0: } rlm@0: inline void SPC_DSP::voice_output( voice_t const* v, int ch ) rlm@0: { rlm@0: // Apply left/right volume rlm@0: int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; rlm@0: rlm@0: // Add to output total rlm@0: m.t_main_out [ch] += amp; rlm@0: CLAMP16( m.t_main_out [ch] ); rlm@0: rlm@0: // Optionally add to echo total rlm@0: if ( m.t_eon & v->vbit ) rlm@0: { rlm@0: m.t_echo_out [ch] += amp; rlm@0: CLAMP16( m.t_echo_out [ch] ); rlm@0: } rlm@0: } rlm@0: VOICE_CLOCK( V4 ) rlm@0: { rlm@0: // Decode BRR rlm@0: m.t_looped = 0; rlm@0: if ( v->interp_pos >= 0x4000 ) rlm@0: { rlm@0: decode_brr( v ); rlm@0: rlm@0: if ( (v->brr_offset += 2) >= brr_block_size ) rlm@0: { rlm@0: // Start decoding next BRR block rlm@0: assert( v->brr_offset == brr_block_size ); rlm@0: v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; rlm@0: if ( m.t_brr_header & 1 ) rlm@0: { rlm@0: v->brr_addr = m.t_brr_next_addr; rlm@0: m.t_looped = v->vbit; rlm@0: } rlm@0: v->brr_offset = 1; rlm@0: } rlm@0: } rlm@0: rlm@0: // Apply pitch rlm@0: v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; rlm@0: rlm@0: // Keep from getting too far ahead (when using pitch modulation) rlm@0: if ( v->interp_pos > 0x7FFF ) rlm@0: v->interp_pos = 0x7FFF; rlm@0: rlm@0: // Output left rlm@0: voice_output( v, 0 ); rlm@0: } rlm@0: inline VOICE_CLOCK( V5 ) rlm@0: { rlm@0: // Output right rlm@0: voice_output( v, 1 ); rlm@0: rlm@0: // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier rlm@0: int endx_buf = REG(endx) | m.t_looped; rlm@0: rlm@0: // Clear bit in ENDX if KON just began rlm@0: if ( v->kon_delay == 5 ) rlm@0: endx_buf &= ~v->vbit; rlm@0: m.endx_buf = (uint8_t) endx_buf; rlm@0: } rlm@0: inline VOICE_CLOCK( V6 ) rlm@0: { rlm@0: (void) v; // avoid compiler warning about unused v rlm@0: m.outx_buf = (uint8_t) (m.t_output >> 8); rlm@0: } rlm@0: inline VOICE_CLOCK( V7 ) rlm@0: { rlm@0: // Update ENDX rlm@0: REG(endx) = m.endx_buf; rlm@0: rlm@0: m.envx_buf = v->t_envx_out; rlm@0: } rlm@0: inline VOICE_CLOCK( V8 ) rlm@0: { rlm@0: // Update OUTX rlm@0: VREG(v->regs,outx) = m.outx_buf; rlm@0: } rlm@0: inline VOICE_CLOCK( V9 ) rlm@0: { rlm@0: // Update ENVX rlm@0: VREG(v->regs,envx) = m.envx_buf; rlm@0: } rlm@0: rlm@0: // Most voices do all these in one clock, so make a handy composite rlm@0: inline VOICE_CLOCK( V3 ) rlm@0: { rlm@0: voice_V3a( v ); rlm@0: voice_V3b( v ); rlm@0: voice_V3c( v ); rlm@0: } rlm@0: rlm@0: // Common combinations of voice steps on different voices. This greatly reduces rlm@0: // code size and allows everything to be inlined in these functions. rlm@0: VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } rlm@0: VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } rlm@0: VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } rlm@0: rlm@0: rlm@0: //// Echo rlm@0: rlm@0: // Current echo buffer pointer for left/right channel rlm@0: #define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) rlm@0: rlm@0: // Sample in echo history buffer, where 0 is the oldest rlm@0: #define ECHO_FIR( i ) (m.echo_hist_pos [i]) rlm@0: rlm@0: // Calculate FIR point for left/right channel rlm@0: #define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) rlm@0: rlm@0: #define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() rlm@0: rlm@0: inline void SPC_DSP::echo_read( int ch ) rlm@0: { rlm@0: int s = GET_LE16SA( ECHO_PTR( ch ) ); rlm@0: // second copy simplifies wrap-around handling rlm@0: ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; rlm@0: } rlm@0: rlm@0: ECHO_CLOCK( 22 ) rlm@0: { rlm@0: // History rlm@0: if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) rlm@0: m.echo_hist_pos = m.echo_hist; rlm@0: rlm@0: m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; rlm@0: echo_read( 0 ); rlm@0: rlm@0: // FIR (using l and r temporaries below helps compiler optimize) rlm@0: int l = CALC_FIR( 0, 0 ); rlm@0: int r = CALC_FIR( 0, 1 ); rlm@0: rlm@0: m.t_echo_in [0] = l; rlm@0: m.t_echo_in [1] = r; rlm@0: } rlm@0: ECHO_CLOCK( 23 ) rlm@0: { rlm@0: int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); rlm@0: int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); rlm@0: rlm@0: m.t_echo_in [0] += l; rlm@0: m.t_echo_in [1] += r; rlm@0: rlm@0: echo_read( 1 ); rlm@0: } rlm@0: ECHO_CLOCK( 24 ) rlm@0: { rlm@0: int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); rlm@0: int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); rlm@0: rlm@0: m.t_echo_in [0] += l; rlm@0: m.t_echo_in [1] += r; rlm@0: } rlm@0: ECHO_CLOCK( 25 ) rlm@0: { rlm@0: int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); rlm@0: int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); rlm@0: rlm@0: l = (int16_t) l; rlm@0: r = (int16_t) r; rlm@0: rlm@0: l += (int16_t) CALC_FIR( 7, 0 ); rlm@0: r += (int16_t) CALC_FIR( 7, 1 ); rlm@0: rlm@0: CLAMP16( l ); rlm@0: CLAMP16( r ); rlm@0: rlm@0: m.t_echo_in [0] = l & ~1; rlm@0: m.t_echo_in [1] = r & ~1; rlm@0: } rlm@0: inline int SPC_DSP::echo_output( int ch ) rlm@0: { rlm@0: int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + rlm@0: (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); rlm@0: CLAMP16( out ); rlm@0: return out; rlm@0: } rlm@0: ECHO_CLOCK( 26 ) rlm@0: { rlm@0: // Left output volumes rlm@0: // (save sample for next clock so we can output both together) rlm@0: m.t_main_out [0] = echo_output( 0 ); rlm@0: rlm@0: // Echo feedback rlm@0: int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); rlm@0: int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); rlm@0: rlm@0: CLAMP16( l ); rlm@0: CLAMP16( r ); rlm@0: rlm@0: m.t_echo_out [0] = l & ~1; rlm@0: m.t_echo_out [1] = r & ~1; rlm@0: } rlm@0: ECHO_CLOCK( 27 ) rlm@0: { rlm@0: // Output rlm@0: int l = m.t_main_out [0]; rlm@0: int r = echo_output( 1 ); rlm@0: m.t_main_out [0] = 0; rlm@0: m.t_main_out [1] = 0; rlm@0: rlm@0: // TODO: global muting isn't this simple (turns DAC on and off rlm@0: // or something, causing small ~37-sample pulse when first muted) rlm@0: if ( REG(flg) & 0x40 ) rlm@0: { rlm@0: l = 0; rlm@0: r = 0; rlm@0: } rlm@0: rlm@0: // Output sample to DAC rlm@0: #ifdef SPC_DSP_OUT_HOOK rlm@0: SPC_DSP_OUT_HOOK( l, r ); rlm@0: #else rlm@0: sample_t* out = m.out; rlm@0: WRITE_SAMPLES( l, r, out ); rlm@0: m.out = out; rlm@0: #endif rlm@0: } rlm@0: ECHO_CLOCK( 28 ) rlm@0: { rlm@0: m.t_echo_enabled = REG(flg); rlm@0: } rlm@0: inline void SPC_DSP::echo_write( int ch ) rlm@0: { rlm@0: if ( !(m.t_echo_enabled & 0x20) ) rlm@0: SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); rlm@0: m.t_echo_out [ch] = 0; rlm@0: } rlm@0: ECHO_CLOCK( 29 ) rlm@0: { rlm@0: m.t_esa = REG(esa); rlm@0: rlm@0: if ( !m.echo_offset ) rlm@0: m.echo_length = (REG(edl) & 0x0F) * 0x800; rlm@0: rlm@0: m.echo_offset += 4; rlm@0: if ( m.echo_offset >= m.echo_length ) rlm@0: m.echo_offset = 0; rlm@0: rlm@0: // Write left echo rlm@0: echo_write( 0 ); rlm@0: rlm@0: m.t_echo_enabled = REG(flg); rlm@0: } rlm@0: ECHO_CLOCK( 30 ) rlm@0: { rlm@0: // Write right echo rlm@0: echo_write( 1 ); rlm@0: } rlm@0: rlm@0: rlm@0: //// Timing rlm@0: rlm@0: // Execute clock for a particular voice rlm@0: #define V( clock, voice ) voice_##clock( &m.voices [voice] ); rlm@0: rlm@0: /* The most common sequence of clocks uses composite operations rlm@0: for efficiency. For example, the following are equivalent to the rlm@0: individual steps on the right: rlm@0: rlm@0: V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) rlm@0: V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) rlm@0: V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ rlm@0: rlm@0: // Voice 0 1 2 3 4 5 6 7 rlm@0: #define GEN_DSP_TIMING \ rlm@0: PHASE( 0) V(V5,0)V(V2,1)\ rlm@0: PHASE( 1) V(V6,0)V(V3,1)\ rlm@0: PHASE( 2) V(V7_V4_V1,0)\ rlm@0: PHASE( 3) V(V8_V5_V2,0)\ rlm@0: PHASE( 4) V(V9_V6_V3,0)\ rlm@0: PHASE( 5) V(V7_V4_V1,1)\ rlm@0: PHASE( 6) V(V8_V5_V2,1)\ rlm@0: PHASE( 7) V(V9_V6_V3,1)\ rlm@0: PHASE( 8) V(V7_V4_V1,2)\ rlm@0: PHASE( 9) V(V8_V5_V2,2)\ rlm@0: PHASE(10) V(V9_V6_V3,2)\ rlm@0: PHASE(11) V(V7_V4_V1,3)\ rlm@0: PHASE(12) V(V8_V5_V2,3)\ rlm@0: PHASE(13) V(V9_V6_V3,3)\ rlm@0: PHASE(14) V(V7_V4_V1,4)\ rlm@0: PHASE(15) V(V8_V5_V2,4)\ rlm@0: PHASE(16) V(V9_V6_V3,4)\ rlm@0: PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ rlm@0: PHASE(18) V(V8_V5_V2,5)\ rlm@0: PHASE(19) V(V9_V6_V3,5)\ rlm@0: PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ rlm@0: PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ rlm@0: PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ rlm@0: PHASE(23) V(V7,7) echo_23();\ rlm@0: PHASE(24) V(V8,7) echo_24();\ rlm@0: PHASE(25) V(V3b,0) V(V9,7) echo_25();\ rlm@0: PHASE(26) echo_26();\ rlm@0: PHASE(27) misc_27(); echo_27();\ rlm@0: PHASE(28) misc_28(); echo_28();\ rlm@0: PHASE(29) misc_29(); echo_29();\ rlm@0: PHASE(30) misc_30();V(V3c,0) echo_30();\ rlm@0: PHASE(31) V(V4,0) V(V1,2)\ rlm@0: rlm@0: #if !SPC_DSP_CUSTOM_RUN rlm@0: rlm@0: void SPC_DSP::run( int clocks_remain ) rlm@0: { rlm@0: require( clocks_remain > 0 ); rlm@0: rlm@0: int const phase = m.phase; rlm@0: m.phase = (phase + clocks_remain) & 31; rlm@0: switch ( phase ) rlm@0: { rlm@0: loop: rlm@0: rlm@0: #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: rlm@0: GEN_DSP_TIMING rlm@0: #undef PHASE rlm@0: rlm@0: if ( --clocks_remain ) rlm@0: goto loop; rlm@0: } rlm@0: } rlm@0: rlm@0: #endif rlm@0: rlm@0: rlm@0: //// Setup rlm@0: rlm@0: void SPC_DSP::init( void* ram_64k ) rlm@0: { rlm@0: m.ram = (uint8_t*) ram_64k; rlm@0: mute_voices( 0 ); rlm@0: disable_surround( false ); rlm@0: set_output( 0, 0 ); rlm@0: reset(); rlm@0: rlm@0: #ifndef NDEBUG rlm@0: // be sure this sign-extends rlm@0: assert( (int16_t) 0x8000 == -0x8000 ); rlm@0: rlm@0: // be sure right shift preserves sign rlm@0: assert( (-1 >> 1) == -1 ); rlm@0: rlm@0: // check clamp macro rlm@0: int i; rlm@0: i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); rlm@0: i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); rlm@0: rlm@0: blargg_verify_byte_order(); rlm@0: #endif rlm@0: } rlm@0: rlm@0: void SPC_DSP::soft_reset_common() rlm@0: { rlm@0: require( m.ram ); // init() must have been called already rlm@0: rlm@0: m.noise = 0x4000; rlm@0: m.echo_hist_pos = m.echo_hist; rlm@0: m.every_other_sample = 1; rlm@0: m.echo_offset = 0; rlm@0: m.phase = 0; rlm@0: rlm@0: init_counter(); rlm@0: } rlm@0: rlm@0: void SPC_DSP::soft_reset() rlm@0: { rlm@0: REG(flg) = 0xE0; rlm@0: soft_reset_common(); rlm@0: } rlm@0: rlm@0: void SPC_DSP::load( uint8_t const regs [register_count] ) rlm@0: { rlm@0: memcpy( m.regs, regs, sizeof m.regs ); rlm@0: memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); rlm@0: rlm@0: // Internal state rlm@0: for ( int i = voice_count; --i >= 0; ) rlm@0: { rlm@0: voice_t* v = &m.voices [i]; rlm@0: v->brr_offset = 1; rlm@0: v->vbit = 1 << i; rlm@0: v->regs = &m.regs [i * 0x10]; rlm@0: } rlm@0: m.new_kon = REG(kon); rlm@0: m.t_dir = REG(dir); rlm@0: m.t_esa = REG(esa); rlm@0: rlm@0: soft_reset_common(); rlm@0: } rlm@0: rlm@0: void SPC_DSP::reset() { load( initial_regs ); } rlm@0: rlm@0: rlm@0: //// State save/load rlm@0: rlm@0: #if !SPC_NO_COPY_STATE_FUNCS rlm@0: rlm@0: void SPC_State_Copier::copy( void* state, size_t size ) rlm@0: { rlm@0: func( buf, state, size ); rlm@0: } rlm@0: rlm@0: int SPC_State_Copier::copy_int( int state, int size ) rlm@0: { rlm@0: BOOST::uint8_t s [2]; rlm@0: SET_LE16( s, state ); rlm@0: func( buf, &s, size ); rlm@0: return GET_LE16( s ); rlm@0: } rlm@0: rlm@0: void SPC_State_Copier::skip( int count ) rlm@0: { rlm@0: if ( count > 0 ) rlm@0: { rlm@0: char temp [64]; rlm@0: memset( temp, 0, sizeof temp ); rlm@0: do rlm@0: { rlm@0: int n = sizeof temp; rlm@0: if ( n > count ) rlm@0: n = count; rlm@0: count -= n; rlm@0: func( buf, temp, n ); rlm@0: } rlm@0: while ( count ); rlm@0: } rlm@0: } rlm@0: rlm@0: void SPC_State_Copier::extra() rlm@0: { rlm@0: int n = 0; rlm@0: SPC_State_Copier& copier = *this; rlm@0: SPC_COPY( uint8_t, n ); rlm@0: skip( n ); rlm@0: } rlm@0: rlm@0: void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) rlm@0: { rlm@0: SPC_State_Copier copier( io, copy ); rlm@0: rlm@0: // DSP registers rlm@0: copier.copy( m.regs, register_count ); rlm@0: rlm@0: // Internal state rlm@0: rlm@0: // Voices rlm@0: int i; rlm@0: for ( i = 0; i < voice_count; i++ ) rlm@0: { rlm@0: voice_t* v = &m.voices [i]; rlm@0: rlm@0: // BRR buffer rlm@0: int i; rlm@0: for ( i = 0; i < brr_buf_size; i++ ) rlm@0: { rlm@0: int s = v->buf [i]; rlm@0: SPC_COPY( int16_t, s ); rlm@0: v->buf [i] = v->buf [i + brr_buf_size] = s; rlm@0: } rlm@0: rlm@0: SPC_COPY( uint16_t, v->interp_pos ); rlm@0: SPC_COPY( uint16_t, v->brr_addr ); rlm@0: SPC_COPY( uint16_t, v->env ); rlm@0: SPC_COPY( int16_t, v->hidden_env ); rlm@0: SPC_COPY( uint8_t, v->buf_pos ); rlm@0: SPC_COPY( uint8_t, v->brr_offset ); rlm@0: SPC_COPY( uint8_t, v->kon_delay ); rlm@0: { rlm@0: int m = v->env_mode; rlm@0: SPC_COPY( uint8_t, m ); rlm@0: v->env_mode = (enum env_mode_t) m; rlm@0: } rlm@0: SPC_COPY( uint8_t, v->t_envx_out ); rlm@0: rlm@0: copier.extra(); rlm@0: } rlm@0: rlm@0: // Echo history rlm@0: for ( i = 0; i < echo_hist_size; i++ ) rlm@0: { rlm@0: int j; rlm@0: for ( j = 0; j < 2; j++ ) rlm@0: { rlm@0: int s = m.echo_hist_pos [i] [j]; rlm@0: SPC_COPY( int16_t, s ); rlm@0: m.echo_hist [i] [j] = s; // write back at offset 0 rlm@0: } rlm@0: } rlm@0: m.echo_hist_pos = m.echo_hist; rlm@0: memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); rlm@0: rlm@0: // Misc rlm@0: SPC_COPY( uint8_t, m.every_other_sample ); rlm@0: SPC_COPY( uint8_t, m.kon ); rlm@0: rlm@0: SPC_COPY( uint16_t, m.noise ); rlm@0: SPC_COPY( uint16_t, m.counter ); rlm@0: SPC_COPY( uint16_t, m.echo_offset ); rlm@0: SPC_COPY( uint16_t, m.echo_length ); rlm@0: SPC_COPY( uint8_t, m.phase ); rlm@0: rlm@0: SPC_COPY( uint8_t, m.new_kon ); rlm@0: SPC_COPY( uint8_t, m.endx_buf ); rlm@0: SPC_COPY( uint8_t, m.envx_buf ); rlm@0: SPC_COPY( uint8_t, m.outx_buf ); rlm@0: rlm@0: SPC_COPY( uint8_t, m.t_pmon ); rlm@0: SPC_COPY( uint8_t, m.t_non ); rlm@0: SPC_COPY( uint8_t, m.t_eon ); rlm@0: SPC_COPY( uint8_t, m.t_dir ); rlm@0: SPC_COPY( uint8_t, m.t_koff ); rlm@0: rlm@0: SPC_COPY( uint16_t, m.t_brr_next_addr ); rlm@0: SPC_COPY( uint8_t, m.t_adsr0 ); rlm@0: SPC_COPY( uint8_t, m.t_brr_header ); rlm@0: SPC_COPY( uint8_t, m.t_brr_byte ); rlm@0: SPC_COPY( uint8_t, m.t_srcn ); rlm@0: SPC_COPY( uint8_t, m.t_esa ); rlm@0: SPC_COPY( uint8_t, m.t_echo_enabled ); rlm@0: rlm@0: SPC_COPY( int16_t, m.t_main_out [0] ); rlm@0: SPC_COPY( int16_t, m.t_main_out [1] ); rlm@0: SPC_COPY( int16_t, m.t_echo_out [0] ); rlm@0: SPC_COPY( int16_t, m.t_echo_out [1] ); rlm@0: SPC_COPY( int16_t, m.t_echo_in [0] ); rlm@0: SPC_COPY( int16_t, m.t_echo_in [1] ); rlm@0: rlm@0: SPC_COPY( uint16_t, m.t_dir_addr ); rlm@0: SPC_COPY( uint16_t, m.t_pitch ); rlm@0: SPC_COPY( int16_t, m.t_output ); rlm@0: SPC_COPY( uint16_t, m.t_echo_ptr ); rlm@0: SPC_COPY( uint8_t, m.t_looped ); rlm@0: rlm@0: copier.extra(); rlm@0: } rlm@0: #endif