mirror of
https://github.com/holub/mame
synced 2025-04-22 08:22:15 +03:00
-qsound_hle: Updated based on actual DSP16 code to give identical output. [superctr, ValleyBell]
This commit is contained in:
parent
da9c7bb7b3
commit
484a29e60b
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user