// license:BSD-3-Clause
// copyright-holders:superctr, Valley Bell
/***************************************************************************
Capcom QSound DL-1425 (HLE)
===========================
Driver by superctr with thanks to Valley Bell.
Based on disassembled DSP code.
Links:
https://siliconpr0n.org/map/capcom/dl-1425
***************************************************************************/
#include "emu.h"
#include "qsoundhle.h"
#include <algorithm>
#include <limits>
// device type definition
DEFINE_DEVICE_TYPE(QSOUND_HLE, qsound_hle_device, "qsound_hle", "QSound (HLE)")
// DSP internal ROM region
ROM_START( qsound_hle )
ROM_REGION16_LE( 0x2000, "dsp", 0 )
// removing WORD_SWAP from original definition
ROM_LOAD16_WORD( "dl-1425.bin", 0x0000, 0x2000, CRC(d6cf5ef5) SHA1(555f50fe5cdf127619da7d854c03f4a244a0c501) )
ROM_IGNORE( 0x4000 )
ROM_END
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// qsound_hle_device - constructor
//-------------------------------------------------
qsound_hle_device::qsound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, QSOUND_HLE, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, device_rom_interface(mconfig, *this, 24)
, m_stream(nullptr)
, m_dsp_rom(*this, "dsp")
, m_data_latch(0)
{
}
//-------------------------------------------------
// rom_bank_updated - the rom bank has changed
//-------------------------------------------------
void qsound_hle_device::rom_bank_updated()
{
m_stream->update();
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void qsound_hle_device::device_start()
{
m_stream = stream_alloc(0, 2, clock() / 2 / 1248); // DSP program uses 1248 machine cycles per iteration
init_register_map();
// state save
// PCM registers
for (int j = 0; j < 16; j++) // PCM voices
{
save_item(NAME(m_voice[j].m_bank), j);
save_item(NAME(m_voice[j].m_addr), j);
save_item(NAME(m_voice[j].m_phase), j);
save_item(NAME(m_voice[j].m_rate), j);
save_item(NAME(m_voice[j].m_loop_len), j);
save_item(NAME(m_voice[j].m_end_addr), j);
save_item(NAME(m_voice[j].m_volume), j);
save_item(NAME(m_voice[j].m_echo), j);
}
for (int j = 0; j < 3; j++) // ADPCM voices
{
save_item(NAME(m_adpcm[j].m_start_addr), j);
save_item(NAME(m_adpcm[j].m_end_addr), j);
save_item(NAME(m_adpcm[j].m_bank), j);
save_item(NAME(m_adpcm[j].m_volume), j);
save_item(NAME(m_adpcm[j].m_flag), j);
save_item(NAME(m_adpcm[j].m_cur_vol), j);
save_item(NAME(m_adpcm[j].m_step_size), j);
save_item(NAME(m_adpcm[j].m_cur_addr), j);
}
// PCM voices
save_item(NAME(m_voice_pan));
// QSound registers
save_item(NAME(m_echo.m_end_pos));
save_item(NAME(m_echo.m_feedback));
save_item(NAME(m_echo.m_length));
save_item(NAME(m_echo.m_last_sample));
save_item(NAME(m_echo.m_delay_line));
save_item(NAME(m_echo.m_delay_pos));
for (int j = 0; j < 2; j++) // left, right
{
save_item(NAME(m_filter[j].m_tap_count), j);
save_item(NAME(m_filter[j].m_delay_pos), j);
save_item(NAME(m_filter[j].m_table_pos), j);
save_item(NAME(m_filter[j].m_taps), j);
save_item(NAME(m_filter[j].m_delay_line), j);
save_item(NAME(m_alt_filter[j].m_tap_count), j);
save_item(NAME(m_alt_filter[j].m_delay_pos), j);
save_item(NAME(m_alt_filter[j].m_table_pos), j);
save_item(NAME(m_alt_filter[j].m_taps), j);
save_item(NAME(m_alt_filter[j].m_delay_line), j);
save_item(NAME(m_wet[j].m_delay), j);
save_item(NAME(m_wet[j].m_volume), j);
save_item(NAME(m_wet[j].m_write_pos), j);
save_item(NAME(m_wet[j].m_read_pos), j);
save_item(NAME(m_wet[j].m_delay_line), j);
save_item(NAME(m_dry[j].m_delay), j);
save_item(NAME(m_dry[j].m_volume), j);
save_item(NAME(m_dry[j].m_write_pos), j);
save_item(NAME(m_dry[j].m_read_pos), j);
save_item(NAME(m_dry[j].m_delay_line), j);
}
save_item(NAME(m_state));
save_item(NAME(m_next_state));
save_item(NAME(m_delay_update));
save_item(NAME(m_state_counter));
save_item(NAME(m_ready_flag));
save_item(NAME(m_data_latch));
save_item(NAME(m_out));
}
//-------------------------------------------------
// rom_region - return a pointer to the device's
// internal ROM region
//-------------------------------------------------
const tiny_rom_entry *qsound_hle_device::device_rom_region() const
{
return ROM_NAME( qsound_hle );
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void qsound_hle_device::device_reset()
{
m_ready_flag = 0;
m_out[0] = m_out[1] = 0;
m_state = STATE_BOOT;
m_state_counter = 0;
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void qsound_hle_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
// Clear the buffers
std::fill_n(outputs[0], samples, 0);
std::fill_n(outputs[1], samples, 0);
for (int i = 0; i < samples; i ++)
{
update_sample();
outputs[0][i] = m_out[0];
outputs[1][i] = m_out[1];
}
}
WRITE8_MEMBER(qsound_hle_device::qsound_w)
{
switch (offset)
{
case 0:
m_data_latch = (m_data_latch & 0x00ff) | (data << 8);
break;
case 1:
m_data_latch = (m_data_latch & 0xff00) | data;
break;
case 2:
m_stream->update();
write_data(data, m_data_latch);
break;
default:
logerror("%s: qsound_w %d = %02x\n", machine().describe_context(), offset, data);
break;
}
}
READ8_MEMBER(qsound_hle_device::qsound_r)
{
// ready bit (0x00 = busy, 0x80 == ready)
m_stream->update();
return m_ready_flag;
}
void qsound_hle_device::write_data(uint8_t address, uint16_t data)
{
uint16_t *destination = m_register_map[address];
if (destination)
*destination = data;
m_ready_flag = 0;
}
void qsound_hle_device::init_register_map()
{
// unused registers
std::fill(std::begin(m_register_map), std::end(m_register_map), nullptr);
// PCM registers
for (int i = 0; i < 16; i++) // PCM voices
{
m_register_map[(i << 3) + 0] = (uint16_t*)&m_voice[(i + 1) % 16].m_bank; // Bank applies to the next channel
m_register_map[(i << 3) + 1] = (uint16_t*)&m_voice[i].m_addr; // Current sample position and start position.
m_register_map[(i << 3) + 2] = (uint16_t*)&m_voice[i].m_rate; // 4.12 fixed point decimal.
m_register_map[(i << 3) + 3] = (uint16_t*)&m_voice[i].m_phase;
m_register_map[(i << 3) + 4] = (uint16_t*)&m_voice[i].m_loop_len;
m_register_map[(i << 3) + 5] = (uint16_t*)&m_voice[i].m_end_addr;
m_register_map[(i << 3) + 6] = (uint16_t*)&m_voice[i].m_volume;
m_register_map[(i << 3) + 7] = nullptr; // unused
m_register_map[i + 0x80] = (uint16_t*)&m_voice_pan[i];
m_register_map[i + 0xba] = (uint16_t*)&m_voice[i].m_echo;
}
// ADPCM registers
for (int i = 0; i < 3; i++) // ADPCM voices
{
// ADPCM sample rate is fixed to 8khz. (one channel is updated every third sample)
m_register_map[(i << 2) + 0xca] = (uint16_t*)&m_adpcm[i].m_start_addr;
m_register_map[(i << 2) + 0xcb] = (uint16_t*)&m_adpcm[i].m_end_addr;
m_register_map[(i << 2) + 0xcc] = (uint16_t*)&m_adpcm[i].m_bank;
m_register_map[(i << 2) + 0xcd] = (uint16_t*)&m_adpcm[i].m_volume;
m_register_map[i + 0xd6] = (uint16_t*)&m_adpcm[i].m_flag; // non-zero to start ADPCM playback
m_register_map[i + 0x90] = (uint16_t*)&m_voice_pan[16 + i];
}
// QSound registers
m_register_map[0x93] = (uint16_t*)&m_echo.m_feedback;
m_register_map[0xd9] = (uint16_t*)&m_echo.m_end_pos;
m_register_map[0xe2] = (uint16_t*)&m_delay_update; // non-zero to update delays
m_register_map[0xe3] = (uint16_t*)&m_next_state;
for (int i = 0; i < 2; i++) // left, right
{
// Wet
m_register_map[(i << 1) + 0xda] = (uint16_t*)&m_filter[i].m_table_pos;
m_register_map[(i << 1) + 0xde] = (uint16_t*)&m_wet[i].m_delay;
m_register_map[(i << 1) + 0xe4] = (uint16_t*)&m_wet[i].m_volume;
// Dry
m_register_map[(i << 1) + 0xdb] = (uint16_t*)&m_alt_filter[i].m_table_pos;
m_register_map[(i << 1) + 0xdf] = (uint16_t*)&m_dry[i].m_delay;
m_register_map[(i << 1) + 0xe5] = (uint16_t*)&m_dry[i].m_volume;
}
}
int16_t qsound_hle_device::read_sample(uint16_t bank, uint16_t address)
{
bank &= 0x7FFF;
const uint32_t rom_addr = (bank << 16) | (address << 0);
const uint8_t sample_data = read_byte(rom_addr);
return (int16_t)(sample_data << 8); // bit0-7 is tied to ground
}
/********************************************************************/
// updates one DSP sample
void qsound_hle_device::update_sample()
{
switch (m_state)
{
default:
case STATE_INIT1:
case STATE_INIT2:
return state_init();
case STATE_REFRESH1:
return state_refresh_filter_1();
case STATE_REFRESH2:
return state_refresh_filter_2();
case STATE_NORMAL1:
case STATE_NORMAL2:
return state_normal_update();
}
}
// Initialization routine
void qsound_hle_device::state_init()
{
int mode = (m_state == STATE_INIT2) ? 1 : 0;
// we're busy for 4 samples, including the filter refresh.
if (m_state_counter >= 2)
{
m_state_counter = 0;
m_state = m_next_state;
return;
}
else if (m_state_counter == 1)
{
m_state_counter++;
return;
}
std::fill(std::begin(m_voice), std::end(m_voice), qsound_voice());
std::fill(std::begin(m_adpcm), std::end(m_adpcm), qsound_adpcm());
std::fill(std::begin(m_filter), std::end(m_filter), qsound_fir());
std::fill(std::begin(m_alt_filter), std::end(m_alt_filter), qsound_fir());
std::fill(std::begin(m_wet), std::end(m_wet), qsound_delay());
std::fill(std::begin(m_dry), std::end(m_dry), qsound_delay());
m_echo = qsound_echo();
for (int i = 0; i < 19; i++)
{
m_voice_pan[i] = DATA_PAN_TAB + 0x10;
m_voice_output[i] = 0;
}
for (int i = 0; i < 16; i++)
m_voice[i].m_bank = 0x8000;
for (int i = 0; i < 3; i++)
m_adpcm[i].m_bank = 0x8000;
if (mode == 0)
{
// mode 1
m_wet[0].m_delay = 0;
m_dry[0].m_delay = 46;
m_wet[1].m_delay = 0;
m_dry[1].m_delay = 48;
m_filter[0].m_table_pos = DATA_FILTER_TAB + (FILTER_ENTRY_SIZE*1);
m_filter[1].m_table_pos = DATA_FILTER_TAB + (FILTER_ENTRY_SIZE*2);
m_echo.m_end_pos = DELAY_BASE_OFFSET + 6;
m_next_state = STATE_REFRESH1;
}
else
{
// mode 2
m_wet[0].m_delay = 1;
m_dry[0].m_delay = 0;
m_wet[1].m_delay = 0;
m_dry[1].m_delay = 0;
m_filter[0].m_table_pos = 0xf73;
m_filter[1].m_table_pos = 0xfa4;
m_alt_filter[0].m_table_pos = 0xf73;
m_alt_filter[1].m_table_pos = 0xfa4;
m_echo.m_end_pos = DELAY_BASE_OFFSET2 + 6;
m_next_state = STATE_REFRESH2;
}
m_wet[0].m_volume = 0x3fff;
m_dry[0].m_volume = 0x3fff;
m_wet[1].m_volume = 0x3fff;
m_dry[1].m_volume = 0x3fff;
m_delay_update = 1;
m_ready_flag = 0;
m_state_counter = 1;
}
// Updates filter parameters for mode 1
void qsound_hle_device::state_refresh_filter_1()
{
for (int ch = 0; ch < 2; ch++)
{
m_filter[ch].m_delay_pos = 0;
m_filter[ch].m_tap_count = 95;
for (int i = 0; i < 95; i++)
m_filter[ch].m_taps[i] = read_dsp_rom(m_filter[ch].m_table_pos + i);
}
m_state = m_next_state = STATE_NORMAL1;
}
// Updates filter parameters for mode 2
void qsound_hle_device::state_refresh_filter_2()
{
for (int ch = 0; ch < 2; ch++)
{
m_filter[ch].m_delay_pos = 0;
m_filter[ch].m_tap_count = 45;
for (int i = 0; i < 45; i++)
m_filter[ch].m_taps[i] = (int16_t)read_dsp_rom(m_filter[ch].m_table_pos + i);
m_alt_filter[ch].m_delay_pos = 0;
m_alt_filter[ch].m_tap_count = 44;
for (int i = 0; i < 44; i++)
m_alt_filter[ch].m_taps[i] = (int16_t)read_dsp_rom(m_alt_filter[ch].m_table_pos + i);
}
m_state = m_next_state = STATE_NORMAL2;
}
// Updates a PCM voice. There are 16 voices, each are updated every sample
// with full rate and volume control.
int16_t qsound_hle_device::qsound_voice::update(qsound_hle_device &dsp, int32_t *echo_out)
{
// Read sample from rom and apply volume
const int16_t output = (m_volume * dsp.read_sample(m_bank, m_addr)) >> 14;
*echo_out += (output * m_echo) << 2;
// Add delta to the phase and loop back if required
int32_t new_phase = m_rate + ((m_addr << 12) | (m_phase >> 4));
if ((new_phase >> 12) >= m_end_addr)
new_phase -= (m_loop_len << 12);
new_phase = std::min<int32_t>(std::max<int32_t>(new_phase, -0x8000000), 0x7FFFFFF);
m_addr = new_phase >> 12;
m_phase = (new_phase << 4)&0xffff;
return output;
}
// Updates an ADPCM voice. There are 3 voices, one is updated every sample
// (effectively making the ADPCM rate 1/3 of the master sample rate), and
// volume is set when starting samples only.
// The ADPCM algorithm is supposedly similar to Yamaha ADPCM. It also seems
// like Capcom never used it, so this was not emulated in the earlier QSound
// emulators.
int16_t qsound_hle_device::qsound_adpcm::update(qsound_hle_device &dsp, int16_t curr_sample, int nibble)
{
int8_t step;
if (!nibble)
{
// Mute voice when it reaches the end address.
if (m_cur_addr == m_end_addr)
m_cur_vol = 0;
// Playback start flag
if (m_flag)
{
curr_sample = 0;
m_flag = 0;
m_step_size = 10;
m_cur_vol = m_volume;
m_cur_addr = m_start_addr;
}
// get top nibble
step = dsp.read_sample(m_bank, m_cur_addr) >> 8;
}
else
{
// get bottom nibble
step = dsp.read_sample(m_bank, m_cur_addr++) >> 4;
}
// shift with sign extend
step >>= 4;
// delta = (0.5 + abs(step)) * m_step_size
int32_t delta = ((1 + abs(step << 1)) * m_step_size) >> 1;
if (step <= 0)
delta = -delta;
delta += curr_sample;
delta = std::min<int32_t>(std::max<int32_t>(delta, -32768), 32767);
m_step_size = (dsp.read_dsp_rom(DATA_ADPCM_TAB + 8 + step) * m_step_size) >> 6;
m_step_size = std::min<int16_t>(std::max<int16_t>(m_step_size, 1), 2000);
return (delta * m_cur_vol) >> 16;
}
// The echo effect is pretty simple. A moving average filter is used on
// the output from the delay line to smooth samples over time.
int16_t qsound_hle_device::qsound_echo::apply(int32_t input)
{
// get average of last 2 samples from the delay line
int32_t old_sample = m_delay_line[m_delay_pos];
const int32_t last_sample = m_last_sample;
m_last_sample = old_sample;
old_sample = (old_sample + last_sample) >> 1;
// add current sample to the delay line
int32_t new_sample = input + ((old_sample * m_feedback) << 2);
m_delay_line[m_delay_pos++] = new_sample >> 16;
if (m_delay_pos >= m_length)
m_delay_pos = 0;
return old_sample;
}
// Process a sample update
void qsound_hle_device::state_normal_update()
{
m_ready_flag = 0x80;
// recalculate echo length
if (m_state == STATE_NORMAL2)
m_echo.m_length = m_echo.m_end_pos - DELAY_BASE_OFFSET2;
else
m_echo.m_length = m_echo.m_end_pos - DELAY_BASE_OFFSET;
m_echo.m_length = std::min<int16_t>(std::max<int16_t>(m_echo.m_length, 0), 1024);
// update PCM voices
int32_t echo_input = 0;
for (int i = 0; i < 16; i++)
m_voice_output[i] = m_voice[i].update(*this, &echo_input);
// update ADPCM voices (one every third sample)
const int adpcm_voice = m_state_counter % 3;
m_voice_output[16 + adpcm_voice] = m_adpcm[adpcm_voice].update(*this, m_voice_output[16 + adpcm_voice], m_state_counter / 3);
int16_t echo_output = m_echo.apply(echo_input);
// now, we do the magic stuff
for (int ch = 0; ch < 2; ch++)
{
// Echo is output on the unfiltered component of the left channel and
// the filtered component of the right channel.
int32_t wet = (ch == 1) ? echo_output << 16 : 0;
int32_t dry = (ch == 0) ? echo_output << 16 : 0;
for (int i = 0; i < 19; i++)
{
uint16_t pan_index = m_voice_pan[i] + (ch * PAN_TABLE_CH_OFFSET);
// Apply different volume tables on the dry and wet inputs.
dry -= (m_voice_output[i] * (int16_t)read_dsp_rom(pan_index + PAN_TABLE_DRY)) << 2;
wet -= (m_voice_output[i] * (int16_t)read_dsp_rom(pan_index + PAN_TABLE_WET)) << 2;
}
// Apply FIR filter on 'wet' input
wet = m_filter[ch].apply(wet >> 16);
// in mode 2, we do this on the 'dry' input too
if (m_state == STATE_NORMAL2)
dry = m_alt_filter[ch].apply(dry >> 16);
// output goes through a delay line and attenuation
int32_t output = (m_wet[ch].apply(wet) + m_dry[ch].apply(dry)) << 2;
// DSP round function
output = (output + 0x8000) & ~0xffff;
m_out[ch] = output >> 16;
if (m_delay_update)
{
m_wet[ch].update();
m_dry[ch].update();
}
}
m_delay_update = 0;
// after 6 samples, the next state is executed.
m_state_counter++;
if (m_state_counter > 5)
{
m_state_counter = 0;
m_state = m_next_state;
}
}
// Apply the FIR filter used as the Q1 transfer function
int32_t qsound_hle_device::qsound_fir::apply(int16_t input)
{
int32_t output = 0, tap = 0;
for (; tap < (m_tap_count - 1); tap++)
{
output -= (m_taps[tap] * m_delay_line[m_delay_pos++]) << 2;
if (m_delay_pos >= m_tap_count - 1)
m_delay_pos = 0;
}
output -= (m_taps[tap] * input) << 2;
m_delay_line[m_delay_pos++] = input;
if (m_delay_pos >= m_tap_count - 1)
m_delay_pos = 0;
return output;
}
// Apply delay line and component volume
int32_t qsound_hle_device::qsound_delay::apply(const int32_t input)
{
m_delay_line[m_write_pos++] = input >> 16;
if (m_write_pos >= 51)
m_write_pos = 0;
const int32_t output = m_delay_line[m_read_pos++] * m_volume;
if (m_read_pos >= 51)
m_read_pos = 0;
return output;
}
// Update the delay read position to match new delay length
void qsound_hle_device::qsound_delay::update()
{
const int16_t new_read_pos = (m_write_pos - m_delay) % 51;
if (new_read_pos < 0)
m_read_pos = new_read_pos + 51;
else
m_read_pos = new_read_pos;
}