Compare commits

...

2 Commits
v029 ... v030

Author SHA1 Message Date
byuu
9e3827e2a2 Update to bsnes v030 release.
I didn't want to release a new version so soon, however there is a rather serious bug in bsnes v029 where the path information for the save RAM files is discarded when one has not selected a default save RAM / cheat path from the path settings tab in the configuration settings window. Because of this, it gets stored to the base directory. For Windows users, this is c:\, and for Linux users, this is /
This bug forced my hand, so I'm releasing v030 to correct this issue. I also cleaned up the S-DSP emulation code to be more consistent with my programming style -- it gets bit-perfect matches to v029's wave output, so I don't foresee there being any problems.
2008-03-24 12:43:32 +00:00
byuu
e499670ad9 Update to bsnes dsp release.
Okay, it's just blargg's. I hope he doesn't mind ...

I rewrote his S-DSP emulator in pure C++. Only took me seven hours,
not bad. anomie's took a few days.

Now, given, it's extremely similar of course. First, the algorithms
are going to be mostly the same regardless of who writes the code.
Second, I really didn't see a reason to waste too much time on this
reverse engineering a bunch of stuff myself, so I pretty much just
took the code and "rewrote" (read: copied) it in my unique style, and
changed a few things here and there. Code flow, variable names,
tables, exact algorithms, etc were blatant, direct copies.

Things I did change:
- counter rate 0 is now hardcoded to not ever hit zero
- counter read is now boolean instead of unsigned short
- a lot of multiplication was converted to shifts
- broke up the program into ~9 source files
- no more global functions anywhere, all in one class
- removed the hooks for things like external channel muting -- will
re-add if I ever add an option like that to bsnes
- modified VREG to not need the voice regs handle passed to it
- all voice functions take a reference instead of pointer to the voice
structs now
- packed 32-line timing table expanded to multi-line
- left everything in their own small chunk functions ... kind of torn
on whether I want to merge that with the main timing function. I like
the encapsulation, but it would remove the need to keep so many
struct-based state variables
- added a few more comments on parts that confused me at first
- removed assignment inside conditional stuff; even though I do that
myself on occasion in other code I write, heh
- yadda yadda, more minor stuff like that

Going to keep working at it -- wanted to get it working now, so that
finding regressions will be easier. I want to remove the double writes
for the ring buffer, make a decision on whether I want to rely on sign
extension, or use sclip<> for that, implement a compile-time option to
bypass libco (will save 2.048 million co_switch calls a second) since
the S-DSP's entire operation fits into a single switch table quite
easily, convert a lot of the mul / div stuff to shifts, convert those
clever split up branches in the envelope and BRR decoding routines to
switch / case tables, remove the shift tables from the BRR decoding,
and try and figure out what's going on with some of the code so that I
can try and document it :)

I'll see if I can contribute something back, too. Perhaps I can look
into what happens when you enable mute or something.

New WIP up which has the new core enabled by default. For those
without WIP access, I've posted the new source for reference. Comments
welcome.

    byuu.org/files/bsnes_dsp.zip


... man, feels weird posting a new topic.

[No archive available]
2008-03-22 15:37:00 +00:00
23 changed files with 1024 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
bsnes
Version: 0.029
Version: 0.030
Author: byuu
--------

View File

@@ -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 ###

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}

63
src/dsp/sdsp/brr.cpp Normal file
View File

@@ -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

52
src/dsp/sdsp/counter.cpp Normal file
View File

@@ -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

138
src/dsp/sdsp/echo.cpp Normal file
View File

@@ -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

62
src/dsp/sdsp/envelope.cpp Normal file
View File

@@ -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

54
src/dsp/sdsp/gaussian.cpp Normal file
View File

@@ -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

35
src/dsp/sdsp/misc.cpp Normal file
View File

@@ -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

272
src/dsp/sdsp/sdsp.cpp Normal file
View File

@@ -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() {
}

165
src/dsp/sdsp/sdsp.h Normal file
View File

@@ -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();
};

172
src/dsp/sdsp/voice.cpp Normal file
View File

@@ -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

View File

@@ -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"