mirror of
https://github.com/holub/mame
synced 2025-07-01 00:09:18 +03:00
QSound LLE available with a 3-character change
This commit is contained in:
parent
8faa36c7e1
commit
3f67473bb4
@ -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
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
324
src/devices/sound/qsoundhle.cpp
Normal file
324
src/devices/sound/qsoundhle.cpp
Normal 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;
|
||||
}
|
||||
}
|
77
src/devices/sound/qsoundhle.h
Normal file
77
src/devices/sound/qsoundhle.h
Normal 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
|
Loading…
Reference in New Issue
Block a user