From e96df776951060c7aa2747aa50e2b521ea47c608 Mon Sep 17 00:00:00 2001 From: Olivier Galibert Date: Sun, 22 Oct 2023 20:04:22 +0200 Subject: [PATCH] mu50: Add the effects and the nvram new WORKING machine ------------------- Yamaha MU50 [O. Galibert, Phil Bennett] --- src/devices/sound/swp00.cpp | 931 ++++++++++++++++++++++++++++++++---- src/devices/sound/swp00.h | 80 +++- src/mame/yamaha/ymmu50.cpp | 41 +- 3 files changed, 933 insertions(+), 119 deletions(-) diff --git a/src/devices/sound/swp00.cpp b/src/devices/sound/swp00.cpp index 54ab5ca3d6eef..9a522275cead0 100644 --- a/src/devices/sound/swp00.cpp +++ b/src/devices/sound/swp00.cpp @@ -6,6 +6,66 @@ #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) @@ -78,7 +138,6 @@ 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); - // fprintf(stderr, "%07x %05x -> %x %08x\n", value, step, e, m); if(m >= 0) return 0; if(e >= 0xc) @@ -99,8 +158,6 @@ s32 swp00_device::fpsub(s32 value, s32 step) bool swp00_device::fpstep(s32 &value, s32 limit, s32 step) { - // fprintf(stderr, "fpstep(%x, %x, %x)\n", value, limit, step); - // value, limit and step are 4.24 but step has its exponent and // top four bits zero @@ -175,8 +232,72 @@ void swp00_device::device_start() save_item(NAME(m_waverom_val)); save_item(NAME(m_meg_control)); - save_item(NAME(m_off)); - save_item(NAME(m_fp)); + 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)); @@ -259,8 +380,68 @@ void swp00_device::device_reset() m_waverom_val = 0; m_meg_control = 0; - std::fill(m_off.begin(), m_off.end(), 0); - std::fill(m_fp.begin(), m_fp.end(), 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); @@ -361,8 +542,8 @@ void swp00_device::map(address_map &map) rctrl(map, 0x0d); // 00 at startup rctrl(map, 0x0e); // 00 at startup - map(0x180, 0x1ff).rw(FUNC(swp00_device::off_r), FUNC(swp00_device::off_w)); - map(0x200, 0x37f).rw(FUNC(swp00_device::fp_r), FUNC(swp00_device::fp_w)); + 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)); } @@ -392,8 +573,8 @@ template void swp00_device::lpf_info_w(offs_t offset, u8 data) if(m_lpf_info[chan] == old) return; - if(!sel) - logerror("lpf_info[%02x] = %04x\n", chan, m_lpf_info[chan]); + // 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; @@ -418,7 +599,7 @@ void swp00_device::lpf_speed_w(offs_t offset, u8 data) return; m_stream->update(); m_lpf_speed[chan] = data; - logerror("lpf_speed[%02x] = %02x\n", chan, m_lpf_speed[chan]); + // logerror("lpf_speed[%02x] = %02x\n", chan, m_lpf_speed[chan]); } u8 swp00_device::lpf_speed_r(offs_t offset) @@ -434,7 +615,7 @@ void swp00_device::lfo_famod_depth_w(offs_t offset, u8 data) return; m_stream->update(); m_lfo_famod_depth[chan] = data; - logerror("lfo_famod_depth[%02x] = %02x\n", chan, m_lfo_famod_depth[chan]); + // logerror("lfo_famod_depth[%02x] = %02x\n", chan, m_lfo_famod_depth[chan]); } u8 swp00_device::lfo_famod_depth_r(offs_t offset) @@ -450,7 +631,7 @@ void swp00_device::rev_level_w(offs_t offset, u8 data) return; m_stream->update(); m_rev_level[chan] = data; - logerror("rev_level[%02x] = %02x\n", chan, m_rev_level[chan]); + // logerror("rev_level[%02x] = %02x\n", chan, m_rev_level[chan]); } u8 swp00_device::rev_level_r(offs_t offset) @@ -466,7 +647,7 @@ void swp00_device::dry_level_w(offs_t offset, u8 data) return; m_stream->update(); m_dry_level[chan] = data; - logerror("dry_level[%02x] = %02x\n", chan, m_dry_level[chan]); + // logerror("dry_level[%02x] = %02x\n", chan, m_dry_level[chan]); } u8 swp00_device::dry_level_r(offs_t offset) @@ -482,7 +663,7 @@ void swp00_device::cho_level_w(offs_t offset, u8 data) return; m_stream->update(); m_cho_level[chan] = data; - logerror("cho_level[%02x] = %02x\n", chan, m_cho_level[chan]); + // logerror("cho_level[%02x] = %02x\n", chan, m_cho_level[chan]); } u8 swp00_device::cho_level_r(offs_t offset) @@ -498,7 +679,7 @@ void swp00_device::var_level_w(offs_t offset, u8 data) return; m_stream->update(); m_var_level[chan] = data; - logerror("var_level[%02x] = %02x\n", chan, m_var_level[chan]); + // logerror("var_level[%02x] = %02x\n", chan, m_var_level[chan]); } u8 swp00_device::var_level_r(offs_t offset) @@ -513,7 +694,7 @@ void swp00_device::glo_level_w(offs_t offset, u8 data) if(m_glo_level[chan] == data) return; m_glo_level[chan] = data; - logerror("glo_level[%02x] = %02x\n", chan, m_glo_level[chan]); + // logerror("glo_level[%02x] = %02x\n", chan, m_glo_level[chan]); } u8 swp00_device::glo_level_r(offs_t offset) @@ -529,7 +710,7 @@ void swp00_device::panning_w(offs_t offset, u8 data) return; m_stream->update(); m_panning[chan] = data; - logerror("panning[%02x] = %02x\n", chan, m_panning[chan]); + // logerror("panning[%02x] = %02x\n", chan, m_panning[chan]); } u8 swp00_device::panning_r(offs_t offset) @@ -545,7 +726,7 @@ void swp00_device::attack_speed_w(offs_t offset, u8 data) return; m_stream->update(); m_attack_speed[chan] = data; - logerror("attack_speed[%02x] = %02x\n", chan, m_attack_speed[chan]); + // logerror("attack_speed[%02x] = %02x\n", chan, m_attack_speed[chan]); } u8 swp00_device::attack_speed_r(offs_t offset) @@ -561,7 +742,7 @@ void swp00_device::attack_level_w(offs_t offset, u8 data) return; m_stream->update(); m_attack_level[chan] = data; - logerror("attack_level[%02x] = %02x\n", chan, m_attack_level[chan]); + // logerror("attack_level[%02x] = %02x\n", chan, m_attack_level[chan]); } u8 swp00_device::attack_level_r(offs_t offset) @@ -582,7 +763,7 @@ void swp00_device::decay_speed_w(offs_t offset, u8 data) if(data & 0x80) m_decay[chan] = true; - logerror("decay_speed[%02x] = %02x\n", chan, m_decay_speed[chan]); + // logerror("decay_speed[%02x] = %02x\n", chan, m_decay_speed[chan]); } u8 swp00_device::decay_speed_r(offs_t offset) @@ -598,7 +779,7 @@ void swp00_device::decay_level_w(offs_t offset, u8 data) return; m_stream->update(); m_decay_level[chan] = data; - logerror("decay_level[%02x] = %02x\n", chan, m_decay_level[chan]); + // logerror("decay_level[%02x] = %02x\n", chan, m_decay_level[chan]); } u8 swp00_device::decay_level_r(offs_t offset) @@ -615,8 +796,8 @@ template void swp00_device::pitch_w(offs_t offset, u8 data) 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]); + // if(!sel) + // logerror("pitch[%02x] = %04x\n", chan, m_pitch[chan]); } template u8 swp00_device::pitch_r(offs_t offset) @@ -631,8 +812,8 @@ template void swp00_device::sample_start_w(offs_t offset, u8 data) 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]); + // if(!sel) + // logerror("sample_start[%02x] = %04x\n", chan, m_sample_start[chan]); } template u8 swp00_device::sample_start_r(offs_t offset) @@ -647,8 +828,8 @@ template void swp00_device::sample_end_w(offs_t offset, u8 data) 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]); + // if(!sel) + // logerror("sample_end[%02x] = %04x\n", chan, m_sample_end[chan]); } template u8 swp00_device::sample_end_r(offs_t offset) @@ -663,7 +844,7 @@ void swp00_device::sample_dec_and_format_w(offs_t offset, u8 data) m_stream->update(); m_sample_dec_and_format[chan] = data; - logerror("sample_dec_and_format[%02x] = %02x\n", chan, m_sample_dec_and_format[chan]); + // logerror("sample_dec_and_format[%02x] = %02x\n", chan, m_sample_dec_and_format[chan]); } u8 swp00_device::sample_dec_and_format_r(offs_t offset) @@ -678,8 +859,8 @@ template void swp00_device::sample_address_w(offs_t offset, u8 data) 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]); + // if(!sel) + // logerror("sample_address[%02x] = %04x\n", chan, m_sample_address[chan]); } template u8 swp00_device::sample_address_r(offs_t offset) @@ -696,7 +877,7 @@ void swp00_device::lfo_step_w(offs_t offset, u8 data) m_stream->update(); m_lfo_step[chan] = data; - logerror("lfo_step[%02x] = %02x\n", chan, m_lfo_step[chan]); + // logerror("lfo_step[%02x] = %02x\n", chan, m_lfo_step[chan]); } u8 swp00_device::lfo_step_r(offs_t offset) @@ -713,7 +894,7 @@ void swp00_device::lfo_pmod_depth_w(offs_t offset, u8 data) m_stream->update(); m_lfo_pmod_depth[chan] = data; - logerror("lfo_pmod_depth[%02x] = %02x\n", chan, m_lfo_pmod_depth[chan]); + // logerror("lfo_pmod_depth[%02x] = %02x\n", chan, m_lfo_pmod_depth[chan]); } u8 swp00_device::lfo_pmod_depth_r(offs_t offset) @@ -725,7 +906,7 @@ u8 swp00_device::lfo_pmod_depth_r(offs_t offset) void swp00_device::keyon(int chan) { m_stream->update(); - logerror("keyon %02x a=%02x/%02x d=%02x/%02x\n", chan, m_attack_speed[chan], m_attack_level[chan], m_decay_speed[chan], m_decay_level[chan]); + // logerror("keyon %02x a=%02x/%02x d=%02x/%02x\n", chan, m_attack_speed[chan], m_attack_level[chan], m_decay_speed[chan], m_decay_level[chan]); m_lfo_phase[chan] = 0; m_sample_pos[chan] = -m_sample_start[chan] << 15; @@ -758,46 +939,46 @@ template void swp00_device::keyon_w(u8 data) keyon(8*sel+i); } -void swp00_device::off_w(offs_t offset, u8 data) +void swp00_device::offset_w(offs_t offset, u8 data) { m_stream->update(); if(offset & 1) - m_off[offset >> 1] = (m_off[offset >> 1] & 0xff00) | data; + m_offset[offset >> 1] = (m_offset[offset >> 1] & 0xff00) | data; else - m_off[offset >> 1] = (m_off[offset >> 1] & 0x00ff) | (data << 8); - if(1) + m_offset[offset >> 1] = (m_offset[offset >> 1] & 0x00ff) | (data << 8); + if(0) if(offset & 1) - logerror("off[%02x] = %04x\n", 3*(offset >> 1), m_off[offset >> 1]); + logerror("offset[%02x] = %04x\n", 3*(offset >> 1), m_offset[offset >> 1]); } -u8 swp00_device::off_r(offs_t offset) +u8 swp00_device::offset_r(offs_t offset) { if(offset & 1) - return m_off[offset >> 1]; + return m_offset[offset >> 1]; else - return m_off[offset >> 1] >> 8; + return m_offset[offset >> 1] >> 8; } -void swp00_device::fp_w(offs_t offset, u8 data) +void swp00_device::const_w(offs_t offset, u8 data) { m_stream->update(); if(offset & 1) - m_fp[offset >> 1] = (m_fp[offset >> 1] & 0xff00) | data; + m_const[offset >> 1] = (m_const[offset >> 1] & 0xff00) | data; else - m_fp[offset >> 1] = (m_fp[offset >> 1] & 0x00ff) | (data << 8); - if(1) + m_const[offset >> 1] = (m_const[offset >> 1] & 0x00ff) | (data << 8); + if(0) if(offset & 1) - logerror("fp[%02x] = %04x\n", offset >> 1, m_fp[offset >> 1]); + logerror("const[%02x] = %04x\n", offset >> 1, m_const[offset >> 1]); } -u8 swp00_device::fp_r(offs_t offset) +u8 swp00_device::const_r(offs_t offset) { if(offset & 1) - return m_fp[offset >> 1]; + return m_const[offset >> 1]; else - return m_fp[offset >> 1] >> 8; + return m_const[offset >> 1] >> 8; } void swp00_device::waverom_access_w(u8 data) @@ -821,7 +1002,7 @@ u8 swp00_device::waverom_val_r() void swp00_device::meg_control_w(u8 data) { m_meg_control = data; - logerror("meg_control %02x (variation %x, %s)\n", m_meg_control >> 6, m_meg_control & 2 ? "mute" : "on"); + 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() @@ -834,7 +1015,6 @@ u8 swp00_device::state_r() { m_stream->update(); - // logerror("state_r %x.%02x\n", m_state_adr >> 5, m_state_adr & 0x1f); int chan = m_state_adr & 0x1f; switch(m_state_adr & 0xe0) { case 0x00: // lpf value @@ -880,64 +1060,203 @@ void swp00_device::state_adr_w(u8 data) // Catch-all -static u8 rr[0x20*0x40]; - u8 swp00_device::snd_r(offs_t offset) { - if(1) { - int chan = (offset >> 1) & 0x1f; - int slot = ((offset >> 5) & 0x3e) | (offset & 1); - 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 == 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 -#endif - preg = util::string_format("%02x.%02x", chan, slot); - logerror("snd_r [%03x] %-5s, %02x\n", offset, preg, rr[offset]); - } - return rr[offset]; + logerror("snd_r [%03x]\n", offset); + return 0; } void swp00_device::snd_w(offs_t offset, u8 data) { - // if(rr[offset] == data) - // return; - - rr[offset] = data; - - int chan = (offset >> 1) & 0x1f; - int slot = ((offset >> 5) & 0x3e) | (offset & 1); - - 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); - else -#endif - preg = util::string_format("%02x.%02x", chan, slot); - - logerror("snd_w [%03x] %-5s, %02x\n", offset, preg, data); + logerror("snd_w [%03x] %02x\n", offset, data); } // Synthesis +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 swp00_device::delay_block::delay_block(swp00_device *swp, std::array &buffer) : + m_swp(swp), + m_buffer(buffer) +{ +} + +template s32 swp00_device::delay_block::r(int offreg) const +{ + return m_buffer[(m_swp->m_buffer_offset + m_swp->m_offset[offreg/3]) & (size - 1)]; +} + +template void swp00_device::delay_block::w(int offreg, s32 value) const +{ + m_buffer[(m_swp->m_buffer_offset + m_swp->m_offset[offreg/3]) & (size - 1)] = value; +} + +template s32 swp00_device::delay_block::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 s32 swp00_device::delay_block::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; +} + void swp00_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) { + 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 != outputs[0].samples(); i++) { s32 dry_l = 0, dry_r = 0; s32 rev = 0; @@ -1081,12 +1400,420 @@ void swp00_device::sound_stream_update(sound_stream &stream, std::vector>= 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, var_out_r; + + 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); + outputs[0].put_int(i, dry_l, 32768); outputs[1].put_int(i, dry_r, 32768); - (void)rev; - (void)cho_l; - (void)cho_r; - (void)var_l; - (void)var_r; + + m_buffer_offset --; } } diff --git a/src/devices/sound/swp00.h b/src/devices/sound/swp00.h index 4a29fedc4c290..36dfba1677dc6 100644 --- a/src/devices/sound/swp00.h +++ b/src/devices/sound/swp00.h @@ -26,7 +26,16 @@ class swp00_device : public device_t, public device_sound_interface, public devi virtual void device_add_mconfig(machine_config &config) override; private: - // required_device m_meg; + template struct delay_block { + swp00_device *m_swp; + std::array &m_buffer; + + delay_block(swp00_device *swp, std::array &buffer); + s32 r(int offreg) const; + s32 rlfo(int offreg, u32 phase, s32 delta_phase, int levelreg) const; + s32 rlfo2(int offreg, s32 offset) const; + void w(int offreg, s32 value) const; + }; sound_stream *m_stream; @@ -41,9 +50,14 @@ class swp00_device : public device_t, public device_sound_interface, public devi static const std::array lfo_shape_offset_saw; static const std::array lfo_shape_offset_tri; + // MEG reverb memory + std::array m_rev_buffer; + std::array m_cho_buffer; + std::array m_var_buffer; + // MEG registers - std::array m_off; - std::array m_fp; + std::array m_offset; + std::array m_const; // AWM registers std::array m_lpf_info; @@ -87,6 +101,36 @@ class swp00_device : public device_t, public device_sound_interface, public devi u8 m_state_adr; u8 m_meg_control; + // MEG state + u32 m_buffer_offset; + s32 m_rev_vol, m_cho_vol, m_var_vol; + + u32 m_var_lfo_phase; + + s32 m_var_lfo_h_1, m_var_lfo_h_2; + s32 m_var_lfo1a, m_var_lfo2a, m_var_lfo3a, m_var_lfo4a; + + s32 m_var_filter_1, m_var_filter_2, m_var_filter_3; + s32 m_var_filter_l_1, m_var_filter_l_2, m_var_filter_l_3; + s32 m_var_filter_r_1, m_var_filter_r_2, m_var_filter_r_3; + s32 m_var_filter2_1, m_var_filter2_2a, m_var_filter2_2b, m_var_filter2_3a, m_var_filter2_3b, m_var_filter2_4; + s32 m_var_filter3_1, m_var_filter3_2; + + s32 m_var_filterp_l_1, m_var_filterp_l_2, m_var_filterp_l_3; + s32 m_var_filterp_l_4, m_var_filterp_l_5, m_var_filterp_l_6; + s32 m_var_filterp_r_1, m_var_filterp_r_2, m_var_filterp_r_3; + s32 m_var_filterp_r_4, m_var_filterp_r_5, m_var_filterp_r_6; + + s32 m_var_h1, m_var_h2, m_var_h3, m_var_h4; + + u32 m_cho_lfo_phase; + s32 m_cho_filter_l_1, m_cho_filter_l_2, m_cho_filter_l_3; + s32 m_cho_filter_r_1, m_cho_filter_r_2, m_cho_filter_r_3; + + s32 m_rev_filter_1, m_rev_filter_2, m_rev_filter_3; + s32 m_rev_hist_a, m_rev_hist_b, m_rev_hist_c, m_rev_hist_d; + + // Voice control template void lpf_info_w(offs_t offset, u8 data); @@ -138,10 +182,10 @@ class swp00_device : public device_t, public device_sound_interface, public devi void state_adr_w(u8 data); // MEG - void off_w(offs_t offset, u8 data); - u8 off_r(offs_t offset); - void fp_w(offs_t offset, u8 data); - u8 fp_r(offs_t offset); + void offset_w(offs_t offset, u8 data); + u8 offset_r(offs_t offset); + void const_w(offs_t offset, u8 data); + u8 const_r(offs_t offset); void meg_control_w(u8 data); u8 meg_control_r(); @@ -173,6 +217,28 @@ class swp00_device : public device_t, public device_sound_interface, public devi static s32 fpsub(s32 value, s32 step); static s32 fpapply(s32 value, s32 sample); static s32 lpffpapply(s32 value, s32 sample); + + s32 rext(int reg) const; + static s32 m7v(s32 value, s32 mult); + s32 m7(s32 value, int reg) const; + static s32 m9v(s32 value, s32 mult); + s32 m9(s32 value, int reg) const; + s32 lfo_get_step(int reg) const; + void lfo_step(u32 &phase, int reg) const; + static u32 lfo_wrap(s32 phase, s32 delta); + static s32 lfo_saturate(s32 phase); + s32 lfo_wrap_reg(s32 phase, int deltareg) const; + void filtered_lfo_step(s32 &position, s32 phase, int deltareg, int postdeltareg, int scalereg, int feedbackreg); + s32 lfo_mod(s32 phase, int scalereg) const; + s32 lfo_scale(s32 phase, int scalereg) const; + + s32 alfo(u32 phase, s32 delta_phase, int levelreg, int offsetreg, bool sub) const; + + s32 sx(int reg) const; + double sx7(int reg) const; + double sx9(int reg) const; + + static s32 saturate(s32 value); }; DECLARE_DEVICE_TYPE(SWP00, swp00_device) diff --git a/src/mame/yamaha/ymmu50.cpp b/src/mame/yamaha/ymmu50.cpp index e6192a05c432b..27c1b9dcc6b55 100644 --- a/src/mame/yamaha/ymmu50.cpp +++ b/src/mame/yamaha/ymmu50.cpp @@ -2,8 +2,16 @@ // copyright-holders:R. Belmont, Olivier Galibert /************************************************************************************* - Yamaha MU-50 : 16-voice polyphonic/multitimbral General MIDI/GS/XG tone modules - Preliminary driver by R. Belmont and O. Galibert + Yamaha MU-50 : 16-part, 32-note polyphonic/multitimbral General MIDI/GS/XG + tone module + Driver by R. Belmont and O. Galibert + + Cost-reduced version of the MU80, uses the SWP00 which is a single-chip + integrated version of the multi-chip SWP20. As a consequence has half the + voices, loses the parametric EQ and the AD inputs. The sample roms are also + smaller, and it only has one midi input. + + A wavetable version exists as the DB50XG. **************************************************************************************/ @@ -14,6 +22,7 @@ #include "cpu/h8/h83003.h" #include "mulcd.h" #include "sound/swp00.h" +#include "machine/nvram.h" #include "debugger.h" #include "speaker.h" @@ -53,11 +62,13 @@ class mu50_state : public driver_device mu50_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_mu50cpu(*this, "mu50cpu") + , m_nvram(*this, "ram") , m_swp00(*this, "swp00") , m_lcd(*this, "lcd") , m_ioport_o0(*this, "O0") , m_ioport_o1(*this, "O1") , m_ioport_o2(*this, "O2") + , m_ram(*this, "ram") { } void mu50(machine_config &config); @@ -70,11 +81,13 @@ class mu50_state : public driver_device }; required_device m_mu50cpu; + required_device m_nvram; required_device m_swp00; required_device m_lcd; required_ioport m_ioport_o0; required_ioport m_ioport_o1; required_ioport m_ioport_o2; + required_shared_ptr m_ram; u8 cur_p6, cur_pa, cur_pb, cur_pc; @@ -112,7 +125,7 @@ void mu50_state::machine_reset() void mu50_state::mu50_map(address_map &map) { map(0x000000, 0x07ffff).rom().region("mu50cpu", 0); - map(0x200000, 0x20ffff).ram(); // 64K work RAM + map(0x200000, 0x20ffff).ram().share(m_ram); // 64K work RAM map(0x400000, 0x4007ff).m(m_swp00, FUNC(swp00_device::map)); } @@ -131,7 +144,6 @@ u16 mu50_state::adc_al_r() // Put the host switch to pure midi u16 mu50_state::adc_midisw_r() { - // -> 20948a // 000-0bf: midi // 0c0-1ff: pc2 // 200-37f: pc1 @@ -236,6 +248,8 @@ void mu50_state::mu50(machine_config &config) m_mu50cpu->read_port7().set_constant(0); m_mu50cpu->read_port9().set_constant(0); + NVRAM(config, m_nvram, nvram_device::DEFAULT_NONE); + MULCD(config, m_lcd); SPEAKER(config, "lspeaker").front_left(); @@ -254,16 +268,23 @@ void mu50_state::mu50(machine_config &config) m_mu50cpu->write_sci_tx<1>().set(mdout, FUNC(midi_port_device::write_txd)); } +#define ROM_LOAD16_WORD_SWAP_BIOS(bios,name,offset,length,hash) \ + ROMX_LOAD(name, offset, length, hash, ROM_GROUPWORD | ROM_REVERSE | ROM_BIOS(bios)) + ROM_START( mu50 ) ROM_REGION( 0x80000, "mu50cpu", 0 ) - ROM_LOAD16_WORD_SWAP( "yamaha_mu50.bin", 0x000000, 0x080000, CRC(507168ad) SHA1(58c41f10d292cac35ef0e8f93029fbc4685df586) ) - - ROM_REGION( 0x400000, "swp00", ROMREGION_ERASE00 ) - ROM_LOAD( "xq730b0.ic9", 0x000000, 0x200000, CRC(d4adbc7e) SHA1(32f653c7644d060f5a6d63a435ae3a7412386d92) BAD_DUMP) // Use the db50xg roms for now - ROM_LOAD( "xq731b0.ic10", 0x200000, 0x200000, CRC(7b68f475) SHA1(adf68689b4842ec5bc9b0ea1bb99cf66d2dec4de) BAD_DUMP) // Note that they may be identical to the mu50 ones + ROM_SYSTEM_BIOS( 0, "bios0", "xr174c0 (v1.05, Aug. 21, 1995)" ) + ROM_LOAD16_WORD_SWAP_BIOS( 0, "xr174c0.ic7", 0x000000, 0x080000, CRC(902520a4) SHA1(9ca892920598f9fdf08544dac4c0e54e7d46ee3c) ) + ROM_SYSTEM_BIOS( 1, "bios1", "? (v1.04, May 22, 1995)" ) + ROM_LOAD16_WORD_SWAP_BIOS( 1, "yamaha_mu50.bin", 0x000000, 0x080000, CRC(507168ad) SHA1(58c41f10d292cac35ef0e8f93029fbc4685df586) ) + + ROM_REGION( 0x400000, "swp00", 0 ) + // Identical to the db50xg roms + ROM_LOAD( "xq057c0.ic18", 0x000000, 0x200000, CRC(d4adbc7e) SHA1(32f653c7644d060f5a6d63a435ae3a7412386d92) ) + ROM_LOAD( "xq058c0.ic19", 0x200000, 0x200000, CRC(7b68f475) SHA1(adf68689b4842ec5bc9b0ea1bb99cf66d2dec4de) ) ROM_END } // anonymous namespace -CONS( 1995, mu50, 0, 0, mu50, mu50, mu50_state, empty_init, "Yamaha", "MU50", MACHINE_NOT_WORKING ) +CONS( 1995, mu50, 0, 0, mu50, mu50, mu50_state, empty_init, "Yamaha", "MU50", 0 )