From 3f67473bb42a6e8c5a47d73abe1ddbfb8118ee1c Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Fri, 16 Mar 2018 02:27:24 +1100 Subject: QSound LLE available with a 3-character change --- scripts/src/sound.lua | 2 + src/devices/cpu/dsp16/dsp16.cpp | 4 +- src/devices/sound/qsound.cpp | 489 +++++++++++++++++++++------------------- src/devices/sound/qsound.h | 60 ++--- src/devices/sound/qsoundhle.cpp | 324 ++++++++++++++++++++++++++ src/devices/sound/qsoundhle.h | 77 +++++++ 6 files changed, 695 insertions(+), 261 deletions(-) create mode 100644 src/devices/sound/qsoundhle.cpp create mode 100644 src/devices/sound/qsoundhle.h 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 +#include - 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 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 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 -- cgit v1.2.3