-qsound_hle: Updated based on actual DSP16 code to give identical output. [superctr, ValleyBell]

This commit is contained in:
superctr 2018-08-02 23:05:25 +02:00 committed by mooglyguy
parent da9c7bb7b3
commit 484a29e60b
3 changed files with 629 additions and 138 deletions

View File

@ -280,11 +280,11 @@ void qsound_device::dsp_io_map(address_map &map)
READ16_MEMBER(qsound_device::dsp_sample_r)
{
// FIXME: DSP16 doesn't like bytes, only signed words - should this zero-pad or byte-smear?
// on CPS2, bit 0-7 of external ROM data is tied to ground
u8 const byte(read_byte((u32(m_rom_bank) << 16) | m_rom_offset));
if (!machine().side_effects_disabled())
m_rom_bank = (m_rom_bank & 0x8000U) | offset;
return (u16(byte) << 8) | u16(byte);
return u16(byte) << 8;
}
WRITE_LINE_MEMBER(qsound_device::dsp_ock_w)

View File

@ -1,25 +1,13 @@
// license:BSD-3-Clause
// copyright-holders:Paul Leaman, Miguel Angel Horna
// copyright-holders:superctr, Valley Bell
/***************************************************************************
Capcom System QSound (HLE)
Capcom QSound DL-1425 (HLE)
===========================
Driver by Paul Leaman and Miguel Angel Horna
Driver by superctr with thanks to Valley Bell.
A 16 channel stereo sample player.
QSpace position is simulated by panning the sound in the stereo space.
Many thanks to CAB (the author of Amuse), without whom this probably would
never have been finished.
TODO:
- hook up the DSP!
- is master volume really linear?
- understand higher bits of reg 0
- understand reg 9
- understand other writes to $90-$ff area
Based on disassembled DSP code.
Links:
https://siliconpr0n.org/map/capcom/dl-1425
@ -32,19 +20,16 @@
#include <algorithm>
#include <limits>
namespace {
constexpr u32 saturate(s64 val)
{
return std::min<s64>(std::max<s64>(val, std::numeric_limits<s32>::min()), std::numeric_limits<s32>::max());
}
} // anonymous namespace
// 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
@ -54,16 +39,16 @@ DEFINE_DEVICE_TYPE(QSOUND_HLE, qsound_hle_device, "qsound_hle", "QSound (HLE)")
// qsound_hle_device - constructor
//-------------------------------------------------
qsound_hle_device::qsound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
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_data(0)
, m_dsp_rom(*this, "dsp")
, m_data_latch(0)
{
}
//-------------------------------------------------
// rom_bank_updated - the rom bank has changed
//-------------------------------------------------
@ -73,7 +58,6 @@ void qsound_hle_device::rom_bank_updated()
m_stream->update();
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
@ -82,25 +66,90 @@ void qsound_hle_device::device_start()
{
m_stream = stream_alloc(0, 2, clock() / 2 / 1248); // DSP program uses 1248 machine cycles per iteration
// create pan table
for (int i = 0; i < 33; i++)
m_pan_table[i] = (int)((256 / sqrt(32.0)) * sqrt((double)i));
// init sound regs
memset(m_channel, 0, sizeof(m_channel));
for (int adr = 0x80; adr < 0x90; adr++)
write_data(adr, 0x120);
init_register_map();
// state save
for (int i = 0; i < 16; i++)
// PCM registers
for (int j = 0; j < 16; j++) // PCM voices
{
save_item(NAME(m_channel[i].reg), i);
save_item(NAME(m_channel[i].lvol), i);
save_item(NAME(m_channel[i].rvol), i);
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
@ -108,11 +157,12 @@ void qsound_hle_device::device_start()
void qsound_hle_device::device_reset()
{
for (qsound_channel &ch : m_channel)
std::fill(std::begin(ch.reg), std::end(ch.reg), 0U);
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
//-------------------------------------------------
@ -120,36 +170,14 @@ void qsound_hle_device::device_reset()
void qsound_hle_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
// Clear the buffers
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
memset(outputs[1], 0, samples * sizeof(*outputs[1]));
std::fill_n(outputs[0], samples, 0);
std::fill_n(outputs[1], samples, 0);
for (unsigned n = 0; ARRAY_LENGTH(m_channel) > n; ++n)
for (int i = 0; i < samples; i ++)
{
qsound_channel &ch(m_channel[n]);
stream_sample_t *lmix(outputs[0]);
stream_sample_t *rmix(outputs[1]);
// Go through the buffer and add voice contributions
offs_t const bank(m_channel[(n + ARRAY_LENGTH(m_channel) - 1) & (ARRAY_LENGTH(m_channel) - 1)].reg[0] & 0x7fff);
for (int i = 0; i < samples; i++)
{
// current sample address (bank comes from previous channel)
offs_t const addr(ch.reg[1] | (bank << 16));
// update based on playback rate
s64 updated(s32(u32(ch.reg[2]) << 4) + s32((u32(ch.reg[1]) << 16) | ch.reg[3]));
ch.reg[3] = u16(saturate(updated));
if (updated >= s32(u32(ch.reg[5]) << 16))
updated -= s32(u32(ch.reg[4]) << 16);
ch.reg[1] = u16(saturate(updated) >> 16);
// get the scaled sample
s32 const scaled(s32(s16(ch.reg[6])) * read_sample(addr));
// apply simple panning
*lmix++ += (((scaled >> 8) * ch.lvol) >> 14);
*rmix++ += (((scaled >> 8) * ch.rvol) >> 14);
}
update_sample();
outputs[0][i] = m_out[0];
outputs[1][i] = m_out[1];
}
}
@ -159,16 +187,16 @@ WRITE8_MEMBER(qsound_hle_device::qsound_w)
switch (offset)
{
case 0:
m_data = (m_data & 0x00ff) | (data << 8);
m_data_latch = (m_data_latch & 0x00ff) | (data << 8);
break;
case 1:
m_data = (m_data & 0xff00) | data;
m_data_latch = (m_data_latch & 0xff00) | data;
break;
case 2:
m_stream->update();
write_data(data, m_data);
write_data(data, m_data_latch);
break;
default:
@ -180,70 +208,414 @@ WRITE8_MEMBER(qsound_hle_device::qsound_w)
READ8_MEMBER(qsound_hle_device::qsound_r)
{
/* Port ready bit (0x80 if ready) */
return 0x80;
// ready bit (0x00 = busy, 0x80 == ready)
m_stream->update();
return m_ready_flag;
}
void qsound_hle_device::write_data(u8 address, u16 data)
void qsound_hle_device::write_data(uint8_t address, uint16_t data)
{
int ch = 0, reg;
uint16_t *destination = m_register_map[address];
if (destination)
*destination = data;
m_ready_flag = 0;
}
if (address < 0x80)
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
{
ch = address >> 3;
reg = address & 7;
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;
}
else if (address < 0x90)
// ADPCM registers
for (int i = 0; i < 3; i++) // ADPCM voices
{
ch = address & 0xf;
reg = 8;
// 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];
}
else if (address >= 0xba && address < 0xca)
// 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
{
ch = address - 0xba;
reg = 9;
// 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
{
// unknown
reg = address;
// 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;
}
switch (reg)
{
case 0: // bank
case 1: // current sample
case 2: // playback rate
case 3: // sample interval counter
case 4: // loop offset
case 5: // end sample
case 6: // channel volume
case 7: // unused
m_channel[ch].reg[reg] = data;
break;
m_wet[0].m_volume = 0x3fff;
m_dry[0].m_volume = 0x3fff;
m_wet[1].m_volume = 0x3fff;
m_dry[1].m_volume = 0x3fff;
case 8:
{
// panning (left=0x0110, centre=0x0120, right=0x0130)
// looks like it doesn't write other values than that
int pan = (data & 0x3f) - 0x10;
if (pan > 0x20)
pan = 0x20;
if (pan < 0)
pan = 0;
m_delay_update = 1;
m_ready_flag = 0;
m_state_counter = 1;
}
m_channel[ch].rvol = m_pan_table[pan];
m_channel[ch].lvol = m_pan_table[0x20 - pan];
break;
// 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);
}
case 9:
// unknown
break;
m_state = m_next_state = STATE_NORMAL1;
}
default:
//logerror("%s: write_data %02x = %04x\n", machine().describe_context(), address, data);
break;
// 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_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;
}

