From 484a29e60ba5cc805122544cc72c75d293a5c9ce Mon Sep 17 00:00:00 2001 From: superctr Date: Thu, 2 Aug 2018 23:05:25 +0200 Subject: [PATCH] -qsound_hle: Updated based on actual DSP16 code to give identical output. [superctr, ValleyBell] --- src/devices/sound/qsound.cpp | 4 +- src/devices/sound/qsoundhle.cpp | 616 +++++++++++++++++++++++++------- src/devices/sound/qsoundhle.h | 147 +++++++- 3 files changed, 629 insertions(+), 138 deletions(-) diff --git a/src/devices/sound/qsound.cpp b/src/devices/sound/qsound.cpp index 31d4f08ac6c..bb6d3da1573 100644 --- a/src/devices/sound/qsound.cpp +++ b/src/devices/sound/qsound.cpp @@ -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) diff --git a/src/devices/sound/qsoundhle.cpp b/src/devices/sound/qsoundhle.cpp index 8d182bb4b1a..4465adcacc6 100644 --- a/src/devices/sound/qsoundhle.cpp +++ b/src/devices/sound/qsoundhle.cpp @@ -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 #include - -namespace { - -constexpr u32 saturate(s64 val) -{ - return std::min(std::max(val, std::numeric_limits::min()), std::numeric_limits::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(std::max(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(std::max(delta, -32768), 32767); + + m_step_size = (dsp.read_dsp_rom(DATA_ADPCM_TAB + 8 + step) * m_step_size) >> 6; + m_step_size = std::min(std::max(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(std::max(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; +} diff --git a/src/devices/sound/qsoundhle.h b/src/devices/sound/qsoundhle.h index 498343ba6cb..9ede58699d2 100644 --- a/src/devices/sound/qsoundhle.h +++ b/src/devices/sound/qsoundhle.h @@ -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 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)