QSound LLE available with a 3-character change

This commit is contained in:
Vas Crabb 2018-03-16 02:27:24 +11:00
parent 8faa36c7e1
commit 3f67473bb4
6 changed files with 700 additions and 266 deletions

View File

@ -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

View File

@ -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")
/***************************************************************************

View File

@ -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
Driver by Paul Leaman and Miguel Angel Horna
Sixteen-channel sample player. Previous HLE implementation by Paul
Leaman and Miguel Angel Horna, with thanks to CAB (author of Amuse).
A 16 channel stereo sample player.
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.
QSpace position is simulated by panning the sound in the stereo space.
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.
Many thanks to CAB (the author of Amuse), without whom this probably would
never have been finished.
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.
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
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).
Links:
https://siliconpr0n.org/map/capcom/dl-1425
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
***************************************************************************/
#include "emu.h"
#define QSOUND_LLE
#include "qsound.h"
#include <algorithm>
#include <fstream>
#define LOG_GENERAL (1U << 0)
#define LOG_COMMAND (1U << 1)
#define LOG_SAMPLE (1U << 2)
//#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__)
// 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,25 +113,47 @@ 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;
}
//-------------------------------------------------
// rom_region - return a pointer to the device's
// internal ROM region
@ -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
//-------------------------------------------------
void qsound_device::rom_bank_updated()
{
m_stream->update();
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void qsound_device::device_start()
{
m_stream = stream_alloc(0, 2, clock() / 15 / 166); // /166 clock divider?
// hope we get good synchronisation between the DSP and the sound system
m_stream = stream_alloc(0, 2, clock() / 2 / 1248);
// create pan table
for (int i = 0; i < 33; i++)
m_pan_table[i] = (int)((256 / sqrt(32.0)) * sqrt((double)i));
// 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));
// 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);
}
// 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_reset - device-specific reset
//-------------------------------------------------
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]));
std::fill_n(outputs[0], samples, m_samples[0]);
std::fill_n(outputs[1], samples, m_samples[1]);
}
for (auto & elem : m_channel)
//-------------------------------------------------
// rom_bank_updated - the rom bank has changed
//-------------------------------------------------
void qsound_device::rom_bank_updated()
{
machine().scheduler().synchronize();
}
// DSP external ROM space
void qsound_device::dsp_io_map(address_map &map)
{
map.unmap_value_high();
map(0x0000, 0x7fff).mirror(0x8000).r(this, FUNC(qsound_device::dsp_sample_r));
}
READ16_MEMBER(qsound_device::dsp_sample_r)
{
// 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);
}
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)
{
if (elem.enabled)
m_sr = (m_sr << 1) | (m_dsp->do_r() ? 0x0001U : 0x0000U);
m_fsr >>= 1;
if (!m_fsr)
{
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);
}
// 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
}
}
// 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;
}
WRITE8_MEMBER(qsound_device::qsound_w)
READ16_MEMBER(qsound_device::dsp_pio_r)
{
switch (offset)
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)
{
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_device::qsound_r)
{
/* Port ready bit (0x80 if ready) */
return 0x80;
}
void qsound_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;
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;
}
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;
return m_cmd_data;
}
}
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);
}
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));
}
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));
}

View File

@ -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
// DSP communication
u16 m_rom_bank, m_rom_offset;
u16 m_cmd_addr, m_cmd_data;
u8 m_cmd_pending, m_dsp_ready;
// 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);
// 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

View File

@ -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;
}
}

View File

@ -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