summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/qs1000.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/qs1000.cpp')
-rw-r--r--src/devices/sound/qs1000.cpp646
1 files changed, 646 insertions, 0 deletions
diff --git a/src/devices/sound/qs1000.cpp b/src/devices/sound/qs1000.cpp
new file mode 100644
index 00000000000..3100800acbc
--- /dev/null
+++ b/src/devices/sound/qs1000.cpp
@@ -0,0 +1,646 @@
+// license:BSD-3-Clause
+// copyright-holders:Philip Bennett
+/***************************************************************************
+
+ qs1000.c
+
+ QS1000 device emulator.
+
+****************************************************************************
+
+ The QS1000 is a 32-voice wavetable synthesizer, believed to be based on
+ the OPTi 82C941. It contains an 8051 core, 256b of RAM and an (undumped)
+ internal program ROM. The internal ROM can be bypassed in favour of an
+ external ROM. Commands are issued to the chip via the 8051 serial port.
+
+ The QS1000 can access 24Mb of sample ROM. To reduce demand on the CPU,
+ instrument parameters such as playback rate, envelope and filter values
+ are encoded in ROM and directly accessed by the wavetable engine.
+ There are table entries for every note of every instrument.
+
+ Registers
+ =========
+
+ [200] = Key on/off
+ 0 = Key on
+ 1 = ?
+ 2 = key off
+ [201] = Address byte 0 (LSB)
+ [202] = Address byte 1
+ [203] = Address byte 2
+ [204] = Pitch
+ [205] = Pitch high byte? (Usually 0)
+ [206] = Left volume
+ [207] = Right volume
+ [208] = Volume
+ [209] = ?
+ [20a] = ?
+ [20b] = ?
+ [20c] = ?
+ [20d] = Velocity
+ [20e] = Channel select
+ [20f] = Modulation
+ [210] = Modulation
+ [211] = 0 - Select global registers?
+ 3 - Select channel registers?
+
+ Velocity register values for MIDI range 0-127:
+
+ 01 01 01 01 01 01 01 02 02 03 03 04 04 05 05 06
+ 06 07 07 08 08 09 09 0A 0A 0B 0B 0C 0C 0D 0D 0E
+ 0E 0F 10 11 11 12 13 14 14 15 16 17 17 18 19 1A
+ 1A 1B 1C 1D 1D 1E 1F 20 20 21 22 23 23 24 25 26
+ 26 27 28 29 29 2A 2B 2C 2C 2D 2E 2F 2F 30 31 32
+ 35 38 3B 3E 41 44 47 4A 4D 50 4F 51 52 53 54 56
+ 57 58 59 5B 5C 5D 5E 60 61 62 63 65 66 67 6A 6B
+ 6C 6E 6F 70 71 73 74 75 76 78 79 7A 7B 7D 7E 7F
+
+ (TODO: Other register values)
+
+ This is the sequence of register writes used to play the Iron Fortress credit sound:
+
+ [211] 0 Select global registers?
+ [200] 1 ?
+ [203] d6 Address byte 2
+ [202] a9 Address byte 1
+ [201] 1 Address byte 0
+ [204] 80 Pitch
+ [205] 0 ?
+ [206] 80 Left volume
+ [207] 80 Right volume
+ [208] b3 Volume
+ [209] 0 ?
+ [20a] ff ?
+ [20b] 0 ?
+ [20c] 0 ?
+ [20d] 78 Velocity
+ [211] 3 Select channel registers
+ [20e] 0 Select channel
+ [200] 0 Key on
+
+
+ Sound Headers
+ =============
+
+ The address registers point to a 6 byte entry in the sound ROM:
+
+ [019be0]
+ 097b 397f 1510
+ ^ ^ ^
+ | | |
+ | | +----- Sound descriptor pointer
+ | +---------- ?
+ +--------------- Playback frequency (fixed point value representing 24MHz clock periods)
+
+ This in turn points to a 24 byte descriptor:
+
+ [1510]:
+ 0 4502D 4508E 45F91 D0 7F 0F 2A 1F 90 00 FF
+ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
+ | | | | | | | | | | | |
+ | | | | | | | | | | | +-- ?
+ | | | | | | | | | | +----- ?
+ | | | | | | | | | +-------- ?
+ | | | | | | | | +----------- ?
+ | | | | | | | +-------------- ?
+ | | | | | | +----------------- Bit 7: Format (0:PCM 1:ADPCM)
+ | | | | | +-------------------- ?
+ | | | | +----------------------- ?
+ | | | +----------------------------- Loop end address
+ | | +----------------------------------- Loop start address
+ | +----------------------------------------- Start address
+ +------------------------------------------- Address most-significant nibble (shared with loop addresses)
+
+ * The unknown parameters are most likely envelope and filter parameters.
+ * Is there a loop flag or do sounds loop indefinitely until stopped?
+
+
+ TODO:
+ * Looping is currently disabled
+ * Figure out unknown sound header parameters
+ * Figure out and implement envelopes and filters
+ * Pitch bending
+ * Dump the internal ROM
+
+***************************************************************************/
+#include "emu.h"
+#include "qs1000.h"
+
+
+#define LOGGING_ENABLED 0
+
+
+// device type definition
+const device_type QS1000 = &device_creator<qs1000_device>;
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+static ADDRESS_MAP_START( qs1000_prg_map, AS_PROGRAM, 8, qs1000_device )
+ AM_RANGE(0x0000, 0x7fff) AM_ROM
+ADDRESS_MAP_END
+
+
+static ADDRESS_MAP_START( qs1000_io_map, AS_IO, 8, qs1000_device )
+ AM_RANGE(0x0000, 0x00ff) AM_RAM
+ AM_RANGE(0x0200, 0x0211) AM_WRITE(wave_w)
+ AM_RANGE(MCS51_PORT_P1, MCS51_PORT_P1) AM_READWRITE(p1_r, p1_w)
+ AM_RANGE(MCS51_PORT_P2, MCS51_PORT_P2) AM_READWRITE(p2_r, p2_w)
+ AM_RANGE(MCS51_PORT_P3, MCS51_PORT_P3) AM_READWRITE(p3_r, p3_w)
+ADDRESS_MAP_END
+
+
+// Machine fragment
+static MACHINE_CONFIG_FRAGMENT( qs1000 )
+ MCFG_CPU_ADD("cpu", I8052, DERIVED_CLOCK(1, 1))
+ MCFG_CPU_PROGRAM_MAP(qs1000_prg_map)
+ MCFG_CPU_IO_MAP(qs1000_io_map)
+MACHINE_CONFIG_END
+
+
+// ROM definition for the QS1000 internal program ROM
+ROM_START( qs1000 )
+ ROM_REGION( 0x10000, "cpu", 0 )
+ ROM_LOAD_OPTIONAL( "qs1000.bin", 0x0000, 0x10000, NO_DUMP )
+ROM_END
+
+
+// Wavetable ROM address map
+static ADDRESS_MAP_START( qs1000, AS_0, 8, qs1000_device )
+ AM_RANGE(0x000000, 0xffffff) AM_ROM AM_REGION("qs1000", 0)
+ADDRESS_MAP_END
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// qs1000_device - constructor
+//-------------------------------------------------
+qs1000_device::qs1000_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, QS1000, "QS1000", tag, owner, clock, "qs1000", __FILE__),
+ device_sound_interface(mconfig, *this),
+ device_memory_interface(mconfig, *this),
+ m_external_rom(false),
+ m_in_p1_cb(*this),
+ m_in_p2_cb(*this),
+ m_in_p3_cb(*this),
+ m_out_p1_cb(*this),
+ m_out_p2_cb(*this),
+ m_out_p3_cb(*this),
+ //m_serial_w_cb(*this),
+ m_space_config("samples", ENDIANNESS_LITTLE, 8, 24, 0, NULL),
+ m_stream(NULL),
+ m_direct(NULL),
+ m_cpu(*this, "cpu")
+{
+ m_address_map[0] = *ADDRESS_MAP_NAME(qs1000);
+}
+
+
+//-------------------------------------------------
+// rom_region - return a pointer to the device's
+// internal ROM region
+//-------------------------------------------------
+const rom_entry *qs1000_device::device_rom_region() const
+{
+ return m_external_rom ? NULL : ROM_NAME( qs1000 );
+}
+
+
+//-------------------------------------------------
+// machine_config_additions - return a pointer to
+// the device's machine fragment
+//-------------------------------------------------
+machine_config_constructor qs1000_device::device_mconfig_additions() const
+{
+ return MACHINE_CONFIG_NAME( qs1000 );
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+void qs1000_device::device_start()
+{
+ // Find our direct access
+ m_direct = &space().direct();
+
+ // The QS1000 operates at 24MHz. Creating a stream at that rate
+ // would be overkill so we opt for a fraction of that rate which
+ // gives reasonable results
+ m_stream = stream_alloc(0, 2, clock() / 32);
+
+ // Resolve CPU port callbacks
+ m_in_p1_cb.resolve_safe(0);
+ m_in_p2_cb.resolve_safe(0);
+ m_in_p3_cb.resolve_safe(0);
+
+ m_out_p1_cb.resolve_safe();
+ m_out_p2_cb.resolve_safe();
+ m_out_p3_cb.resolve_safe();
+
+ //m_serial_w_cb.resolve_safe();
+
+ m_cpu->i8051_set_serial_rx_callback(read8_delegate(FUNC(qs1000_device::data_to_i8052),this));
+
+ save_item(NAME(m_serial_data_in));
+ save_item(NAME(m_wave_regs));
+
+ for (int i = 0; i < QS1000_CHANNELS; i++)
+ {
+ save_item(NAME(m_channels[i].m_acc), i);
+ save_item(NAME(m_channels[i].m_adpcm_signal), i);
+ save_item(NAME(m_channels[i].m_start), i);
+ save_item(NAME(m_channels[i].m_addr), i);
+ save_item(NAME(m_channels[i].m_adpcm_addr), i);
+ save_item(NAME(m_channels[i].m_loop_start), i);
+ save_item(NAME(m_channels[i].m_loop_end), i);
+ save_item(NAME(m_channels[i].m_freq), i);
+ save_item(NAME(m_channels[i].m_flags), i);
+ save_item(NAME(m_channels[i].m_regs), i);
+ save_item(NAME(m_channels[i].m_adpcm.m_signal), i);
+ save_item(NAME(m_channels[i].m_adpcm.m_step), i);
+ }
+}
+
+
+//-------------------------------------------------
+// serial_in - send data to the chip
+//-------------------------------------------------
+void qs1000_device::serial_in(UINT8 data)
+{
+ m_serial_data_in = data;
+
+ // Signal to the CPU that data is available
+ m_cpu->set_input_line(MCS51_RX_LINE, ASSERT_LINE);
+ m_cpu->set_input_line(MCS51_RX_LINE, CLEAR_LINE);
+}
+
+
+//-------------------------------------------------
+// set_irq - interrupt the internal CPU
+//-------------------------------------------------
+void qs1000_device::set_irq(int state)
+{
+ // Signal to the CPU that data is available
+ m_cpu->set_input_line(MCS51_INT1_LINE, state ? ASSERT_LINE : CLEAR_LINE);
+}
+
+
+//-------------------------------------------------
+// data_to_i8052 - called by the 8052 core to
+// receive serial data
+//-------------------------------------------------
+READ8_MEMBER(qs1000_device::data_to_i8052)
+{
+ return m_serial_data_in;
+}
+
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+void qs1000_device::device_reset()
+{
+ for (int ch = 0; ch < QS1000_CHANNELS; ++ch)
+ {
+ m_channels[ch].m_flags = 0;
+ }
+}
+
+
+//-------------------------------------------------
+// memory_space_config - return a description of
+// any address spaces owned by this device
+//-------------------------------------------------
+const address_space_config *qs1000_device::memory_space_config(address_spacenum spacenum) const
+{
+ return (spacenum == 0) ? &m_space_config : NULL;
+}
+
+
+//-------------------------------------------------
+// device_timer - handle deferred writes and
+// resets as a timer callback
+//-------------------------------------------------
+void qs1000_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+}
+
+
+//-------------------------------------------------
+// p0_r
+//-------------------------------------------------
+READ8_MEMBER( qs1000_device::p0_r )
+{
+ return 0xff;
+}
+
+
+//-------------------------------------------------
+// p1_r
+//-------------------------------------------------
+READ8_MEMBER( qs1000_device::p1_r )
+{
+ return m_in_p1_cb(0);
+}
+
+
+//-------------------------------------------------
+// p2_r
+//-------------------------------------------------
+READ8_MEMBER( qs1000_device::p2_r )
+{
+ return m_in_p2_cb(0);
+}
+
+
+//-------------------------------------------------
+// p3_r
+//-------------------------------------------------
+READ8_MEMBER( qs1000_device::p3_r )
+{
+ return m_in_p3_cb(0);
+}
+
+
+//-------------------------------------------------
+// p0_w
+//-------------------------------------------------
+WRITE8_MEMBER( qs1000_device::p0_w )
+{
+}
+
+
+//-------------------------------------------------
+// p1_w
+//-------------------------------------------------
+
+WRITE8_MEMBER( qs1000_device::p1_w )
+{
+ m_out_p1_cb((offs_t)0, data);
+}
+
+
+//-------------------------------------------------
+// p2_w
+//-------------------------------------------------
+
+WRITE8_MEMBER( qs1000_device::p2_w )
+{
+ m_out_p2_cb((offs_t)0, data);
+}
+
+
+//-------------------------------------------------
+// p3_w
+//-------------------------------------------------
+
+WRITE8_MEMBER( qs1000_device::p3_w )
+{
+ m_out_p3_cb((offs_t)0, data);
+}
+
+
+//-------------------------------------------------
+// wave_w - process writes to wavetable engine
+//-------------------------------------------------
+
+WRITE8_MEMBER( qs1000_device::wave_w )
+{
+ m_stream->update();
+
+ if (LOGGING_ENABLED)
+ printf("QS1000 W[%x] %x\n", 0x200 + offset, data);
+
+ switch (offset)
+ {
+ case 0x00:
+ {
+ int ch = m_wave_regs[0xe];
+
+ if (data == 0)
+ {
+ // TODO
+ for (int i = 0; i < 16; ++i)
+ m_channels[ch].m_regs[i] = m_wave_regs[i];
+
+ // Key on
+ start_voice(ch);
+ }
+ if (data == 1)
+ {
+ // ?
+ }
+ else if (data == 2)
+ {
+ // Key off
+ m_channels[ch].m_flags &= ~QS1000_KEYON;
+ }
+ break;
+ }
+
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ {
+ if (m_wave_regs[0x11] == 3)
+ {
+ // Channel-specific write?
+ m_channels[m_wave_regs[0xe]].m_regs[offset] = data;
+ }
+ else
+ {
+ // Global write?
+ m_wave_regs[offset] = data;
+ }
+ break;
+ }
+
+ default:
+ m_wave_regs[offset] = data;
+ }
+}
+
+
+//-------------------------------------------------
+// sound_stream_update -
+//-------------------------------------------------
+void qs1000_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ // Rset the output stream
+ memset(outputs[0], 0x0, samples * sizeof(*outputs[0]));
+ memset(outputs[1], 0x0, samples * sizeof(*outputs[1]));
+
+ // Iterate over voices and accumulate sample data
+ for (int ch = 0; ch < QS1000_CHANNELS; ch++)
+ {
+ qs1000_channel &chan = m_channels[ch];
+
+ UINT8 lvol = chan.m_regs[6];
+ UINT8 rvol = chan.m_regs[7];
+ UINT8 vol = chan.m_regs[8];
+
+ if (chan.m_flags & QS1000_PLAYING)
+ {
+ if (chan.m_flags & QS1000_ADPCM)
+ {
+ for (int samp = 0; samp < samples; samp++)
+ {
+ if (chan.m_addr >= chan.m_loop_end)
+ {
+#if 0 // Looping disabled until envelopes work
+ if (chan.m_flags & QS1000_KEYON)
+ {
+ chan.m_addr = chan.m_loop_start;
+ }
+ else
+#endif
+ {
+ chan.m_flags &= ~QS1000_PLAYING;
+ break;
+ }
+ }
+
+ // Not too keen on this but it'll do for now
+ while (chan.m_start + chan.m_adpcm_addr != chan.m_addr)
+ {
+ chan.m_adpcm_addr++;
+
+ if (chan.m_start + chan.m_adpcm_addr >= chan.m_loop_end)
+ chan.m_adpcm_addr = chan.m_loop_start - chan.m_start;
+
+ UINT8 data = m_direct->read_byte(chan.m_start + (chan.m_adpcm_addr >> 1));
+ UINT8 nibble = (chan.m_adpcm_addr & 1 ? data : data >> 4) & 0xf;
+ chan.m_adpcm_signal = chan.m_adpcm.clock(nibble);
+ }
+
+ INT8 result = (chan.m_adpcm_signal >> 4);
+ chan.m_acc += chan.m_freq;
+ chan.m_addr = (chan.m_addr + (chan.m_acc >> 18)) & QS1000_ADDRESS_MASK;
+ chan.m_acc &= ((1 << 18) - 1);
+
+ outputs[0][samp] += (result * 4 * lvol * vol) >> 12;
+ outputs[1][samp] += (result * 4 * rvol * vol) >> 12;
+ }
+ }
+ else
+ {
+ for (int samp = 0; samp < samples; samp++)
+ {
+ if (chan.m_addr >= chan.m_loop_end)
+ {
+#if 0 // Looping disabled until envelopes work
+ if (chan.m_flags & QS1000_KEYON)
+ {
+ chan.m_addr = chan.m_loop_start;
+ }
+ else
+#endif
+ {
+ chan.m_flags &= ~QS1000_PLAYING;
+ break;
+ }
+ }
+
+ INT8 result = m_direct->read_byte(chan.m_addr) - 128;
+
+ chan.m_acc += chan.m_freq;
+ chan.m_addr = (chan.m_addr + (chan.m_acc >> 18)) & QS1000_ADDRESS_MASK;
+ chan.m_acc &= ((1 << 18) - 1);
+
+ outputs[0][samp] += (result * lvol * vol) >> 12;
+ outputs[1][samp] += (result * rvol * vol) >> 12;
+ }
+ }
+ }
+ }
+}
+
+
+void qs1000_device::start_voice(int ch)
+{
+ UINT32 table_addr = (m_channels[ch].m_regs[0x01] << 16) | (m_channels[ch].m_regs[0x02] << 8) | m_channels[ch].m_regs[0x03];
+
+ // Fetch the sound information
+ UINT16 freq = (m_direct->read_byte(table_addr + 0) << 8) | m_direct->read_byte(table_addr + 1);
+ UINT16 word1 = (m_direct->read_byte(table_addr + 2) << 8) | m_direct->read_byte(table_addr + 3);
+ UINT16 base = (m_direct->read_byte(table_addr + 4) << 8) | m_direct->read_byte(table_addr + 5);
+
+ if (LOGGING_ENABLED)
+ printf("[%.6x] Freq:%.4x ????:%.4x Addr:%.4x\n", table_addr, freq, word1, base);
+
+ // See Raccoon World and Wyvern Wings NULL sound
+ if (freq == 0)
+ return;
+
+ // Fetch the sample pointers and flags
+ UINT8 byte0 = m_direct->read_byte(base);
+
+ UINT32 start_addr;
+
+ start_addr = byte0 << 16;
+ start_addr |= m_direct->read_byte(base + 1) << 8;
+ start_addr |= m_direct->read_byte(base + 2) << 0;
+ start_addr &= QS1000_ADDRESS_MASK;
+
+ UINT32 loop_start;
+
+ loop_start = (byte0 & 0xf0) << 16;
+ loop_start |= m_direct->read_byte(base + 3) << 12;
+ loop_start |= m_direct->read_byte(base + 4) << 4;
+ loop_start |= m_direct->read_byte(base + 5) >> 4;
+ loop_start &= QS1000_ADDRESS_MASK;
+
+ UINT32 loop_end;
+
+ loop_end = (byte0 & 0xf0) << 16;
+ loop_end |= (m_direct->read_byte(base + 5) & 0xf) << 16;
+ loop_end |= m_direct->read_byte(base + 6) << 8;
+ loop_end |= m_direct->read_byte(base + 7);
+ loop_end &= QS1000_ADDRESS_MASK;
+
+ UINT8 byte8 = m_direct->read_byte(base + 8);
+
+ if (LOGGING_ENABLED)
+ {
+ UINT8 byte9 = m_direct->read_byte(base + 9);
+ UINT8 byte10 = m_direct->read_byte(base + 10);
+ UINT8 byte11 = m_direct->read_byte(base + 11);
+ UINT8 byte12 = m_direct->read_byte(base + 12);
+ UINT8 byte13 = m_direct->read_byte(base + 13);
+ UINT8 byte14 = m_direct->read_byte(base + 14);
+ UINT8 byte15 = m_direct->read_byte(base + 15);
+
+ printf("[%.6x] Sample Start:%.6x Loop Start:%.6x Loop End:%.6x Params: %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x\n", base, start_addr, loop_start, loop_end, byte8, byte9, byte10, byte11, byte12, byte13, byte14, byte15);
+ }
+
+ m_channels[ch].m_acc = 0;
+ m_channels[ch].m_start = start_addr;
+ m_channels[ch].m_addr = start_addr;
+ m_channels[ch].m_loop_start = loop_start;
+ m_channels[ch].m_loop_end = loop_end;
+ m_channels[ch].m_freq = freq;
+ m_channels[ch].m_flags = QS1000_PLAYING | QS1000_KEYON;
+
+ if (byte8 & 0x08)
+ {
+ m_channels[ch].m_adpcm.reset();
+ m_channels[ch].m_adpcm_addr = -1;
+// m_channels[ch].m_adpcm_signal = -2;
+ m_channels[ch].m_flags |= QS1000_ADPCM;
+ }
+}