summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/qsoundhle.cpp
diff options
context:
space:
mode:
author arbee <rb6502@users.noreply.github.com>2018-08-18 15:36:39 -0400
committer arbee <rb6502@users.noreply.github.com>2018-08-18 15:36:39 -0400
commitc07731b3d4b49117233e1c0fadbc2663319e391b (patch)
treeb20c8678818a0154a9aa0d50997dc6953398792a /src/devices/sound/qsoundhle.cpp
parentf5a6bfbe2d2dbd9e733c95d82450f5a3d4027b5e (diff)
Revert "-qsound_hle: Updated based on actual DSP16 code to give identical output. [superctr, ValleyBell]"
This reverts commit 484a29e60ba5cc805122544cc72c75d293a5c9ce.
Diffstat (limited to 'src/devices/sound/qsoundhle.cpp')
-rw-r--r--src/devices/sound/qsoundhle.cpp610
1 files changed, 119 insertions, 491 deletions
diff --git a/src/devices/sound/qsoundhle.cpp b/src/devices/sound/qsoundhle.cpp
index 4465adcacc6..8d182bb4b1a 100644
--- a/src/devices/sound/qsoundhle.cpp
+++ b/src/devices/sound/qsoundhle.cpp
@@ -1,13 +1,25 @@
// license:BSD-3-Clause
-// copyright-holders:superctr, Valley Bell
+// copyright-holders:Paul Leaman, Miguel Angel Horna
/***************************************************************************
- Capcom QSound DL-1425 (HLE)
+ Capcom System QSoundâ„¢ (HLE)
===========================
- Driver by superctr with thanks to Valley Bell.
+ Driver by Paul Leaman and Miguel Angel Horna
- Based on disassembled DSP code.
+ 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
Links:
https://siliconpr0n.org/map/capcom/dl-1425
@@ -20,16 +32,19 @@
#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
@@ -39,16 +54,16 @@ ROM_END
// qsound_hle_device - constructor
//-------------------------------------------------
-qsound_hle_device::qsound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+qsound_hle_device::qsound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 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)
+ , m_data(0)
{
}
+
//-------------------------------------------------
// rom_bank_updated - the rom bank has changed
//-------------------------------------------------
@@ -58,6 +73,7 @@ void qsound_hle_device::rom_bank_updated()
m_stream->update();
}
+
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
@@ -66,90 +82,25 @@ 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();
+ // create pan table
+ for (int i = 0; i < 33; i++)
+ m_pan_table[i] = (int)((256 / sqrt(32.0)) * sqrt((double)i));
- // 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);
- }
+ // init sound regs
+ memset(m_channel, 0, sizeof(m_channel));
- 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));
+ for (int adr = 0x80; adr < 0x90; adr++)
+ write_data(adr, 0x120);
- // 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
+ // state save
+ for (int i = 0; i < 16; i++)
{
- 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_channel[i].reg), i);
+ save_item(NAME(m_channel[i].lvol), i);
+ save_item(NAME(m_channel[i].rvol), i);
}
-
- 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
@@ -157,12 +108,11 @@ const tiny_rom_entry *qsound_hle_device::device_rom_region() const
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;
+ for (qsound_channel &ch : m_channel)
+ std::fill(std::begin(ch.reg), std::end(ch.reg), 0U);
}
+
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
@@ -170,14 +120,36 @@ 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
- std::fill_n(outputs[0], samples, 0);
- std::fill_n(outputs[1], samples, 0);
+ memset(outputs[0], 0, samples * sizeof(*outputs[0]));
+ memset(outputs[1], 0, samples * sizeof(*outputs[1]));
- for (int i = 0; i < samples; i ++)
+ for (unsigned n = 0; ARRAY_LENGTH(m_channel) > n; ++n)
{
- update_sample();
- outputs[0][i] = m_out[0];
- outputs[1][i] = m_out[1];
+ 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);
+ }
}
}
@@ -187,16 +159,16 @@ WRITE8_MEMBER(qsound_hle_device::qsound_w)
switch (offset)
{
case 0:
- m_data_latch = (m_data_latch & 0x00ff) | (data << 8);
+ m_data = (m_data & 0x00ff) | (data << 8);
break;
case 1:
- m_data_latch = (m_data_latch & 0xff00) | data;
+ m_data = (m_data & 0xff00) | data;
break;
case 2:
m_stream->update();
- write_data(data, m_data_latch);
+ write_data(data, m_data);
break;
default:
@@ -208,414 +180,70 @@ WRITE8_MEMBER(qsound_hle_device::qsound_w)
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;
- }
+ /* Port ready bit (0x80 if ready) */
+ return 0x80;
}
-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()
+void qsound_hle_device::write_data(u8 address, u16 data)
{
- int mode = (m_state == STATE_INIT2) ? 1 : 0;
+ int ch = 0, reg;
- // we're busy for 4 samples, including the filter refresh.
- if (m_state_counter >= 2)
+ if (address < 0x80)
{
- m_state_counter = 0;
- m_state = m_next_state;
- return;
+ ch = address >> 3;
+ reg = address & 7;
}
- else if (m_state_counter == 1)
+ else if (address < 0x90)
{
- m_state_counter++;
- return;
+ ch = address & 0xf;
+ reg = 8;
}
-
- 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++)
+ else if (address >= 0xba && address < 0xca)
{
- 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;
+ ch = address - 0xba;
+ reg = 9;
}
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;
+ // unknown
+ reg = address;
}
- 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++)
+ switch (reg)
{
- m_filter[ch].m_delay_pos = 0;
- m_filter[ch].m_tap_count = 95;
+ 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;
- 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++)
+ case 8:
{
- 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;
+ // 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;
- 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_channel[ch].rvol = m_pan_table[pan];
+ m_channel[ch].lvol = m_pan_table[0x20 - pan];
+ break;
}
- 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));
+ case 9:
+ // unknown
+ break;
- 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;
+ default:
+ //logerror("%s: write_data %02x = %04x\n", machine().describe_context(), address, data);
+ break;
}
- 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;
}