mirror of
https://github.com/holub/mame
synced 2025-07-01 16:19:38 +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 {
|
files {
|
||||||
MAME_DIR .. "src/devices/sound/qsound.cpp",
|
MAME_DIR .. "src/devices/sound/qsound.cpp",
|
||||||
MAME_DIR .. "src/devices/sound/qsound.h",
|
MAME_DIR .. "src/devices/sound/qsound.h",
|
||||||
|
MAME_DIR .. "src/devices/sound/qsoundhle.cpp",
|
||||||
|
MAME_DIR .. "src/devices/sound/qsoundhle.h",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,8 +103,8 @@
|
|||||||
DEVICE TYPE DEFINITIONS
|
DEVICE TYPE DEFINITIONS
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
DEFINE_DEVICE_TYPE(DSP16, dsp16_device, "dsp16", "DSP16")
|
DEFINE_DEVICE_TYPE(DSP16, dsp16_device, "dsp16", "WE|AT&T DSP16")
|
||||||
DEFINE_DEVICE_TYPE(DSP16A, dsp16a_device, "dsp16a", "DSP16A")
|
DEFINE_DEVICE_TYPE(DSP16A, dsp16a_device, "dsp16a", "WE|AT&T DSP16A")
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
@ -1,45 +1,108 @@
|
|||||||
// license:BSD-3-Clause
|
// 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
|
On servicing an external interrupt, the DSP reads pdx0 three times,
|
||||||
never have been finished.
|
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:
|
The DSP program uses 2 kilowords of internal RAM and reads data from
|
||||||
- hook up the DSP!
|
external ROM while executing from internal ROM. As such, it
|
||||||
- is master volume really linear?
|
requires a DSP16A core (the original DSP16 only has 512 words of
|
||||||
- understand higher bits of reg 0
|
internal RAM and can't read external ROM with internal ROM enabled).
|
||||||
- understand reg 9
|
|
||||||
- understand other writes to $90-$ff area
|
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:
|
Links:
|
||||||
https://siliconpr0n.org/map/capcom/dl-1425
|
* https://siliconpr0n.org/map/capcom/dl-1425
|
||||||
|
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
|
#define QSOUND_LLE
|
||||||
#include "qsound.h"
|
#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
|
// device type definition
|
||||||
DEFINE_DEVICE_TYPE(QSOUND, qsound_device, "qsound", "Q-Sound")
|
DEFINE_DEVICE_TYPE(QSOUND, qsound_device, "qsound", "QSound")
|
||||||
|
|
||||||
|
|
||||||
// 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
|
// DSP internal ROM region
|
||||||
@ -50,25 +113,47 @@ ROM_START( qsound )
|
|||||||
ROM_END
|
ROM_END
|
||||||
|
|
||||||
|
|
||||||
//**************************************************************************
|
|
||||||
// LIVE DEVICE
|
|
||||||
//**************************************************************************
|
|
||||||
|
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
// qsound_device - constructor
|
// 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_t(mconfig, QSOUND, tag, owner, clock)
|
||||||
, device_sound_interface(mconfig, *this)
|
, device_sound_interface(mconfig, *this)
|
||||||
, device_rom_interface(mconfig, *this, 24)
|
, device_rom_interface(mconfig, *this, 24)
|
||||||
, m_dsp(*this, "dsp")
|
, m_dsp(*this, "dsp"), m_stream(nullptr)
|
||||||
, 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_data(0)
|
, 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
|
// rom_region - return a pointer to the device's
|
||||||
// internal ROM region
|
// 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)
|
MACHINE_CONFIG_START(qsound_device::device_add_mconfig)
|
||||||
MCFG_CPU_ADD("dsp", DSP16A, DERIVED_CLOCK(1, 1))
|
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
|
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
|
// device_start - device-specific startup
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
|
|
||||||
void qsound_device::device_start()
|
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
|
// save DSP communication state
|
||||||
for (int i = 0; i < 33; i++)
|
save_item(NAME(m_rom_bank));
|
||||||
m_pan_table[i] = (int)((256 / sqrt(32.0)) * sqrt((double)i));
|
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
|
// save serial sample recovery state
|
||||||
memset(m_channel, 0, sizeof(m_channel));
|
save_item(NAME(m_last_time));
|
||||||
|
save_item(NAME(m_samples));
|
||||||
for (int adr = 0x7f; adr >= 0; adr--)
|
save_item(NAME(m_sr));
|
||||||
write_data(adr, 0);
|
save_item(NAME(m_fsr));
|
||||||
for (int adr = 0x80; adr < 0x90; adr++)
|
save_item(NAME(m_ock));
|
||||||
write_data(adr, 0x120);
|
save_item(NAME(m_old));
|
||||||
|
save_item(NAME(m_ready));
|
||||||
// state save
|
save_item(NAME(m_channel));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
// device_reset - device-specific reset
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
void qsound_device::device_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)
|
void qsound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
|
||||||
{
|
{
|
||||||
// Clear the buffers
|
std::fill_n(outputs[0], samples, m_samples[0]);
|
||||||
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
|
std::fill_n(outputs[1], samples, m_samples[1]);
|
||||||
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_device::qsound_w)
|
//-------------------------------------------------
|
||||||
|
// rom_bank_updated - the rom bank has changed
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
void qsound_device::rom_bank_updated()
|
||||||
{
|
{
|
||||||
switch (offset)
|
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)
|
||||||
{
|
{
|
||||||
case 0:
|
m_sr = (m_sr << 1) | (m_dsp->do_r() ? 0x0001U : 0x0000U);
|
||||||
m_data = (m_data & 0x00ff) | (data << 8);
|
m_fsr >>= 1;
|
||||||
break;
|
if (!m_fsr)
|
||||||
|
{
|
||||||
case 1:
|
// FIXME: this is an epic hack, but I don't know how the hardware actually identifies channels
|
||||||
m_data = (m_data & 0xff00) | data;
|
u64 const now(m_dsp->total_cycles());
|
||||||
break;
|
if ((now - m_last_time) > 500)
|
||||||
|
m_channel = 0U;
|
||||||
case 2:
|
m_last_time = now;
|
||||||
|
LOGSAMPLE("QSound: recovered channel %u sample %04X\n", m_channel, m_sr);
|
||||||
|
if (!m_channel)
|
||||||
m_stream->update();
|
m_stream->update();
|
||||||
write_data(data, m_data);
|
m_samples[m_channel] = m_sr;
|
||||||
break;
|
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"
|
||||||
default:
|
static std::ofstream logfile("qsound.pcm", std::ios::binary);
|
||||||
logerror("%s: qsound_w %d = %02x\n", machine().describe_context(), offset, data);
|
logfile.put(u8(m_sr >> 8));
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
READ8_MEMBER(qsound_device::qsound_r)
|
READ16_MEMBER(qsound_device::dsp_pio_r)
|
||||||
{
|
{
|
||||||
/* Port ready bit (0x80 if ready) */
|
LOGCOMMAND(
|
||||||
return 0x80;
|
"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)
|
||||||
|
|
||||||
void qsound_device::write_data(uint8_t address, uint16_t data)
|
|
||||||
{
|
|
||||||
int ch = 0, reg;
|
|
||||||
|
|
||||||
// direct sound reg
|
|
||||||
if (address < 0x80)
|
|
||||||
{
|
{
|
||||||
ch = address >> 3;
|
m_cmd_pending = 0U;
|
||||||
reg = address & 7;
|
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;
|
||||||
// >= 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
|
else
|
||||||
{
|
{
|
||||||
// unknown
|
return m_cmd_data;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// license:BSD-3-Clause
|
||||||
// copyright-holders:Paul Leaman, Miguel Angel Horna
|
// copyright-holders:Vas Crabb
|
||||||
/*********************************************************
|
/*********************************************************
|
||||||
|
|
||||||
Capcom System QSound™
|
Capcom System QSound™
|
||||||
@ -15,14 +15,6 @@
|
|||||||
// default 60MHz clock (divided by 2 for DSP core clock, and then by 1248 for sample rate)
|
// default 60MHz clock (divided by 2 for DSP core clock, and then by 1248 for sample rate)
|
||||||
#define QSOUND_CLOCK 60_MHz_XTAL
|
#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
|
class qsound_device : public device_t, public device_sound_interface, public device_rom_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -44,35 +36,43 @@ protected:
|
|||||||
// device_rom_interface implementation
|
// device_rom_interface implementation
|
||||||
virtual void rom_bank_updated() override;
|
virtual void rom_bank_updated() override;
|
||||||
|
|
||||||
|
void dsp_io_map(address_map &map);
|
||||||
|
|
||||||
private:
|
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
|
// MAME resources
|
||||||
required_device<dsp16_device_base> m_dsp;
|
required_device<dsp16_device_base> m_dsp;
|
||||||
sound_stream *m_stream;
|
sound_stream *m_stream;
|
||||||
|
|
||||||
struct qsound_channel
|
// DSP communication
|
||||||
{
|
u16 m_rom_bank, m_rom_offset;
|
||||||
uint32_t bank; // bank
|
u16 m_cmd_addr, m_cmd_data;
|
||||||
uint32_t address; // start/cur address
|
u8 m_cmd_pending, m_dsp_ready;
|
||||||
uint16_t loop; // loop address
|
|
||||||
uint16_t end; // end address
|
|
||||||
uint32_t freq; // frequency
|
|
||||||
uint16_t vol; // master volume
|
|
||||||
|
|
||||||
// work variables
|
// serial sample recovery
|
||||||
bool enabled; // key on / key off
|
u64 m_last_time;
|
||||||
int lvol; // left volume
|
s16 m_samples[2];
|
||||||
int rvol; // right volume
|
u16 m_sr, m_fsr;
|
||||||
uint32_t step_ptr; // current offset counter
|
u8 m_ock, m_old, m_ready, m_channel;
|
||||||
} 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, qsound_device)
|
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
|
#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