View File

@ -1,8 +1,8 @@
// license:BSD-3-Clause
// copyright-holders:Paul Leaman, Miguel Angel Horna
// copyright-holders:superctr, Valley Bell
/*********************************************************
Capcom System QSound (HLE)
Capcom QSound DL-1425 (HLE)
*********************************************************/
#ifndef MAME_SOUND_QSOUNDHLE_H
@ -17,13 +17,14 @@ class qsound_hle_device : public device_t, public device_sound_interface, public
{
public:
// default 60MHz clock (divided by 2 for DSP core clock, and then by 1248 for sample rate)
qsound_hle_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 60'000'000);
qsound_hle_device(machine_config const &mconfig, char const *tag, device_t *owner, uint32_t clock = 60'000'000);
DECLARE_WRITE8_MEMBER(qsound_w);
DECLARE_READ8_MEMBER(qsound_r);
protected:
// device_t implementation
tiny_rom_entry const *device_rom_region() const override;
virtual void device_start() override;
virtual void device_reset() override;
@ -34,23 +35,141 @@ protected:
virtual void rom_bank_updated() override;
private:
// DSP ROM sample map
enum {
DATA_PAN_TAB = 0x110,
DATA_ADPCM_TAB = 0x9dc,
DATA_FILTER_TAB = 0xd53, // dual filter mode, 5 tables * 95 taps each
DATA_FILTER_TAB2 = 0xf2e, // overlapping data (95+15+95)
STATE_BOOT = 0x000,
STATE_INIT1 = 0x288,
STATE_INIT2 = 0x61a,
STATE_REFRESH1 = 0x039,
STATE_REFRESH2 = 0x04f,
STATE_NORMAL1 = 0x314,
STATE_NORMAL2 = 0x6b2
};
const uint16_t PAN_TABLE_DRY = 0;
const uint16_t PAN_TABLE_WET = 98;
const uint16_t PAN_TABLE_CH_OFFSET = 196;
const uint16_t FILTER_ENTRY_SIZE = 95;
const uint16_t DELAY_BASE_OFFSET = 0x554;
const uint16_t DELAY_BASE_OFFSET2 = 0x53c;
struct qsound_voice {
uint16_t m_bank = 0;
int16_t m_addr = 0; // top word is the sample address
uint16_t m_phase = 0;
uint16_t m_rate = 0;
int16_t m_loop_len = 0;
int16_t m_end_addr = 0;
int16_t m_volume = 0;
int16_t m_echo = 0;
int16_t update(qsound_hle_device &dsp, int32_t *echo_out);
};
struct qsound_adpcm {
uint16_t m_start_addr = 0;
uint16_t m_end_addr = 0;
uint16_t m_bank = 0;
int16_t m_volume = 0;
uint16_t m_flag = 0;
int16_t m_cur_vol = 0;
int16_t m_step_size = 0;
uint16_t m_cur_addr = 0;
int16_t update(qsound_hle_device &dsp, int16_t curr_sample, int nibble);
};
// Q1 Filter
struct qsound_fir {
int m_tap_count = 0; // usually 95
int m_delay_pos = 0;
uint16_t m_table_pos = 0;
int16_t m_taps[95] = { 0 };
int16_t m_delay_line[95] = { 0 };
int32_t apply(int16_t input);
};
// Delay line
struct qsound_delay {
int16_t m_delay = 0;
int16_t m_volume = 0;
int16_t m_write_pos = 0;
int16_t m_read_pos = 0;
int16_t m_delay_line[51] = { 0 };
int32_t apply(const int32_t input);
void update();
};
struct qsound_echo {
uint16_t m_end_pos = 0;
int16_t m_feedback = 0;
int16_t m_length = 0;
int16_t m_last_sample = 0;
int16_t m_delay_line[1024] = { 0 };
int16_t m_delay_pos = 0;
int16_t apply(int32_t input);
};
// MAME resources
sound_stream *m_stream;
required_region_ptr<uint16_t> m_dsp_rom;
struct qsound_channel
{
u16 reg[8]; // channel control registers
uint16_t m_data_latch;
int16_t m_out[2];
// work variables
int lvol; // left volume
int rvol; // right volume
} m_channel[16];
qsound_voice m_voice[16];
qsound_adpcm m_adpcm[3];
int m_pan_table[33]; // pan volume table
u16 m_data; // register latch data
uint16_t m_voice_pan[16+3];
int16_t m_voice_output[16+3];
inline s16 read_sample(u32 offset) { return u16(read_byte(offset)) << 8; }
void write_data(u8 address, u16 data);
qsound_echo m_echo;
qsound_fir m_filter[2];
qsound_fir m_alt_filter[2];
qsound_delay m_wet[2];
qsound_delay m_dry[2];
uint16_t m_state;
uint16_t m_next_state;
uint16_t m_delay_update;
int m_state_counter;
int m_ready_flag;
uint16_t *m_register_map[256];
inline uint16_t read_dsp_rom(uint16_t addr) { return m_dsp_rom[addr&0xfff]; }
void write_data(uint8_t addr, uint16_t data);
uint16_t read_data(uint8_t addr);
void init_register_map();
void update_sample();
// DSP states
void state_init();
void state_refresh_filter_1();
void state_refresh_filter_2();
void state_normal_update();
// sub functions
int16_t read_sample(uint16_t bank, uint16_t address);
inline int16_t pcm_update(struct qsound_voice *v, int32_t *echo_out);
inline void adpcm_update(int voice_no, int nibble);
};
DECLARE_DEVICE_TYPE(QSOUND_HLE, qsound_hle_device)