diff --git a/readme.txt b/readme.txt index 5d74c4f5..552bddf8 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ bsnes -Version: 0.029 +Version: 0.030 Author: byuu -------- diff --git a/src/Makefile b/src/Makefile index cba8839e..852bbacc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -76,7 +76,7 @@ link += $(if $(findstring input.sdl,$(ruby)),`sdl-config --libs`) #################################### objects = main libco hiro ruby libfilter string reader cart cheat \ - memory smemory cpu scpu smp ssmp bdsp ppu bppu snes \ + memory smemory cpu scpu smp ssmp sdsp ppu bppu snes \ bsx srtc sdd1 cx4 dsp1 dsp2 dsp3 dsp4 obc1 st010 ifeq ($(enable_gzip),true) @@ -175,6 +175,7 @@ obj/ssmp.$(obj): smp/ssmp/ssmp.cpp smp/ssmp/* smp/ssmp/core/* smp/ssmp/memory/* obj/adsp.$(obj): dsp/adsp/adsp.cpp dsp/adsp/* obj/bdsp.$(obj): dsp/bdsp/bdsp.cpp dsp/bdsp/* +obj/sdsp.$(obj): dsp/sdsp/sdsp.cpp dsp/sdsp/* ########### ### ppu ### diff --git a/src/base.h b/src/base.h index 6966e20b..408fed04 100644 --- a/src/base.h +++ b/src/base.h @@ -1,10 +1,10 @@ -#define BSNES_VERSION "0.029" +#define BSNES_VERSION "0.030" #define BSNES_TITLE "bsnes v" BSNES_VERSION #define BUSCORE sBus #define CPUCORE sCPU #define SMPCORE sSMP -#define DSPCORE bDSP +#define DSPCORE sDSP #define PPUCORE bPPU //FAST_FRAMESKIP disables calculation of RTO during frameskip diff --git a/src/cart/cart_file.cpp b/src/cart/cart_file.cpp index 6f71ae4a..bb697e3b 100644 --- a/src/cart/cart_file.cpp +++ b/src/cart/cart_file.cpp @@ -52,10 +52,10 @@ char* Cartridge::get_base_filename(char *filename) { char* Cartridge::get_path_filename(char *filename, const char *path, const char *source, const char *extension) { strcpy(filename, source); for(char *p = filename; *p; p++) { if(*p == '\\') *p = '/'; } - modify_extension(filename, extension); + modify_extension(filename, extension); //override path with user-specified folder, if one was defined - if(path != "") { + if(*path) { lstring part; split(part, "/", filename); string fn = path; diff --git a/src/cc.bat b/src/cc.bat index 2efde1c3..1ce4b193 100644 --- a/src/cc.bat +++ b/src/cc.bat @@ -1,3 +1,3 @@ -@make platform=win compiler=mingw32-gcc -::@make platform=win compiler=mingw32-gcc enable_gzip=true enable_jma=true +::@make platform=win compiler=mingw32-gcc +@make platform=win compiler=mingw32-gcc enable_gzip=true enable_jma=true @pause diff --git a/src/dsp/adsp/adsp.cpp b/src/dsp/adsp/adsp.cpp index 5a3879d5..e29edb1f 100644 --- a/src/dsp/adsp/adsp.cpp +++ b/src/dsp/adsp/adsp.cpp @@ -580,7 +580,7 @@ int32 fir_samplel, fir_sampler; msampler = sclamp<16>(msampler); } - snes.audio_update(msamplel, msampler); + snes.audio.update(msamplel, msampler); scheduler.addclocks_dsp(32 * 3 * 8); } diff --git a/src/dsp/bdsp_official/bdsp.cpp b/src/dsp/bdsp/reference/bdsp.cpp similarity index 100% rename from src/dsp/bdsp_official/bdsp.cpp rename to src/dsp/bdsp/reference/bdsp.cpp diff --git a/src/dsp/bdsp_official/bdsp.h b/src/dsp/bdsp/reference/bdsp.h similarity index 100% rename from src/dsp/bdsp_official/bdsp.h rename to src/dsp/bdsp/reference/bdsp.h diff --git a/src/dsp/bdsp_official/readme.txt b/src/dsp/bdsp/reference/readme.txt similarity index 100% rename from src/dsp/bdsp_official/readme.txt rename to src/dsp/bdsp/reference/readme.txt diff --git a/src/dsp/bdsp_official/spc_dsp.cpp b/src/dsp/bdsp/reference/spc_dsp.cpp similarity index 100% rename from src/dsp/bdsp_official/spc_dsp.cpp rename to src/dsp/bdsp/reference/spc_dsp.cpp diff --git a/src/dsp/bdsp_official/spc_dsp.h b/src/dsp/bdsp/reference/spc_dsp.h similarity index 100% rename from src/dsp/bdsp_official/spc_dsp.h rename to src/dsp/bdsp/reference/spc_dsp.h diff --git a/src/dsp/bdsp_official/spc_dsp_timing.h b/src/dsp/bdsp/reference/spc_dsp_timing.h similarity index 100% rename from src/dsp/bdsp_official/spc_dsp_timing.h rename to src/dsp/bdsp/reference/spc_dsp_timing.h diff --git a/src/dsp/bdsp_official/spc_endian.h b/src/dsp/bdsp/reference/spc_endian.h similarity index 100% rename from src/dsp/bdsp_official/spc_endian.h rename to src/dsp/bdsp/reference/spc_endian.h diff --git a/src/dsp/sdsp/brr.cpp b/src/dsp/sdsp/brr.cpp new file mode 100644 index 00000000..b6ceab71 --- /dev/null +++ b/src/dsp/sdsp/brr.cpp @@ -0,0 +1,63 @@ +#ifdef SDSP_CPP + +void sDSP::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) + ram[(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); + + //write to next four samples in circular buffer + int *pos = &v.buf[v.buf_pos]; + v.buf_pos += 4; + if(v.buf_pos >= brr_buf_size) v.buf_pos = 0; + + //decode four samples + for(int *end = pos + 4; pos < end; pos++) { + int s = sclip<4>(nybbles >> 12); //extract upper nybble and sign extend + nybbles <<= 4; //advance nybble position + if(scale <= 12) { + s <<= scale; + s >>= 1; + } else { + s &= ~0x7ff; + } + + //apply IIR filter (2 is the most commonly used) + const int p1 = pos[brr_buf_size - 1]; + const int p2 = pos[brr_buf_size - 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 = sclip<16>(s << 1); + pos[brr_buf_size] = pos[0] = s; //second copy simplifies wrap-around + } +} + +#endif //ifdef SDSP_CPP diff --git a/src/dsp/sdsp/counter.cpp b/src/dsp/sdsp/counter.cpp new file mode 100644 index 00000000..97ac6fa2 --- /dev/null +++ b/src/dsp/sdsp/counter.cpp @@ -0,0 +1,52 @@ +#ifdef SDSP_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 sDSP::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 sDSP::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 sDSP::counter_tick() { + state.counter--; + if(state.counter < 0) state.counter = counter_range - 1; +} + +//return true if counter event should trigger + +inline bool sDSP::counter_poll(unsigned rate) { + if(rate == 0) return false; + return (((unsigned)state.counter + counter_offset[rate]) % counter_rate[rate]) == 0; +} + +#endif //ifdef SDSP_CPP diff --git a/src/dsp/sdsp/echo.cpp b/src/dsp/sdsp/echo.cpp new file mode 100644 index 00000000..cf08182e --- /dev/null +++ b/src/dsp/sdsp/echo.cpp @@ -0,0 +1,138 @@ +#ifdef SDSP_CPP + +//current echo buffer pointer for left/right channel +#define ECHO_PTR(ch) (&ram[state.t_echo_ptr + ch * 2]) + +//sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR(i) state.echo_hist_pos[i] + +//calculate FIR point for left/right channel +#define CALC_FIR(i, ch) ((ECHO_FIR(i + 1)[ch] * (int8)REG(fir + i * 0x10)) >> 6) + +void sDSP::echo_read(bool channel) { + uint8 *in = ECHO_PTR(channel); + int s = (int8)in[1] * 0x100 + in[0]; + //second copy simplifies wrap-around handling + ECHO_FIR(0)[channel] = ECHO_FIR(8)[channel] = s >> 1; +} + +int sDSP::echo_output(bool channel) { + int output = sclip<16>((state.t_main_out[channel] * (int8)REG(mvoll + channel * 0x10)) >> 7) + + sclip<16>((state.t_echo_in [channel] * (int8)REG(evoll + channel * 0x10)) >> 7); + return sclamp<16>(output); +} + +void sDSP::echo_write(bool channel) { + if(!(state.t_echo_enabled & 0x20)) { + uint8 *out = ECHO_PTR(channel); + int s = state.t_echo_out[channel]; + out[0] = (uint8)s; + out[1] = (uint8)(s >> 8); + } + + state.t_echo_out[channel] = 0; +} + +void sDSP::echo_22() { + //history + state.echo_hist_pos++; + if(state.echo_hist_pos >= &state.echo_hist[8]) state.echo_hist_pos = state.echo_hist; + + 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 sDSP::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 sDSP::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 sDSP::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 = sclip<16>(l); + r = sclip<16>(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 sDSP::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] + sclip<16>((state.t_echo_in[0] * (int8)REG(efb)) >> 7); + int r = state.t_echo_out[1] + sclip<16>((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 sDSP::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 + snes.audio.update(outl, outr); +} + +void sDSP::echo_28() { + state.t_echo_enabled = REG(flg); +} + +void sDSP::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_enabled = REG(flg); +} + +void sDSP::echo_30() { + //write right echo + echo_write(1); +} + +#endif //ifdef SDSP_CPP diff --git a/src/dsp/sdsp/envelope.cpp b/src/dsp/sdsp/envelope.cpp new file mode 100644 index 00000000..4508b19b --- /dev/null +++ b/src/dsp/sdsp/envelope.cpp @@ -0,0 +1,62 @@ +#ifdef SDSP_CPP + +void sDSP::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 //ifdef SDSP_CPP diff --git a/src/dsp/sdsp/gaussian.cpp b/src/dsp/sdsp/gaussian.cpp new file mode 100644 index 00000000..75f47da1 --- /dev/null +++ b/src/dsp/sdsp/gaussian.cpp @@ -0,0 +1,54 @@ +#ifdef SDSP_CPP + +const int16 sDSP::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 sDSP::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 + + const int* in = &v.buf[(v.interp_pos >> 12) + v.buf_pos]; + int output; + output = (fwd[ 0] * in[0]) >> 11; + output += (fwd[256] * in[1]) >> 11; + output += (rev[256] * in[2]) >> 11; + output = sclip<16>(output); + output += (rev[ 0] * in[3]) >> 11; + return sclamp<16>(output) & ~1; +} + +#endif //ifdef SDSP_CPP diff --git a/src/dsp/sdsp/misc.cpp b/src/dsp/sdsp/misc.cpp new file mode 100644 index 00000000..2201c376 --- /dev/null +++ b/src/dsp/sdsp/misc.cpp @@ -0,0 +1,35 @@ +#ifdef SDSP_CPP + +void sDSP::misc_27() { + state.t_pmon = REG(pmon) & ~1; //voice 0 doesn't support PMON +} + +void sDSP::misc_28() { + state.t_non = REG(non); + state.t_eon = REG(eon); + state.t_dir = REG(dir); +} + +void sDSP::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 sDSP::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 //ifdef SDSP_CPP diff --git a/src/dsp/sdsp/sdsp.cpp b/src/dsp/sdsp/sdsp.cpp new file mode 100644 index 00000000..7fed4b5e --- /dev/null +++ b/src/dsp/sdsp/sdsp.cpp @@ -0,0 +1,272 @@ +/* + S-DSP emulator + license: LGPLv2 + + Note: this is basically a C++ cothreaded implementation of Shay Green's (blargg's) S-DSP emulator. + The actual algorithms, timing information, tables, variable names, etc were all from him. +*/ + +#include "../../base.h" +#define SDSP_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 */ + +void sDSP::enter() { + #define tick() scheduler.addclocks_dsp(3 * 8) + + tick(); //temporary to sync with bDSP timing + + loop: + + // 0 + voice_5(voice[0]); + voice_2(voice[1]); + tick(); + + // 1 + voice_6(voice[0]); + voice_3(voice[1]); + tick(); + + // 2 + voice_7(voice[0]); + voice_4(voice[1]); + voice_1(voice[3]); + tick(); + + // 3 + voice_8(voice[0]); + voice_5(voice[1]); + voice_2(voice[2]); + tick(); + + // 4 + voice_9(voice[0]); + voice_6(voice[1]); + voice_3(voice[2]); + tick(); + + // 5 + voice_7(voice[1]); + voice_4(voice[2]); + voice_1(voice[4]); + tick(); + + // 6 + voice_8(voice[1]); + voice_5(voice[2]); + voice_2(voice[3]); + tick(); + + // 7 + voice_9(voice[1]); + voice_6(voice[2]); + voice_3(voice[3]); + tick(); + + // 8 + voice_7(voice[2]); + voice_4(voice[3]); + voice_1(voice[5]); + tick(); + + // 9 + voice_8(voice[2]); + voice_5(voice[3]); + voice_2(voice[4]); + tick(); + + //10 + voice_9(voice[2]); + voice_6(voice[3]); + voice_3(voice[4]); + tick(); + + //11 + voice_7(voice[3]); + voice_4(voice[4]); + voice_1(voice[6]); + tick(); + + //12 + voice_8(voice[3]); + voice_5(voice[4]); + voice_2(voice[5]); + tick(); + + //13 + voice_9(voice[3]); + voice_6(voice[4]); + voice_3(voice[5]); + tick(); + + //14 + voice_7(voice[4]); + voice_4(voice[5]); + voice_1(voice[7]); + tick(); + + //15 + voice_8(voice[4]); + voice_5(voice[5]); + voice_2(voice[6]); + tick(); + + //16 + voice_9(voice[4]); + voice_6(voice[5]); + voice_3(voice[6]); + tick(); + + //17 + voice_1(voice[0]); + voice_7(voice[5]); + voice_4(voice[6]); + tick(); + + //18 + voice_8(voice[5]); + voice_5(voice[6]); + voice_2(voice[7]); + tick(); + + //19 + voice_9(voice[5]); + voice_6(voice[6]); + voice_3(voice[7]); + tick(); + + //20 + voice_1(voice[1]); + voice_7(voice[6]); + voice_4(voice[7]); + tick(); + + //21 + voice_8(voice[6]); + voice_5(voice[7]); + voice_2(voice[0]); + tick(); + + //22 + voice_3a(voice[0]); + voice_9(voice[6]); + voice_6(voice[7]); + echo_22(); + tick(); + + //23 + voice_7(voice[7]); + echo_23(); + tick(); + + //24 + voice_8(voice[7]); + echo_24(); + tick(); + + //25 + voice_3b(voice[0]); + voice_9(voice[7]); + echo_25(); + tick(); + + //26 + echo_26(); + tick(); + + //27 + misc_27(); + echo_27(); + tick(); + + //28 + misc_28(); + echo_28(); + tick(); + + //29 + misc_29(); + echo_29(); + tick(); + + //30 + misc_30(); + voice_3c(voice[0]); + echo_30(); + tick(); + + //31 + voice_4(voice[0]); + voice_1(voice[2]); + tick(); + + goto loop; + + #undef tick +} + +/* register interface for S-SMP $00f2,$00f3 */ + +uint8 sDSP::read(uint8 addr) { + return state.regs[addr]; +} + +void sDSP::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; + } +} + +/* initialization */ + +void sDSP::power() { + ram = (uint8*)smp.get_spcram_handle(); //TODO: move to sMemory + memset(&state, 0, sizeof(state_t)); + + for(unsigned i = 0; i < 8; i++) { + memset(&voice[i], 0, sizeof(voice_t)); + voice[i].vbit = 1 << i; + voice[i].vidx = i * 0x10; + voice[i].brr_offset = 1; + } + + reset(); +} + +void sDSP::reset() { + REG(flg) = 0xe0; + + state.noise = 0x4000; + state.echo_hist_pos = state.echo_hist; + state.every_other_sample = 1; + state.echo_offset = 0; + state.counter = 0; +} + +sDSP::sDSP() { +} + +sDSP::~sDSP() { +} diff --git a/src/dsp/sdsp/sdsp.h b/src/dsp/sdsp/sdsp.h new file mode 100644 index 00000000..197a59f9 --- /dev/null +++ b/src/dsp/sdsp/sdsp.h @@ -0,0 +1,165 @@ +class sDSP : public DSP { +public: + void enter(); + + uint8 read(uint8 addr); + void write(uint8 addr, uint8 data); + + void power(); + void reset(); + + sDSP(); + ~sDSP(); + +private: + //external + uint8 *ram; + + //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 voice state + enum { brr_buf_size = 12 }; + enum { brr_block_size = 9 }; + + //global state + struct state_t { + uint8 regs[128]; + + //echo history keeps most recent 8 samples (twice the size to simplify wrap handling) + int echo_hist[8 * 2][2]; + int (*echo_hist_pos)[2]; //&echo_hist[0 to 7] + + 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_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]; + } state; + + //voice state + 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 + 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 + env_mode_t 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 + void echo_read(bool channel); + int echo_output(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(); +}; diff --git a/src/dsp/sdsp/voice.cpp b/src/dsp/sdsp/voice.cpp new file mode 100644 index 00000000..f1998889 --- /dev/null +++ b/src/dsp/sdsp/voice.cpp @@ -0,0 +1,172 @@ +#ifdef SDSP_CPP + +inline void sDSP::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 sDSP::voice_1(voice_t &v) { + state.t_dir_addr = (state.t_dir << 8) + (state.t_srcn << 2); + state.t_srcn = VREG(srcn); +} + +void sDSP::voice_2(voice_t &v) { + //read sample pointer (ignored if not needed) + const uint8 *entry = &ram[state.t_dir_addr]; + if(!v.kon_delay) entry += 2; + state.t_brr_next_addr = (entry[1] << 8) + entry[0]; + + state.t_adsr0 = VREG(adsr0); + + //read pitch, spread over two clocks + state.t_pitch = VREG(pitchl); +} + +void sDSP::voice_3(voice_t &v) { + voice_3a(v); + voice_3b(v); + voice_3c(v); +} + +void sDSP::voice_3a(voice_t &v) { + state.t_pitch += (VREG(pitchh) & 0x3f) << 8; +} + +void sDSP::voice_3b(voice_t &v) { + state.t_brr_byte = ram[(uint16)(v.brr_addr + v.brr_offset)]; + state.t_brr_header = ram[(uint16)(v.brr_addr)]; +} + +void sDSP::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 sDSP::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 sDSP::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 sDSP::voice_6(voice_t &v) { + state.outx_buf = state.t_output >> 8; +} + +void sDSP::voice_7(voice_t &v) { + //update ENDX + REG(endx) = (uint8)state.endx_buf; + state.envx_buf = v.t_envx_out; +} + +void sDSP::voice_8(voice_t &v) { + //update OUTX + VREG(outx) = (uint8)state.outx_buf; +} + +void sDSP::voice_9(voice_t &v) { + //update ENVX + VREG(envx) = (uint8)state.envx_buf; +} + +#endif //ifdef SDSP_CPP diff --git a/src/interface.h b/src/interface.h index e3c76a6c..b70bc306 100644 --- a/src/interface.h +++ b/src/interface.h @@ -14,7 +14,7 @@ #include "smp/ssmp/ssmp.h" #include "dsp/dsp.h" -#include "dsp/bdsp/bdsp.h" +#include "dsp/sdsp/sdsp.h" #include "ppu/ppu.h" #include "ppu/bppu/bppu.h"