summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2018-03-16 02:27:24 +1100
committer Vas Crabb <vas@vastheman.com>2018-03-16 02:27:24 +1100
commit3f67473bb42a6e8c5a47d73abe1ddbfb8118ee1c (patch)
tree13a787d12e034a8c1052c502a50a3e5b84dce2a0
parent8faa36c7e1486d0b0b6b5b47c73ddebceee9b46c (diff)
QSound LLE available with a 3-character change
-rw-r--r--scripts/src/sound.lua2
-rw-r--r--src/devices/cpu/dsp16/dsp16.cpp4
-rw-r--r--src/devices/sound/qsound.cpp489
-rw-r--r--src/devices/sound/qsound.h60
-rw-r--r--src/devices/sound/qsoundhle.cpp324
-rw-r--r--src/devices/sound/qsoundhle.h77
6 files changed, 695 insertions, 261 deletions
diff --git a/scripts/src/sound.lua b/scripts/src/sound.lua
index 27bb7dbb826..72b21469220 100644
--- a/scripts/src/sound.lua
+++ b/scripts/src/sound.lua
@@ -755,6 +755,8 @@ if (SOUNDS["QSOUND"]~=null) then
files {
MAME_DIR .. "src/devices/sound/qsound.cpp",
MAME_DIR .. "src/devices/sound/qsound.h",
+ MAME_DIR .. "src/devices/sound/qsoundhle.cpp",
+ MAME_DIR .. "src/devices/sound/qsoundhle.h",
}
end
diff --git a/src/devices/cpu/dsp16/dsp16.cpp b/src/devices/cpu/dsp16/dsp16.cpp
index ba5737087b0..7beda7ab0e8 100644
--- a/src/devices/cpu/dsp16/dsp16.cpp
+++ b/src/devices/cpu/dsp16/dsp16.cpp
@@ -103,8 +103,8 @@
DEVICE TYPE DEFINITIONS
***************************************************************************/
-DEFINE_DEVICE_TYPE(DSP16, dsp16_device, "dsp16", "DSP16")
-DEFINE_DEVICE_TYPE(DSP16A, dsp16a_device, "dsp16a", "DSP16A")
+DEFINE_DEVICE_TYPE(DSP16, dsp16_device, "dsp16", "WE|AT&T DSP16")
+DEFINE_DEVICE_TYPE(DSP16A, dsp16a_device, "dsp16a", "WE|AT&T DSP16A")
/***************************************************************************
diff --git a/src/devices/sound/qsound.cpp b/src/devices/sound/qsound.cpp
index d0c3852becf..f5411dfa94b 100644
--- a/src/devices/sound/qsound.cpp
+++ b/src/devices/sound/qsound.cpp
@@ -1,45 +1,108 @@
// license:BSD-3-Clause
-// copyright-holders:Paul Leaman, Miguel Angel Horna
+// copyright-holders:Vas Crabb
/***************************************************************************
- Capcom System QSound™
- =====================
+ Capcom System QSound™
+
+ Sixteen-channel sample player. Previous HLE implementation by Paul
+ Leaman and Miguel Angel Horna, with thanks to CAB (author of Amuse).
+
+ The key components are a DSP16A, a TDA1543 dual 16-bit DAC with I2S
+ input, and a TC9185P electronic volume control. The TDA1543 is
+ simulated here; no attempt is being made to emulate theTC9185P.
+
+ Commands work by writing an address/data word pair to be written to
+ DSP's internal RAM. In theory it's possible to write anywhere in
+ DSP RAM, but the glue logic only allows writing to the first 256
+ words. The host writes the high and low bytes the data word to
+ offsets 0 and 1, respectively, and the address to offset 2. Writing
+ the address also asserts the DSP's INT pin. The host can read back
+ a single bit, which I've assumed reflects the current state of the
+ DSP's INT pin (low when asserted). The host won't send further
+ commands until this bit goes high.
+
+ On servicing an external interrupt, the DSP reads pdx0 three times,
+ expecting to get the address and data in that order (the third read
+ is needed because DSP16 has latent PIO reads in active mode). I've
+ assumed that reading PIO with PSEL low when INT is asserted will
+ return the address and cause INT to be de-asserted, and reading PIO
+ with PSEL low when int is not asserted will return the data word.
+
+ The DSP program uses 2 kilowords of internal RAM and reads data from
+ external ROM while executing from internal ROM. As such, it
+ requires a DSP16A core (the original DSP16 only has 512 words of
+ internal RAM and can't read external ROM with internal ROM enabled).
+
+ To read external ROM, the DSP writes the desired sample offset to
+ PDX0, then reads external ROM at address (bank | 0x8000), for a
+ theoretical maximum of 2 gigasamples. The bank applies to the next
+ read, not the current read. A dummy read is required to set the
+ bank for the very first read. This latency could just be a quirk of
+ how Capcom hooks the DSP up to the sample ROMs. In theory, samples
+ are 16-bit signed values, but Capcom only has 8-bit ROMs connected.
+ I'm assuming byte smearing, but it may be zero-padded in the LSBs.
+
+ The DSP sends out 16-bit samples on its SIO port clocked at 2.5 MHz.
+ The stereo samples aren't loaded fast enough for consecutive frames
+ so there's an empty frame between them. Sample pairs are loaded
+ every 1,248 machine cycles, giving a sample rate of 24.03846 kHz
+ (60 MHz / 2 / 1248). It's unknown how the real hardware identifies
+ left/right samples - I'm using a massive hack at the moment.
+
+ The DSP writes values to pdx1 every sample cycle (alternating
+ between zero and non-zero values). This may be for the volume
+ control chip or something else.
+
+ The photographs of the DL-1425 die (WEDSP16A-M14) show 12 kilowords
+ of internal ROM compared to 4 kilowords as documented. It's unknown
+ if/how the additional ROM is mapped in the DSP's internal ROM space.
+ The internal program only uses internal ROM from 0x0000 to 0x0fff
+ and external space from 0x8000 onwards. The additional ROM could
+ be anywhere in between.
+
+ Meanings for known command words (0x00 to 0x8f mostly understood):
+ (((ch - 1) << 3) & 0x78 sample bank
+ (ch << 3) | 0x01 current/starting sample offset
+ (ch << 3) | 0x02 rate (zero for key-off)
+ (ch << 3) | 0x03 key-on
+ (ch << 3) | 0x04 loop offset (relative to end)
+ (ch << 3) | 0x05 end sample offset
+ (ch << 3) | 0x06 channel volume
+ ch | 0x80 position on sound stage
+
+ The games don't seem to use (ch << 3) | 0x07 for anything. The
+ games use 0xba to 0xcb, but the purpose is not understood.
+
+ The weird way of setting the sample bank is due to the one-read
+ latency described above. Since the bank applies to the next read,
+ you need to set it on the channel before the desired channel.
+
+ Links:
+ * https://siliconpr0n.org/map/capcom/dl-1425
- Driver by Paul Leaman and Miguel Angel Horna
-
- A 16 channel stereo sample player.
+***************************************************************************/
- QSpace position is simulated by panning the sound in the stereo space.
+#include "emu.h"
+#define QSOUND_LLE
+#include "qsound.h"
- Many thanks to CAB (the author of Amuse), without whom this probably would
- never have been finished.
+#include <algorithm>
+#include <fstream>
- 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
+#define LOG_GENERAL (1U << 0)
+#define LOG_COMMAND (1U << 1)
+#define LOG_SAMPLE (1U << 2)
- Links:
- https://siliconpr0n.org/map/capcom/dl-1425
+//#define VERBOSE (LOG_GENERAL | LOG_SAMPLE)
+//#define LOG_OUTPUT_STREAM std::cout
+#include "logmacro.h"
-***************************************************************************/
+#define LOGCOMMAND(...) LOGMASKED(LOG_COMMAND, __VA_ARGS__)
+#define LOGSAMPLE(...) LOGMASKED(LOG_SAMPLE, __VA_ARGS__)
-#include "emu.h"
-#include "qsound.h"
// device type definition
-DEFINE_DEVICE_TYPE(QSOUND, qsound_device, "qsound", "Q-Sound")
-
-
-// program map for the DSP16A; note that apparently Western Electric/AT&T
-// expanded the size of the available mask ROM on the DSP16A over time after
-// it was released.
-// As originally released, the DSP16A had 4096 words of ROM, but the DL-1425
-// chip decapped by siliconpr0n clearly shows 3x as much ROM as that, a total
-// of 12288 words of internal ROM.
-// The older DSP16 non-a part has 2048 words of ROM.
+DEFINE_DEVICE_TYPE(QSOUND, qsound_device, "qsound", "QSound")
// DSP internal ROM region
@@ -50,22 +113,44 @@ ROM_START( qsound )
ROM_END
-//**************************************************************************
-// LIVE DEVICE
-//**************************************************************************
-
//-------------------------------------------------
// qsound_device - constructor
//-------------------------------------------------
-qsound_device::qsound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+qsound_device::qsound_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
: device_t(mconfig, QSOUND, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, device_rom_interface(mconfig, *this, 24)
- , m_dsp(*this, "dsp")
- , m_stream(nullptr)
- , m_data(0)
+ , m_dsp(*this, "dsp"), m_stream(nullptr)
+ , m_rom_bank(0U), m_rom_offset(0U), m_cmd_addr(0U), m_cmd_data(0U), m_cmd_pending(0U), m_dsp_ready(1U)
+ , m_last_time(0U), m_samples{ 0, 0 }, m_sr(0U), m_fsr(0U), m_ock(1U), m_old(1U), m_ready(0U), m_channel(0U)
+{
+}
+
+
+WRITE8_MEMBER(qsound_device::qsound_w)
+{
+ switch (offset)
+ {
+ case 0:
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(qsound_device::set_cmd_data_high), this), unsigned(data));
+ break;
+ case 1:
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(qsound_device::set_cmd_data_low), this), unsigned(data));
+ break;
+ case 2:
+ m_dsp_ready = 0U;
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(qsound_device::set_cmd_addr), this), unsigned(data));
+ break;
+ default:
+ logerror("QSound: host write to unknown register %01X = %02X (%s)\n", offset, data, machine().describe_context());
+ }
+}
+
+
+READ8_MEMBER(qsound_device::qsound_r)
{
+ return m_dsp_ready ? 0x80 : 0x00;
}
@@ -86,58 +171,52 @@ const tiny_rom_entry *qsound_device::device_rom_region() const
MACHINE_CONFIG_START(qsound_device::device_add_mconfig)
MCFG_CPU_ADD("dsp", DSP16A, DERIVED_CLOCK(1, 1))
- MCFG_DEVICE_DISABLE()
+ MCFG_CPU_IO_MAP(dsp_io_map)
+ MCFG_DSP16_OCK_CB(WRITELINE(qsound_device, dsp_ock_w))
+ MCFG_DSP16_PIO_R_CB(READ16(qsound_device, dsp_pio_r))
+ MCFG_DSP16_PIO_W_CB(WRITE16(qsound_device, dsp_pio_w))
MACHINE_CONFIG_END
//-------------------------------------------------
-// rom_bank_updated - the rom bank has changed
+// device_start - device-specific startup
//-------------------------------------------------
-void qsound_device::rom_bank_updated()
+void qsound_device::device_start()
{
- m_stream->update();
+ // hope we get good synchronisation between the DSP and the sound system
+ m_stream = stream_alloc(0, 2, clock() / 2 / 1248);
+
+ // save DSP communication state
+ save_item(NAME(m_rom_bank));
+ save_item(NAME(m_rom_offset));
+ save_item(NAME(m_cmd_addr));
+ save_item(NAME(m_cmd_data));
+ save_item(NAME(m_cmd_pending));
+ save_item(NAME(m_dsp_ready));
+
+ // save serial sample recovery state
+ save_item(NAME(m_last_time));
+ save_item(NAME(m_samples));
+ save_item(NAME(m_sr));
+ save_item(NAME(m_fsr));
+ save_item(NAME(m_ock));
+ save_item(NAME(m_old));
+ save_item(NAME(m_ready));
+ save_item(NAME(m_channel));
}
//-------------------------------------------------
-// device_start - device-specific startup
+// device_reset - device-specific reset
//-------------------------------------------------
-void qsound_device::device_start()
-{
- m_stream = stream_alloc(0, 2, clock() / 15 / 166); // /166 clock divider?
-
- // 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 = 0x7f; adr >= 0; adr--)
- write_data(adr, 0);
- for (int adr = 0x80; adr < 0x90; adr++)
- write_data(adr, 0x120);
-
- // state save
- for (int i = 0; i < 16; i++)
- {
- save_item(NAME(m_channel[i].bank), i);
- save_item(NAME(m_channel[i].address), i);
- save_item(NAME(m_channel[i].freq), i);
- save_item(NAME(m_channel[i].loop), i);
- save_item(NAME(m_channel[i].end), i);
- save_item(NAME(m_channel[i].vol), i);
- save_item(NAME(m_channel[i].enabled), i);
- save_item(NAME(m_channel[i].lvol), i);
- save_item(NAME(m_channel[i].rvol), i);
- save_item(NAME(m_channel[i].step_ptr), i);
- }
-}
-
void qsound_device::device_reset()
{
+ // TODO: does this get automatically cleared on reset or not?
+ m_cmd_pending = 0U;
+ m_dsp_ready = 1U;
+ m_dsp->set_input_line(DSP16_INT_LINE, CLEAR_LINE);
}
@@ -147,182 +226,134 @@ void qsound_device::device_reset()
void qsound_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]));
-
- for (auto & elem : m_channel)
- {
- if (elem.enabled)
- {
- stream_sample_t *lmix=outputs[0];
- stream_sample_t *rmix=outputs[1];
-
- // Go through the buffer and add voice contributions
- for (int i = 0; i < samples; i++)
- {
- elem.address += (elem.step_ptr >> 12);
- elem.step_ptr &= 0xfff;
- elem.step_ptr += elem.freq;
-
- if (elem.address >= elem.end)
- {
- if (elem.loop)
- {
- // Reached the end, restart the loop
- elem.address -= elem.loop;
-
- // Make sure we don't overflow (what does the real chip do in this case?)
- if (elem.address >= elem.end)
- elem.address = elem.end - elem.loop;
-
- elem.address &= 0xffff;
- }
- else
- {
- // Reached the end of a non-looped sample
- elem.enabled = false;
- break;
- }
- }
-
- int8_t sample = read_sample(elem.bank | elem.address);
- *lmix++ += ((sample * elem.lvol * elem.vol) >> 14);
- *rmix++ += ((sample * elem.rvol * elem.vol) >> 14);
- }
- }
- }
+ std::fill_n(outputs[0], samples, m_samples[0]);
+ std::fill_n(outputs[1], samples, m_samples[1]);
}
-WRITE8_MEMBER(qsound_device::qsound_w)
+//-------------------------------------------------
+// rom_bank_updated - the rom bank has changed
+//-------------------------------------------------
+
+void qsound_device::rom_bank_updated()
{
- switch (offset)
- {
- case 0:
- m_data = (m_data & 0x00ff) | (data << 8);
- break;
-
- case 1:
- m_data = (m_data & 0xff00) | data;
- break;
-
- case 2:
- m_stream->update();
- write_data(data, m_data);
- break;
-
- default:
- logerror("%s: qsound_w %d = %02x\n", machine().describe_context(), offset, data);
- break;
- }
+ machine().scheduler().synchronize();
}
-READ8_MEMBER(qsound_device::qsound_r)
+// DSP external ROM space
+void qsound_device::dsp_io_map(address_map &map)
{
- /* Port ready bit (0x80 if ready) */
- return 0x80;
+ map.unmap_value_high();
+ map(0x0000, 0x7fff).mirror(0x8000).r(this, FUNC(qsound_device::dsp_sample_r));
}
-void qsound_device::write_data(uint8_t address, uint16_t data)
+READ16_MEMBER(qsound_device::dsp_sample_r)
{
- int ch = 0, reg;
+ // TODO: DSP16 doesn't like bytes, only signed words - should this zero-pad or byte-smear?
+ u8 const byte(read_byte((u32(m_rom_bank) << 16) | m_rom_offset));
+ if (!machine().side_effects_disabled())
+ m_rom_bank = offset;
+ return (u16(byte) << 8) | u16(byte);
+}
- // direct sound reg
- if (address < 0x80)
+WRITE_LINE_MEMBER(qsound_device::dsp_ock_w)
+{
+ // detect active edge
+ if (bool(state) == bool(m_ock))
+ return;
+ m_ock = state;
+ if (!state)
+ return;
+
+ // detect start of word
+ if (m_ready && !m_fsr && !m_dsp->ose_r())
+ m_fsr = 0xffffU;
+
+ // shift in data
+ if (m_fsr)
{
- ch = address >> 3;
- reg = address & 7;
+ m_sr = (m_sr << 1) | (m_dsp->do_r() ? 0x0001U : 0x0000U);
+ m_fsr >>= 1;
+ if (!m_fsr)
+ {
+ // FIXME: this is an epic hack, but I don't know how the hardware actually identifies channels
+ u64 const now(m_dsp->total_cycles());
+ if ((now - m_last_time) > 500)
+ m_channel = 0U;
+ m_last_time = now;
+ LOGSAMPLE("QSound: recovered channel %u sample %04X\n", m_channel, m_sr);
+ if (!m_channel)
+ m_stream->update();
+ m_samples[m_channel] = m_sr;
+ m_channel = m_channel ? 0U : 1U;
+#if 0 // enable to log PCM to a file - can be imported with "ffmpeg -f s16be -ar 24038 -ac 2 -i qsound.pcm qsound.wav"
+ static std::ofstream logfile("qsound.pcm", std::ios::binary);
+ logfile.put(u8(m_sr >> 8));
+ logfile.put(u8(m_sr));
+#endif
+ }
}
- // >= 0x80 is probably for the dsp?
- else if (address < 0x90)
- {
- ch = address & 0xf;
- reg = 8;
- }
- else if (address >= 0xba && address < 0xca)
+ // detect falling OLD - indicates next bit could be start of a word
+ u8 const old(m_dsp->old_r());
+ m_ready = (m_old && !old);
+ m_old = old;
+}
+
+WRITE16_MEMBER(qsound_device::dsp_pio_w)
+{
+ if (offset)
+ LOG("QSound: DSP PDX1 = %04X\n", data);
+ else
+ m_rom_offset = data;
+}
+
+
+READ16_MEMBER(qsound_device::dsp_pio_r)
+{
+ LOGCOMMAND(
+ "QSound: DSP PIO read returning %s = %04X\n",
+ m_cmd_pending ? "addr" : "data", m_cmd_pending ? m_cmd_addr : m_cmd_data);
+ if (m_cmd_pending)
{
- ch = address - 0xba;
- reg = 9;
+ m_cmd_pending = 0U;
+ m_dsp->set_input_line(DSP16_INT_LINE, CLEAR_LINE);
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(qsound_device::set_dsp_ready), this));
+ return m_cmd_addr;
}
else
{
- // unknown
- reg = address;
+ return m_cmd_data;
}
+}
- switch (reg)
- {
- case 0:
- // bank, high bits unknown
- ch = (ch + 1) & 0xf; // strange ...
- m_channel[ch].bank = data << 16;
- break;
-
- case 1:
- // start/cur address
- m_channel[ch].address = data;
- break;
-
- case 2:
- // frequency
- m_channel[ch].freq = data;
- if (data == 0)
- {
- // key off
- m_channel[ch].enabled = false;
- }
- break;
-
- case 3:
- // key on (does the value matter? it always writes 0x8000)
- m_channel[ch].enabled = true;
- m_channel[ch].step_ptr = 0;
- break;
-
- case 4:
- // loop address
- m_channel[ch].loop = data;
- break;
-
- case 5:
- // end address
- m_channel[ch].end = data;
- break;
-
- case 6:
- // master volume
- m_channel[ch].vol = data;
- break;
-
- case 7:
- // unused?
- break;
-
- 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_channel[ch].rvol = m_pan_table[pan];
- m_channel[ch].lvol = m_pan_table[0x20 - pan];
- break;
- }
+void qsound_device::set_dsp_ready(void *ptr, s32 param)
+{
+ m_dsp_ready = 1U;
+}
+
+void qsound_device::set_cmd_addr(void *ptr, s32 param)
+{
+ LOGCOMMAND("QSound: DSP command @%02X = %04X\n", param, m_cmd_data);
+ m_cmd_addr = u16(u32(param));
+ m_cmd_pending = 1U;
+ m_dsp->set_input_line(DSP16_INT_LINE, ASSERT_LINE);
+}
- case 9:
- // unknown
- break;
+void qsound_device::set_cmd_data_high(void *ptr, s32 param)
+{
+ LOGCOMMAND(
+ "QSound: set command data[h] = %02X (%04X -> %04X)\n",
+ param, m_cmd_data, (m_cmd_data & 0x00ffU) | (u32(param) << 8));
+ m_cmd_data = u16((m_cmd_data & 0x00ffU) | (u32(param) << 8));
+}
- default:
- //logerror("%s: write_data %02x = %04x\n", machine().describe_context(), address, data);
- break;
- }
+void qsound_device::set_cmd_data_low(void *ptr, s32 param)
+{
+ LOGCOMMAND(
+ "QSound: set command data[l] = %02X (%04X -> %04X)\n",
+ param, m_cmd_data, (m_cmd_data & 0xff00U) | u32(param));
+ m_cmd_data = u16((m_cmd_data & 0xff00U) | u32(param));
}
diff --git a/src/devices/sound/qsound.h b/src/devices/sound/qsound.h
index 36ca47896d5..53966e8b701 100644
--- a/src/devices/sound/qsound.h
+++ b/src/devices/sound/qsound.h
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Paul Leaman, Miguel Angel Horna
+// copyright-holders:Vas Crabb
/*********************************************************
Capcom System QSound™
@@ -15,14 +15,6 @@
// default 60MHz clock (divided by 2 for DSP core clock, and then by 1248 for sample rate)
#define QSOUND_CLOCK 60_MHz_XTAL
-
-//**************************************************************************
-// INTERFACE CONFIGURATION MACROS
-//**************************************************************************
-
-
-// ======================> qsound_device
-
class qsound_device : public device_t, public device_sound_interface, public device_rom_interface
{
public:
@@ -44,35 +36,43 @@ protected:
// device_rom_interface implementation
virtual void rom_bank_updated() override;
+ void dsp_io_map(address_map &map);
+
private:
+ // DSP ROM access
+ DECLARE_READ16_MEMBER(dsp_sample_r);
+ DECLARE_WRITE16_MEMBER(dsp_pio_w);
+
+ // for synchronised DSP communication
+ DECLARE_WRITE_LINE_MEMBER(dsp_ock_w);
+ DECLARE_READ16_MEMBER(dsp_pio_r);
+ void set_dsp_ready(void *ptr, s32 param);
+ void set_cmd_addr(void *ptr, s32 param);
+ void set_cmd_data_high(void *ptr, s32 param);
+ void set_cmd_data_low(void *ptr, s32 param);
// MAME resources
required_device<dsp16_device_base> m_dsp;
sound_stream *m_stream;
- struct qsound_channel
- {
- uint32_t bank; // bank
- uint32_t address; // start/cur address
- uint16_t loop; // loop address
- uint16_t end; // end address
- uint32_t freq; // frequency
- uint16_t vol; // master volume
-
- // work variables
- bool enabled; // key on / key off
- int lvol; // left volume
- int rvol; // right volume
- uint32_t step_ptr; // current offset counter
- } m_channel[16];
-
- int m_pan_table[33]; // pan volume table
- uint16_t m_data; // register latch data
-
- inline int8_t read_sample(uint32_t offset) { return (int8_t)read_byte(offset); }
- void write_data(uint8_t address, uint16_t data);
+ // DSP communication
+ u16 m_rom_bank, m_rom_offset;
+ u16 m_cmd_addr, m_cmd_data;
+ u8 m_cmd_pending, m_dsp_ready;
+
+ // serial sample recovery
+ u64 m_last_time;
+ s16 m_samples[2];
+ u16 m_sr, m_fsr;
+ u8 m_ock, m_old, m_ready, m_channel;
};
DECLARE_DEVICE_TYPE(QSOUND, qsound_device)
+#if !defined(QSOUND_LLE) // && 0
+#include "qsoundhle.h"
+#define qsound_device qsound_hle_device
+#define QSOUND QSOUND_HLE
+#endif // QSOUND_LLE
+
#endif // MAME_SOUND_QSOUND_H
diff --git a/src/devices/sound/qsoundhle.cpp b/src/devices/sound/qsoundhle.cpp
new file mode 100644
index 00000000000..31b28b36e49
--- /dev/null
+++ b/src/devices/sound/qsoundhle.cpp
@@ -0,0 +1,324 @@
+// license:BSD-3-Clause
+// copyright-holders:Paul Leaman, Miguel Angel Horna
+/***************************************************************************
+
+ Capcom System QSound™ (HLE)
+ ===========================
+
+ Driver by Paul Leaman and Miguel Angel Horna
+
+ 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
+
+***************************************************************************/
+
+#include "emu.h"
+#include "qsound.h"
+
+// device type definition
+DEFINE_DEVICE_TYPE(QSOUND_HLE, qsound_hle_device, "qsound_hle", "QSound (HLE)")
+
+
+// program map for the DSP16A; note that apparently Western Electric/AT&T
+// expanded the size of the available mask ROM on the DSP16A over time after
+// it was released.
+// As originally released, the DSP16A had 4096 words of ROM, but the DL-1425
+// chip decapped by siliconpr0n clearly shows 3x as much ROM as that, a total
+// of 12288 words of internal ROM.
+// The older DSP16 non-a part has 2048 words of ROM.
+
+
+// DSP internal ROM region
+ROM_START( qsound_hle )
+ ROM_REGION( 0x2000, "dsp", 0 )
+ ROM_LOAD16_WORD_SWAP( "dl-1425.bin", 0x0000, 0x2000, CRC(d6cf5ef5) SHA1(555f50fe5cdf127619da7d854c03f4a244a0c501) )
+ ROM_IGNORE( 0x4000 )
+ROM_END
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// qsound_hle_device - constructor
+//-------------------------------------------------
+
+qsound_hle_device::qsound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, QSOUND_HLE, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , device_rom_interface(mconfig, *this, 24)
+ , m_dsp(*this, "dsp")
+ , m_stream(nullptr)
+ , m_data(0)
+{
+}
+
+
+//-------------------------------------------------
+// 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_add_mconfig - add device configuration
+//-------------------------------------------------
+
+MACHINE_CONFIG_START(qsound_hle_device::device_add_mconfig)
+ MCFG_CPU_ADD("dsp", DSP16A, DERIVED_CLOCK(1, 1))
+ MCFG_DEVICE_DISABLE()
+MACHINE_CONFIG_END
+
+
+//-------------------------------------------------
+// rom_bank_updated - the rom bank has changed
+//-------------------------------------------------
+
+void qsound_hle_device::rom_bank_updated()
+{
+ m_stream->update();
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void qsound_hle_device::device_start()
+{
+ m_stream = stream_alloc(0, 2, clock() / 15 / 166); // /166 clock divider?
+
+ // 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 = 0x7f; adr >= 0; adr--)
+ write_data(adr, 0);
+ for (int adr = 0x80; adr < 0x90; adr++)
+ write_data(adr, 0x120);
+
+ // state save
+ for (int i = 0; i < 16; i++)
+ {
+ save_item(NAME(m_channel[i].bank), i);
+ save_item(NAME(m_channel[i].address), i);
+ save_item(NAME(m_channel[i].freq), i);
+ save_item(NAME(m_channel[i].loop), i);
+ save_item(NAME(m_channel[i].end), i);
+ save_item(NAME(m_channel[i].vol), i);
+ save_item(NAME(m_channel[i].enabled), i);
+ save_item(NAME(m_channel[i].lvol), i);
+ save_item(NAME(m_channel[i].rvol), i);
+ save_item(NAME(m_channel[i].step_ptr), i);
+ }
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - handle a stream update
+//-------------------------------------------------
+
+void qsound_hle_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ // Clear the buffers
+ memset(outputs[0], 0, samples * sizeof(*outputs[0]));
+ memset(outputs[1], 0, samples * sizeof(*outputs[1]));
+
+ for (auto & elem : m_channel)
+ {
+ if (elem.enabled)
+ {
+ stream_sample_t *lmix=outputs[0];
+ stream_sample_t *rmix=outputs[1];
+
+ // Go through the buffer and add voice contributions
+ for (int i = 0; i < samples; i++)
+ {
+ elem.address += (elem.step_ptr >> 12);
+ elem.step_ptr &= 0xfff;
+ elem.step_ptr += elem.freq;
+
+ if (elem.address >= elem.end)
+ {
+ if (elem.loop)
+ {
+ // Reached the end, restart the loop
+ elem.address -= elem.loop;
+
+ // Make sure we don't overflow (what does the real chip do in this case?)
+ if (elem.address >= elem.end)
+ elem.address = elem.end - elem.loop;
+
+ elem.address &= 0xffff;
+ }
+ else
+ {
+ // Reached the end of a non-looped sample
+ elem.enabled = false;
+ break;
+ }
+ }
+
+ int8_t sample = read_sample(elem.bank | elem.address);
+ *lmix++ += ((sample * elem.lvol * elem.vol) >> 14);
+ *rmix++ += ((sample * elem.rvol * elem.vol) >> 14);
+ }
+ }
+ }
+}
+
+
+WRITE8_MEMBER(qsound_hle_device::qsound_w)
+{
+ switch (offset)
+ {
+ case 0:
+ m_data = (m_data & 0x00ff) | (data << 8);
+ break;
+
+ case 1:
+ m_data = (m_data & 0xff00) | data;
+ break;
+
+ case 2:
+ m_stream->update();
+ write_data(data, m_data);
+ break;
+
+ default:
+ logerror("%s: qsound_w %d = %02x\n", machine().describe_context(), offset, data);
+ break;
+ }
+}
+
+
+READ8_MEMBER(qsound_hle_device::qsound_r)
+{
+ /* Port ready bit (0x80 if ready) */
+ return 0x80;
+}
+
+
+void qsound_hle_device::write_data(uint8_t address, uint16_t data)
+{
+ int ch = 0, reg;
+
+ // direct sound reg
+ if (address < 0x80)
+ {
+ ch = address >> 3;
+ reg = address & 7;
+ }
+
+ // >= 0x80 is probably for the dsp?
+ else if (address < 0x90)
+ {
+ ch = address & 0xf;
+ reg = 8;
+ }
+ else if (address >= 0xba && address < 0xca)
+ {
+ ch = address - 0xba;
+ reg = 9;
+ }
+ else
+ {
+ // unknown
+ reg = address;
+ }
+
+ switch (reg)
+ {
+ case 0:
+ // bank, high bits unknown
+ ch = (ch + 1) & 0xf; // strange ...
+ m_channel[ch].bank = data << 16;
+ break;
+
+ case 1:
+ // start/cur address
+ m_channel[ch].address = data;
+ break;
+
+ case 2:
+ // frequency
+ m_channel[ch].freq = data;
+ if (data == 0)
+ {
+ // key off
+ m_channel[ch].enabled = false;
+ }
+ break;
+
+ case 3:
+ // key on (does the value matter? it always writes 0x8000)
+ m_channel[ch].enabled = true;
+ m_channel[ch].step_ptr = 0;
+ break;
+
+ case 4:
+ // loop address
+ m_channel[ch].loop = data;
+ break;
+
+ case 5:
+ // end address
+ m_channel[ch].end = data;
+ break;
+
+ case 6:
+ // master volume
+ m_channel[ch].vol = data;
+ break;
+
+ case 7:
+ // unused?
+ break;
+
+ 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_channel[ch].rvol = m_pan_table[pan];
+ m_channel[ch].lvol = m_pan_table[0x20 - pan];
+ break;
+ }
+
+ case 9:
+ // unknown
+ break;
+
+ default:
+ //logerror("%s: write_data %02x = %04x\n", machine().describe_context(), address, data);
+ break;
+ }
+}
diff --git a/src/devices/sound/qsoundhle.h b/src/devices/sound/qsoundhle.h
new file mode 100644
index 00000000000..c2a7d43a6de
--- /dev/null
+++ b/src/devices/sound/qsoundhle.h
@@ -0,0 +1,77 @@
+// license:BSD-3-Clause
+// copyright-holders:Paul Leaman, Miguel Angel Horna
+/*********************************************************
+
+ Capcom System QSound™ (HLE)
+
+*********************************************************/
+#ifndef MAME_SOUND_QSOUNDHLE_H
+#define MAME_SOUND_QSOUNDHLE_H
+
+#pragma once
+
+#include "cpu/dsp16/dsp16.h"
+
+// default 60MHz clock (divided by 2 for DSP core clock, and then by 1248 for sample rate)
+#define QSOUND_CLOCK 60_MHz_XTAL
+
+
+//**************************************************************************
+// INTERFACE CONFIGURATION MACROS
+//**************************************************************************
+
+
+// ======================> qsound_hle_device
+
+class qsound_hle_device : public device_t, public device_sound_interface, public device_rom_interface
+{
+public:
+ qsound_hle_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
+
+ 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_add_mconfig(machine_config &config) override;
+ virtual void device_start() override;
+
+ // device_sound_interface implementation
+ virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
+
+ // device_rom_interface implementation
+ virtual void rom_bank_updated() override;
+
+private:
+
+ // MAME resources
+ required_device<dsp16_device_base> m_dsp;
+ sound_stream *m_stream;
+
+ struct qsound_channel
+ {
+ uint32_t bank; // bank
+ uint32_t address; // start/cur address
+ uint16_t loop; // loop address
+ uint16_t end; // end address
+ uint32_t freq; // frequency
+ uint16_t vol; // master volume
+
+ // work variables
+ bool enabled; // key on / key off
+ int lvol; // left volume
+ int rvol; // right volume
+ uint32_t step_ptr; // current offset counter
+ } m_channel[16];
+
+ int m_pan_table[33]; // pan volume table
+ uint16_t m_data; // register latch data
+
+ inline int8_t read_sample(uint32_t offset) { return (int8_t)read_byte(offset); }
+ void write_data(uint8_t address, uint16_t data);
+};
+
+DECLARE_DEVICE_TYPE(QSOUND_HLE, qsound_hle_device)
+
+#endif // MAME_SOUND_QSOUNDHLE_H