// snes_spc 0.9.0. http://www.slack.net/~ant/ #include #include #include "SPC_DSP.h" #include "blargg_endian.h" /* 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 static const uint8_t initial_regs[] = { 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 }; // if ( io < -32768 ) io = -32768; // if ( io > 32767 ) io = 32767; template static inline void CLAMP16(T &io) { if (static_cast(io) != io) io = (io >> 31) ^ 0x7FFF; } void SPC_DSP::set_output(sample_t *out, int size) { assert(!(size & 1)); // must be even if (!out) { out = this->m.extra; size = extra_size; } this->m.out_begin = this->m.out = out; this->m.out_end = out + 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 const short gauss[] = { 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, }; int SPC_DSP::interpolate(const voice_t *v) { // Make pointers into gaussian based on fractional position between samples int offset = (v->interp_pos >> 4) & 0xFF; auto fwd = gauss + 255 - offset; auto rev = gauss + offset; // mirror left half of gaussian auto in = &v->buf[(v->interp_pos >> 12) + v->buf_pos]; int out = (fwd[0] * in[0]) >> 11; out += (fwd[256] * in[1]) >> 11; out += (rev[256] * in[2]) >> 11; out = static_cast(out); out += (rev[0] * in[3]) >> 11; CLAMP16(out); out &= ~1; return out; } //// Counters static const int simple_counter_range = 2048 * 5 * 3; // 30720 static const unsigned counter_rates[] = { 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 const unsigned counter_offsets[] = { 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 }; void SPC_DSP::init_counter() { this->m.counter = 0; } void SPC_DSP::run_counters() { if (--this->m.counter < 0) this->m.counter = simple_counter_range - 1; } unsigned SPC_DSP::read_counter(int rate) { return (static_cast(this->m.counter) + counter_offsets[rate]) % counter_rates[rate]; } //// Envelope void SPC_DSP::run_envelope(voice_t *const v) { int env = v->env; if (v->env_mode == env_release) // 60% { if ((env -= 0x8) < 0) env = 0; v->env = env; } else { int rate; int env_data = v->regs[v_adsr1]; if (this->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 = ((this->m.t_adsr0 >> 3) & 0x0E) + 0x10; } else // env_attack { rate = (this->m.t_adsr0 & 0x0F) * 2 + 1; env += rate < 31 ? 0x20 : 0x400; } } else // GAIN { env_data = v->regs[v_gain]; int 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 && static_cast(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 (static_cast(env) > 0x7FF) { env = env < 0 ? 0 : 0x7FF; if (v->env_mode == env_attack) v->env_mode = env_decay; } if (!this->read_counter(rate)) v->env = env; // nothing else is controlled by the counter } } //// BRR Decoding void SPC_DSP::decode_brr(voice_t *v) { // Arrange the four input nybbles in 0xABCD order for easy decoding int nybbles = this->m.t_brr_byte * 0x100 + this->m.ram[(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; int header = this->m.t_brr_header; // Write to next four samples in circular buffer int *pos = &v->buf[v->buf_pos]; int *end; if ((v->buf_pos += 4) >= brr_buf_size) v->buf_pos = 0; // Decode four samples for (end = pos + 4; pos < end; ++pos, nybbles <<= 4) { // Extract nybble and sign-extend int s = static_cast(nybbles) >> 12; // Shift sample based on header int 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) int filter = header & 0x0C; int p1 = pos[brr_buf_size - 1]; int 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 = static_cast(s * 2); pos[brr_buf_size] = pos[0] = s; // second copy simplifies wrap-around } } //// Misc void SPC_DSP::misc_27() { this->m.t_pmon = this->m.regs[r_pmon] & 0xFE; // voice 0 doesn't support PMON } void SPC_DSP::misc_28() { this->m.t_non = this->m.regs[r_non]; this->m.t_eon = this->m.regs[r_eon]; this->m.t_dir = this->m.regs[r_dir]; } void SPC_DSP::misc_29() { this->m.every_other_sample = !this->m.every_other_sample; if (this->m.every_other_sample) this->m.new_kon &= ~this->m.kon; // clears KON 63 clocks after it was last read } void SPC_DSP::misc_30() { if (this->m.every_other_sample) { this->m.kon = this->m.new_kon; this->m.t_koff = this->m.regs[r_koff] | this->m.mute_mask; } this->run_counters(); // Noise if (!this->read_counter(this->m.regs[r_flg] & 0x1F)) { int feedback = (this->m.noise << 13) ^ (this->m.noise << 14); this->m.noise = (feedback & 0x4000) ^ (this->m.noise >> 1); } } //// Voices void SPC_DSP::voice_V1(voice_t *const v) { this->m.t_dir_addr = this->m.t_dir * 0x100 + this->m.t_srcn * 4; this->m.t_srcn = v->regs[v_srcn]; } void SPC_DSP::voice_V2(voice_t *const v) { // Read sample pointer (ignored if not needed) auto entry = &this->m.ram[m.t_dir_addr]; if (!v->kon_delay) entry += 2; this->m.t_brr_next_addr = get_le16(entry); this->m.t_adsr0 = v->regs[v_adsr0]; // Read pitch, spread over two clocks this->m.t_pitch = v->regs[v_pitchl]; } void SPC_DSP::voice_V3a(voice_t *const v) { this->m.t_pitch += (v->regs[v_pitchh] & 0x3F) << 8; } void SPC_DSP::voice_V3b(voice_t *const v) { // Read BRR header and byte this->m.t_brr_byte = this->m.ram[(v->brr_addr + v->brr_offset) & 0xFFFF]; this->m.t_brr_header = this->m.ram[v->brr_addr]; // brr_addr doesn't need masking } void SPC_DSP::voice_V3c(voice_t *const v) { // Pitch modulation using previous voice's output if (this->m.t_pmon & v->vbit) this->m.t_pitch += ((this->m.t_output >> 5) * this->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 = this->m.t_brr_next_addr; v->brr_offset = 1; v->buf_pos = 0; this->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 this->m.t_pitch = 0; } // Gaussian interpolation int output = this->interpolate(v); // Noise if (this->m.t_non & v->vbit) output = static_cast(this->m.noise * 2); // Apply envelope this->m.t_output = (output * v->env) >> 11 & ~1; v->t_envx_out = static_cast(v->env >> 4); // Immediate silence due to end of sample or soft reset if (this->m.regs[r_flg] & 0x80 || (this->m.t_brr_header & 3) == 1) { v->env_mode = env_release; v->env = 0; } if (this->m.every_other_sample) { // KOFF if (this->m.t_koff & v->vbit) v->env_mode = env_release; // KON if (this->m.kon & v->vbit) { v->kon_delay = 5; v->env_mode = env_attack; } } // Run envelope for next sample if (!v->kon_delay) this->run_envelope(v); } void SPC_DSP::voice_output(const voice_t *v, int ch) { // Apply left/right volume int amp = (this->m.t_output * static_cast(v->regs[v_voll + ch])) >> 7; amp *= (this->stereo_switch & (1 << (v->voice_number + ch * voice_count))) ? 1 : 0; // Add to output total this->m.t_main_out[ch] += amp; CLAMP16(this->m.t_main_out[ch]); // Optionally add to echo total if (this->m.t_eon & v->vbit) { this->m.t_echo_out[ch] += amp; CLAMP16(this->m.t_echo_out[ch]); } } void SPC_DSP::voice_V4(voice_t *const v) { // Decode BRR this->m.t_looped = 0; if (v->interp_pos >= 0x4000) { this->decode_brr(v); if ((v->brr_offset += 2) >= brr_block_size) { // Start decoding next BRR block assert(v->brr_offset == brr_block_size); v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; if (this->m.t_brr_header & 1) { v->brr_addr = this->m.t_brr_next_addr; this->m.t_looped = v->vbit; } v->brr_offset = 1; } } // Apply pitch v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; // Keep from getting too far ahead (when using pitch modulation) if (v->interp_pos > 0x7FFF) v->interp_pos = 0x7FFF; // Output left this->voice_output(v, 0); } void SPC_DSP::voice_V5(voice_t *const v) { // Output right this->voice_output(v, 1); // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier int endx_buf = this->m.regs[r_endx] | this->m.t_looped; // Clear bit in ENDX if KON just began if (v->kon_delay == 5) endx_buf &= ~v->vbit; this->m.endx_buf = static_cast(endx_buf); } void SPC_DSP::voice_V6(voice_t *const) { this->m.outx_buf = static_cast(this->m.t_output >> 8); } void SPC_DSP::voice_V7(voice_t *const v) { // Update ENDX this->m.regs[r_endx] = this->m.endx_buf; this->m.envx_buf = v->t_envx_out; } void SPC_DSP::voice_V8(voice_t *const v) { // Update OUTX v->regs[v_outx] = this->m.outx_buf; } void SPC_DSP::voice_V9(voice_t *const v) { // Update ENVX v->regs[v_envx] = this->m.envx_buf; } // Most voices do all these in one clock, so make a handy composite void SPC_DSP::voice_V3(voice_t *const v) { this->voice_V3a(v); this->voice_V3b(v); this->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. void SPC_DSP::voice_V7_V4_V1(voice_t *const v) { this->voice_V7(v); this->voice_V1(v + 3); this->voice_V4(v + 1); } void SPC_DSP::voice_V8_V5_V2(voice_t *const v) { this->voice_V8(v); this->voice_V5(v + 1); this->voice_V2(v + 2); } void SPC_DSP::voice_V9_V6_V3(voice_t *const v) { this->voice_V9(v); this->voice_V6(v + 1); this->voice_V3(v + 2); } //// Echo void SPC_DSP::echo_read(int ch) { int s = static_cast(get_le16(this->ECHO_PTR(ch))); // second copy simplifies wrap-around handling this->ECHO_FIR(0)[ch] = this->ECHO_FIR(8)[ch] = s >> 1; } void SPC_DSP::echo_22() { // History if (++this->m.echo_hist_pos >= &this->m.echo_hist[echo_hist_size]) this->m.echo_hist_pos = this->m.echo_hist; this->m.t_echo_ptr = (this->m.t_esa * 0x100 + this->m.echo_offset) & 0xFFFF; this->echo_read(0); // FIR (using l and r temporaries below helps compiler optimize) int l = this->CALC_FIR(0, 0); int r = this->CALC_FIR(0, 1); this->m.t_echo_in[0] = l; this->m.t_echo_in[1] = r; } void SPC_DSP::echo_23() { int l = this->CALC_FIR(1, 0) + this->CALC_FIR(2, 0); int r = this->CALC_FIR(1, 1) + this->CALC_FIR(2, 1); this->m.t_echo_in[0] += l; this->m.t_echo_in[1] += r; echo_read(1); } void SPC_DSP::echo_24() { int l = this->CALC_FIR(3, 0) + this->CALC_FIR(4, 0) + this->CALC_FIR(5, 0); int r = this->CALC_FIR(3, 1) + this->CALC_FIR(4, 1) + this->CALC_FIR(5, 1); this->m.t_echo_in[0] += l; this->m.t_echo_in[1] += r; } void SPC_DSP::echo_25() { int l = this->m.t_echo_in[0] + this->CALC_FIR(6, 0); int r = this->m.t_echo_in[1] + this->CALC_FIR(6, 1); l = static_cast(l); r = static_cast(r); l += static_cast(this->CALC_FIR(7, 0)); r += static_cast(this->CALC_FIR(7, 1)); CLAMP16(l); CLAMP16(r); this->m.t_echo_in[0] = l & ~1; this->m.t_echo_in[1] = r & ~1; } int SPC_DSP::echo_output(int ch) { int out = static_cast((this->m.t_main_out [ch] * static_cast(this->m.regs[r_mvoll + ch * 0x10])) >> 7) + static_cast((this->m.t_echo_in [ch] * static_cast(this->m.regs[r_evoll + ch * 0x10])) >> 7); CLAMP16(out); return out; } void SPC_DSP::echo_26() { // Left output volumes // (save sample for next clock so we can output both together) this->m.t_main_out[0] = echo_output(0); // Echo feedback int l = this->m.t_echo_out[0] + static_cast((this->m.t_echo_in[0] * static_cast(this->m.regs[r_efb])) >> 7); int r = this->m.t_echo_out[1] + static_cast((this->m.t_echo_in[1] * static_cast(this->m.regs[r_efb])) >> 7); CLAMP16(l); CLAMP16(r); this->m.t_echo_out[0] = l & ~1; this->m.t_echo_out[1] = r & ~1; } void SPC_DSP::echo_27() { // Output int l = this->m.t_main_out[0]; int r = echo_output(1); this->m.t_main_out[0] = this->m.t_main_out[1] = 0; // TODO: global muting isn't this simple (turns DAC on and off // or something, causing small ~37-sample pulse when first muted) if (this->m.regs[r_flg] & 0x40) l = r = 0; // Output sample to DAC #ifdef SPC_DSP_OUT_HOOK SPC_DSP_OUT_HOOK(l, r); #else sample_t *out = this->m.out; out[0] = l; out[1] = r; out += 2; if (out >= m.out_end) { out = this->m.extra; this->m.out_end = &this->m.extra[extra_size]; } this->m.out = out; #endif } void SPC_DSP::echo_28() { this->m.t_echo_enabled = this->m.regs[r_flg]; } void SPC_DSP::echo_write(int ch) { if (!(this->m.t_echo_enabled & 0x20)) { if (this->m.t_echo_ptr >= 0xffc0 && this->rom_enabled) set_le16(&this->hi_ram[this->m.t_echo_ptr + ch * 2 - 0xffc0], this->m.t_echo_out [ch]); else set_le16(this->ECHO_PTR(ch), this->m.t_echo_out[ch]); } this->m.t_echo_out[ch] = 0; } void SPC_DSP::echo_29() { this->m.t_esa = this->m.regs[r_esa]; if (!this->m.echo_offset) this->m.echo_length = (this->m.regs[r_edl] & 0x0F) * 0x800; this->m.echo_offset += 4; if (this->m.echo_offset >= this->m.echo_length) this->m.echo_offset = 0; // Write left echo this->echo_write(0); this->m.t_echo_enabled = this->m.regs[r_flg]; } void SPC_DSP::echo_30() { // Write right echo this->echo_write(1); } //// Timing // Execute clock for a particular voice #define V(clock, voice) voice_##clock(&this->m.voices[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 #define GEN_DSP_TIMING \ PHASE(0) V(V5, 0) V(V2, 1) \ PHASE(1) V(V6, 0) V(V3, 1) \ PHASE(2) V(V7_V4_V1, 0) \ PHASE(3) V(V8_V5_V2, 0) \ PHASE(4) V(V9_V6_V3, 0) \ PHASE(5) V(V7_V4_V1, 1) \ PHASE(6) V(V8_V5_V2, 1) \ PHASE(7) V(V9_V6_V3, 1) \ PHASE(8) V(V7_V4_V1, 2) \ PHASE(9) V(V8_V5_V2, 2) \ PHASE(10) V(V9_V6_V3, 2) \ PHASE(11) V(V7_V4_V1, 3) \ PHASE(12) V(V8_V5_V2, 3) \ PHASE(13) V(V9_V6_V3, 3) \ PHASE(14) V(V7_V4_V1, 4) \ PHASE(15) V(V8_V5_V2, 4) \ PHASE(16) V(V9_V6_V3, 4) \ PHASE(17) V(V1, 0) V(V7, 5) V(V4, 6) \ PHASE(18) V(V8_V5_V2, 5) \ PHASE(19) V(V9_V6_V3, 5) \ PHASE(20) V(V1, 1) V(V7, 6) V(V4, 7) \ PHASE(21) V(V8, 6) V(V5, 7) V(V2, 0) /* t_brr_next_addr order dependency */ \ PHASE(22) V(V3a, 0) V(V9, 6) V(V6, 7) echo_22(); \ PHASE(23) V(V7, 7) echo_23(); \ PHASE(24) V(V8, 7) echo_24(); \ PHASE(25) V(V3b, 0) V(V9, 7) echo_25(); \ PHASE(26) echo_26(); \ PHASE(27) misc_27(); echo_27(); \ PHASE(28) misc_28(); echo_28(); \ PHASE(29) misc_29(); echo_29(); \ PHASE(30) misc_30(); V(V3c, 0) echo_30(); \ PHASE(31) V(V4, 0) V(V1, 2) #if !SPC_DSP_CUSTOM_RUN void SPC_DSP::run(int clocks_remain) { assert(clocks_remain > 0); int phase = this->m.phase; this->m.phase = (phase + clocks_remain) & 31; switch (phase) { loop: #define PHASE(n) if (n && !--clocks_remain) break; case n: GEN_DSP_TIMING #undef PHASE if (--clocks_remain) goto loop; } } #endif //// Setup void SPC_DSP::init(uint8_t *ram_64k) { this->m.ram = ram_64k; this->mute_voices(0); this->disable_surround(false); this->set_output(nullptr, 0); this->reset(); this->stereo_switch = 0xffff; #ifndef NDEBUG // be sure this sign-extends assert(static_cast(0x8000) == -0x8000); // be sure right shift preserves sign assert((-1 >> 1) == -1); // check clamp macro int i = 0x8000; CLAMP16(i); assert(i == 0x7FFF); i = -0x8001; CLAMP16(i); assert(i == -0x8000); blargg_verify_byte_order(); #endif } void SPC_DSP::soft_reset_common() { assert(this->m.ram); // init() must have been called already this->m.noise = 0x4000; this->m.echo_hist_pos = this->m.echo_hist; this->m.every_other_sample = true; this->m.echo_offset = 0; this->m.phase = 0; this->init_counter(); for (int i = 0; i < voice_count; ++i) this->m.voices[i].voice_number = i; } void SPC_DSP::soft_reset() { this->m.regs[r_flg] = 0xE0; this->soft_reset_common(); } void SPC_DSP::load(const uint8_t regs[register_count]) { std::copy_n(®s[0], static_cast(register_count), &this->m.regs[0]); memset(&this->m.regs[register_count], 0, offsetof(state_t, ram) - register_count); // Internal state for (int i = voice_count; --i >= 0;) { auto &v = this->m.voices[i]; v.brr_offset = 1; v.vbit = 1 << i; v.regs = &this->m.regs[i * 0x10]; } this->m.new_kon = this->m.regs[r_kon]; this->m.t_dir = this->m.regs[r_dir]; this->m.t_esa = this->m.regs[r_esa]; this->soft_reset_common(); } void SPC_DSP::reset() { this->load(initial_regs); } //// Snes9x Accessor void SPC_DSP::set_stereo_switch(int value) { this->stereo_switch = value; } uint8_t SPC_DSP::reg_value(int ch, int addr) { return this->m.voices[ch].regs[addr]; } int SPC_DSP::envx_value(int ch) { return this->m.voices[ch].env; }