Path: blob/main/misc/emulator/xnes/snes9x/apu/SPC_DSP.cpp
28798 views
// snes_spc 0.9.0. http://www.slack.net/~ant/12#include "SPC_DSP.h"34#include "blargg_endian.h"5#include <string.h>67/* Copyright (C) 2007 Shay Green. This module is free software; you8can redistribute it and/or modify it under the terms of the GNU Lesser9General Public License as published by the Free Software Foundation; either10version 2.1 of the License, or (at your option) any later version. This11module is distributed in the hope that it will be useful, but WITHOUT ANY12WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS13FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more14details. You should have received a copy of the GNU Lesser General Public15License along with this module; if not, write to the Free Software Foundation,16Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */1718#include "blargg_source.h"1920#ifdef BLARGG_ENABLE_OPTIMIZER21#include BLARGG_ENABLE_OPTIMIZER22#endif2324#if INT_MAX < 0x7FFFFFFF25#error "Requires that int type have at least 32 bits"26#endif2728// TODO: add to blargg_endian.h29#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr ))30#define GET_LE16A( addr ) GET_LE16( addr )31#define SET_LE16A( addr, data ) SET_LE16( addr, data )3233static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] =34{350x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,360x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,370xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,380x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,390xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,400x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x00,0x4E,0x7B,0xFF,410x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,420xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF43};4445// if ( io < -32768 ) io = -32768;46// if ( io > 32767 ) io = 32767;47#define CLAMP16( io )\48{\49if ( (int16_t) io != io )\50io = (io >> 31) ^ 0x7FFF;\51}5253// Access global DSP register54#define REG(n) m.regs [r_##n]5556// Access voice DSP register57#define VREG(r,n) r [v_##n]5859#define WRITE_SAMPLES( l, r, out ) \60{\61out [0] = l;\62out [1] = r;\63out += 2;\64if ( out >= m.out_end )\65{\66check( out == m.out_end );\67check( m.out_end != &m.extra [extra_size] || \68(m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\69out = m.extra;\70m.out_end = &m.extra [extra_size];\71}\72}\7374void SPC_DSP::set_output( sample_t* out, int size )75{76require( (size & 1) == 0 ); // must be even77if ( !out )78{79out = m.extra;80size = extra_size;81}82m.out_begin = out;83m.out = out;84m.out_end = out + size;85}8687// Volume registers and efb are signed! Easy to forget int8_t cast.88// Prefixes are to avoid accidental use of locals with same names.8990// Gaussian interpolation9192static short const gauss [512] =93{940, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,951, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,962, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,976, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,9811, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17,9918, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27,10028, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40,10141, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,10258, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77,10378, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102,104104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132,105134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168,106171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210,107212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257,108260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311,109314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370,110374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434,111439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504,112508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577,113582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654,114659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732,115737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811,116816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889,117894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965,118969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036,1191040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,1201106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160,1211164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210,1221213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251,1231253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280,1241282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298,1251299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,126};127128inline int SPC_DSP::interpolate( voice_t const* v )129{130// Make pointers into gaussian based on fractional position between samples131int offset = v->interp_pos >> 4 & 0xFF;132short const* fwd = gauss + 255 - offset;133short const* rev = gauss + offset; // mirror left half of gaussian134135int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];136int out;137out = (fwd [ 0] * in [0]) >> 11;138out += (fwd [256] * in [1]) >> 11;139out += (rev [256] * in [2]) >> 11;140out = (int16_t) out;141out += (rev [ 0] * in [3]) >> 11;142143CLAMP16( out );144out &= ~1;145return out;146}147148149//// Counters150151int const simple_counter_range = 2048 * 5 * 3; // 30720152153static unsigned const counter_rates [32] =154{155simple_counter_range + 1, // never fires1562048, 1536,1571280, 1024, 768,158640, 512, 384,159320, 256, 192,160160, 128, 96,16180, 64, 48,16240, 32, 24,16320, 16, 12,16410, 8, 6,1655, 4, 3,1662,1671168};169170static unsigned const counter_offsets [32] =171{1721, 0, 1040,173536, 0, 1040,174536, 0, 1040,175536, 0, 1040,176536, 0, 1040,177536, 0, 1040,178536, 0, 1040,179536, 0, 1040,180536, 0, 1040,181536, 0, 1040,1820,1830184};185186inline void SPC_DSP::init_counter()187{188m.counter = 0;189}190191inline void SPC_DSP::run_counters()192{193if ( --m.counter < 0 )194m.counter = simple_counter_range - 1;195}196197inline unsigned SPC_DSP::read_counter( int rate )198{199return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate];200}201202203//// Envelope204205inline void SPC_DSP::run_envelope( voice_t* const v )206{207int env = v->env;208if ( v->env_mode == env_release ) // 60%209{210if ( (env -= 0x8) < 0 )211env = 0;212v->env = env;213}214else215{216int rate;217int env_data = VREG(v->regs,adsr1);218if ( m.t_adsr0 & 0x80 ) // 99% ADSR219{220if ( v->env_mode >= env_decay ) // 99%221{222env--;223env -= env >> 8;224rate = env_data & 0x1F;225if ( v->env_mode == env_decay ) // 1%226rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10;227}228else // env_attack229{230rate = (m.t_adsr0 & 0x0F) * 2 + 1;231env += rate < 31 ? 0x20 : 0x400;232}233}234else // GAIN235{236int mode;237env_data = VREG(v->regs,gain);238mode = env_data >> 5;239if ( mode < 4 ) // direct240{241env = env_data * 0x10;242rate = 31;243}244else245{246rate = env_data & 0x1F;247if ( mode == 4 ) // 4: linear decrease248{249env -= 0x20;250}251else if ( mode < 6 ) // 5: exponential decrease252{253env--;254env -= env >> 8;255}256else // 6,7: linear increase257{258env += 0x20;259if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )260env += 0x8 - 0x20; // 7: two-slope linear increase261}262}263}264265// Sustain level266if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )267v->env_mode = env_sustain;268269v->hidden_env = env;270271// unsigned cast because linear decrease going negative also triggers this272if ( (unsigned) env > 0x7FF )273{274env = (env < 0 ? 0 : 0x7FF);275if ( v->env_mode == env_attack )276v->env_mode = env_decay;277}278279if ( !read_counter( rate ) )280v->env = env; // nothing else is controlled by the counter281}282}283284285//// BRR Decoding286287inline void SPC_DSP::decode_brr( voice_t* v )288{289// Arrange the four input nybbles in 0xABCD order for easy decoding290int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];291292int const header = m.t_brr_header;293294// Write to next four samples in circular buffer295int* pos = &v->buf [v->buf_pos];296int* end;297if ( (v->buf_pos += 4) >= brr_buf_size )298v->buf_pos = 0;299300// Decode four samples301for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 )302{303// Extract nybble and sign-extend304int s = (int16_t) nybbles >> 12;305306// Shift sample based on header307int const shift = header >> 4;308s = (s << shift) >> 1;309if ( shift >= 0xD ) // handle invalid range310s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0)311312// Apply IIR filter (8 is the most commonly used)313int const filter = header & 0x0C;314int const p1 = pos [brr_buf_size - 1];315int const p2 = pos [brr_buf_size - 2] >> 1;316if ( filter >= 8 )317{318s += p1;319s -= p2;320if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875321{322s += p2 >> 4;323s += (p1 * -3) >> 6;324}325else // s += p1 * 0.8984375 - p2 * 0.40625326{327s += (p1 * -13) >> 7;328s += (p2 * 3) >> 4;329}330}331else if ( filter ) // s += p1 * 0.46875332{333s += p1 >> 1;334s += (-p1) >> 5;335}336337// Adjust and write sample338CLAMP16( s );339s = (int16_t) (s * 2);340pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around341}342}343344345//// Misc346347#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n()348349MISC_CLOCK( 27 )350{351m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON352}353MISC_CLOCK( 28 )354{355m.t_non = REG(non);356m.t_eon = REG(eon);357m.t_dir = REG(dir);358}359MISC_CLOCK( 29 )360{361if ( (m.every_other_sample ^= 1) != 0 )362m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read363}364MISC_CLOCK( 30 )365{366if ( m.every_other_sample )367{368m.kon = m.new_kon;369m.t_koff = REG(koff) | m.mute_mask;370}371372run_counters();373374// Noise375if ( !read_counter( REG(flg) & 0x1F ) )376{377int feedback = (m.noise << 13) ^ (m.noise << 14);378m.noise = (feedback & 0x4000) ^ (m.noise >> 1);379}380}381382383//// Voices384385#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v )386387inline VOICE_CLOCK( V1 )388{389m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4;390m.t_srcn = VREG(v->regs,srcn);391}392inline VOICE_CLOCK( V2 )393{394// Read sample pointer (ignored if not needed)395uint8_t const* entry = &m.ram [m.t_dir_addr];396if ( !v->kon_delay )397entry += 2;398m.t_brr_next_addr = GET_LE16A( entry );399400m.t_adsr0 = VREG(v->regs,adsr0);401402// Read pitch, spread over two clocks403m.t_pitch = VREG(v->regs,pitchl);404}405inline VOICE_CLOCK( V3a )406{407m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8;408}409inline VOICE_CLOCK( V3b )410{411// Read BRR header and byte412m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF];413m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking414}415VOICE_CLOCK( V3c )416{417// Pitch modulation using previous voice's output418if ( m.t_pmon & v->vbit )419m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10;420421if ( v->kon_delay )422{423// Get ready to start BRR decoding on next sample424if ( v->kon_delay == 5 )425{426v->brr_addr = m.t_brr_next_addr;427v->brr_offset = 1;428v->buf_pos = 0;429m.t_brr_header = 0; // header is ignored on this sample430m.kon_check = true;431432if (take_spc_snapshot)433{434take_spc_snapshot = 0;435if (spc_snapshot_callback)436spc_snapshot_callback();437}438}439440// Envelope is never run during KON441v->env = 0;442v->hidden_env = 0;443444// Disable BRR decoding until last three samples445v->interp_pos = 0;446if ( --v->kon_delay & 3 )447v->interp_pos = 0x4000;448449// Pitch is never added during KON450m.t_pitch = 0;451}452453// Gaussian interpolation454{455int output = interpolate( v );456457// Noise458if ( m.t_non & v->vbit )459output = (int16_t) (m.noise * 2);460461// Apply envelope462m.t_output = (output * v->env) >> 11 & ~1;463v->t_envx_out = (uint8_t) (v->env >> 4);464}465466// Immediate silence due to end of sample or soft reset467if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 )468{469v->env_mode = env_release;470v->env = 0;471}472473if ( m.every_other_sample )474{475// KOFF476if ( m.t_koff & v->vbit )477v->env_mode = env_release;478479// KON480if ( m.kon & v->vbit )481{482v->kon_delay = 5;483v->env_mode = env_attack;484}485}486487// Run envelope for next sample488if ( !v->kon_delay )489run_envelope( v );490}491492inline void SPC_DSP::voice_output( voice_t const* v, int ch )493{494// Apply left/right volume495int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7;496amp *= ((stereo_switch & (1 << (v->voice_number + ch * voice_count))) ? 1 : 0);497498// Add to output total499m.t_main_out [ch] += amp;500CLAMP16( m.t_main_out [ch] );501502// Optionally add to echo total503if ( m.t_eon & v->vbit )504{505m.t_echo_out [ch] += amp;506CLAMP16( m.t_echo_out [ch] );507}508}509VOICE_CLOCK( V4 )510{511// Decode BRR512m.t_looped = 0;513if ( v->interp_pos >= 0x4000 )514{515decode_brr( v );516517if ( (v->brr_offset += 2) >= brr_block_size )518{519// Start decoding next BRR block520assert( v->brr_offset == brr_block_size );521v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;522if ( m.t_brr_header & 1 )523{524v->brr_addr = m.t_brr_next_addr;525m.t_looped = v->vbit;526}527v->brr_offset = 1;528}529}530531// Apply pitch532v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch;533534// Keep from getting too far ahead (when using pitch modulation)535if ( v->interp_pos > 0x7FFF )536v->interp_pos = 0x7FFF;537538// Output left539voice_output( v, 0 );540}541inline VOICE_CLOCK( V5 )542{543// Output right544voice_output( v, 1 );545546// ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier547int endx_buf = REG(endx) | m.t_looped;548549// Clear bit in ENDX if KON just began550if ( v->kon_delay == 5 )551endx_buf &= ~v->vbit;552m.endx_buf = (uint8_t) endx_buf;553}554inline VOICE_CLOCK( V6 )555{556(void) v; // avoid compiler warning about unused v557m.outx_buf = (uint8_t) (m.t_output >> 8);558}559inline VOICE_CLOCK( V7 )560{561// Update ENDX562REG(endx) = m.endx_buf;563564m.envx_buf = v->t_envx_out;565}566inline VOICE_CLOCK( V8 )567{568// Update OUTX569VREG(v->regs,outx) = m.outx_buf;570}571inline VOICE_CLOCK( V9 )572{573// Update ENVX574VREG(v->regs,envx) = m.envx_buf;575}576577// Most voices do all these in one clock, so make a handy composite578inline VOICE_CLOCK( V3 )579{580voice_V3a( v );581voice_V3b( v );582voice_V3c( v );583}584585// Common combinations of voice steps on different voices. This greatly reduces586// code size and allows everything to be inlined in these functions.587VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); }588VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); }589VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }590591592//// Echo593594// Current echo buffer pointer for left/right channel595#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2])596597// Sample in echo history buffer, where 0 is the oldest598#define ECHO_FIR( i ) (m.echo_hist_pos [i])599600// Calculate FIR point for left/right channel601#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6)602603#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n()604605inline void SPC_DSP::echo_read( int ch )606{607int s;608if ( m.t_echo_ptr >= 0xffc0 && rom_enabled )609s = GET_LE16SA( &hi_ram [m.t_echo_ptr + ch * 2 - 0xffc0] );610else611s = GET_LE16SA( ECHO_PTR( ch ) );612// second copy simplifies wrap-around handling613ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1;614}615616ECHO_CLOCK( 22 )617{618// History619if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] )620m.echo_hist_pos = m.echo_hist;621622m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF;623echo_read( 0 );624625// FIR (using l and r temporaries below helps compiler optimize)626int l = CALC_FIR( 0, 0 );627int r = CALC_FIR( 0, 1 );628629m.t_echo_in [0] = l;630m.t_echo_in [1] = r;631}632ECHO_CLOCK( 23 )633{634int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 );635int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 );636637m.t_echo_in [0] += l;638m.t_echo_in [1] += r;639640echo_read( 1 );641}642ECHO_CLOCK( 24 )643{644int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 );645int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 );646647m.t_echo_in [0] += l;648m.t_echo_in [1] += r;649}650ECHO_CLOCK( 25 )651{652int l = m.t_echo_in [0] + CALC_FIR( 6, 0 );653int r = m.t_echo_in [1] + CALC_FIR( 6, 1 );654655l = (int16_t) l;656r = (int16_t) r;657658l += (int16_t) CALC_FIR( 7, 0 );659r += (int16_t) CALC_FIR( 7, 1 );660661CLAMP16( l );662CLAMP16( r );663664m.t_echo_in [0] = l & ~1;665m.t_echo_in [1] = r & ~1;666}667inline int SPC_DSP::echo_output( int ch )668{669int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) +670(int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7);671CLAMP16( out );672return out;673}674ECHO_CLOCK( 26 )675{676// Left output volumes677// (save sample for next clock so we can output both together)678m.t_main_out [0] = echo_output( 0 );679680// Echo feedback681int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7);682int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7);683684CLAMP16( l );685CLAMP16( r );686687m.t_echo_out [0] = l & ~1;688m.t_echo_out [1] = r & ~1;689}690ECHO_CLOCK( 27 )691{692// Output693int l = m.t_main_out [0];694int r = echo_output( 1 );695m.t_main_out [0] = 0;696m.t_main_out [1] = 0;697698// TODO: global muting isn't this simple (turns DAC on and off699// or something, causing small ~37-sample pulse when first muted)700if ( REG(flg) & 0x40 )701{702l = 0;703r = 0;704}705706// Output sample to DAC707#ifdef SPC_DSP_OUT_HOOK708SPC_DSP_OUT_HOOK( l, r );709#else710sample_t* out = m.out;711WRITE_SAMPLES( l, r, out );712m.out = out;713#endif714}715ECHO_CLOCK( 28 )716{717m.t_echo_enabled = REG(flg);718}719inline void SPC_DSP::echo_write( int ch )720{721if ( !(m.t_echo_enabled & 0x20) )722{723if ( m.t_echo_ptr >= 0xffc0 && rom_enabled )724SET_LE16A( &hi_ram [m.t_echo_ptr + ch * 2 - 0xffc0], m.t_echo_out [ch] );725else726SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] );727}728729m.t_echo_out [ch] = 0;730}731ECHO_CLOCK( 29 )732{733m.t_esa = REG(esa);734735if ( !m.echo_offset )736m.echo_length = (REG(edl) & 0x0F) * 0x800;737738m.echo_offset += 4;739if ( m.echo_offset >= m.echo_length )740m.echo_offset = 0;741742// Write left echo743echo_write( 0 );744745m.t_echo_enabled = REG(flg);746}747ECHO_CLOCK( 30 )748{749// Write right echo750echo_write( 1 );751}752753754//// Timing755756// Execute clock for a particular voice757#define V( clock, voice ) voice_##clock( &m.voices [voice] );758759/* The most common sequence of clocks uses composite operations760for efficiency. For example, the following are equivalent to the761individual steps on the right:762763V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5)764V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4)765V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */766767// Voice 0 1 2 3 4 5 6 7768#define GEN_DSP_TIMING \769PHASE( 0) V(V5,0)V(V2,1)\770PHASE( 1) V(V6,0)V(V3,1)\771PHASE( 2) V(V7_V4_V1,0)\772PHASE( 3) V(V8_V5_V2,0)\773PHASE( 4) V(V9_V6_V3,0)\774PHASE( 5) V(V7_V4_V1,1)\775PHASE( 6) V(V8_V5_V2,1)\776PHASE( 7) V(V9_V6_V3,1)\777PHASE( 8) V(V7_V4_V1,2)\778PHASE( 9) V(V8_V5_V2,2)\779PHASE(10) V(V9_V6_V3,2)\780PHASE(11) V(V7_V4_V1,3)\781PHASE(12) V(V8_V5_V2,3)\782PHASE(13) V(V9_V6_V3,3)\783PHASE(14) V(V7_V4_V1,4)\784PHASE(15) V(V8_V5_V2,4)\785PHASE(16) V(V9_V6_V3,4)\786PHASE(17) V(V1,0) V(V7,5)V(V4,6)\787PHASE(18) V(V8_V5_V2,5)\788PHASE(19) V(V9_V6_V3,5)\789PHASE(20) V(V1,1) V(V7,6)V(V4,7)\790PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\791PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\792PHASE(23) V(V7,7) echo_23();\793PHASE(24) V(V8,7) echo_24();\794PHASE(25) V(V3b,0) V(V9,7) echo_25();\795PHASE(26) echo_26();\796PHASE(27) misc_27(); echo_27();\797PHASE(28) misc_28(); echo_28();\798PHASE(29) misc_29(); echo_29();\799PHASE(30) misc_30();V(V3c,0) echo_30();\800PHASE(31) V(V4,0) V(V1,2)\801802#if !SPC_DSP_CUSTOM_RUN803804void SPC_DSP::run( int clocks_remain )805{806require( clocks_remain > 0 );807808int const phase = m.phase;809m.phase = (phase + clocks_remain) & 31;810switch ( phase )811{812loop:813814#define PHASE( n ) if ( n && !--clocks_remain ) break; case n:815GEN_DSP_TIMING816#undef PHASE817818if ( --clocks_remain )819goto loop;820}821}822823#endif824825826//// Setup827828void SPC_DSP::init( void* ram_64k )829{830m.ram = (uint8_t*) ram_64k;831mute_voices( 0 );832disable_surround( false );833set_output( 0, 0 );834reset();835836stereo_switch = 0xffff;837take_spc_snapshot = 0;838spc_snapshot_callback = 0;839840#ifndef NDEBUG841// be sure this sign-extends842assert( (int16_t) 0x8000 == -0x8000 );843844// be sure right shift preserves sign845assert( (-1 >> 1) == -1 );846847// check clamp macro848int i;849i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );850i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );851852blargg_verify_byte_order();853#endif854}855856void SPC_DSP::soft_reset_common()857{858require( m.ram ); // init() must have been called already859860m.noise = 0x4000;861m.echo_hist_pos = m.echo_hist;862m.every_other_sample = 1;863m.echo_offset = 0;864m.phase = 0;865866init_counter();867868for (int i = 0; i < voice_count; i++)869m.voices[i].voice_number = i;870}871872void SPC_DSP::soft_reset()873{874REG(flg) = 0xE0;875soft_reset_common();876}877878void SPC_DSP::load( uint8_t const regs [register_count] )879{880memcpy( m.regs, regs, sizeof m.regs );881memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count );882883// Internal state884for ( int i = voice_count; --i >= 0; )885{886voice_t* v = &m.voices [i];887v->brr_offset = 1;888v->vbit = 1 << i;889v->regs = &m.regs [i * 0x10];890}891m.new_kon = REG(kon);892m.t_dir = REG(dir);893m.t_esa = REG(esa);894895soft_reset_common();896}897898void SPC_DSP::reset() { load( initial_regs ); }899900901//// State save/load902903#if !SPC_NO_COPY_STATE_FUNCS904905void SPC_State_Copier::copy( void* state, size_t size )906{907func( buf, state, size );908}909910int SPC_State_Copier::copy_int( int state, int size )911{912BOOST::uint8_t s [2];913SET_LE16( s, state );914func( buf, &s, size );915return GET_LE16( s );916}917918void SPC_State_Copier::skip( int count )919{920if ( count > 0 )921{922char temp [64];923memset( temp, 0, sizeof temp );924do925{926int n = sizeof temp;927if ( n > count )928n = count;929count -= n;930func( buf, temp, n );931}932while ( count );933}934}935936void SPC_State_Copier::extra()937{938int n = 0;939SPC_State_Copier& copier = *this;940SPC_COPY( uint8_t, n );941skip( n );942}943944void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy )945{946SPC_State_Copier copier( io, copy );947948// DSP registers949copier.copy( m.regs, register_count );950951// Internal state952953// Voices954int i;955for ( i = 0; i < voice_count; i++ )956{957voice_t* v = &m.voices [i];958959// BRR buffer960int i;961for ( i = 0; i < brr_buf_size; i++ )962{963int s = v->buf [i];964SPC_COPY( int16_t, s );965v->buf [i] = v->buf [i + brr_buf_size] = s;966}967968SPC_COPY( uint16_t, v->interp_pos );969SPC_COPY( uint16_t, v->brr_addr );970SPC_COPY( uint16_t, v->env );971SPC_COPY( int16_t, v->hidden_env );972SPC_COPY( uint8_t, v->buf_pos );973SPC_COPY( uint8_t, v->brr_offset );974SPC_COPY( uint8_t, v->kon_delay );975{976int m = v->env_mode;977SPC_COPY( uint8_t, m );978v->env_mode = (enum env_mode_t) m;979}980SPC_COPY( uint8_t, v->t_envx_out );981982copier.extra();983}984985// Echo history986for ( i = 0; i < echo_hist_size; i++ )987{988int j;989for ( j = 0; j < 2; j++ )990{991int s = m.echo_hist_pos [i] [j];992SPC_COPY( int16_t, s );993m.echo_hist [i] [j] = s; // write back at offset 0994}995}996m.echo_hist_pos = m.echo_hist;997memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] );998999// Misc1000SPC_COPY( uint8_t, m.every_other_sample );1001SPC_COPY( uint8_t, m.kon );10021003SPC_COPY( uint16_t, m.noise );1004SPC_COPY( uint16_t, m.counter );1005SPC_COPY( uint16_t, m.echo_offset );1006SPC_COPY( uint16_t, m.echo_length );1007SPC_COPY( uint8_t, m.phase );10081009SPC_COPY( uint8_t, m.new_kon );1010SPC_COPY( uint8_t, m.endx_buf );1011SPC_COPY( uint8_t, m.envx_buf );1012SPC_COPY( uint8_t, m.outx_buf );10131014SPC_COPY( uint8_t, m.t_pmon );1015SPC_COPY( uint8_t, m.t_non );1016SPC_COPY( uint8_t, m.t_eon );1017SPC_COPY( uint8_t, m.t_dir );1018SPC_COPY( uint8_t, m.t_koff );10191020SPC_COPY( uint16_t, m.t_brr_next_addr );1021SPC_COPY( uint8_t, m.t_adsr0 );1022SPC_COPY( uint8_t, m.t_brr_header );1023SPC_COPY( uint8_t, m.t_brr_byte );1024SPC_COPY( uint8_t, m.t_srcn );1025SPC_COPY( uint8_t, m.t_esa );1026SPC_COPY( uint8_t, m.t_echo_enabled );10271028SPC_COPY( int16_t, m.t_main_out [0] );1029SPC_COPY( int16_t, m.t_main_out [1] );1030SPC_COPY( int16_t, m.t_echo_out [0] );1031SPC_COPY( int16_t, m.t_echo_out [1] );1032SPC_COPY( int16_t, m.t_echo_in [0] );1033SPC_COPY( int16_t, m.t_echo_in [1] );10341035SPC_COPY( uint16_t, m.t_dir_addr );1036SPC_COPY( uint16_t, m.t_pitch );1037SPC_COPY( int16_t, m.t_output );1038SPC_COPY( uint16_t, m.t_echo_ptr );1039SPC_COPY( uint8_t, m.t_looped );10401041copier.extra();1042}1043#endif104410451046//// Snes9x Accessor10471048void SPC_DSP::set_spc_snapshot_callback( void (*callback) (void) )1049{1050spc_snapshot_callback = callback;1051}10521053void SPC_DSP::dump_spc_snapshot( void )1054{1055take_spc_snapshot = 1;1056}10571058void SPC_DSP::set_stereo_switch( int value )1059{1060stereo_switch = value;1061}10621063SPC_DSP::uint8_t SPC_DSP::reg_value( int ch, int addr )1064{1065return m.voices[ch].regs[addr];1066}10671068int SPC_DSP::envx_value( int ch )1069{1070return m.voices[ch].env;1071}107210731074