1849 lines
58 KiB
C++
1849 lines
58 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Olivier Galibert
|
|
|
|
// Yamaha SWP00, rompler/dsp combo
|
|
|
|
#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)
|
|
{
|
|
}
|
|
|
|
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_pre_change()
|
|
{
|
|
m_stream->update();
|
|
}
|
|
|
|
void swp00_device::map(address_map &map)
|
|
{
|
|
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));
|
|
}
|
|
|
|
|
|
// Voice control
|
|
|
|
void swp00_device::slot8_w(offs_t offset, u8 data)
|
|
{
|
|
if(data == 0x80)
|
|
return;
|
|
logerror("slot8[%02x] = %02x\n", offset >> 1, 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)
|
|
{
|
|
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_sample_pos[chan] = 0;
|
|
|
|
m_active[chan] = true;
|
|
m_decay[chan] = false;
|
|
m_decay_done[chan] = false;
|
|
|
|
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;
|
|
|
|
m_lpf_value[chan] = m_lpf_target_value[chan];
|
|
m_lpf_timer[chan] = 0x4000000;
|
|
m_lpf_ha[chan] = 0;
|
|
m_lpf_hb[chan] = 0;
|
|
|
|
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
|
|
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;
|
|
}
|
|
|
|
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
|
|
|
|
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, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &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;
|
|
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);
|
|
|
|
outputs[0].put_int(i, dry_l, 32768);
|
|
outputs[1].put_int(i, dry_r, 32768);
|
|
|
|
m_buffer_offset --;
|
|
}
|
|
}
|