diff --git a/Makefile b/Makefile index 394e75db..2dedd988 100755 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include nall/Makefile snes := snes -profile := accuracy +profile := compatibility ui := qt # compiler diff --git a/qt/base/about.cpp b/qt/base/about.cpp index 580ebc3c..e84380a1 100755 --- a/qt/base/about.cpp +++ b/qt/base/about.cpp @@ -8,13 +8,13 @@ AboutWindow::AboutWindow() { application.windowList.append(this); #if defined(DEBUGGER) - setStyleSheet("background: #e0e0a0"); + setStyleSheet("background: #c0c080"); #elif defined(PROFILE_ACCURACY) - setStyleSheet("background: #e0a0a0"); + setStyleSheet("background: #c08080"); #elif defined(PROFILE_COMPATIBILITY) - setStyleSheet("background: #a0a0e0"); + setStyleSheet("background: #8080c0"); #elif defined(PROFILE_PERFORMANCE) - setStyleSheet("background: #a0e0a0"); + setStyleSheet("background: #80c080"); #endif layout = new QVBoxLayout; diff --git a/qt/tools/statemanager.cpp b/qt/tools/statemanager.cpp index a81aa496..c7cdad78 100755 --- a/qt/tools/statemanager.cpp +++ b/qt/tools/statemanager.cpp @@ -177,6 +177,10 @@ bool StateManagerWindow::isStateValid(unsigned slot) { if(signature == 0) { fp.close(); return false; } uint32_t version = fp.readl(4); if(version != SNES::Info::SerializerVersion) { fp.close(); return false; } + fp.readl(4); //skip CRC32 + char profile[16]; + fp.read((uint8_t*)profile, 16); + if(strcmp(profile, SNES::Info::Profile)) { fp.close(); return false; } fp.close(); return true; } @@ -185,11 +189,11 @@ string StateManagerWindow::getStateDescription(unsigned slot) { if(isStateValid(slot) == false) return ""; file fp; fp.open(filename(), file::mode_read); - char description[513]; - fp.seek(slot * SNES::system.serialize_size() + 12); + char description[512]; + fp.seek(slot * SNES::system.serialize_size() + 28); fp.read((uint8_t*)description, 512); fp.close(); - description[512] = 0; + description[511] = 0; return description; } @@ -200,7 +204,7 @@ void StateManagerWindow::setStateDescription(unsigned slot, const string &text) char description[512]; memset(&description, 0, sizeof description); strncpy(description, text, 512); - fp.seek(slot * SNES::system.serialize_size() + 12); + fp.seek(slot * SNES::system.serialize_size() + 28); fp.write((uint8_t*)description, 512); fp.close(); } diff --git a/snes/cpu/dma/dma.hpp b/snes/cpu/dma/dma.hpp index 1eb07d59..33755bde 100755 --- a/snes/cpu/dma/dma.hpp +++ b/snes/cpu/dma/dma.hpp @@ -11,7 +11,7 @@ struct { bool unused; bool reverse_transfer; bool fixed_transfer; - uint8 transfer_mode; + uint3 transfer_mode; //$43x1 uint8 dest_addr; diff --git a/snes/fast/cpu/cpu.hpp b/snes/fast/cpu/cpu.hpp index 7e9aa794..b46692f6 100755 --- a/snes/fast/cpu/cpu.hpp +++ b/snes/fast/cpu/cpu.hpp @@ -30,7 +30,7 @@ public: private: //cpu static void Enter(); - void op_step(); + debugvirtual void op_step(); void op_irq(uint16 vector); //timing diff --git a/snes/fast/cpu/dma.cpp b/snes/fast/cpu/dma.cpp index 3e595971..addb6dbc 100755 --- a/snes/fast/cpu/dma.cpp +++ b/snes/fast/cpu/dma.cpp @@ -82,6 +82,8 @@ void CPU::dma_run() { do { dma_transfer(channel[i].direction, dma_bbus(i, index++), dma_addr(i)); } while(channel[i].dma_enabled && --channel[i].transfer_size); + + channel[i].dma_enabled = false; } status.irq_lock = true; @@ -175,24 +177,24 @@ void CPU::dma_reset() { channel[i].dma_enabled = false; channel[i].hdma_enabled = false; - channel[i].direction = 0; - channel[i].indirect = false; - channel[i].unused = false; - channel[i].reverse_transfer = false; - channel[i].fixed_transfer = false; - channel[i].transfer_mode = 0x00; + channel[i].direction = 1; + channel[i].indirect = true; + channel[i].unused = true; + channel[i].reverse_transfer = true; + channel[i].fixed_transfer = true; + channel[i].transfer_mode = 0x07; - channel[i].dest_addr = 0x0000; - channel[i].source_addr = 0x0000; - channel[i].source_bank = 0x00; + channel[i].dest_addr = 0xff; + channel[i].source_addr = 0xffff; + channel[i].source_bank = 0xff; - channel[i].transfer_size = 0x0000; - channel[i].indirect_addr = 0x0000; + channel[i].transfer_size = 0xffff; + channel[i].indirect_addr = 0xffff; - channel[i].indirect_bank = 0x00; - channel[i].hdma_addr = 0x00; - channel[i].line_counter = 0x00; - channel[i].unknown = 0x00; + channel[i].indirect_bank = 0xff; + channel[i].hdma_addr = 0xff; + channel[i].line_counter = 0xff; + channel[i].unknown = 0xff; channel[i].hdma_completed = false; channel[i].hdma_do_transfer = false; diff --git a/snes/fast/dsp/SPC_DSP.cpp b/snes/fast/dsp/SPC_DSP.cpp new file mode 100755 index 00000000..b0121407 --- /dev/null +++ b/snes/fast/dsp/SPC_DSP.cpp @@ -0,0 +1,1027 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_DSP.h" + +#include "blargg_endian.h" +#include + +/* 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 */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#if INT_MAX < 0x7FFFFFFF + #error "Requires that int type have at least 32 bits" +#endif + +// TODO: add to blargg_endian.h +#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) +#define GET_LE16A( addr ) GET_LE16( addr ) +#define SET_LE16A( addr, data ) SET_LE16( addr, data ) + +static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +// 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,0x9C,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; +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +// Access global DSP register +#define REG(n) 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 >= m.out_end )\ + {\ + check( out == m.out_end );\ + check( m.out_end != &m.extra [extra_size] || \ + (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ + out = m.extra;\ + m.out_end = &m.extra [extra_size];\ + }\ +}\ + +void SPC_DSP::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // must be even + if ( !out ) + { + out = m.extra; + size = extra_size; + } + m.out_begin = out; + m.out = out; + 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 short const 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, +}; + +inline int SPC_DSP::interpolate( voice_t const* v ) +{ + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + int out; + 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 + +int const simple_counter_range = 2048 * 5 * 3; // 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 +}; + +inline void SPC_DSP::init_counter() +{ + m.counter = 0; +} + +inline void SPC_DSP::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned SPC_DSP::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline 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 = VREG(v->regs,adsr1); + if ( 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 = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,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 + +inline void SPC_DSP::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = 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 = (int16_t) nybbles >> 12; + + // Shift sample based on header + int const 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 const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const 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 + +#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | m.mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + uint8_t const* entry = &m.ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * 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 = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + m.kon_check = true; + } + + // 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 + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); + + // Noise + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + 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 ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = env_attack; + } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void SPC_DSP::voice_output( voice_t const* v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + 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 ( m.t_brr_header & 1 ) + { + v->brr_addr = m.t_brr_next_addr; + 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 + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (uint8_t) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (uint8_t) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + 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. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (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_CLOCK( n ) inline void SPC_DSP::echo_##n() + +inline void SPC_DSP::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int SPC_DSP::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + 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 ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + #ifdef SPC_DSP_OUT_HOOK + SPC_DSP_OUT_HOOK( l, r ); + #else + sample_t* out = m.out; + WRITE_SAMPLES( l, r, out ); + m.out = out; + #endif +} +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void SPC_DSP::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &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 ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + 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( void* ram_64k ) +{ + m.ram = (uint8_t*) ram_64k; + mute_voices( 0 ); + disable_surround( false ); + set_output( 0, 0 ); + reset(); + + #ifndef NDEBUG + // be sure this sign-extends + assert( (int16_t) 0x8000 == -0x8000 ); + + // be sure right shift preserves sign + assert( (-1 >> 1) == -1 ); + + // check clamp macro + int i; + 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() +{ + require( m.ram ); // init() must have been called already + + m.noise = 0x4000; + m.echo_hist_pos = m.echo_hist; + m.every_other_sample = 1; + m.echo_offset = 0; + m.phase = 0; + + init_counter(); +} + +void SPC_DSP::soft_reset() +{ + REG(flg) = 0xE0; + soft_reset_common(); +} + +void SPC_DSP::load( uint8_t const regs [register_count] ) +{ + memcpy( m.regs, regs, sizeof m.regs ); + memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); + + // Internal state + for ( int i = voice_count; --i >= 0; ) + { + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &m.regs [i * 0x10]; + } + m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); + + soft_reset_common(); +} + +void SPC_DSP::reset() { load( initial_regs ); } + + +//// State save/load + +#if !SPC_NO_COPY_STATE_FUNCS + +void SPC_State_Copier::copy( void* state, size_t size ) +{ + func( buf, state, size ); +} + +int SPC_State_Copier::copy_int( int state, int size ) +{ + BOOST::uint8_t s [2]; + SET_LE16( s, state ); + func( buf, &s, size ); + return GET_LE16( s ); +} + +void SPC_State_Copier::skip( int count ) +{ + if ( count > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof temp ); + do + { + int n = sizeof temp; + if ( n > count ) + n = count; + count -= n; + func( buf, temp, n ); + } + while ( count ); + } +} + +void SPC_State_Copier::extra() +{ + int n = 0; + SPC_State_Copier& copier = *this; + SPC_COPY( uint8_t, n ); + skip( n ); +} + +void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // DSP registers + copier.copy( m.regs, register_count ); + + // Internal state + + // Voices + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + + // BRR buffer + int i; + for ( i = 0; i < brr_buf_size; i++ ) + { + int s = v->buf [i]; + SPC_COPY( int16_t, s ); + v->buf [i] = v->buf [i + 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 = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = (enum env_mode_t) m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + copier.extra(); + } + + // Echo history + for ( i = 0; i < echo_hist_size; i++ ) + { + int j; + for ( j = 0; j < 2; j++ ) + { + int s = m.echo_hist_pos [i] [j]; + SPC_COPY( int16_t, s ); + m.echo_hist [i] [j] = s; // write back at offset 0 + } + } + m.echo_hist_pos = m.echo_hist; + memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); + + // Misc + SPC_COPY( uint8_t, m.every_other_sample ); + SPC_COPY( uint8_t, m.kon ); + + SPC_COPY( uint16_t, m.noise ); + SPC_COPY( uint16_t, m.counter ); + SPC_COPY( uint16_t, m.echo_offset ); + SPC_COPY( uint16_t, m.echo_length ); + SPC_COPY( uint8_t, m.phase ); + + SPC_COPY( uint8_t, m.new_kon ); + SPC_COPY( uint8_t, m.endx_buf ); + SPC_COPY( uint8_t, m.envx_buf ); + SPC_COPY( uint8_t, m.outx_buf ); + + SPC_COPY( uint8_t, m.t_pmon ); + SPC_COPY( uint8_t, m.t_non ); + SPC_COPY( uint8_t, m.t_eon ); + SPC_COPY( uint8_t, m.t_dir ); + SPC_COPY( uint8_t, m.t_koff ); + + SPC_COPY( uint16_t, m.t_brr_next_addr ); + SPC_COPY( uint8_t, m.t_adsr0 ); + SPC_COPY( uint8_t, m.t_brr_header ); + SPC_COPY( uint8_t, m.t_brr_byte ); + SPC_COPY( uint8_t, m.t_srcn ); + SPC_COPY( uint8_t, m.t_esa ); + SPC_COPY( uint8_t, m.t_echo_enabled ); + + SPC_COPY( int16_t, m.t_main_out [0] ); + SPC_COPY( int16_t, m.t_main_out [1] ); + SPC_COPY( int16_t, m.t_echo_out [0] ); + SPC_COPY( int16_t, m.t_echo_out [1] ); + SPC_COPY( int16_t, m.t_echo_in [0] ); + SPC_COPY( int16_t, m.t_echo_in [1] ); + + SPC_COPY( uint16_t, m.t_dir_addr ); + SPC_COPY( uint16_t, m.t_pitch ); + SPC_COPY( int16_t, m.t_output ); + SPC_COPY( uint16_t, m.t_echo_ptr ); + SPC_COPY( uint8_t, m.t_looped ); + + copier.extra(); +} +#endif diff --git a/snes/fast/dsp/SPC_DSP.h b/snes/fast/dsp/SPC_DSP.h new file mode 100755 index 00000000..4522ace9 --- /dev/null +++ b/snes/fast/dsp/SPC_DSP.h @@ -0,0 +1,304 @@ +// Highly accurate SNES SPC-700 DSP emulator + +// snes_spc 0.9.0 +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } + +class SPC_DSP { +public: + typedef BOOST::uint8_t uint8_t; + +// Setup + + // Initializes DSP and has it use the 64K RAM provided + void init( void* ram_64k ); + + // Sets destination for output samples. If out is NULL or out_size is 0, + // doesn't generate any. + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since it was last set, always + // a multiple of 2. Undefined if more samples were generated than + // output buffer could hold. + int sample_count() const; + +// Emulation + + // Resets DSP to power-on state + void reset(); + + // Emulates pressing reset switch on SNES + void soft_reset(); + + // Reads/writes DSP registers. For accuracy, you must first call run() + // to catch the DSP up to present. + int read ( int addr ) const; + void write( int addr, int data ); + + // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks + // a pair of samples is be generated. + void run( int clock_count ); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + +// State + + // Resets DSP and uses supplied values to initialize registers + enum { register_count = 128 }; + void load( uint8_t const regs [register_count] ); + + // Saves/loads exact emulator state + enum { state_size = 640 }; // maximum space needed when saving + typedef dsp_copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Returns non-zero if new key-on events occurred since last call + bool check_kon(); + +// DSP register addresses + + // Global registers + enum { + r_mvoll = 0x0C, r_mvolr = 0x1C, + r_evoll = 0x2C, r_evolr = 0x3C, + r_kon = 0x4C, r_koff = 0x5C, + r_flg = 0x6C, r_endx = 0x7C, + r_efb = 0x0D, r_pmon = 0x2D, + r_non = 0x3D, r_eon = 0x4D, + r_dir = 0x5D, r_esa = 0x6D, + r_edl = 0x7D, + r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F + }; + + // Voice registers + enum { + v_voll = 0x00, v_volr = 0x01, + v_pitchl = 0x02, v_pitchh = 0x03, + v_srcn = 0x04, v_adsr0 = 0x05, + v_adsr1 = 0x06, v_gain = 0x07, + v_envx = 0x08, v_outx = 0x09 + }; + +public: + enum { extra_size = 16 }; + sample_t* extra() { return m.extra; } + sample_t const* out_pos() const { return m.out; } + void disable_surround( bool ) { } // not supported +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::int8_t int8_t; + typedef BOOST::int16_t int16_t; + + enum { echo_hist_size = 8 }; + + enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; + enum { brr_buf_size = 12 }; + struct voice_t + { + int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) + int buf_pos; // place in buffer where next samples will be decoded + int interp_pos; // relative fractional position in sample (0x1000 = 1.0) + int brr_addr; // address of current BRR block + int brr_offset; // current decoding offset in BRR block + uint8_t* regs; // pointer to voice's DSP registers + int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. + int kon_delay; // KON delay/current setup phase + env_mode_t env_mode; + int env; // current envelope level + int hidden_env; // used by GAIN mode 7, very obscure quirk + uint8_t t_envx_out; + }; +private: + enum { brr_block_size = 9 }; + + struct state_t + { + uint8_t regs [register_count]; + + // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) + int echo_hist [echo_hist_size * 2] [2]; + int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] + + int every_other_sample; // toggles every sample + int kon; // KON value when last checked + int noise; + int counter; + int echo_offset; // offset from ESA in echo buffer + int echo_length; // number of bytes that echo_offset will stop at + int phase; // next clock cycle to run (0-31) + bool kon_check; // set when a new KON occurs + + // Hidden registers also written to when main register is written to + int new_kon; + uint8_t endx_buf; + uint8_t envx_buf; + uint8_t outx_buf; + + // Temporary state between clocks + + // read once per sample + int t_pmon; + int t_non; + int t_eon; + int t_dir; + int t_koff; + + // read a few clocks ahead then used + int t_brr_next_addr; + int t_adsr0; + int t_brr_header; + int t_brr_byte; + int t_srcn; + int t_esa; + int t_echo_enabled; + + // internal state that is recalculated every sample + int t_dir_addr; + int t_pitch; + int t_output; + int t_looped; + int t_echo_ptr; + + // left/right sums + int t_main_out [2]; + int t_echo_out [2]; + int t_echo_in [2]; + + voice_t voices [voice_count]; + + // non-emulation state + uint8_t* ram; // 64K shared RAM between DSP and SMP + int mute_mask; + sample_t* out; + sample_t* out_end; + sample_t* out_begin; + sample_t extra [extra_size]; + }; + state_t m; + + void init_counter(); + void run_counters(); + unsigned read_counter( int rate ); + + int interpolate( voice_t const* v ); + void run_envelope( voice_t* const v ); + void decode_brr( voice_t* v ); + + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + void voice_output( voice_t const* v, int ch ); + void voice_V1( voice_t* const ); + void voice_V2( voice_t* const ); + void voice_V3( voice_t* const ); + void voice_V3a( voice_t* const ); + void voice_V3b( voice_t* const ); + void voice_V3c( voice_t* const ); + void voice_V4( voice_t* const ); + void voice_V5( voice_t* const ); + void voice_V6( voice_t* const ); + void voice_V7( voice_t* const ); + void voice_V8( voice_t* const ); + void voice_V9( voice_t* const ); + void voice_V7_V4_V1( voice_t* const ); + void voice_V8_V5_V2( voice_t* const ); + void voice_V9_V6_V3( voice_t* const ); + + void echo_read( int ch ); + int echo_output( int ch ); + void echo_write( int ch ); + void echo_22(); + void echo_23(); + void echo_24(); + void echo_25(); + void echo_26(); + void echo_27(); + void echo_28(); + void echo_29(); + void echo_30(); + + void soft_reset_common(); +}; + +#include + +inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; } + +inline int SPC_DSP::read( int addr ) const +{ + assert( (unsigned) addr < register_count ); + return m.regs [addr]; +} + +inline void SPC_DSP::write( int addr, int data ) +{ + assert( (unsigned) addr < register_count ); + + m.regs [addr] = (uint8_t) data; + switch ( addr & 0x0F ) + { + case v_envx: + m.envx_buf = (uint8_t) data; + break; + + case v_outx: + m.outx_buf = (uint8_t) data; + break; + + case 0x0C: + if ( addr == r_kon ) + m.new_kon = (uint8_t) data; + + if ( addr == r_endx ) // always cleared, regardless of data written + { + m.endx_buf = 0; + m.regs [r_endx] = 0; + } + break; + } +} + +inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } + +inline bool SPC_DSP::check_kon() +{ + bool old = m.kon_check; + m.kon_check = 0; + return old; +} + +#if !SPC_NO_COPY_STATE_FUNCS + +class SPC_State_Copier { + SPC_DSP::copy_func_t func; + unsigned char** buf; +public: + SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } + void copy( void* state, size_t size ); + int copy_int( int state, int size ); + void skip( int count ); + void extra(); +}; + +#define SPC_COPY( type, state )\ +{\ + state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ + assert( (BOOST::type) state == state );\ +} + +#endif + +#endif diff --git a/snes/fast/dsp/blargg_common.h b/snes/fast/dsp/blargg_common.h new file mode 100755 index 00000000..75edff39 --- /dev/null +++ b/snes/fast/dsp/blargg_common.h @@ -0,0 +1,186 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// snes_spc 0.9.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// BLARGG_RESTRICT: equivalent to restrict, where supported +#if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + // TODO: blargg_common.cpp to hold this as an outline function, ugh + void* p = realloc( begin_, n * sizeof (T) ); + if ( p ) + begin_ = (T*) p; + else if ( n > size_ ) // realloc failure only a problem if expanding + return "Out of memory"; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if operator new can return NULL + #if __cplusplus >= 199711 || defined (__GNUC__) + #define BLARGG_THROWS( spec ) throw spec + #else + #define BLARGG_THROWS( spec ) + #endif + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include + #define BLARGG_NEW new (std::nothrow) +#endif + +// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough + +#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blargg_long; +#else + typedef int blargg_long; +#endif + +#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF + typedef unsigned long blargg_ulong; +#else + typedef unsigned blargg_ulong; +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#endif +#endif diff --git a/snes/fast/dsp/blargg_config.h b/snes/fast/dsp/blargg_config.h new file mode 100755 index 00000000..d85d2663 --- /dev/null +++ b/snes/fast/dsp/blargg_config.h @@ -0,0 +1,24 @@ +// snes_spc 0.9.0 user configuration file. Don't replace when updating library. + +// snes_spc 0.9.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to disable debugging checks +#define NDEBUG 1 + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/snes/fast/dsp/blargg_endian.h b/snes/fast/dsp/blargg_endian.h new file mode 100755 index 00000000..f2daca64 --- /dev/null +++ b/snes/fast/dsp/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// snes_spc 0.9.0 +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline blargg_ulong get_le32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [3] << 24 | + (blargg_ulong) ((unsigned char const*) p) [2] << 16 | + (blargg_ulong) ((unsigned char const*) p) [1] << 8 | + (blargg_ulong) ((unsigned char const*) p) [0]; +} + +inline blargg_ulong get_be32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [0] << 24 | + (blargg_ulong) ((unsigned char const*) p) [1] << 16 | + (blargg_ulong) ((unsigned char const*) p) [2] << 8 | + (blargg_ulong) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif diff --git a/snes/fast/dsp/blargg_source.h b/snes/fast/dsp/blargg_source.h new file mode 100755 index 00000000..5e45c4fb --- /dev/null +++ b/snes/fast/dsp/blargg_source.h @@ -0,0 +1,100 @@ +/* Included at the beginning of library source files, after all other #include lines. +Sets up helpful macros and services used in my source code. They don't need +module an annoying module prefix on their names since they are defined after +all other #include lines. */ + +// snes_spc 0.9.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +static inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +#define DEF_MIN_MAX( type ) \ + static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\ + static inline type max( type x, type y ) { if ( y < x ) return x; return y; } + +DEF_MIN_MAX( int ) +DEF_MIN_MAX( unsigned ) +DEF_MIN_MAX( long ) +DEF_MIN_MAX( unsigned long ) +DEF_MIN_MAX( float ) +DEF_MIN_MAX( double ) + +#undef DEF_MIN_MAX + +/* +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +// TODO: remove +inline int min( int x, int y ) +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} +*/ + +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/snes/fast/dsp/brr.cpp b/snes/fast/dsp/brr.cpp deleted file mode 100755 index abdf2a0d..00000000 --- a/snes/fast/dsp/brr.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifdef DSP_CPP - -void DSP::brr_decode(voice_t &v) { - //state.t_brr_byte = ram[v.brr_addr + v.brr_offset] cached from previous clock cycle - int nybbles = (state.t_brr_byte << 8) + memory::apuram[(uint16)(v.brr_addr + v.brr_offset + 1)]; - - const int filter = (state.t_brr_header >> 2) & 3; - const int scale = (state.t_brr_header >> 4); - - //decode four samples - for(unsigned i = 0; i < 4; i++) { - //bits 12-15 = current nybble; sign extend, then shift right to 4-bit precision - //result: s = 4-bit sign-extended sample value - int s = (int16)nybbles >> 12; - nybbles <<= 4; //slide nybble so that on next loop iteration, bits 12-15 = current nybble - - if(scale <= 12) { - s <<= scale; - s >>= 1; - } else { - s &= ~0x7ff; - } - - //apply IIR filter (2 is the most commonly used) - const int p1 = v.buffer[v.buf_pos - 1]; - const int p2 = v.buffer[v.buf_pos - 2] >> 1; - - switch(filter) { - case 0: break; //no filter - - case 1: { - //s += p1 * 0.46875 - s += p1 >> 1; - s += (-p1) >> 5; - } break; - - case 2: { - //s += p1 * 0.953125 - p2 * 0.46875 - s += p1; - s -= p2; - s += p2 >> 4; - s += (p1 * -3) >> 6; - } break; - - case 3: { - //s += p1 * 0.8984375 - p2 * 0.40625 - s += p1; - s -= p2; - s += (p1 * -13) >> 7; - s += (p2 * 3) >> 4; - } break; - } - - //adjust and write sample - s = sclamp<16>(s); - s = (int16)(s << 1); - v.buffer.write(v.buf_pos++, s); - if(v.buf_pos >= brr_buf_size) v.buf_pos = 0; - } -} - -#endif diff --git a/snes/fast/dsp/counter.cpp b/snes/fast/dsp/counter.cpp deleted file mode 100755 index f65fdd26..00000000 --- a/snes/fast/dsp/counter.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifdef DSP_CPP - -//counter_rate = number of samples per counter event -//all rates are evenly divisible by counter_range (0x7800, 30720, or 2048 * 5 * 3) -//note that rate[0] is a special case, which never triggers - -const uint16 DSP::counter_rate[32] = { - 0, 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, -}; - -//counter_offset = counter offset from zero -//counters do not appear to be aligned at zero for all rates - -const uint16 DSP::counter_offset[32] = { - 0, 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, -}; - -inline void DSP::counter_tick() { - state.counter--; - if(state.counter < 0) state.counter = counter_range - 1; -} - -//return true if counter event should trigger - -inline bool DSP::counter_poll(unsigned rate) { - if(rate == 0) return false; - return (((unsigned)state.counter + counter_offset[rate]) % counter_rate[rate]) == 0; -} - -#endif diff --git a/snes/fast/dsp/debugger/debugger.cpp b/snes/fast/dsp/debugger/debugger.cpp deleted file mode 100755 index 72d9d3e2..00000000 --- a/snes/fast/dsp/debugger/debugger.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifdef DSP_CPP - -bool DSPDebugger::property(unsigned id, string &name, string &value) { - unsigned n = 0; - - #define item(name_, value_) \ - if(id == n++) { \ - name = name_; \ - value = value_; \ - return true; \ - } - - item("Main Volume - Left", (unsigned)state.regs[0x0c]); - item("Main Volume - Right", (unsigned)state.regs[0x1c]); - item("Echo Volume - Left", (unsigned)state.regs[0x2c]); - item("Echo Volume - Right", (unsigned)state.regs[0x3c]); - item("Key On", string("0x", strhex<2>(state.regs[0x4c]))); - item("Key Off", string("0x", strhex<2>(state.regs[0x5c]))); - item("Flag - Reset", (bool)(state.regs[0x6c] & 0x80)); - item("Flag - Mute", (bool)(state.regs[0x6c] & 0x40)); - item("Flag - Echo Disable", (bool)(state.regs[0x6c] & 0x20)); - item("Flag - Noise Clock", (unsigned)state.regs[0x6c] & 0x1f); - item("Source End Block", (unsigned)state.regs[0x7c]); - item("Echo Feedback", (unsigned)state.regs[0x0d]); - item("Pitch Modulation Enable", string("0x", strhex<2>(state.regs[0x2d]))); - item("Noise Enable", string("0x", strhex<2>(state.regs[0x3d]))); - item("Echo Enable", string("0x", strhex<2>(state.regs[0x4d]))); - item("Source Directory", (unsigned)state.regs[0x5d]); - item("Echo Start Address", (unsigned)state.regs[0x6d]); - item("Echo Directory", (unsigned)state.regs[0x7d]); - - for(unsigned i = 0; i < 8; i++) { - item(string("Coefficient ", i), string("0x", strhex<2>(state.regs[(i << 4) + 0x0f]))); - } - - for(unsigned i = 0; i < 8; i++) { - item(string("Voice ", i), ""); - item("Volume - Left", (unsigned)state.regs[(i << 4) + 0x00]); - item("Volume - Right", (unsigned)state.regs[(i << 4) + 0x01]); - item("Pitch Height", string("0x", strhex<4>(state.regs[(i << 4) + 0x02] + (state.regs[(i << 4) + 0x03] << 8)))); - item("Source Number", (unsigned)state.regs[(i << 4) + 0x04]); - item("ADSR1", (unsigned)state.regs[(i << 4) + 0x05]); - item("ADSR2", (unsigned)state.regs[(i << 4) + 0x06]); - item("GAIN", (unsigned)state.regs[(i << 4) + 0x07]); - item("ENVX", (unsigned)state.regs[(i << 4) + 0x08]); - item("OUTX", (unsigned)state.regs[(i << 4) + 0x09]); - } - - #undef item - return false; -} - -#endif diff --git a/snes/fast/dsp/debugger/debugger.hpp b/snes/fast/dsp/debugger/debugger.hpp deleted file mode 100755 index 3339ea3d..00000000 --- a/snes/fast/dsp/debugger/debugger.hpp +++ /dev/null @@ -1,4 +0,0 @@ -class DSPDebugger : public DSP, public ChipDebugger { -public: - bool property(unsigned id, string &name, string &value); -}; diff --git a/snes/fast/dsp/dsp.cpp b/snes/fast/dsp/dsp.cpp index fe35bbbe..b93d9a6f 100755 --- a/snes/fast/dsp/dsp.cpp +++ b/snes/fast/dsp/dsp.cpp @@ -3,27 +3,10 @@ #define DSP_CPP namespace SNES { -#if defined(DEBUGGER) - #include "debugger/debugger.cpp" - DSPDebugger dsp; -#else - DSP dsp; -#endif +DSP dsp; #include "serialization.cpp" - -#define REG(n) state.regs[r_##n] -#define VREG(n) state.regs[v.vidx + v_##n] - -#include "gaussian.cpp" -#include "counter.cpp" -#include "envelope.cpp" -#include "brr.cpp" -#include "misc.cpp" -#include "voice.cpp" -#include "echo.cpp" - -/* timing */ +#include "SPC_DSP.cpp" void DSP::step(unsigned clocks) { clock += clocks; @@ -37,301 +20,34 @@ void DSP::synchronize_smp() { } } -void DSP::Enter() { dsp.enter(); } - void DSP::enter() { - switch(phase) { - case 0: - voice_5(voice[0]); - voice_2(voice[1]); - return tick(); + spc_dsp.run(1); + step(24); - case 1: - voice_6(voice[0]); - voice_3(voice[1]); - return tick(); - - case 2: - voice_7(voice[0]); - voice_4(voice[1]); - voice_1(voice[3]); - return tick(); - - case 3: - voice_8(voice[0]); - voice_5(voice[1]); - voice_2(voice[2]); - return tick(); - - case 4: - voice_9(voice[0]); - voice_6(voice[1]); - voice_3(voice[2]); - return tick(); - - case 5: - voice_7(voice[1]); - voice_4(voice[2]); - voice_1(voice[4]); - return tick(); - - case 6: - voice_8(voice[1]); - voice_5(voice[2]); - voice_2(voice[3]); - return tick(); - - case 7: - voice_9(voice[1]); - voice_6(voice[2]); - voice_3(voice[3]); - return tick(); - - case 8: - voice_7(voice[2]); - voice_4(voice[3]); - voice_1(voice[5]); - return tick(); - - case 9: - voice_8(voice[2]); - voice_5(voice[3]); - voice_2(voice[4]); - return tick(); - - case 10: - voice_9(voice[2]); - voice_6(voice[3]); - voice_3(voice[4]); - return tick(); - - case 11: - voice_7(voice[3]); - voice_4(voice[4]); - voice_1(voice[6]); - return tick(); - - case 12: - voice_8(voice[3]); - voice_5(voice[4]); - voice_2(voice[5]); - return tick(); - - case 13: - voice_9(voice[3]); - voice_6(voice[4]); - voice_3(voice[5]); - return tick(); - - case 14: - voice_7(voice[4]); - voice_4(voice[5]); - voice_1(voice[7]); - return tick(); - - case 15: - voice_8(voice[4]); - voice_5(voice[5]); - voice_2(voice[6]); - return tick(); - - case 16: - voice_9(voice[4]); - voice_6(voice[5]); - voice_3(voice[6]); - return tick(); - - case 17: - voice_1(voice[0]); - voice_7(voice[5]); - voice_4(voice[6]); - return tick(); - - case 18: - voice_8(voice[5]); - voice_5(voice[6]); - voice_2(voice[7]); - return tick(); - - case 19: - voice_9(voice[5]); - voice_6(voice[6]); - voice_3(voice[7]); - return tick(); - - case 20: - voice_1(voice[1]); - voice_7(voice[6]); - voice_4(voice[7]); - return tick(); - - case 21: - voice_8(voice[6]); - voice_5(voice[7]); - voice_2(voice[0]); - return tick(); - - case 22: - voice_3a(voice[0]); - voice_9(voice[6]); - voice_6(voice[7]); - echo_22(); - return tick(); - - case 23: - voice_7(voice[7]); - echo_23(); - return tick(); - - case 24: - voice_8(voice[7]); - echo_24(); - return tick(); - - case 25: - voice_3b(voice[0]); - voice_9(voice[7]); - echo_25(); - return tick(); - - case 26: - echo_26(); - return tick(); - - case 27: - misc_27(); - echo_27(); - return tick(); - - case 28: - misc_28(); - echo_28(); - return tick(); - - case 29: - misc_29(); - echo_29(); - return tick(); - - case 30: - misc_30(); - voice_3c(voice[0]); - echo_30(); - return tick(); - - case 31: - voice_4(voice[0]); - voice_1(voice[2]); - return tick(); + signed count = spc_dsp.sample_count(); + if(count > 0) { + for(unsigned n = 0; n < count; n += 2) audio.sample(samplebuffer[n + 0], samplebuffer[n + 1]); + spc_dsp.set_output(samplebuffer, 8192); } } -void DSP::tick() { - step(3 * 8); - synchronize_smp(); - phase = (phase + 1) & 31; -} - -/* register interface for S-SMP $00f2,$00f3 */ - uint8 DSP::read(uint8 addr) { - return state.regs[addr]; + return spc_dsp.read(addr); } void DSP::write(uint8 addr, uint8 data) { - state.regs[addr] = data; - - if((addr & 0x0f) == v_envx) { - state.envx_buf = data; - } else if((addr & 0x0f) == v_outx) { - state.outx_buf = data; - } else if(addr == r_kon) { - state.new_kon = data; - } else if(addr == r_endx) { - //always cleared, regardless of data written - state.endx_buf = 0; - state.regs[r_endx] = 0; - } + spc_dsp.write(addr, data); } -/* initialization */ - void DSP::power() { - memset(&state.regs, 0, sizeof state.regs); - state.echo_hist_pos = 0; - state.every_other_sample = false; - state.kon = 0; - state.noise = 0; - state.counter = 0; - state.echo_offset = 0; - state.echo_length = 0; - state.new_kon = 0; - state.endx_buf = 0; - state.envx_buf = 0; - state.outx_buf = 0; - state.t_pmon = 0; - state.t_non = 0; - state.t_eon = 0; - state.t_dir = 0; - state.t_koff = 0; - state.t_brr_next_addr = 0; - state.t_adsr0 = 0; - state.t_brr_header = 0; - state.t_brr_byte = 0; - state.t_srcn = 0; - state.t_esa = 0; - state.t_echo_disabled = 0; - state.t_dir_addr = 0; - state.t_pitch = 0; - state.t_output = 0; - state.t_looped = 0; - state.t_echo_ptr = 0; - state.t_main_out[0] = state.t_main_out[1] = 0; - state.t_echo_out[0] = state.t_echo_out[1] = 0; - state.t_echo_in[0] = state.t_echo_in[1] = 0; - - for(unsigned i = 0; i < 8; i++) { - voice[i].buf_pos = 0; - voice[i].interp_pos = 0; - voice[i].brr_addr = 0; - voice[i].brr_offset = 1; - voice[i].vbit = 1 << i; - voice[i].vidx = i * 0x10; - voice[i].kon_delay = 0; - voice[i].env_mode = env_release; - voice[i].env = 0; - voice[i].t_envx_out = 0; - voice[i].hidden_env = 0; - } - - reset(); + spc_dsp.init(memory::apuram.data()); + spc_dsp.reset(); + spc_dsp.set_output(samplebuffer, 8192); } void DSP::reset() { - create(Enter, system.apu_frequency()); - - REG(flg) = 0xe0; - - state.noise = 0x4000; - state.echo_hist_pos = 0; - state.every_other_sample = 1; - state.echo_offset = 0; - state.counter = 0; -} - -DSP::DSP() { - static_assert(sizeof(int) >= 32 / 8, "int >= 32-bits"); - static_assert((int8)0x80 == -0x80, "8-bit sign extension"); - static_assert((int16)0x8000 == -0x8000, "16-bit sign extension"); - static_assert((uint16)0xffff0000 == 0, "16-bit unsigned clip"); - static_assert((-1 >> 1) == -1, "arithmetic shift right"); - - //-0x8000 <= n <= +0x7fff - assert(sclamp<16>(+0x8000) == +0x7fff); - assert(sclamp<16>(-0x8001) == -0x8000); -} - -DSP::~DSP() { + spc_dsp.soft_reset(); + spc_dsp.set_output(samplebuffer, 8192); } } diff --git a/snes/fast/dsp/dsp.hpp b/snes/fast/dsp/dsp.hpp index 581a0e48..1d3d234c 100755 --- a/snes/fast/dsp/dsp.hpp +++ b/snes/fast/dsp/dsp.hpp @@ -1,4 +1,6 @@ -class DSP : public Processor { +#include "SPC_DSP.h" + +class DSP : public Processor, public ChipDebugger { public: enum : bool { Threaded = false }; alwaysinline void step(unsigned clocks); @@ -12,171 +14,11 @@ public: void reset(); void serialize(serializer&); - DSP(); - ~DSP(); + bool property(unsigned id, string &name, string &value) { return false; } private: - unsigned phase; - - //global registers - enum global_reg_t { - r_mvoll = 0x0c, r_mvolr = 0x1c, - r_evoll = 0x2c, r_evolr = 0x3c, - r_kon = 0x4c, r_koff = 0x5c, - r_flg = 0x6c, r_endx = 0x7c, - r_efb = 0x0d, r_pmon = 0x2d, - r_non = 0x3d, r_eon = 0x4d, - r_dir = 0x5d, r_esa = 0x6d, - r_edl = 0x7d, r_fir = 0x0f, //8 coefficients at 0x0f, 0x1f, ... 0x7f - }; - - //voice registers - enum voice_reg_t { - v_voll = 0x00, v_volr = 0x01, - v_pitchl = 0x02, v_pitchh = 0x03, - v_srcn = 0x04, v_adsr0 = 0x05, - v_adsr1 = 0x06, v_gain = 0x07, - v_envx = 0x08, v_outx = 0x09, - }; - - //internal envelope modes - enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; - - //internal constants - enum { echo_hist_size = 8 }; - enum { brr_buf_size = 12 }; - enum { brr_block_size = 9 }; - - //global state - struct state_t { - uint8 regs[128]; - - modulo_array echo_hist[2]; //echo history keeps most recent 8 samples - int echo_hist_pos; - - bool every_other_sample; //toggles every sample - int kon; //KON value when last checked - int noise; - int counter; - int echo_offset; //offset from ESA in echo buffer - int echo_length; //number of bytes that echo_offset will stop at - - //hidden registers also written to when main register is written to - int new_kon; - int endx_buf; - int envx_buf; - int outx_buf; - - //temporary state between clocks - - //read once per sample - int t_pmon; - int t_non; - int t_eon; - int t_dir; - int t_koff; - - //read a few clocks ahead before used - int t_brr_next_addr; - int t_adsr0; - int t_brr_header; - int t_brr_byte; - int t_srcn; - int t_esa; - int t_echo_disabled; - - //internal state that is recalculated every sample - int t_dir_addr; - int t_pitch; - int t_output; - int t_looped; - int t_echo_ptr; - - //left/right sums - int t_main_out[2]; - int t_echo_out[2]; - int t_echo_in [2]; - } state; - - //voice state - struct voice_t { - modulo_array buffer; //decoded samples - int buf_pos; //place in buffer where next samples will be decoded - int interp_pos; //relative fractional position in sample (0x1000 = 1.0) - int brr_addr; //address of current BRR block - int brr_offset; //current decoding offset in BRR block - int vbit; //bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc - int vidx; //voice channel register index: 0x00 for voice 0, 0x10 for voice 1, etc - int kon_delay; //KON delay/current setup phase - int env_mode; - int env; //current envelope level - int t_envx_out; - int hidden_env; //used by GAIN mode 7, very obscure quirk - } voice[8]; - - //gaussian - static const int16 gaussian_table[512]; - int gaussian_interpolate(const voice_t &v); - - //counter - enum { counter_range = 2048 * 5 * 3 }; //30720 (0x7800) - static const uint16 counter_rate[32]; - static const uint16 counter_offset[32]; - void counter_tick(); - bool counter_poll(unsigned rate); - - //envelope - void envelope_run(voice_t &v); - - //brr - void brr_decode(voice_t &v); - - //misc - void misc_27(); - void misc_28(); - void misc_29(); - void misc_30(); - - //voice - void voice_output(voice_t &v, bool channel); - void voice_1 (voice_t &v); - void voice_2 (voice_t &v); - void voice_3 (voice_t &v); - void voice_3a(voice_t &v); - void voice_3b(voice_t &v); - void voice_3c(voice_t &v); - void voice_4 (voice_t &v); - void voice_5 (voice_t &v); - void voice_6 (voice_t &v); - void voice_7 (voice_t &v); - void voice_8 (voice_t &v); - void voice_9 (voice_t &v); - - //echo - int calc_fir(int i, bool channel); - int echo_output(bool channel); - void echo_read(bool channel); - void echo_write(bool channel); - void echo_22(); - void echo_23(); - void echo_24(); - void echo_25(); - void echo_26(); - void echo_27(); - void echo_28(); - void echo_29(); - void echo_30(); - - //dsp - static void Enter(); - alwaysinline void tick(); - - friend class DSPDebugger; + SPC_DSP spc_dsp; + int16 samplebuffer[8192]; }; -#if defined(DEBUGGER) - #include "debugger/debugger.hpp" - extern DSPDebugger dsp; -#else - extern DSP dsp; -#endif +extern DSP dsp; diff --git a/snes/fast/dsp/echo.cpp b/snes/fast/dsp/echo.cpp deleted file mode 100755 index c0cf5f37..00000000 --- a/snes/fast/dsp/echo.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#ifdef DSP_CPP - -int DSP::calc_fir(int i, bool channel) { - int s = state.echo_hist[channel][state.echo_hist_pos + i + 1]; - return (s * (int8)REG(fir + i * 0x10)) >> 6; -} - -int DSP::echo_output(bool channel) { - int output = (int16)((state.t_main_out[channel] * (int8)REG(mvoll + channel * 0x10)) >> 7) - + (int16)((state.t_echo_in [channel] * (int8)REG(evoll + channel * 0x10)) >> 7); - return sclamp<16>(output); -} - -void DSP::echo_read(bool channel) { - unsigned addr = state.t_echo_ptr + channel * 2; - uint8 lo = memory::apuram[(uint16)(addr + 0)]; - uint8 hi = memory::apuram[(uint16)(addr + 1)]; - int s = (int16)((hi << 8) + lo); - state.echo_hist[channel].write(state.echo_hist_pos, s >> 1); -} - -void DSP::echo_write(bool channel) { - if(!(state.t_echo_disabled & 0x20)) { - unsigned addr = state.t_echo_ptr + channel * 2; - int s = state.t_echo_out[channel]; - memory::apuram[(uint16)(addr + 0)] = s; - memory::apuram[(uint16)(addr + 1)] = s >> 8; - } - - state.t_echo_out[channel] = 0; -} - -void DSP::echo_22() { - //history - state.echo_hist_pos++; - if(state.echo_hist_pos >= echo_hist_size) state.echo_hist_pos = 0; - - state.t_echo_ptr = (uint16)((state.t_esa << 8) + state.echo_offset); - echo_read(0); - - //FIR - int l = calc_fir(0, 0); - int r = calc_fir(0, 1); - - state.t_echo_in[0] = l; - state.t_echo_in[1] = r; -} - -void DSP::echo_23() { - int l = calc_fir(1, 0) + calc_fir(2, 0); - int r = calc_fir(1, 1) + calc_fir(2, 1); - - state.t_echo_in[0] += l; - state.t_echo_in[1] += r; - - echo_read(1); -} - -void DSP::echo_24() { - int l = calc_fir(3, 0) + calc_fir(4, 0) + calc_fir(5, 0); - int r = calc_fir(3, 1) + calc_fir(4, 1) + calc_fir(5, 1); - - state.t_echo_in[0] += l; - state.t_echo_in[1] += r; -} - -void DSP::echo_25() { - int l = state.t_echo_in[0] + calc_fir(6, 0); - int r = state.t_echo_in[1] + calc_fir(6, 1); - - l = (int16)l; - r = (int16)r; - - l += (int16)calc_fir(7, 0); - r += (int16)calc_fir(7, 1); - - state.t_echo_in[0] = sclamp<16>(l) & ~1; - state.t_echo_in[1] = sclamp<16>(r) & ~1; -} - -void DSP::echo_26() { - //left output volumes - //(save sample for next clock so we can output both together) - state.t_main_out[0] = echo_output(0); - - //echo feedback - int l = state.t_echo_out[0] + (int16)((state.t_echo_in[0] * (int8)REG(efb)) >> 7); - int r = state.t_echo_out[1] + (int16)((state.t_echo_in[1] * (int8)REG(efb)) >> 7); - - state.t_echo_out[0] = sclamp<16>(l) & ~1; - state.t_echo_out[1] = sclamp<16>(r) & ~1; -} - -void DSP::echo_27() { - //output - int outl = state.t_main_out[0]; - int outr = echo_output(1); - state.t_main_out[0] = 0; - state.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(REG(flg) & 0x40) { - outl = 0; - outr = 0; - } - - //output sample to DAC - audio.sample(outl, outr); -} - -void DSP::echo_28() { - state.t_echo_disabled = REG(flg); -} - -void DSP::echo_29() { - state.t_esa = REG(esa); - - if(!state.echo_offset) state.echo_length = (REG(edl) & 0x0f) << 11; - - state.echo_offset += 4; - if(state.echo_offset >= state.echo_length) state.echo_offset = 0; - - //write left echo - echo_write(0); - - state.t_echo_disabled = REG(flg); -} - -void DSP::echo_30() { - //write right echo - echo_write(1); -} - -#endif diff --git a/snes/fast/dsp/envelope.cpp b/snes/fast/dsp/envelope.cpp deleted file mode 100755 index 9ae0d3e0..00000000 --- a/snes/fast/dsp/envelope.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifdef DSP_CPP - -void DSP::envelope_run(voice_t &v) { - int env = v.env; - - if(v.env_mode == env_release) { //60% - env -= 0x8; - if(env < 0) env = 0; - v.env = env; - return; - } - - int rate; - int env_data = VREG(adsr1); - if(state.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 = ((state.t_adsr0 >> 3) & 0x0e) + 0x10; - } - } else { //env_attack - rate = ((state.t_adsr0 & 0x0f) << 1) + 1; - env += rate < 31 ? 0x20 : 0x400; - } - } else { //GAIN - env_data = VREG(gain); - int mode = env_data >> 5; - if(mode < 4) { //direct - env = env_data << 4; - 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 underflowing also triggers this - if((unsigned)env > 0x7ff) { - env = (env < 0 ? 0 : 0x7ff); - if(v.env_mode == env_attack) v.env_mode = env_decay; - } - - if(counter_poll(rate) == true) v.env = env; -} - -#endif diff --git a/snes/fast/dsp/gaussian.cpp b/snes/fast/dsp/gaussian.cpp deleted file mode 100755 index 80aed8ad..00000000 --- a/snes/fast/dsp/gaussian.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifdef DSP_CPP - -const int16 DSP::gaussian_table[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, -}; - -int DSP::gaussian_interpolate(const voice_t &v) { - //make pointers into gaussian table based on fractional position between samples - int offset = (v.interp_pos >> 4) & 0xff; - const int16 *fwd = gaussian_table + 255 - offset; - const int16 *rev = gaussian_table + offset; //mirror left half of gaussian table - - offset = v.buf_pos + (v.interp_pos >> 12); - int output; - output = (fwd[ 0] * v.buffer[offset + 0]) >> 11; - output += (fwd[256] * v.buffer[offset + 1]) >> 11; - output += (rev[256] * v.buffer[offset + 2]) >> 11; - output = (int16)output; - output += (rev[ 0] * v.buffer[offset + 3]) >> 11; - return sclamp<16>(output) & ~1; -} - -#endif diff --git a/snes/fast/dsp/misc.cpp b/snes/fast/dsp/misc.cpp deleted file mode 100755 index 244fc51f..00000000 --- a/snes/fast/dsp/misc.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifdef DSP_CPP - -void DSP::misc_27() { - state.t_pmon = REG(pmon) & ~1; //voice 0 doesn't support PMON -} - -void DSP::misc_28() { - state.t_non = REG(non); - state.t_eon = REG(eon); - state.t_dir = REG(dir); -} - -void DSP::misc_29() { - state.every_other_sample ^= 1; - if(state.every_other_sample) { - state.new_kon &= ~state.kon; //clears KON 63 clocks after it was last read - } -} - -void DSP::misc_30() { - if(state.every_other_sample) { - state.kon = state.new_kon; - state.t_koff = REG(koff); - } - - counter_tick(); - - //noise - if(counter_poll(REG(flg) & 0x1f) == true) { - int feedback = (state.noise << 13) ^ (state.noise << 14); - state.noise = (feedback & 0x4000) ^ (state.noise >> 1); - } -} - -#endif diff --git a/snes/fast/dsp/serialization.cpp b/snes/fast/dsp/serialization.cpp index 62b3acdf..0565a1b5 100755 --- a/snes/fast/dsp/serialization.cpp +++ b/snes/fast/dsp/serialization.cpp @@ -1,66 +1,30 @@ #ifdef DSP_CPP +static void dsp_state_save(unsigned char **out, void *in, size_t size) { + memcpy(*out, in, size); + *out += size; +} + +static void dsp_state_load(unsigned char **in, void *out, size_t size) { + memcpy(out, *in, size); + *in += size; +} + void DSP::serialize(serializer &s) { Processor::serialize(s); - s.integer(phase); + s.array(samplebuffer); - s.array(state.regs, 128); - state.echo_hist[0].serialize(s); - state.echo_hist[1].serialize(s); - s.integer(state.echo_hist_pos); - - s.integer(state.every_other_sample); - s.integer(state.kon); - s.integer(state.noise); - s.integer(state.counter); - s.integer(state.echo_offset); - s.integer(state.echo_length); - - s.integer(state.new_kon); - s.integer(state.endx_buf); - s.integer(state.envx_buf); - s.integer(state.outx_buf); - - s.integer(state.t_pmon); - s.integer(state.t_non); - s.integer(state.t_eon); - s.integer(state.t_dir); - s.integer(state.t_koff); - - s.integer(state.t_brr_next_addr); - s.integer(state.t_adsr0); - s.integer(state.t_brr_header); - s.integer(state.t_brr_byte); - s.integer(state.t_srcn); - s.integer(state.t_esa); - s.integer(state.t_echo_disabled); - - s.integer(state.t_dir_addr); - s.integer(state.t_pitch); - s.integer(state.t_output); - s.integer(state.t_looped); - s.integer(state.t_echo_ptr); - - s.integer(state.t_main_out[0]); - s.integer(state.t_main_out[1]); - s.integer(state.t_echo_out[0]); - s.integer(state.t_echo_out[1]); - s.integer(state.t_echo_in [0]); - s.integer(state.t_echo_in [1]); - - for(unsigned n = 0; n < 8; n++) { - voice[n].buffer.serialize(s); - s.integer(voice[n].buf_pos); - s.integer(voice[n].interp_pos); - s.integer(voice[n].brr_addr); - s.integer(voice[n].brr_offset); - s.integer(voice[n].vbit); - s.integer(voice[n].vidx); - s.integer(voice[n].kon_delay); - s.integer(voice[n].env_mode); - s.integer(voice[n].env); - s.integer(voice[n].t_envx_out); - s.integer(voice[n].hidden_env); + unsigned char state[SPC_DSP::state_size]; + unsigned char *p = state; + memset(&state, 0, SPC_DSP::state_size); + if(s.mode() == serializer::Save) { + spc_dsp.copy_state(&p, dsp_state_save); + s.array(state); + } else if(s.mode() == serializer::Load) { + s.array(state); + spc_dsp.copy_state(&p, dsp_state_load); + } else { + s.array(state); } } diff --git a/snes/fast/dsp/voice.cpp b/snes/fast/dsp/voice.cpp deleted file mode 100755 index 2e882f21..00000000 --- a/snes/fast/dsp/voice.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#ifdef DSP_CPP - -inline void DSP::voice_output(voice_t &v, bool channel) { - //apply left/right volume - int amp = (state.t_output * (int8)VREG(voll + channel)) >> 7; - - //add to output total - state.t_main_out[channel] += amp; - state.t_main_out[channel] = sclamp<16>(state.t_main_out[channel]); - - //optionally add to echo total - if(state.t_eon & v.vbit) { - state.t_echo_out[channel] += amp; - state.t_echo_out[channel] = sclamp<16>(state.t_echo_out[channel]); - } -} - -void DSP::voice_1(voice_t &v) { - state.t_dir_addr = (state.t_dir << 8) + (state.t_srcn << 2); - state.t_srcn = VREG(srcn); -} - -void DSP::voice_2(voice_t &v) { - //read sample pointer (ignored if not needed) - uint16 addr = state.t_dir_addr; - if(!v.kon_delay) addr += 2; - uint8 lo = memory::apuram[(uint16)(addr + 0)]; - uint8 hi = memory::apuram[(uint16)(addr + 1)]; - state.t_brr_next_addr = ((hi << 8) + lo); - - state.t_adsr0 = VREG(adsr0); - - //read pitch, spread over two clocks - state.t_pitch = VREG(pitchl); -} - -void DSP::voice_3(voice_t &v) { - voice_3a(v); - voice_3b(v); - voice_3c(v); -} - -void DSP::voice_3a(voice_t &v) { - state.t_pitch += (VREG(pitchh) & 0x3f) << 8; -} - -void DSP::voice_3b(voice_t &v) { - state.t_brr_byte = memory::apuram[(uint16)(v.brr_addr + v.brr_offset)]; - state.t_brr_header = memory::apuram[(uint16)(v.brr_addr)]; -} - -void DSP::voice_3c(voice_t &v) { - //pitch modulation using previous voice's output - - if(state.t_pmon & v.vbit) { - state.t_pitch += ((state.t_output >> 5) * state.t_pitch) >> 10; - } - - if(v.kon_delay) { - //get ready to start BRR decoding on next sample - if(v.kon_delay == 5) { - v.brr_addr = state.t_brr_next_addr; - v.brr_offset = 1; - v.buf_pos = 0; - state.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; - v.kon_delay--; - if(v.kon_delay & 3) v.interp_pos = 0x4000; - - //pitch is never added during KON - state.t_pitch = 0; - } - - //gaussian interpolation - int output = gaussian_interpolate(v); - - //noise - if(state.t_non & v.vbit) { - output = (int16)(state.noise << 1); - } - - //apply envelope - state.t_output = ((output * v.env) >> 11) & ~1; - v.t_envx_out = v.env >> 4; - - //immediate silence due to end of sample or soft reset - if(REG(flg) & 0x80 || (state.t_brr_header & 3) == 1) { - v.env_mode = env_release; - v.env = 0; - } - - if(state.every_other_sample) { - //KOFF - if(state.t_koff & v.vbit) { - v.env_mode = env_release; - } - - //KON - if(state.kon & v.vbit) { - v.kon_delay = 5; - v.env_mode = env_attack; - } - } - - //run envelope for next sample - if(!v.kon_delay) envelope_run(v); -} - -void DSP::voice_4(voice_t &v) { - //decode BRR - state.t_looped = 0; - if(v.interp_pos >= 0x4000) { - brr_decode(v); - v.brr_offset += 2; - if(v.brr_offset >= 9) { - //start decoding next BRR block - v.brr_addr = (uint16)(v.brr_addr + 9); - if(state.t_brr_header & 1) { - v.brr_addr = state.t_brr_next_addr; - state.t_looped = v.vbit; - } - v.brr_offset = 1; - } - } - - //apply pitch - v.interp_pos = (v.interp_pos & 0x3fff) + state.t_pitch; - - //keep from getting too far ahead (when using pitch modulation) - if(v.interp_pos > 0x7fff) v.interp_pos = 0x7fff; - - //output left - voice_output(v, 0); -} - -void DSP::voice_5(voice_t &v) { - //output right - voice_output(v, 1); - - //ENDX, OUTX and ENVX won't update if you wrote to them 1-2 clocks earlier - state.endx_buf = REG(endx) | state.t_looped; - - //clear bit in ENDX if KON just began - if(v.kon_delay == 5) state.endx_buf &= ~v.vbit; -} - -void DSP::voice_6(voice_t &v) { - state.outx_buf = state.t_output >> 8; -} - -void DSP::voice_7(voice_t &v) { - //update ENDX - REG(endx) = (uint8)state.endx_buf; - state.envx_buf = v.t_envx_out; -} - -void DSP::voice_8(voice_t &v) { - //update OUTX - VREG(outx) = (uint8)state.outx_buf; -} - -void DSP::voice_9(voice_t &v) { - //update ENVX - VREG(envx) = (uint8)state.envx_buf; -} - -#endif diff --git a/snes/memory/memory.cpp b/snes/memory/memory.cpp index 0ae1c1fb..63385c75 100755 --- a/snes/memory/memory.cpp +++ b/snes/memory/memory.cpp @@ -42,6 +42,10 @@ void MMIOAccess::write(unsigned addr, uint8 data) { mmio[addr & 0x7fff]->mmio_write(addr, data); } +MMIOAccess::MMIOAccess() { + for(unsigned i = 0; i < 0x8000; i++) mmio[i] = &memory::mmio_unmapped; +} + unsigned Bus::mirror(unsigned addr, unsigned size) { unsigned base = 0; if(size) { diff --git a/snes/memory/memory.hpp b/snes/memory/memory.hpp index c53dec94..f5fd6ee4 100755 --- a/snes/memory/memory.hpp +++ b/snes/memory/memory.hpp @@ -62,6 +62,7 @@ struct MMIOAccess : Memory { void map(unsigned addr, MMIO &access); uint8 read(unsigned addr); void write(unsigned addr, uint8 data); + MMIOAccess(); private: MMIO *mmio[0x8000]; diff --git a/snes/snes.hpp b/snes/snes.hpp index a5a03480..8b9a0ce5 100755 --- a/snes/snes.hpp +++ b/snes/snes.hpp @@ -1,7 +1,7 @@ namespace SNES { namespace Info { static const char Name[] = "bsnes"; - static const char Version[] = "067.25"; + static const char Version[] = "067.26"; static const unsigned SerializerVersion = 12; } } @@ -49,6 +49,7 @@ namespace SNES { typedef uint64_t uint64; typedef uint_t<2> uint2; + typedef uint_t<3> uint3; typedef uint_t<10> uint10; typedef uint_t<17> uint17; typedef uint_t<24> uint24; diff --git a/snes/system/serialization.cpp b/snes/system/serialization.cpp index bc1e52e7..4a673e65 100755 --- a/snes/system/serialization.cpp +++ b/snes/system/serialization.cpp @@ -4,13 +4,15 @@ serializer System::serialize() { serializer s(serialize_size); unsigned signature = 0x31545342, version = Info::SerializerVersion, crc32 = cartridge.crc32(); - char description[512]; + char profile[16], description[512]; + memset(&profile, 0, sizeof profile); memset(&description, 0, sizeof description); - strcpy(description, Info::Profile); + strlcpy(profile, Info::Profile, sizeof profile); s.integer(signature); s.integer(version); s.integer(crc32); + s.array(profile); s.array(description); serialize_all(s); @@ -19,17 +21,18 @@ serializer System::serialize() { bool System::unserialize(serializer &s) { unsigned signature, version, crc32; - char description[512]; + char profile[16], description[512]; s.integer(signature); s.integer(version); s.integer(crc32); + s.array(profile); s.array(description); if(signature != 0x31545342) return false; if(version != Info::SerializerVersion) return false; //if(crc32 != cartridge.crc32()) return false; - if(strcmp(description, Info::Profile)) return false; + if(strcmp(profile, Info::Profile)) return false; reset(); serialize_all(s); @@ -76,11 +79,12 @@ void System::serialize_init() { serializer s; unsigned signature = 0, version = 0, crc32 = 0; - char description[512]; + char profile[16], description[512]; s.integer(signature); s.integer(version); s.integer(crc32); + s.array(profile); s.array(description); serialize_all(s);