diff options
Diffstat (limited to 'src/devices/sound/swp00.cpp')
-rw-r--r-- | src/devices/sound/swp00.cpp | 1834 |
1 files changed, 1789 insertions, 45 deletions
diff --git a/src/devices/sound/swp00.cpp b/src/devices/sound/swp00.cpp index eb0d7c59107..f04161b22b3 100644 --- a/src/devices/sound/swp00.cpp +++ b/src/devices/sound/swp00.cpp @@ -6,12 +6,72 @@ #include "emu.h" #include "swp00.h" +/* + + Used in the MU50, the SWP00 is the combination of a rompler called + AWM2 (Advanced Wave Memory 2) and an effects DSP called MEG + (Multiple Effects Generator). It is the simpler variant of those, a + simplification and integration of the SWP20/SWD/MEG/EQ combo use in + the MU80. + + Its clock is 33.9MHz and the output is at 44100Hz stereo (768 cycles + per sample pair) per dac output. + + + AWM2: + + The AWM2 is in charge of handling the individual channels. It + manages reading the rom, decoding the samples, applying volume and + envelopes and lfos and filtering the result. The channels are + volume-modulated and summed into 7 outputs which are then processed + by the MEG. + + As all the SWPs, the sound data can be four formats (8 bits, 12 + bits, 16 bits, and a 8-bits log format with roughly 10 bits of + dynamic). It's interesting to note that the 8-bits format is not + used by the MU50. The rom bus is 24 bits address and 8 bits data + wide. It applies a single, Chamberlin-configuration LPF to the + sample data. Envelopes are handled semi-automatically, and the + final result volume-modulated (global volume, pan, tremolo, dispatch + in dry/reverb/chorus/variation) in 7 output channels. + + + MEG: + + The MEG in this case is an internal DSP with a fixed program in four + selectable variants. It has 192 steps of program, and can issue a + memory access to the effects DRAM every 3 cycles. The programs are + internal and as far as we know not dumpable. We managed a + reimplementation though. + + The program does the effects "reverb", "chorus" and "variation" and + mixing between all those. The four variants only in practice impact + the variation segment, in addresses 109-191 roughly. + + Each instruction is associated with a dynamically changeable 10-bit + constant used as a fixed point value (either 1.9 or 3.7 depending on + the instruction). Every third instruction (pc multiple of 3) is + also associated with a 16-bits offset for the potential memory + access. + + + Interface: + + The interface is 8-bits wide but would have wanted to be 16-bits, with + 11 address bits. There are three address formats depending on the + part of the chip one speaks to: + 000 0sss ssss Global controls + 001 1ppp pppl MEG, offsets (16-bits values, l=high/low byte, pc 00-bd, divided by 3) + 01p pppp pppl MEG, constants (16-bits values, l=high/low byte, pc 00-bf) + sss sscc cccs AWM2, channel/slot combination (slot = 8-b and 20-37) +*/ + DEFINE_DEVICE_TYPE(SWP00, swp00_device, "swp00", "Yamaha SWP00 (TC170C120SF / XQ036A00) sound chip") swp00_device::swp00_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, SWP00, tag, owner, clock), device_sound_interface(mconfig, *this), - device_rom_interface(mconfig, *this, 25+2, ENDIANNESS_LITTLE, 32) + device_rom_interface(mconfig, *this) { } @@ -19,84 +79,1768 @@ void swp00_device::device_add_mconfig(machine_config &config) { } +const std::array<u32, 4> swp00_device::lfo_shape_centered_saw = { 0x00000000, 0x00000000, 0xfff00000, 0xfff00000 }; // --////-- +const std::array<u32, 4> swp00_device::lfo_shape_centered_tri = { 0x00000000, 0x0007ffff, 0xfff7ffff, 0xfff00000 }; // --/\/\-- +const std::array<u32, 4> swp00_device::lfo_shape_offset_saw = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; // __////__ +const std::array<u32, 4> swp00_device::lfo_shape_offset_tri = { 0x00000000, 0x00000000, 0x000fffff, 0x000fffff }; // __/\/\__ + +const std::array<s32, 16> swp00_device::panmap = { + 0x000, 0x040, 0x080, 0x0c0, + 0x100, 0x140, 0x180, 0x1c0, + 0x200, 0x240, 0x280, 0x2c0, + 0x300, 0x340, 0x380, 0xfff +}; + +const std::array<u8, 4> swp00_device::dpcm_offset = { 7, 6, 4, 0 }; + +bool swp00_device::istep(s32 &value, s32 limit, s32 step) +{ + // fprintf(stderr, "istep(%x, %x, %x)\n", value, limit, step); + if(value < limit) { + value += step; + if(value >= limit) { + value = limit; + return true; + } + return false; + } + + if(value > limit) { + value -= step; + if(value <= limit) { + value = limit; + return true; + } + return false; + } + + return true; +} + +s32 swp00_device::fpadd(s32 value, s32 step) +{ + s32 e = value >> 24; + s32 m = value & 0xffffff; + + m += step << e; + if(m & 0xfe000000) + return 0xfffffff; + + while(m & 0x01000000) { + m <<= 1; + e ++; + } + if(e >= 16) + return 0xfffffff; + return (e << 24) | (m & 0xffffff); +} + +s32 swp00_device::fpsub(s32 value, s32 step) +{ + s32 e = value >> 24; + s32 m = (value & 0xffffff) | 0xfe000000; + m = e < 0xc ? m - (step << e) : (m >> (e - 0xb)) - (step << 0xb); + if(m >= 0) + return 0; + if(e >= 0xc) + e = 0xb; + while(m < 0xfe000000) { + if(!e) + return 0; + e --; + m >>= 1; + } + while(e != 0xf && (m >= 0xff000000)) { + e ++; + m <<= 1; + } + + return (e << 24) | (m & 0xffffff); +} + +bool swp00_device::fpstep(s32 &value, s32 limit, s32 step) +{ + // value, limit and step are 4.24 but step has its exponent and + // top four bits zero + + if(value == limit) + return true; + if(value < limit) { + value = fpadd(value, step); + if(value >= limit) { + value = limit; + return true; + } + return false; + } + + value = fpsub(value, step); + if(value <= limit) { + value = limit; + return true; + } + return false; +} + +// sample is signed 24.8 +s32 swp00_device::fpapply(s32 value, s32 sample) +{ + if(value >= 0x10000000) + return 0; + return (s64(sample) - ((s64(sample) * ((value >> 9) & 0x7fff)) >> 16)) >> (value >> 24); +} + +// sample is signed 24.8 +s32 swp00_device::lpffpapply(s32 value, s32 sample) +{ + return ((((value >> 7) & 0x7fff) | 0x8000) * s64(sample)) >> (31 - (value >> 22)); +} + +// Some tables we need. Maybe they're in roms inside the chip, +// maybe they're logic. Probably slightly inexact too, would need +// a complicated hardware setup to really test them. + +const std::array<s32, 0x80> swp00_device::attack_linear_step = { + 0x00027, 0x0002b, 0x0002f, 0x00033, 0x00037, 0x0003d, 0x00042, 0x00048, + 0x0004d, 0x00056, 0x0005e, 0x00066, 0x0006f, 0x0007a, 0x00085, 0x00090, + 0x0009b, 0x000ac, 0x000bd, 0x000cc, 0x000de, 0x000f4, 0x00109, 0x00120, + 0x00135, 0x00158, 0x00179, 0x00199, 0x001bc, 0x001e7, 0x00214, 0x00240, + 0x0026b, 0x002af, 0x002f2, 0x00332, 0x00377, 0x003d0, 0x0042c, 0x00480, + 0x004dc, 0x0055e, 0x005e9, 0x0066e, 0x006f4, 0x007a4, 0x00857, 0x0090b, + 0x009c3, 0x00acb, 0x00bd6, 0x00ce6, 0x00e00, 0x00f5e, 0x010d2, 0x01234, + 0x0139e, 0x015d0, 0x017f3, 0x01a20, 0x01c4a, 0x01f52, 0x02232, 0x0250f, + 0x027ff, 0x02c72, 0x03109, 0x0338b, 0x039c4, 0x04038, 0x04648, 0x04c84, + 0x05262, 0x05c1c, 0x065af, 0x06f5c, 0x07895, 0x0866f, 0x09470, 0x0a19e, + 0x0ae4c, 0x0c566, 0x0db8d, 0x0f00f, 0x10625, 0x12937, 0x14954, 0x16c17, + 0x1886e, 0x1c71c, 0x20000, 0x239e1, 0x2647c, 0x2aaab, 0x2ecfc, 0x3241f, + 0x35e51, 0x3a83b, 0x40000, 0x4325c, 0x47dc1, 0x4c8f9, 0x50505, 0x55555, + 0x58160, 0x5d174, 0x60606, 0x62b2e, 0x67b24, 0x6a63c, 0x6d3a0, 0x6eb3e, + 0x71c72, 0x73616, 0x75075, 0x76b98, 0x78788, 0x78788, 0x7a44c, 0x7a44c, + 0x7a44c, 0x7a44c, 0x7a44c, 0x7a44c, 0x7a44c, 0x7a44c, 0x7a44c, 0x7a44c, +}; + +const std::array<s32, 0x20> swp00_device::decay_linear_step = { + 0x15083, 0x17ad2, 0x1a41a, 0x1cbe7, 0x1f16d, 0x22ef1, 0x26a44, 0x2a1e4, + 0x2da35, 0x34034, 0x3a197, 0x40000, 0x45b82, 0x4b809, 0x51833, 0x57262, + 0x5d9f7, 0x6483f, 0x6b15c, 0x71c72, 0x77976, 0x7d119, 0x83127, 0x88889, + 0x8d3dd, 0x939a8, 0x991f2, 0x9d89e, 0xa0a0a, 0xa57eb, 0xa72f0, 0xac769, +}; + void swp00_device::device_start() { m_stream = stream_alloc(0, 2, 44100); + + save_item(NAME(m_waverom_access)); + save_item(NAME(m_waverom_val)); + save_item(NAME(m_meg_control)); + + save_item(NAME(m_buffer_offset)); + save_item(NAME(m_rev_vol)); + save_item(NAME(m_cho_vol)); + save_item(NAME(m_var_vol)); + + save_item(NAME(m_var_lfo_phase)); + save_item(NAME(m_var_lfo_h_1)); + save_item(NAME(m_var_lfo_h_2)); + save_item(NAME(m_var_lfo1a)); + save_item(NAME(m_var_lfo2a)); + save_item(NAME(m_var_lfo3a)); + save_item(NAME(m_var_lfo4a)); + + save_item(NAME(m_var_filter_1)); + save_item(NAME(m_var_filter_2)); + save_item(NAME(m_var_filter_3)); + + save_item(NAME(m_var_filter2_1)); + save_item(NAME(m_var_filter2_2a)); + save_item(NAME(m_var_filter2_2b)); + save_item(NAME(m_var_filter2_3a)); + save_item(NAME(m_var_filter2_3b)); + save_item(NAME(m_var_filter2_4)); + + save_item(NAME(m_var_filterp_l_1)); + save_item(NAME(m_var_filterp_l_2)); + save_item(NAME(m_var_filterp_l_3)); + save_item(NAME(m_var_filterp_l_4)); + save_item(NAME(m_var_filterp_l_5)); + save_item(NAME(m_var_filterp_l_6)); + save_item(NAME(m_var_filterp_r_1)); + save_item(NAME(m_var_filterp_r_2)); + save_item(NAME(m_var_filterp_r_3)); + save_item(NAME(m_var_filterp_r_4)); + save_item(NAME(m_var_filterp_r_5)); + save_item(NAME(m_var_filterp_r_6)); + + save_item(NAME(m_var_filter3_1)); + save_item(NAME(m_var_filter3_2)); + + save_item(NAME(m_var_h1)); + save_item(NAME(m_var_h2)); + save_item(NAME(m_var_h3)); + save_item(NAME(m_var_h4)); + + save_item(NAME(m_cho_lfo_phase)); + save_item(NAME(m_cho_filter_l_1)); + save_item(NAME(m_cho_filter_l_2)); + save_item(NAME(m_cho_filter_l_3)); + save_item(NAME(m_cho_filter_r_1)); + save_item(NAME(m_cho_filter_r_2)); + save_item(NAME(m_cho_filter_r_3)); + + save_item(NAME(m_rev_filter_1)); + save_item(NAME(m_rev_filter_2)); + save_item(NAME(m_rev_filter_3)); + save_item(NAME(m_rev_hist_a)); + save_item(NAME(m_rev_hist_b)); + save_item(NAME(m_rev_hist_c)); + save_item(NAME(m_rev_hist_d)); + + save_item(NAME(m_rev_buffer)); + save_item(NAME(m_cho_buffer)); + save_item(NAME(m_var_buffer)); + save_item(NAME(m_offset)); + save_item(NAME(m_const)); + save_item(NAME(m_lpf_info)); + save_item(NAME(m_lpf_speed)); + save_item(NAME(m_lfo_famod_depth)); + save_item(NAME(m_rev_level)); + save_item(NAME(m_dry_level)); + save_item(NAME(m_cho_level)); + save_item(NAME(m_var_level)); + save_item(NAME(m_glo_level)); + save_item(NAME(m_panning)); + save_item(NAME(m_attack_speed)); + save_item(NAME(m_attack_level)); + save_item(NAME(m_decay_speed)); + save_item(NAME(m_decay_level)); + save_item(NAME(m_pitch)); + save_item(NAME(m_sample_start)); + save_item(NAME(m_sample_end)); + save_item(NAME(m_sample_dpcm_and_format)); + save_item(NAME(m_sample_address)); + save_item(NAME(m_lfo_step)); + save_item(NAME(m_lfo_pmod_depth)); + + save_item(NAME(m_lfo_phase)); + save_item(NAME(m_sample_pos)); + save_item(NAME(m_envelope_level)); + + save_item(NAME(m_glo_level_cur)); + save_item(NAME(m_pan_l)); + save_item(NAME(m_pan_r)); + + save_item(NAME(m_lpf_feedback)); + save_item(NAME(m_lpf_target_value)); + save_item(NAME(m_lpf_value)); + save_item(NAME(m_lpf_timer)); + save_item(NAME(m_lpf_ha)); + save_item(NAME(m_lpf_hb)); + + save_item(NAME(m_active)); + save_item(NAME(m_decay)); + save_item(NAME(m_decay_done)); + save_item(NAME(m_lpf_done)); + + save_item(NAME(m_dpcm_current)); + save_item(NAME(m_dpcm_next)); + save_item(NAME(m_dpcm_address)); + save_item(NAME(m_dpcm_sum)); + + for(int i=0; i != 128; i++) { + u32 v = 0; + switch(i >> 3) { + default: v = ((i & 7) + 8) << (1 + (i >> 3)); break; + case 0xb: v = ((i & 7) + 4) << 13; break; + case 0xc: v = ((i & 6) + 6) << 14; break; + case 0xd: v = ((i & 4) + 7) << 15; break; + case 0xe: v = 15 << 15; break; + case 0xf: v = 31 << 15; break; + } + m_global_step[i] = v; + } + + // Delta-packed samples decompression. + + for(int i=0; i<128; i++) { + s16 base = ((i & 0x1f) << (3+(i >> 5))) + (((1 << (i >> 5))-1) << 8); + m_dpcm[i | 0x80] = - base; + m_dpcm[i] = + base; + } } void swp00_device::device_reset() { + m_waverom_access = 0; + m_waverom_val = 0; + m_meg_control = 0; + + m_buffer_offset = 0; + m_rev_vol = 0; + m_cho_vol = 0; + m_var_vol = 0; + + m_var_lfo_phase = 0; + m_var_lfo_h_1 = 0; + m_var_lfo_h_2 = 0; + m_var_lfo1a = 0; + m_var_lfo2a = 0; + m_var_lfo3a = 0; + m_var_lfo4a = 0; + m_var_filter_1 = 0; + m_var_filter_2 = 0; + m_var_filter_3 = 0; + m_var_filter2_1 = 0; + m_var_filter2_2a = 0; + m_var_filter2_2b = 0; + m_var_filter2_3a = 0; + m_var_filter2_3b = 0; + m_var_filter2_4 = 0; + m_var_filter3_1 = 0; + m_var_filter3_2 = 0; + m_var_filterp_l_1 = 0; + m_var_filterp_l_2 = 0; + m_var_filterp_l_3 = 0; + m_var_filterp_l_4 = 0; + m_var_filterp_l_5 = 0; + m_var_filterp_l_6 = 0; + m_var_filterp_r_1 = 0; + m_var_filterp_r_2 = 0; + m_var_filterp_r_3 = 0; + m_var_filterp_r_4 = 0; + m_var_filterp_r_5 = 0; + m_var_filterp_r_6 = 0; + + m_var_h1 = 0; + m_var_h2 = 0; + m_var_h3 = 0; + m_var_h4 = 0; + + m_cho_lfo_phase = 0; + m_cho_filter_l_1 = 0; + m_cho_filter_l_2 = 0; + m_cho_filter_l_3 = 0; + m_cho_filter_r_1 = 0; + m_cho_filter_r_2 = 0; + m_cho_filter_r_3 = 0; + + m_rev_filter_1 = 0; + m_rev_filter_2 = 0; + m_rev_filter_3 = 0; + m_rev_hist_a = 0; + m_rev_hist_b = 0; + m_rev_hist_c = 0; + m_rev_hist_d = 0; + + std::fill(m_rev_buffer.begin(), m_rev_buffer.end(), 0); + std::fill(m_cho_buffer.begin(), m_cho_buffer.end(), 0); + std::fill(m_var_buffer.begin(), m_var_buffer.end(), 0); + std::fill(m_offset.begin(), m_offset.end(), 0); + std::fill(m_const.begin(), m_const.end(), 0); + std::fill(m_lpf_info.begin(), m_lpf_info.end(), 0); + std::fill(m_lpf_speed.begin(), m_lpf_speed.end(), 0); + std::fill(m_lfo_famod_depth.begin(), m_lfo_famod_depth.end(), 0); + std::fill(m_rev_level.begin(), m_rev_level.end(), 0); + std::fill(m_dry_level.begin(), m_dry_level.end(), 0); + std::fill(m_cho_level.begin(), m_cho_level.end(), 0); + std::fill(m_var_level.begin(), m_var_level.end(), 0); + std::fill(m_glo_level.begin(), m_glo_level.end(), 0); + std::fill(m_panning.begin(), m_panning.end(), 0); + std::fill(m_attack_speed.begin(), m_attack_speed.end(), 0); + std::fill(m_attack_level.begin(), m_attack_level.end(), 0); + std::fill(m_decay_speed.begin(), m_decay_speed.end(), 0); + std::fill(m_decay_level.begin(), m_decay_level.end(), 0); + std::fill(m_pitch.begin(), m_pitch.end(), 0); + std::fill(m_sample_start.begin(), m_sample_start.end(), 0); + std::fill(m_sample_end.begin(), m_sample_end.end(), 0); + std::fill(m_sample_dpcm_and_format.begin(), m_sample_dpcm_and_format.end(), 0); + std::fill(m_sample_address.begin(), m_sample_address.end(), 0); + std::fill(m_lfo_step.begin(), m_lfo_step.end(), 0); + std::fill(m_lfo_pmod_depth.begin(), m_lfo_pmod_depth.end(), 0); + + std::fill(m_lfo_phase.begin(), m_lfo_phase.end(), 0); + std::fill(m_sample_pos.begin(), m_sample_pos.end(), 0); + std::fill(m_envelope_level.begin(), m_envelope_level.end(), 0); + + std::fill(m_glo_level_cur.begin(), m_glo_level_cur.end(), 0); + std::fill(m_pan_l.begin(), m_pan_l.end(), 0); + std::fill(m_pan_r.begin(), m_pan_r.end(), 0); + + std::fill(m_lpf_feedback.begin(), m_lpf_feedback.end(), 0); + std::fill(m_lpf_target_value.begin(), m_lpf_target_value.end(), 0); + std::fill(m_lpf_value.begin(), m_lpf_value.end(), 0); + std::fill(m_lpf_timer.begin(), m_lpf_timer.end(), 0); + std::fill(m_lpf_ha.begin(), m_lpf_ha.end(), 0); + std::fill(m_lpf_hb.begin(), m_lpf_hb.end(), 0); + + std::fill(m_active.begin(), m_active.end(), false); + std::fill(m_decay.begin(), m_decay.end(), false); + std::fill(m_decay_done.begin(), m_decay_done.end(), false); + std::fill(m_lpf_done.begin(), m_lpf_done.end(), false); + + std::fill(m_dpcm_current.begin(), m_dpcm_current.end(), false); + std::fill(m_dpcm_next.begin(), m_dpcm_next.end(), false); + std::fill(m_dpcm_address.begin(), m_dpcm_address.end(), false); + std::fill(m_dpcm_sum.begin(), m_dpcm_sum.end(), 0); } -void swp00_device::rom_bank_updated() +void swp00_device::rom_bank_pre_change() { m_stream->update(); } void swp00_device::map(address_map &map) { - map(0x0000, 0x7ff).rw(FUNC(swp00_device::snd_r), FUNC(swp00_device::snd_w)); + map(0x000, 0x7ff).rw(FUNC(swp00_device::snd_r), FUNC(swp00_device::snd_w)); + + // 00-01: control + + rchan(map, 0x08).w(FUNC(swp00_device::slot8_w)); // always 80 + rchan(map, 0x09).w(FUNC(swp00_device::slot9_w)); // always 00 + rchan(map, 0x0a).rw(FUNC(swp00_device::sample_start_r<1>), FUNC(swp00_device::sample_start_w<1>)); + rchan(map, 0x0b).rw(FUNC(swp00_device::sample_start_r<0>), FUNC(swp00_device::sample_start_w<0>)); + + // 0c-0f: meg offsets + // 10-1b: meg values + + rchan(map, 0x20).rw(FUNC(swp00_device::lpf_info_r<1>), FUNC(swp00_device::lpf_info_w<1>)); + rchan(map, 0x21).rw(FUNC(swp00_device::lpf_info_r<0>), FUNC(swp00_device::lpf_info_w<0>)); + rchan(map, 0x22).rw(FUNC(swp00_device::lpf_speed_r), FUNC(swp00_device::lpf_speed_w)); + rchan(map, 0x23).rw(FUNC(swp00_device::lfo_famod_depth_r), FUNC(swp00_device::lfo_famod_depth_w)); + rchan(map, 0x24).rw(FUNC(swp00_device::lfo_step_r), FUNC(swp00_device::lfo_step_w)); + rchan(map, 0x25).rw(FUNC(swp00_device::lfo_pmod_depth_r), FUNC(swp00_device::lfo_pmod_depth_w)); + rchan(map, 0x26).rw(FUNC(swp00_device::attack_speed_r), FUNC(swp00_device::attack_speed_w)); + rchan(map, 0x27).rw(FUNC(swp00_device::attack_level_r), FUNC(swp00_device::attack_level_w)); + rchan(map, 0x28).rw(FUNC(swp00_device::decay_speed_r), FUNC(swp00_device::decay_speed_w)); + rchan(map, 0x29).rw(FUNC(swp00_device::decay_level_r), FUNC(swp00_device::decay_level_w)); + rchan(map, 0x2a).rw(FUNC(swp00_device::rev_level_r), FUNC(swp00_device::rev_level_w)); + rchan(map, 0x2b).rw(FUNC(swp00_device::dry_level_r), FUNC(swp00_device::dry_level_w)); + rchan(map, 0x2c).rw(FUNC(swp00_device::cho_level_r), FUNC(swp00_device::cho_level_w)); + rchan(map, 0x2d).rw(FUNC(swp00_device::var_level_r), FUNC(swp00_device::var_level_w)); + rchan(map, 0x2e).rw(FUNC(swp00_device::glo_level_r), FUNC(swp00_device::glo_level_w)); + rchan(map, 0x2f).rw(FUNC(swp00_device::panning_r), FUNC(swp00_device::panning_w)); + rchan(map, 0x30).rw(FUNC(swp00_device::sample_dpcm_and_format_r), FUNC(swp00_device::sample_dpcm_and_format_w)); + rchan(map, 0x31).rw(FUNC(swp00_device::sample_address_r<2>), FUNC(swp00_device::sample_address_w<2>)); + rchan(map, 0x32).rw(FUNC(swp00_device::sample_address_r<1>), FUNC(swp00_device::sample_address_w<1>)); + rchan(map, 0x33).rw(FUNC(swp00_device::sample_address_r<0>), FUNC(swp00_device::sample_address_w<0>)); + rchan(map, 0x34).rw(FUNC(swp00_device::pitch_r<1>), FUNC(swp00_device::pitch_w<1>)); + rchan(map, 0x35).rw(FUNC(swp00_device::pitch_r<0>), FUNC(swp00_device::pitch_w<0>)); + rchan(map, 0x36).rw(FUNC(swp00_device::sample_end_r<1>), FUNC(swp00_device::sample_end_w<1>)); + rchan(map, 0x37).rw(FUNC(swp00_device::sample_end_r<0>), FUNC(swp00_device::sample_end_w<0>)); + + rctrl(map, 0x00); // 01 at startup + rctrl(map, 0x01).rw(FUNC(swp00_device::state_r), FUNC(swp00_device::state_adr_w)); + rctrl(map, 0x02).rw(FUNC(swp00_device::waverom_access_r), FUNC(swp00_device::waverom_access_w)); + rctrl(map, 0x03).r(FUNC(swp00_device::waverom_val_r)); + rctrl(map, 0x04).rw(FUNC(swp00_device::meg_control_r), FUNC(swp00_device::meg_control_w)); + rctrl(map, 0x08).w(FUNC(swp00_device::keyon_w<3>)); + rctrl(map, 0x09).w(FUNC(swp00_device::keyon_w<2>)); + rctrl(map, 0x0a).w(FUNC(swp00_device::keyon_w<1>)); + rctrl(map, 0x0b).w(FUNC(swp00_device::keyon_w<0>)); + rctrl(map, 0x0c); // 00 at startup + rctrl(map, 0x0d); // 00 at startup + rctrl(map, 0x0e); // 00 at startup + + map(0x180, 0x1ff).rw(FUNC(swp00_device::offset_r), FUNC(swp00_device::offset_w)); + map(0x200, 0x37f).rw(FUNC(swp00_device::const_r), FUNC(swp00_device::const_w)); } -static u8 rr[0x20*0x20]; +// Voice control -u8 swp00_device::snd_r(offs_t offset) +void swp00_device::slot8_w(offs_t offset, u8 data) { - if(0) { - int chan = (offset >> 6) & 0x3f; - int slot = offset & 0x3f; - std::string preg = "-"; - if(slot >= 0x21 && slot <= 0x2b && (slot & 1)) - preg = util::string_format("fp%03x", (slot-0x21)/2 + 6*chan); - else if(slot == 0x30 || slot == 0x31) - preg = util::string_format("dt%02x", (slot-0x30) + 2*chan); - else if(slot == 0x0e || slot == 0x0f) - preg = util::string_format("ct%02x", (slot-0x0e) + 2*chan); - else - preg = util::string_format("%02x.%02x", chan, slot); - logerror("snd_r [%04x %04x] %-5s, %04x\n", offset, offset*2, preg, rr[offset]); - } - if(0 && offset == 0x080f) - machine().debug_break(); - if(offset == 0x080f) - return 0; - return rr[offset]; + if(data == 0x80) + return; + logerror("slot8[%02x] = %02x\n", offset >> 1, data); } -void swp00_device::snd_w(offs_t offset, u8 data) +void swp00_device::slot9_w(offs_t offset, u8 data) +{ + if(data == 0x00) + return; + logerror("slot9[%02x] = %02x\n", offset >> 1, data); +} + +template<int sel> void swp00_device::lpf_info_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + u16 old = m_lpf_info[chan]; + m_stream->update(); + + m_lpf_info[chan] = (m_lpf_info[chan] & ~(0xff << (8*sel))) | (data << (8*sel)); + if(m_lpf_info[chan] == old) + return; + + // if(!sel) + // logerror("lpf_info[%02x] = %04x\n", chan, m_lpf_info[chan]); + + u32 fb = m_lpf_info[chan] >> 11; + u32 level = m_lpf_info[chan] & 0x7ff; + if(fb < 4 && level > 0x7c0) + level = 0x7c0; + if(level) + level |= 0x800; + m_lpf_feedback[chan] = (fb + 4) << 21; + m_lpf_target_value[chan] = level << 14; +} + +template<int sel> u8 swp00_device::lpf_info_r(offs_t offset) +{ + int chan = offset >> 1; + return m_lpf_info[chan] >> (8*sel); +} + +void swp00_device::lpf_speed_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_lpf_speed[chan] == data) + return; + m_stream->update(); + m_lpf_speed[chan] = data; + // logerror("lpf_speed[%02x] = %02x\n", chan, m_lpf_speed[chan]); +} + +u8 swp00_device::lpf_speed_r(offs_t offset) { - if(rr[offset] == data) + int chan = offset >> 1; + return m_lpf_speed[chan]; +} + +void swp00_device::lfo_famod_depth_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_lfo_famod_depth[chan] == data) return; + m_stream->update(); + m_lfo_famod_depth[chan] = data; + // logerror("lfo_famod_depth[%02x] = %02x\n", chan, m_lfo_famod_depth[chan]); +} + +u8 swp00_device::lfo_famod_depth_r(offs_t offset) +{ + int chan = offset >> 1; + return m_lfo_famod_depth[chan]; +} + +void swp00_device::rev_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_rev_level[chan] == data) + return; + m_stream->update(); + m_rev_level[chan] = data; + // logerror("rev_level[%02x] = %02x\n", chan, m_rev_level[chan]); +} + +u8 swp00_device::rev_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_rev_level[chan]; +} + +void swp00_device::dry_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_dry_level[chan] == data) + return; + m_stream->update(); + m_dry_level[chan] = data; + // logerror("dry_level[%02x] = %02x\n", chan, m_dry_level[chan]); +} + +u8 swp00_device::dry_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_dry_level[chan]; +} + +void swp00_device::cho_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_cho_level[chan] == data) + return; + m_stream->update(); + m_cho_level[chan] = data; + // logerror("cho_level[%02x] = %02x\n", chan, m_cho_level[chan]); +} + +u8 swp00_device::cho_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_cho_level[chan]; +} + +void swp00_device::var_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_var_level[chan] == data) + return; + m_stream->update(); + m_var_level[chan] = data; + // logerror("var_level[%02x] = %02x\n", chan, m_var_level[chan]); +} + +u8 swp00_device::var_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_var_level[chan]; +} + +void swp00_device::glo_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_glo_level[chan] == data) + return; + m_glo_level[chan] = data; + // logerror("glo_level[%02x] = %02x\n", chan, m_glo_level[chan]); +} + +u8 swp00_device::glo_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_glo_level[chan]; +} + +void swp00_device::panning_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_panning[chan] == data) + return; + m_stream->update(); + m_panning[chan] = data; + // logerror("panning[%02x] = %02x\n", chan, m_panning[chan]); +} + +u8 swp00_device::panning_r(offs_t offset) +{ + int chan = offset >> 1; + return m_panning[chan]; +} + +void swp00_device::attack_speed_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_attack_speed[chan] == data) + return; + m_stream->update(); + m_attack_speed[chan] = data; + logerror("attack_speed[%02x] = %02x\n", chan, m_attack_speed[chan]); +} + +u8 swp00_device::attack_speed_r(offs_t offset) +{ + int chan = offset >> 1; + return m_attack_speed[chan]; +} + +void swp00_device::attack_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_attack_level[chan] == data) + return; + m_stream->update(); + m_attack_level[chan] = data; + logerror("attack_level[%02x] = %02x\n", chan, m_attack_level[chan]); +} + +u8 swp00_device::attack_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_attack_level[chan]; +} + +void swp00_device::decay_speed_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_decay_speed[chan] == data) + return; + + m_stream->update(); + m_decay_speed[chan] = data; + + if(data & 0x80) + m_decay[chan] = true; + + logerror("decay_speed[%02x] = %02x\n", chan, m_decay_speed[chan]); +} + +u8 swp00_device::decay_speed_r(offs_t offset) +{ + int chan = offset >> 1; + return m_decay_speed[chan]; +} + +void swp00_device::decay_level_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_decay_level[chan] == data) + return; + m_stream->update(); + m_decay_level[chan] = data; + logerror("decay_level[%02x] = %02x\n", chan, m_decay_level[chan]); +} + +u8 swp00_device::decay_level_r(offs_t offset) +{ + int chan = offset >> 1; + return m_decay_level[chan]; +} + +template<int sel> void swp00_device::pitch_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + u16 old = m_pitch[chan]; + m_stream->update(); + m_pitch[chan] = (m_pitch[chan] & ~(0xff << (8*sel))) | (data << (8*sel)); + if(m_pitch[chan] == old) + return; + // if(!sel) + // logerror("pitch[%02x] = %04x\n", chan, m_pitch[chan]); +} + +template<int sel> u8 swp00_device::pitch_r(offs_t offset) +{ + int chan = offset >> 1; + return m_pitch[chan] >> (8*sel); +} + +template<int sel> void swp00_device::sample_start_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + m_stream->update(); + + m_sample_start[chan] = (m_sample_start[chan] & ~(0xff << (8*sel))) | (data << (8*sel)); + // if(!sel) + // logerror("sample_start[%02x] = %04x\n", chan, m_sample_start[chan]); +} + +template<int sel> u8 swp00_device::sample_start_r(offs_t offset) +{ + int chan = offset >> 1; + return m_sample_start[chan] >> (8*sel); +} + +template<int sel> void swp00_device::sample_end_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + m_stream->update(); + + m_sample_end[chan] = (m_sample_end[chan] & ~(0xff << (8*sel))) | (data << (8*sel)); + // if(!sel) + // logerror("sample_end[%02x] = %04x\n", chan, m_sample_end[chan]); +} + +template<int sel> u8 swp00_device::sample_end_r(offs_t offset) +{ + int chan = offset >> 1; + return m_sample_end[chan] >> (8*sel); +} + +void swp00_device::sample_dpcm_and_format_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + m_stream->update(); + + m_sample_dpcm_and_format[chan] = data; + // logerror("sample_dpcm_and_format[%02x] = %02x\n", chan, m_sample_dpcm_and_format[chan]); +} + +u8 swp00_device::sample_dpcm_and_format_r(offs_t offset) +{ + int chan = offset >> 1; + return m_sample_dpcm_and_format[chan]; +} + +template<int sel> void swp00_device::sample_address_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + m_stream->update(); + + m_sample_address[chan] = (m_sample_address[chan] & ~(0xff << (8*sel))) | (data << (8*sel)); + // if(!sel) + // logerror("sample_address[%02x] = %04x\n", chan, m_sample_address[chan]); +} + +template<int sel> u8 swp00_device::sample_address_r(offs_t offset) +{ + int chan = offset >> 1; + return m_sample_address[chan] >> (8*sel); +} + +void swp00_device::lfo_step_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_lfo_step[chan] == data) + return; + m_stream->update(); + + m_lfo_step[chan] = data; + // logerror("lfo_step[%02x] = %02x\n", chan, m_lfo_step[chan]); +} + +u8 swp00_device::lfo_step_r(offs_t offset) +{ + int chan = offset >> 1; + return m_lfo_step[chan]; +} + +void swp00_device::lfo_pmod_depth_w(offs_t offset, u8 data) +{ + int chan = offset >> 1; + if(m_lfo_pmod_depth[chan] == data) + return; + m_stream->update(); + + m_lfo_pmod_depth[chan] = data; + // logerror("lfo_pmod_depth[%02x] = %02x\n", chan, m_lfo_pmod_depth[chan]); +} + +u8 swp00_device::lfo_pmod_depth_r(offs_t offset) +{ + int chan = offset >> 1; + return m_lfo_pmod_depth[chan]; +} + +void swp00_device::keyon(int chan) +{ + m_stream->update(); + logerror("keyon %02x a=%02x/%02x d=%02x/%02x glo=%02x pan=%02x [%x %x %x %x]\n", chan, m_attack_speed[chan], m_attack_level[chan], m_decay_speed[chan], m_decay_level[chan], m_glo_level[chan], m_panning[chan], m_sample_start[chan], m_sample_end[chan], m_sample_address[chan], m_sample_dpcm_and_format[chan]); + m_lfo_phase[chan] = 0; + m_sample_pos[chan] = -m_sample_start[chan] << 15; + + m_active[chan] = true; + m_decay[chan] = false; + m_decay_done[chan] = false; - rr[offset] = data; + m_dpcm_current[chan] = 0; + m_dpcm_next[chan] = 0; + m_dpcm_address[chan] = m_sample_address[chan] - m_sample_start[chan]; + m_dpcm_sum[chan] = 0; - int chan = (offset >> 6) & 0x1f; - int slot = offset & 0x3f; + m_lpf_value[chan] = m_lpf_target_value[chan]; + m_lpf_timer[chan] = 0x4000000; + m_lpf_ha[chan] = 0; + m_lpf_hb[chan] = 0; - std::string preg = "-"; -#if 0 - if(slot >= 0x21 && slot <= 0x2b && (slot & 1)) - preg = util::string_format("fp%03x", (slot-0x21)/2 + 6*chan); - else if(slot == 0x0e || slot == 0x0f) - preg = util::string_format("sy%02x", (slot-0x0e) + 2*chan); - else if(slot == 0x30 || slot == 0x31) - preg = util::string_format("dt%02x", (slot-0x30) + 2*chan); - else if(slot == 0x38) - preg = util::string_format("vl%02x", chan); - else if(slot == 0x3e || slot == 0x3f) - preg = util::string_format("lf%02x", (slot-0x3e) + 2*chan); + m_glo_level_cur[chan] = m_glo_level[chan] << 4; + m_pan_l[chan] = panmap[m_panning[chan] >> 4]; + m_pan_r[chan] = panmap[m_panning[chan] & 15]; + + if(m_decay_speed[chan] & 0x80) { + m_envelope_level[chan] = 0; + m_decay[chan] = true; + } else if((m_attack_speed[chan] & 0x80) || m_attack_level[chan]) + m_envelope_level[chan] = m_attack_level[chan] << 20; + else + m_envelope_level[chan] = 0x8000000; +} + +template<int sel> void swp00_device::keyon_w(u8 data) +{ + for(int i=0; i < 8; i++) + if(BIT(data, i)) + keyon(8*sel+i); +} + +void swp00_device::offset_w(offs_t offset, u8 data) +{ + m_stream->update(); + + if(offset & 1) + m_offset[offset >> 1] = (m_offset[offset >> 1] & 0xff00) | data; + else + m_offset[offset >> 1] = (m_offset[offset >> 1] & 0x00ff) | (data << 8); + if(0) + if(offset & 1) + logerror("offset[%02x] = %04x\n", 3*(offset >> 1), m_offset[offset >> 1]); +} + +u8 swp00_device::offset_r(offs_t offset) +{ + if(offset & 1) + return m_offset[offset >> 1]; + else + return m_offset[offset >> 1] >> 8; +} + +void swp00_device::const_w(offs_t offset, u8 data) +{ + m_stream->update(); + + if(offset & 1) + m_const[offset >> 1] = (m_const[offset >> 1] & 0xff00) | data; else -#endif - preg = util::string_format("%02x.%02x", chan, slot); + m_const[offset >> 1] = (m_const[offset >> 1] & 0x00ff) | (data << 8); + if(0) + if(offset & 1) + logerror("const[%02x] = %04x\n", offset >> 1, m_const[offset >> 1]); +} + +u8 swp00_device::const_r(offs_t offset) +{ + if(offset & 1) + return m_const[offset >> 1]; + else + return m_const[offset >> 1] >> 8; +} + +void swp00_device::waverom_access_w(u8 data) +{ + m_waverom_access = data; +} + +u8 swp00_device::waverom_access_r() +{ + return 0x00; // non-zero = busy reading the rom +} + +u8 swp00_device::waverom_val_r() +{ + u8 val = read_byte(m_sample_address[0x1f]); + logerror("waverom read adr=%08x -> %02x\n", m_sample_address[0x1f], val); + m_sample_address[0x1f] = (m_sample_address[0x1f] + 1) & 0xffffff; + return val; +} - logerror("snd_w [%04x %04x] %-5s, %02x\n", offset, offset*2, preg, data); +void swp00_device::meg_control_w(u8 data) +{ + m_meg_control = data; + logerror("meg_control %02x (variation %x, %s)\n", m_meg_control, m_meg_control >> 6, m_meg_control & 2 ? "mute" : "on"); +} + +u8 swp00_device::meg_control_r() +{ + return m_meg_control; +} + +// Counters state access +u8 swp00_device::state_r() +{ + m_stream->update(); + + int chan = m_state_adr & 0x1f; + switch(m_state_adr & 0xe0) { + case 0x00: // lpf value + return (m_lpf_value[chan] >> 20) | (m_lpf_done[chan] ? 0x80 : 0x00); + + case 0x40: { // Envelope state + if(!m_active[chan]) + return 0xff; + + u8 vol; + if(m_decay[chan] || m_attack_level[chan] || (m_attack_speed[chan] & 0x80)) + vol = m_envelope_level[chan] >> 22; + else + vol = 0; + + if(m_decay_done[chan]) + vol |= 0x40; + if(m_decay[chan]) + vol |= 0x80; + + return vol; + } + + case 0x60: // global level + return (m_glo_level_cur[chan] >> 6) | ((m_glo_level_cur[chan] == (m_glo_level[chan] << 4)) ? 0x80 : 0x00); + + case 0x80: // panning l + return (m_pan_l[chan] >> 6) | ((m_pan_l[chan] == panmap[m_panning[chan] >> 4]) ? 0x80 : 0x00); + + case 0xa0: // panning r + return (m_pan_r[chan] >> 6) | ((m_pan_r[chan] == panmap[m_panning[chan] & 15]) ? 0x80 : 0x00); + } + + logerror("state %02x unsupported\n"); + return 0; +} + +void swp00_device::state_adr_w(u8 data) +{ + m_state_adr = data; +} + + +// Catch-all + +u8 swp00_device::snd_r(offs_t offset) +{ + logerror("snd_r [%03x]\n", offset); + return 0; +} + +void swp00_device::snd_w(offs_t offset, u8 data) +{ + logerror("snd_w [%03x] %02x\n", offset, data); } // Synthesis -void swp00_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +s32 swp00_device::rext(int reg) const +{ + s32 val = m_const[reg] & 0x3ff; + if(val > 0x200) // Not 100% a real 2-complement fixed-point, e.g. the max value is positive, not negative + val |= 0xfffffc00; + return val; +} + +s32 swp00_device::m7v(s32 value, s32 mult) +{ + return (s64(value) * mult) >> 7; +} + +s32 swp00_device::m7(s32 value, int reg) const +{ + return m7v(value, rext(reg)); +} + +s32 swp00_device::m9v(s32 value, s32 mult) +{ + return (s64(value) * mult) >> 9; +} + +s32 swp00_device::m9(s32 value, int reg) const +{ + return m9v(value, rext(reg)); +} + +template<size_t size> swp00_device::delay_block<size>::delay_block(swp00_device *swp, std::array<s32, size> &buffer) : + m_swp(swp), + m_buffer(buffer) +{ +} + +template<size_t size> s32 swp00_device::delay_block<size>::r(int offreg) const +{ + return m_buffer[(m_swp->m_buffer_offset + m_swp->m_offset[offreg/3]) & (size - 1)]; +} + +template<size_t size> void swp00_device::delay_block<size>::w(int offreg, s32 value) const +{ + m_buffer[(m_swp->m_buffer_offset + m_swp->m_offset[offreg/3]) & (size - 1)] = value; +} + +template<size_t size> s32 swp00_device::delay_block<size>::rlfo(int offreg, u32 phase, s32 delta_phase, int levelreg) const +{ + // Phase is on 23 bits + // Delta phase is on 10 bits shifts for a maximum of a full period (e.g. left shift of 13) + // Phase is wrapped into a triangle on 22 bits + // Level register is 10 bits where 1 = 4 samples of offset, for a maximum of 4096 samples + + u32 lfo_phase = lfo_wrap(phase, delta_phase); + + // Offset is 12.22 + u64 lfo_offset = lfo_phase * m_swp->rext(levelreg); + u32 lfo_i_offset = lfo_offset >> 22; + s32 lfo_i_frac = lfo_offset & 0x3fffff; + + // Uses in reality offreg and offreg+3 (which are offset by 1) + u32 pos = m_swp->m_buffer_offset + m_swp->m_offset[offreg/3] + lfo_i_offset; + s32 val0 = m_buffer[pos & (size - 1)]; + s32 val1 = m_buffer[(pos + 1) & (size - 1)]; + + // fprintf(stderr, "lfo %02x %x %x\n", offreg, val0, val1); + return s32((val1 * s64(lfo_i_frac) + val0 * s64(0x400000 - lfo_i_frac)) >> 22); +} + +template<size_t size> s32 swp00_device::delay_block<size>::rlfo2(int offreg, s32 offset) const +{ + // Offset is 12.11 + u32 lfo_i_offset = offset >> 11; + s32 lfo_i_frac = offset & 0x7ff; + + // Uses in reality offreg and offreg+3 (which are offset by 1) + u32 pos = m_swp->m_buffer_offset + m_swp->m_offset[offreg/3] + lfo_i_offset; + s32 val0 = m_buffer[pos & (size - 1)]; + s32 val1 = m_buffer[(pos + 1) & (size - 1)]; + + // fprintf(stderr, "lfo %02x %x %x\n", offreg, val0, val1); + return s32((val1 * s64(lfo_i_frac) + val0 * s64(0x800 - lfo_i_frac)) >> 11); +} + +s32 swp00_device::lfo_get_step(int reg) const +{ + u32 e = (m_const[reg] >> 7) & 7; + return (m_const[reg] & 0x7f) << (e == 7 ? 15 : e); +} + +void swp00_device::lfo_step(u32 &phase, int reg) const +{ + phase = (phase + lfo_get_step(reg)) & 0x7fffff; +} + +s32 swp00_device::lfo_saturate(s32 phase) +{ + if(phase < -0x400000) + return -0x400000; + if(phase >= 0x400000) + return 0x3fffff; + return phase; +} + +u32 swp00_device::lfo_wrap(s32 phase, s32 delta_phase) +{ + s32 lfo_phase = (phase - (delta_phase << 13)) & 0x7fffff; + if(lfo_phase & 0x400000) + lfo_phase ^= 0x7fffff; + return lfo_phase; +} + +void swp00_device::filtered_lfo_step(s32 &position, s32 phase, int deltareg, int postdeltareg, int scalereg, int feedbackreg) +{ + s32 phase1 = lfo_saturate((deltareg == -1 ? phase : lfo_wrap(phase, deltareg)) - (rext(postdeltareg) << 13)); + s64 phase2 = s64(lfo_get_step(scalereg)) * phase1 + s64(0x400000 - lfo_get_step(feedbackreg)) * position; + position = phase2 >> 22; +} + +s32 swp00_device::alfo(u32 phase, s32 delta_phase, int levelreg, int offsetreg, bool sub) const +{ + u32 lfo_phase = lfo_wrap(phase, delta_phase); + s32 offset = rext(offsetreg); + if(sub) + offset = -offset; + s32 base = s32((s64(lfo_phase) * rext(levelreg)) >> 19) + (offset << 3); + s32 bamp = ((base & 0x1ff) | 0x200) << ((base >> 9) & 15); + bamp >>= 8; + if(bamp <= -0x200) + bamp = -0x1ff; + else if(bamp >= 0x200) + bamp = 0x200; + return bamp; +} + +s32 swp00_device::lfo_mod(s32 phase, int scalereg) const +{ + return (m9(phase, scalereg) >> 13) + 0x200; +} + +s32 swp00_device::lfo_scale(s32 phase, int scalereg) const +{ + return lfo_saturate((phase - (rext(scalereg) << 13)) * 4); +} + +s32 swp00_device::lfo_wrap_reg(s32 phase, int deltareg) const +{ + return lfo_wrap(phase, rext(deltareg)); +} + +s32 swp00_device::sx(int reg) const +{ + s32 mult = m_const[reg]; + if(mult & 0x200) + mult |= 0xfffffc00; + return mult; +} + +double swp00_device::sx7(int reg) const +{ + return sx(reg) / 128.0; +} + +double swp00_device::sx9(int reg) const { + return sx(reg) / 512.0; +} + +s32 swp00_device::saturate(s32 value) +{ + if(value <= -0x20000) + return -0x20000; + else if(value > 0x1ffff) + return 0x1ffff; + else + return value; +} + +double v2f2(s32 value) +{ + return (1.0 - (value & 0xffffff) / 33554432.0) / (1 << (value >> 24)); +} + +void swp00_device::sound_stream_update(sound_stream &stream) +{ + const delay_block brev(this, m_rev_buffer); + const delay_block bcho(this, m_cho_buffer); + const delay_block bvar(this, m_var_buffer); + + for(int i=0; i != stream.samples(); i++) { + s32 dry_l = 0, dry_r = 0; + s32 rev = 0; + s32 cho_l = 0, cho_r = 0; + s32 var_l = 0, var_r = 0; + + for(int chan = 0; chan != 32; chan++) { + if(!m_active[chan]) + continue; + + u32 lfo_phase = m_lfo_phase[chan] >> 7; + s32 lfo_p_phase = lfo_phase ^ (m_lfo_step[chan] & 0x40 ? lfo_shape_centered_tri : lfo_shape_centered_saw)[lfo_phase >> 18]; + s32 lfo_fa_phase = lfo_phase ^ (m_lfo_step[chan] & 0x40 ? lfo_shape_offset_tri : lfo_shape_offset_saw )[lfo_phase >> 18]; + + s16 val0, val1; + u32 base_address = m_sample_address[chan]; + s32 spos = m_sample_pos[chan] >> 15; + switch(m_sample_dpcm_and_format[chan] >> 6) { + case 0: { // 16-bits linear + offs_t adr = base_address + (spos << 1); + val0 = read_word(adr); + val1 = read_word(adr+2); + break; + } + + case 1: { // 12-bits linear + offs_t adr = base_address + (spos >> 2)*6; + switch(spos & 3) { + case 0: { // Cabc ..AB .... .... + u16 w0 = read_word(adr); + u16 w1 = read_word(adr+2); + val0 = (w0 & 0x0fff) << 4; + val1 = ((w0 & 0xf000) >> 8) | ((w1 & 0x00ff) << 8); + break; + } + case 1: { // c... BCab ...A .... + u16 w0 = read_word(adr); + u16 w1 = read_word(adr+2); + u16 w2 = read_word(adr+4); + val0 = ((w0 & 0xf000) >> 8) | ((w1 & 0x00ff) << 8); + val1 = ((w1 & 0xff00) >> 4) | ((w2 & 0x000f) << 12); + break; + } + case 2: { // .... bc.. ABCa .... + u16 w1 = read_word(adr+2); + u16 w2 = read_word(adr+4); + val0 = ((w1 & 0xff00) >> 4) | ((w2 & 0x000f) << 12); + val1 = w2 & 0xfff0; + break; + } + case 3: { // .... .... abc. .ABC + u16 w2 = read_word(adr+4); + u16 w3 = read_word(adr+6); + val0 = w2 & 0xfff0; + val1 = (w3 & 0x0fff) << 4; + break; + } + } + break; + } + + case 2: // 8-bits linear + val0 = (read_byte(base_address + spos) << 8); + val1 = (read_byte(base_address + spos + 1) << 8); + break; + + case 3: { // 8-bits delta-pcm + u8 offset = dpcm_offset[m_sample_dpcm_and_format[chan] & 3]; + u8 scale = (m_sample_dpcm_and_format[chan] >> 2) & 7; + u32 target_address = base_address + spos + 1; + while(m_dpcm_address[chan] <= target_address) { + m_dpcm_current[chan] = m_dpcm_next[chan]; + m_dpcm_sum[chan] += m_dpcm[read_byte(m_dpcm_address[chan])] - offset; + s32 sample = (m_dpcm_sum[chan] << scale) >> 3; + m_dpcm_address[chan] ++; + if(sample < -0x8000) + sample = -0x8000; + else if(sample > 0x7fff) + sample = 0x7fff; + m_dpcm_next[chan] = sample; + } + val0 = m_dpcm_current[chan]; + val1 = m_dpcm_next[chan]; + break; + } + } + + s32 mul = m_sample_pos[chan] & 0x7fff; + s32 sample = (val1 * mul + val0 * (0x8000 - mul)) >> 7; + + s32 lpf_value = m_lpf_value[chan] + ((lfo_fa_phase * (m_lfo_famod_depth[chan] >> 5)) << (m_lfo_step[chan] & 0x40 ? 2 : 1)); + + m_lpf_ha[chan] += lpffpapply(lpf_value, sample - 2*fpapply(m_lpf_feedback[chan], m_lpf_ha[chan]) - m_lpf_hb[chan]); + m_lpf_hb[chan] += lpffpapply(lpf_value, m_lpf_ha[chan]); + + sample = m_lpf_hb[chan]; + + s32 envelope_level; + if(m_decay[chan] || m_attack_level[chan] || (m_attack_speed[chan] & 0x80)) + envelope_level = m_envelope_level[chan]; + else + envelope_level = 0; + + s32 tremolo_level = (lfo_fa_phase * (m_lfo_famod_depth[chan] & 0x1f)) << ((m_lfo_step[chan] & 0x40) ? 3 : 2); + + dry_l += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_dry_level[chan] << 20) + (m_pan_l[chan] << 16), sample); + dry_r += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_dry_level[chan] << 20) + (m_pan_r[chan] << 16), sample); + rev += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_rev_level[chan] << 20), sample); + cho_l += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_cho_level[chan] << 20) + (m_pan_l[chan] << 16), sample); + cho_r += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_cho_level[chan] << 20) + (m_pan_r[chan] << 16), sample); + var_l += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_var_level[chan] << 20) + (m_pan_l[chan] << 16), sample); + var_r += fpapply(envelope_level + (m_glo_level_cur[chan] << 16) + tremolo_level + (m_var_level[chan] << 20) + (m_pan_r[chan] << 16), sample); + + m_lfo_phase[chan] = (m_lfo_phase[chan] + m_global_step[0x20 + (m_lfo_step[chan] & 0x3f)]) & 0x7ffffff; + + u32 sample_increment = ((m_pitch[chan] & 0xfff) << (8 + (s16(m_pitch[chan]) >> 12))) >> 4; + m_sample_pos[chan] += (sample_increment * (0x800 + ((lfo_p_phase * m_lfo_pmod_depth[chan]) >> (m_lfo_step[chan] & 0x40 ? 18 : 19)))) >> 11; + + if((m_sample_pos[chan] >> 15) >= m_sample_end[chan]) { + if(!m_sample_end[chan]) + m_active[chan] = false; + else { + s32 prev = m_sample_pos[chan]; + do + m_sample_pos[chan] -= m_sample_end[chan] << 15; + while((m_sample_pos[chan] >> 15) >= m_sample_end[chan]); + m_dpcm_address[chan] += (m_sample_pos[chan] >> 15) - (prev >> 15); + m_dpcm_sum[chan] = 0; + } + } + + if(m_lpf_speed[chan] & 0x80) + m_lpf_done[chan] = istep(m_lpf_timer[chan], 0, m_global_step[m_lpf_speed[chan] & 0x7f] >> 1); + else + m_lpf_done[chan] = istep(m_lpf_value[chan], m_lpf_target_value[chan], m_global_step[m_lpf_speed[chan]] >> 1); + + istep(m_glo_level_cur[chan], m_glo_level[chan] << 4, 1); + istep(m_pan_l[chan], panmap[m_panning[chan] >> 4], 1); + istep(m_pan_r[chan], panmap[m_panning[chan] & 15], 1); + + if(m_decay[chan]) { + if((m_decay_speed[chan] & 0x60) == 0x60) + m_decay_done[chan] = fpstep(m_envelope_level[chan], m_decay_level[chan] << 20, decay_linear_step[m_decay_speed[chan] & 0x1f]); + else + m_decay_done[chan] = istep(m_envelope_level[chan], m_decay_level[chan] << 20, m_global_step[m_decay_speed[chan]] << 1); + if(m_envelope_level[chan] & 0x8000000) + m_active[chan] = false; + + } else if(m_attack_speed[chan] & 0x80) + m_decay[chan] = fpstep(m_envelope_level[chan], 0, attack_linear_step[m_attack_speed[chan] & 0x7f]); + else + m_decay[chan] = istep(m_envelope_level[chan], 0, m_global_step[m_attack_speed[chan]] << 1); + } + + dry_l >>= 8; + dry_r >>= 8; + rev >>= 8; + cho_l >>= 8; + cho_r >>= 8; + var_l >>= 8; + var_r >>= 8; + + + // Variation block + // Update the output volume + m_var_vol = m9(m_var_vol, 0xbd) + m_const[0xbc]; + + // Scale the input + var_l = m7(var_l, 0x04); + var_r = m7(var_r, 0x07); + + // Split depending on the variant selected + s32 var_out_l = 0, var_out_r = 0; + + switch(m_meg_control & 0xc0) { + case 0x00: { + // Used by: + // - 2-band EQ + // - Auto Pan + // - Celeste + // - Chorus + // - Delays + // - Flanger + // - Rotary Speaker + // - Symphonic + // - Tremolo + + // Two stages of filtering + s32 var_filter_l_2 = m7(m_var_filter_l_1, 0x7e) + m7(var_l, 0x7f) + m9(m_var_filter_l_2, 0x80); + s32 var_filtered_l = m7(m_var_filter_l_2, 0xa7) + m7(var_filter_l_2, 0xa8) + m9(m_var_filter_l_3, 0xa9); + + m_var_filter_l_1 = var_l; + m_var_filter_l_2 = var_filter_l_2; + m_var_filter_l_3 = var_filtered_l; + + s32 var_filter_r_2 = m7(m_var_filter_r_1, 0x98) + m7(var_r, 0x99) + m9(m_var_filter_r_2, 0x9a); + s32 var_filtered_r = m7(m_var_filter_r_2, 0x9b) + m7(var_filter_r_2, 0x9c) + m9(m_var_filter_r_3, 0x9d); + + m_var_filter_r_1 = var_r; + m_var_filter_r_2 = var_filter_r_2; + m_var_filter_r_3 = var_filtered_r; + + // Rest is like, complex and stuff + lfo_step(m_var_lfo_phase, 0x77); + s32 var_lfo_phase_2 = m7(m7(m_var_lfo_phase, 0x6d), 0x70) & 0x7fffff; + + filtered_lfo_step(m_var_lfo1a, m_var_lfo_phase, 0x6e, 0x6f, 0x72, 0x71); + filtered_lfo_step(m_var_lfo2a, m_var_lfo_phase, 0x79, 0x7a, 0x7c, 0x7b); + filtered_lfo_step(m_var_lfo3a, m_var_lfo_phase, 0x88, 0x89, 0x8b, 0x8a); + + s32 lfo1b = lfo_scale(m_var_lfo1a, 0x73); + s32 lfo2b = lfo_scale(m_var_lfo1a, 0x7d); + s32 lfo3b = lfo_scale(m_var_lfo1a, 0x8c); + + s32 lfo1c = lfo_wrap_reg(var_lfo_phase_2, 0x74); + s32 lfo2c = lfo_wrap_reg(var_lfo_phase_2, 0x84); + s32 lfo3c = lfo_wrap_reg(var_lfo_phase_2, 0x8d); + + filtered_lfo_step(m_var_lfo4a, lfo3c, -1, 0x8e, 0x90, 0x8f); + s32 lfo4b = lfo_scale(m_var_lfo4a, 0x91); + + s32 tap1 = bvar.rlfo2(0x78, m9(lfo1b, 0x75) + m9(lfo1c, 0x76)); + s32 tap2 = bvar.rlfo2(0x87, m9(lfo2b, 0x85) + m9(lfo2c, 0x86)); + s32 tap3 = bvar.rlfo2(0x99, m9(lfo3b, 0x95) + m9(lfo3c, 0x96)); + s32 tap4 = bvar.rlfo2(0xa8, m9(lfo4b, 0xa5)); + + s32 mod1 = lfo_mod(lfo1b, 0x83); + s32 mod2 = lfo_mod(lfo2b, 0x94); + s32 mod3 = lfo_mod(lfo3b, 0xa4); + + m_var_lfo_h_1 = m9(m_var_lfo_h_1, 0x9e) + m9(tap1, 0x9f); + m_var_lfo_h_2 = m9(m_var_lfo_h_2, 0xa0) + m9(tap1, 0xa1); + + bvar.w(0xae, var_filtered_l + m9(var_filtered_r, 0xaa) + m9(m_var_lfo_h_1, 0xab) + m9(m_var_lfo_h_2, 0xac)); + bvar.w(0xb1, m9(var_filtered_r, 0xad) + m9(m_var_lfo_h_1, 0xae) + m9(m_var_lfo_h_2, 0xaf)); + + var_out_l = m9(var_filtered_l, 0xb2) + m9(var_filtered_r, 0xb3) + m9(m9v(tap2, mod1), 0xb4) + m9(m9v(tap3, mod3), 0xb5) + m9(tap4, 0xb6); + var_out_r = m9(var_filtered_l, 0xb7) + m9(var_filtered_r, 0xb8) + m9(m9v(tap2, mod2), 0xb9) + m9(m9v(tap3, mod3), 0xba) + m9(tap4, 0xbb); + + break; + } + + case 0x40: { + // Used by: + // - Phaser + + // Two stages of filtering + s32 var_filter_l_2 = m7(m_var_filter_l_1, 0x6d) + m7(var_l, 0x6e) + m9(m_var_filter_l_2, 0x6f); + s32 var_filtered_l = m7(m_var_filter_l_2, 0x70) + m7(var_filter_l_2, 0x71) + m9(m_var_filter_l_3, 0x72); + + m_var_filter_l_1 = var_l; + m_var_filter_l_2 = var_filter_l_2; + m_var_filter_l_3 = var_filtered_l; + s32 var_filter_r_2 = m7(m_var_filter_r_1, 0x73) + m7(var_r, 0x74) + m9(m_var_filter_r_2, 0x75); + s32 var_filtered_r = m7(m_var_filter_r_2, 0x76) + m7(var_filter_r_2, 0x77) + m9(m_var_filter_r_3, 0x78); + + m_var_filter_r_1 = var_r; + m_var_filter_r_2 = var_filter_r_2; + m_var_filter_r_3 = var_filtered_r; + + // A very funky amplitude lfo with a lot of stages + s32 var_raw_l = m9(m_var_filterp_l_4, 0x7b) + m9(m_var_filterp_l_5, 0x7c) + m9(m_var_filterp_l_6, 0x7d); + s32 var_raw_r = m9(m_var_filterp_r_4, 0x7e) + m9(m_var_filterp_r_5, 0x7f) + m9(m_var_filterp_r_6, 0x80); + + s32 var_o_l = m9(var_raw_l, 0xa3) + m9(m_var_filterp_r_3, 0xa4) + m9(m_var_filterp_r_5, 0xa5); + s32 var_o_r = m9(var_raw_r, 0xa7); + + lfo_step(m_var_lfo_phase, 0x79); + s32 alfo_l = 0x200 - alfo(m_var_lfo_phase, 0, 0x83, 0x82, false); + s32 alfo_r = 0x200 - alfo(m_var_lfo_phase, m_const[0x9c], 0x9e, 0x9d, false); + + s32 var_l_1 = m9(var_filtered_l, 0x84) + m9(var_filtered_r, 0x85) + m9(var_raw_l, 0x86) + m9(var_raw_r, 0x87); + s32 var_l_2 = m_var_filterp_l_1 + m9v(m_var_filterp_l_2 - var_l_1, alfo_l); + m_var_filterp_l_1 = var_l_1; + s32 var_l_3 = m_var_filterp_l_2 + m9v(m_var_filterp_l_3 - var_l_2, alfo_l); + m_var_filterp_l_2 = var_l_2; + s32 var_l_4 = m_var_filterp_l_3 + m9v(m_var_filterp_l_4 - var_l_3, alfo_l); + m_var_filterp_l_3 = var_l_3; + s32 var_l_5 = m_var_filterp_l_4 + m9v(m_var_filterp_l_5 - var_l_4, alfo_l); + m_var_filterp_l_4 = var_l_4; + m_var_filterp_l_6 = m_var_filterp_l_5 + m9v(m_var_filterp_l_6 - var_l_5, alfo_l); + m_var_filterp_l_5 = var_l_5; + + s32 var_r_1 = m9(var_filtered_r, 0x9f) + m9(var_raw_l, 0xa0) + m9(var_raw_r, 0xa1); + s32 var_r_2 = m_var_filterp_r_1 + m9v(m_var_filterp_r_2 - var_r_1, alfo_r); + m_var_filterp_r_1 = var_r_1; + s32 var_r_3 = m_var_filterp_r_2 + m9v(m_var_filterp_r_3 - var_r_2, alfo_r); + m_var_filterp_r_2 = var_r_2; + s32 var_r_4 = m_var_filterp_r_3 + m9v(m_var_filterp_r_4 - var_r_3, alfo_r); + m_var_filterp_r_3 = var_r_3; + s32 var_r_5 = m_var_filterp_r_4 + m9v(m_var_filterp_r_5 - var_r_4, alfo_r); + m_var_filterp_r_4 = var_r_4; + m_var_filterp_r_6 = m_var_filterp_r_5 + m9v(m_var_filterp_r_6 - var_r_5, alfo_r); + m_var_filterp_r_5 = var_r_5; + + var_out_l = var_o_l + m9(var_filtered_l, 0xa2); + var_out_r = var_o_r + m9(var_filtered_r, 0xa6); + break; + } + + case 0x80: { + // Used by: + // - 3-band EQ + // - Amp simulation + // - Distortion + // - Gating + + // Compute a center value + s32 var_m = m9(var_l, 0x6d) + m9(var_r, 0x6e); + + // Two stages of filtering on the center value + s32 var_filter_2 = m7(m_var_filter_1, 0x6f) + m7(var_m, 0x70) + m9(m_var_filter_2, 0x71); + s32 var_filtered = m7(m_var_filter_2, 0x72) + m7(var_filter_2, 0x73) + m9(m_var_filter_3, 0x74); + + m_var_filter_1 = var_m; + m_var_filter_2 = var_filter_2; + m_var_filter_3 = var_filtered; + + // Gating/ER reverb injection with some filtering + bvar.w(0x7e, m9(bvar.r(0x6c), 0x7b) + m9(var_m, 0x7c)); + s32 tap0 = m7(bvar.r(0x6c), 0x7e) + m7(var_m, 0x7f); + bvar.w(0x84, m9(bvar.r(0x78), 0x81) + m9(tap0, 0x82)); + + s32 var_f3_1 = bvar.r(0x6f); + s32 var_f3_2 = m7(m_var_filter2_1, 0x77) + m7(var_f3_1, 0x78) + m9(m_var_filter3_2, 0x79); + bvar.w(0x87, m7(bvar.r(0x78), 0x84) + m7(tap0, 0x85) + m9(var_f3_2, 0x86)); + + m_var_filter3_1 = var_f3_1; + m_var_filter3_2 = var_f3_2; + + // Multi-tap on reverb + s32 tap1 = m9(bvar.r(0x6f), 0x99) + m9(bvar.r(0x72), 0x9a) + m9(bvar.r(0x75), 0x9b) + m9(bvar.r(0x8d), 0x9c) + m9(bvar.r(0x90), 0x9d) + m9(bvar.r(0x93), 0x9e) + m9(bvar.r(0x96), 0x9f); + s32 tap2 = m9(bvar.r(0x9f), 0xb4) + m9(bvar.r(0xa2), 0xb5) + m9(bvar.r(0xa5), 0xb6) + m9(bvar.r(0xa8), 0xb7) + m9(bvar.r(0xab), 0xb8) + m9(bvar.r(0xae), 0xb9) + m9(bvar.r(0xb1), 0xba); + + bvar.w(0xb7, tap1); + bvar.w(0xba, tap2); + + s32 tap2b = tap2 + m9(brev.r(0xb4), 0xbb); + bvar.w(0x8a, m9(bvar.r(0x7b), 0x88) + m9(tap2b, 0x89)); + s32 var_gate_l = m7(bvar.r(0x7b), 0x8b) + m7(tap2b, 0x8c); + + s32 tap1b = tap1 + m9(brev.r(0x99), 0xa0); + bvar.w(0x9c, m9(bvar.r(0x81), 0x8e) + m9(tap1b, 0x8f)); + s32 var_gate_r = m7(bvar.r(0x7b), 0x8b) + m7(tap1b, 0x8c); + + // Distortion stage + s32 dist1 = saturate(m7(var_filtered, 0x76)); + s32 dist2 = saturate(m7(dist1, 0x83)); + s32 dist3 = saturate(m7(dist2, 0x87)); + s32 dist4 = saturate(m7(dist3, 0x8a)); + s32 dist5 = saturate(m7(dist4, 0x8d)); + s32 dist6 = saturate(m7(dist5, 0x90)); + s32 disto = m9(m9(dist1, 0x91) + m9(dist2, 0x92) + m9(dist3, 0x93) + m9(dist4, 0x94) + m9(dist5, 0x95) + m9(dist6, 0x96), 0xa1); + + // Filtering again, 3 stages + s32 var_f2_2 = m7(m_var_filter2_1, 0xa2) + m7(disto, 0xa3) + m9(m_var_filter2_2a, 0xa4); + s32 var_f2_3 = m7(m_var_filter2_3b, 0xa5) + m7(m_var_filter2_3a, 0xa6) + m7(m_var_filter2_2b, 0xa7) + m7(m_var_filter2_2a, 0xa8) + m7(var_f2_2, 0xa9); + s32 var_f2_4 = m7(m_var_filter2_3a, 0xaa) + m7(var_f2_3, 0xab) + m9(m_var_filter2_4, 0xac); + + m_var_filter2_1 = disto; + m_var_filter2_2b = m_var_filter2_2a; + m_var_filter2_2a = var_f2_2; + m_var_filter2_3b = m_var_filter2_3a; + m_var_filter2_3a = var_f2_3; + m_var_filter2_4 = var_f2_4; + + // Mix in both paths + var_out_l = m9(var_l, 0xad) + m9(var_gate_l, 0xaf) + m9(var_f2_4, 0xb0); + var_out_r = m9(var_r, 0xb1) + m9(var_gate_r, 0xb2) + m9(var_f2_4, 0xb3); + + break; + } + + case 0xc0: { + // Used by: + // - Auto wah + // - Hall + // - Karaoke + // - Plate + // - Room + // - Stage + + // Compute a center value + s32 var_m = m9(var_l, 0x6d) + m9(var_r, 0x6e); + + // Two stages of filtering on the center value + s32 var_filter_2 = m7(m_var_filter_1, 0x6f) + m7(var_m, 0x70) + m9(m_var_filter_2, 0x71); + s32 var_filtered = m7(m_var_filter_2, 0x72) + m7(var_filter_2, 0x73) + m9(m_var_filter_3, 0x74); + m_var_filter_1 = var_m; + m_var_filter_2 = var_filter_2; + m_var_filter_3 = var_filtered; + + // Inject in the reverb buffer and loop with filtering + s32 tap1a = bvar.r(0x6c); // 36 v19 + s32 tap1b = bvar.r(0x6f); // 37 v21 + s32 tap1c = bvar.r(0x72); // 38 v27 + + bvar.w(0x75, var_filtered + m9(tap1a, 0x75)); + bvar.w(0x78, m9(tap1b, 0x76) + m9(tap1a, 0x77)); + + s32 tap2a = m7(tap1b, 0x78) + m7(tap1a, 0x79); + + bvar.w(0x7b, m9(tap1b, 0x7a) + m9(tap2a, 0x7b)); + + s32 tap2b = m7(tap1c, 0x7c) + m7(tap2a, 0x7d); + + s32 tap1d = bvar.r(0x9c); + s32 tap1e = bvar.r(0x9f); + + bvar.w(0xa8, m9(m_var_h1, 0xa5) + m9(tap1d, 0xa6) + m9(tap2b, 0xa7)); + m_var_h1 = tap1d; + + bvar.w(0xae, m9(m_var_h2, 0xa8) + m9(tap1e, 0xa9) + m9(tap2b, 0xaa)); + m_var_h2 = tap1e; + + s32 tap1f = bvar.r(0xab); + s32 tap1g = bvar.r(0xb1); + + bvar.w(0xb7, m9(m_var_h3, 0xb3) + m9(tap1f, 0xb4) + m9(tap2b, 0xb5)); + m_var_h3 = tap1f; + + bvar.w(0xba, m9(m_var_h4, 0xb6) + m9(tap1g, 0xb7) + m9(tap2b, 0xb8)); + m_var_h4 = tap1g; + + s32 tap1h = bvar.r(0x7e); + + s32 tap3a = m9(bvar.r(0x81) + bvar.r(0x84) + bvar.r(0x87) + bvar.r(0x8a), 0x8f) + m9(tap1h, 0x93); + s32 tap3b = bvar.r(0xa5); + bvar.w(0xb4, m9(tap3b, 0xaf) + m9(tap3a, 0xb0)); + s32 var_o_l = m7(tap3b, 0xb1) + m7(tap3a, 0xb2); + + s32 tap4a = m9(bvar.r(0x8d) + bvar.r(0x90) + bvar.r(0x93) + bvar.r(0x96), 0x9c) + m9(tap1h, 0xa0); + s32 tap4b = bvar.r(0x99); + bvar.w(0xa2, m9(tap4b, 0xa1) + m9(tap4a, 0xa2)); + s32 var_o_r = m7(tap4b, 0xa3) + m7(tap4a, 0xa4); + + // auto-wah effect with lfo + // Two stages of filtering + s32 var_filter_l_2 = m7(m_var_filter_l_1, 0x80) + m7(var_l, 0x81) + m9(m_var_filter_l_2, 0x82); + s32 var_filtered_l = m7(m_var_filter_l_2, 0x83) + m7(var_filter_l_2, 0x84) + m9(m_var_filter_l_3, 0x85); + + m_var_filter_l_1 = var_l; + m_var_filter_l_2 = var_filter_l_2; + m_var_filter_l_3 = var_filtered_l; + s32 var_filter_r_2 = m7(m_var_filter_r_1, 0x6f) + m7(var_r, 0x70) + m9(m_var_filter_r_2, 0x71); + s32 var_filtered_r = m7(m_var_filter_r_2, 0x72) + m7(var_filter_r_2, 0x73) + m9(m_var_filter_r_3, 0x74); + + m_var_filter_r_1 = var_r; + m_var_filter_r_2 = var_filter_r_2; + m_var_filter_r_3 = var_filtered_r; + + // Mixing + s32 var_w_l = m7(var_filtered_l, 0x94) + m7(var_filtered_r, 0x95); + s32 var_w_r = m7(var_filtered_r, 0x88); + + // Amplitude LFO and filtering + lfo_step(m_var_lfo_phase, 0x7e); + s32 amp = alfo(m_var_lfo_phase, 0, 0x86, 0x87, true); + + m_var_filterp_l_1 = m9v(m9(m_var_filterp_l_1, 0x89) + m9(m_var_filterp_l_2, 0x8a) + var_w_l, amp) + m9(m_var_filterp_l_1, 0x8b); + m_var_filterp_l_2 = m9v(m_var_filterp_l_1, amp) + m9(m_var_filterp_l_2, 0x8d); + + m_var_filterp_r_1 = m9v(m9(m_var_filterp_r_1, 0x96) + m9(m_var_filterp_r_2, 0x97) + var_w_r, amp) + m9(m_var_filterp_r_1, 0x98); + m_var_filterp_r_2 = m9v(m_var_filterp_r_1, amp) + m9(m_var_filterp_r_2, 0x9a); + + var_out_l = m9(var_filtered_l, 0xb9) + m9(m_var_filterp_l_1, 0xba) + m9(var_o_l, 0xbb); + var_out_r = m9(var_filtered_r, 0xab) + m9(var_r, 0xac) + m9(m_var_filterp_r_1, 0xad) + m9(var_o_r, 0xae); + break; + } + } + + + // Chorus block + // Update the output volume + m_cho_vol = m9(m_cho_vol, 0x58) + m_const[0x57]; + + // Scale the input + cho_l = m7(cho_l, 0x02); + cho_r = m7(cho_r, 0x05); + + // Add in the other channels + cho_l += m9v(m7(var_out_l, 0x03), m_var_vol); + cho_r += m9v(m7(var_out_r, 0x06), m_var_vol); + + // A LFO with (up to) three phases to pick up the reverb + lfo_step(m_cho_lfo_phase, 0x09); + + s32 cho_lfo_1 = bcho.rlfo(0x1b, m_cho_lfo_phase, 0, 0x1a); + s32 cho_lfo_2 = bcho.rlfo(0x2a, m_cho_lfo_phase, m_const[0x25], 0x28); + s32 cho_lfo_3 = bcho.rlfo(0x39, m_cho_lfo_phase, m_const[0x34], 0x37); + + // Two stages of filtering + s32 cho_filter_r_2 = m7(m_cho_filter_r_1, 0x3c) + m7(cho_r, 0x3d) + m9(m_cho_filter_r_2, 0x3e); + s32 cho_filtered_r = m7(m_cho_filter_r_2, 0x3f) + m7(cho_filter_r_2, 0x40) + m9(m_cho_filter_r_3, 0x41); + + m_cho_filter_r_1 = cho_r; + m_cho_filter_r_2 = cho_filter_r_2; + m_cho_filter_r_3 = cho_filtered_r; + + s32 cho_filter_l_2 = m7(m_cho_filter_l_1, 0x49) + m7(cho_l, 0x4a) + m9(m_cho_filter_l_2, 0x4b); + s32 cho_filtered_l = m7(m_cho_filter_l_2, 0x4c) + m7(cho_filter_l_2, 0x4d) + m9(m_cho_filter_l_3, 0x4e); + + m_cho_filter_l_1 = cho_l; + m_cho_filter_l_2 = cho_filter_l_2; + m_cho_filter_l_3 = cho_filtered_l; + + // Reverb feedback from there, slighly assymetric to cover more possibilities + bcho.w(0x42, m9(cho_lfo_2, 0x42) + cho_filtered_r); + bcho.w(0x51, m9(cho_lfo_1, 0x4f) + cho_filtered_l + m9(cho_filtered_r, 0x50)); + + // Final value by combining the LFO-ed reverbs + s32 cho_out_l = m9(cho_lfo_1, 0x60) + m9(cho_lfo_3, 0x61); + s32 cho_out_r = m9(cho_lfo_2, 0x69) + m9(cho_lfo_3, 0x6a); + + + + // Reverb block + // Update the output volume + m_rev_vol = m9(m_rev_vol, 0x0c) + m_const[0x0b]; + + // Scale the input + rev = m7(rev, 0x11); + + // Add in the other channels + rev += m9v(m7(cho_out_l, 0x12) + m7(cho_out_r, 0x13), m_cho_vol); + rev += m9v(m7(var_out_l, 0x14) + m7(var_out_r, 0x15), m_var_vol); + + // Two stages of filtering (hpf then lpf) + s32 rev_filter_2 = m7(m_rev_filter_1, 0x2d) + m7(rev, 0x2e) + m9(m_rev_filter_2, 0x2f); + s32 rev_filtered = m7(m_rev_filter_2, 0x30) + m7(rev_filter_2, 0x31) + m9(m_rev_filter_3, 0x32); + + m_rev_filter_1 = rev; + m_rev_filter_2 = rev_filter_2; + m_rev_filter_3 = rev_filtered; + + // Main reverb + brev.w(0x30, m9(brev.r(0x21), 0x29) + m9(brev.r(0x18), 0x2a)); + brev.w(0x33, m9(brev.r(0x1b), 0x33) + rev_filtered); + + // Second dual reverb + s32 rev_1 = m7(brev.r(0x33), 0x2b) + m7(brev.r(0x18), 0x2c); + s32 rev_2 = m7(brev.r(0x27), 0x3a) + m7(rev_1, 0x3b); + brev.w(0x3f, m9(brev.r(0x39), 0x38) + m9(rev_1, 0x39)); + + // Four more parallel layers with filtering + brev.w(0x5d, m9(m_rev_hist_a, 0x59) + m9(brev.r(0x24), 0x5a) + m9(rev_2, 0x5b)); + m_rev_hist_a = brev.r(0x24); + brev.w(0x63, m9(m_rev_hist_b, 0x5c) + m9(brev.r(0x54), 0x5d) + m9(rev_2, 0x5e)); + m_rev_hist_b = brev.r(0x54); + brev.w(0x69, m9(m_rev_hist_c, 0x62) + m9(brev.r(0x5a), 0x63) + m9(rev_2, 0x64)); + m_rev_hist_c = brev.r(0x63); + brev.w(0x6c, m9(m_rev_hist_d, 0x65) + m9(brev.r(0x60), 0x66) + m9(rev_2, 0x67)); + m_rev_hist_d = brev.r(0x66); + + // Split final pick-up and injection + s32 rev_base_l = m9(brev.r(0x00) + brev.r(0x03) + brev.r(0x06) + brev.r(0x09), 0x1c) + m9(brev.r(0xbd), 0x1b); + brev.w(0x48, m9(brev.r(0x36), 0x45) + m9(rev_base_l, 0x46)); + s32 rev_out_l = m7(brev.r(0x36), 0x47) + m7(rev_base_l, 0x48); + + s32 rev_base_r = m9(brev.r(0x0c) + brev.r(0x0f) + brev.r(0x12) + brev.r(0x15), 0x21) + m9(brev.r(0xbd), 0x20); + brev.w(0x48, m9(brev.r(0x36), 0x51) + m9(rev_base_r, 0x52)); + s32 rev_out_r = m7(brev.r(0x36), 0x53) + m7(rev_base_r, 0x54); + + + // Scale the dry input + dry_l = m7(dry_l, 0xbe); + dry_r = m7(dry_r, 0x01); + + + // Add in the other channels + dry_l += m9v(rev_out_l, m_rev_vol) + m9v(m9(cho_out_l, 0x17), m_cho_vol) + m9v(m9(var_out_l, 0x18), m_var_vol); + dry_r += m9v(rev_out_r, m_rev_vol) + m9v(m9(cho_out_r, 0x0e), m_cho_vol) + m9v(m9(var_out_r, 0x0f), m_var_vol); + + stream.put_int(0, i, dry_l, 32768); + stream.put_int(1, i, dry_r, 32768); + + m_buffer_offset --; + } } |