summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ymz770.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ymz770.cpp')
-rw-r--r--src/devices/sound/ymz770.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/src/devices/sound/ymz770.cpp b/src/devices/sound/ymz770.cpp
new file mode 100644
index 00000000000..f367f4d10eb
--- /dev/null
+++ b/src/devices/sound/ymz770.cpp
@@ -0,0 +1,345 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+/***************************************************************************
+
+ ymz770.c
+
+ Emulation by R. Belmont
+ AMM decode by Olivier Galibert
+
+-----
+TODO:
+- A lot of unimplemented features, even simple ones like panning,
+ these should be added once we find out any software that uses it.
+- Is channel volume linear(current implementation) or logarithmic?
+- Sequencer is very preliminary
+- What does channel ATBL mean?
+- Is YMZ774(and other variants) the same family as this chip?
+ What are the differences?
+
+***************************************************************************/
+
+#include "emu.h"
+#include "ymz770.h"
+#include "mpeg_audio.h"
+
+// device type definition
+const device_type YMZ770 = &device_creator<ymz770_device>;
+
+//-------------------------------------------------
+// ymz770_device - constructor
+//-------------------------------------------------
+
+ymz770_device::ymz770_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, YMZ770, "Yamaha YMZ770", tag, owner, clock, "ymz770", __FILE__),
+ device_sound_interface(mconfig, *this),
+ m_cur_reg(0),
+ m_mute(0),
+ m_doen(0),
+ m_vlma(0),
+ m_bsl(0),
+ m_cpl(0)
+{
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void ymz770_device::device_start()
+{
+ // create the stream
+ m_stream = machine().sound().stream_alloc(*this, 0, 2, 16000);
+ m_rom_base = region()->base();
+ m_rom_limit = region()->bytes() * 8;
+
+ for (int i = 0; i < 8; i++)
+ {
+ m_channels[i].is_playing = false;
+ m_channels[i].is_seq_playing = false;
+ m_channels[i].decoder = new mpeg_audio(m_rom_base, mpeg_audio::AMM, false, 0);
+ }
+
+ // register for save states
+ save_item(NAME(m_cur_reg));
+ save_item(NAME(m_mute));
+ save_item(NAME(m_doen));
+ save_item(NAME(m_vlma));
+ save_item(NAME(m_bsl));
+ save_item(NAME(m_cpl));
+
+ for (int ch = 0; ch < 8; ch++)
+ {
+ save_item(NAME(m_channels[ch].phrase), ch);
+ save_item(NAME(m_channels[ch].pan), ch);
+ save_item(NAME(m_channels[ch].volume), ch);
+ save_item(NAME(m_channels[ch].control), ch);
+ save_item(NAME(m_channels[ch].is_playing), ch);
+ save_item(NAME(m_channels[ch].last_block), ch);
+ save_item(NAME(m_channels[ch].output_remaining), ch);
+ save_item(NAME(m_channels[ch].output_ptr), ch);
+ save_item(NAME(m_channels[ch].atbl), ch);
+ save_item(NAME(m_channels[ch].pptr), ch);
+ save_item(NAME(m_channels[ch].sequence), ch);
+ save_item(NAME(m_channels[ch].seqcontrol), ch);
+ save_item(NAME(m_channels[ch].seqdelay), ch);
+ save_item(NAME(m_channels[ch].is_seq_playing), ch);
+ save_item(NAME(m_channels[ch].output_data), ch);
+ }
+}
+
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+
+void ymz770_device::device_reset()
+{
+ for (int ch = 0; ch < 8; ch++)
+ {
+ m_channels[ch].phrase = 0;
+ m_channels[ch].pan = 8;
+ m_channels[ch].volume = 0;
+ m_channels[ch].control = 0;
+ m_channels[ch].sequence = 0;
+ m_channels[ch].seqcontrol = 0;
+ m_channels[ch].seqdelay = 0;
+ m_channels[ch].is_playing = false;
+ m_channels[ch].is_seq_playing = false;
+ m_channels[ch].output_remaining = 0;
+ m_channels[ch].decoder->clear();
+ }
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - handle update requests for
+// our sound stream
+//-------------------------------------------------
+
+void ymz770_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ stream_sample_t *outL, *outR;
+
+ outL = outputs[0];
+ outR = outputs[1];
+
+ for (int i = 0; i < samples; i++)
+ {
+ // run sequencers (should probably be in separate timer callbacks)
+ for (int ch = 0; ch < 8; ch++)
+ {
+ if (m_channels[ch].is_seq_playing)
+ {
+ if (m_channels[ch].seqdelay > 0)
+ {
+ m_channels[ch].seqdelay--;
+ }
+ else
+ {
+ int reg = *m_channels[ch].seqdata++;
+ UINT8 data = *m_channels[ch].seqdata++;
+ switch (reg)
+ {
+ case 0x0f:
+ if (m_channels[ch].seqcontrol & 1)
+ {
+ // loop sequence
+ UINT8 sqn = m_channels[ch].sequence;
+ UINT32 pptr = m_rom_base[(4*sqn)+1+0x400]<<16 | m_rom_base[(4*sqn)+2+0x400]<<8 | m_rom_base[(4*sqn)+3+0x400];
+ m_channels[ch].seqdata = &m_rom_base[pptr];
+ }
+ else
+ {
+ m_channels[ch].is_seq_playing = false;
+ }
+ break;
+ case 0x0e:
+ m_channels[ch].seqdelay = 32 - 1;
+ break;
+ default:
+ internal_reg_write(reg, data);
+ break;
+ }
+ }
+ }
+ }
+
+ // process channels
+ INT32 mix = 0;
+
+ for (int ch = 0; ch < 8; ch++)
+ {
+ if (m_channels[ch].output_remaining > 0)
+ {
+ // force finish current block
+ mix += (m_channels[ch].output_data[m_channels[ch].output_ptr++]*m_channels[ch].volume);
+ m_channels[ch].output_remaining--;
+
+ if (m_channels[ch].output_remaining == 0 && !m_channels[ch].is_playing)
+ m_channels[ch].decoder->clear();
+ }
+
+ else if (m_channels[ch].is_playing)
+ {
+retry:
+ if (m_channels[ch].last_block)
+ {
+ if (m_channels[ch].control & 1)
+ {
+ // loop sample
+ UINT8 phrase = m_channels[ch].phrase;
+ m_channels[ch].atbl = m_rom_base[(4*phrase)+0] >> 4 & 7;
+ m_channels[ch].pptr = 8*(m_rom_base[(4*phrase)+1]<<16 | m_rom_base[(4*phrase)+2]<<8 | m_rom_base[(4*phrase)+3]);
+ }
+ else
+ {
+ m_channels[ch].is_playing = false;
+ m_channels[ch].output_remaining = 0;
+ m_channels[ch].decoder->clear();
+ }
+ }
+
+ if (m_channels[ch].is_playing)
+ {
+ // next block
+ int sample_rate, channel_count;
+ if (!m_channels[ch].decoder->decode_buffer(m_channels[ch].pptr, m_rom_limit, m_channels[ch].output_data, m_channels[ch].output_remaining, sample_rate, channel_count) || m_channels[ch].output_remaining == 0)
+ {
+ m_channels[ch].is_playing = !m_channels[ch].last_block; // detect infinite retry loop
+ m_channels[ch].last_block = true;
+ m_channels[ch].output_remaining = 0;
+ goto retry;
+ }
+
+ m_channels[ch].last_block = m_channels[ch].output_remaining < 1152;
+ m_channels[ch].output_remaining--;
+ m_channels[ch].output_ptr = 1;
+
+ mix += (m_channels[ch].output_data[0]*m_channels[ch].volume);
+ }
+ }
+ }
+
+ outL[i] = outR[i] = mix>>8;
+ }
+}
+
+
+//-------------------------------------------------
+// write - write to the chip's registers
+//-------------------------------------------------
+
+WRITE8_MEMBER( ymz770_device::write )
+{
+ if (offset & 1)
+ {
+ m_stream->update();
+ internal_reg_write(m_cur_reg, data);
+ }
+ else
+ {
+ m_cur_reg = data;
+ }
+}
+
+
+void ymz770_device::internal_reg_write(UINT8 reg, UINT8 data)
+{
+ // global registers
+ if (reg < 0x40)
+ {
+ switch (reg)
+ {
+ case 0x00:
+ m_mute = data & 1;
+ m_doen = data >> 1 & 1;
+ break;
+
+ case 0x01:
+ m_vlma = data;
+ break;
+
+ case 0x02:
+ m_bsl = data & 7;
+ m_cpl = data >> 4 & 7;
+ break;
+
+ // unused
+ default:
+ break;
+ }
+ }
+
+ // playback registers
+ else if (reg < 0x60)
+ {
+ int ch = reg >> 2 & 0x07;
+
+ switch (reg & 0x03)
+ {
+ case 0:
+ m_channels[ch].phrase = data;
+ break;
+
+ case 1:
+ m_channels[ch].volume = data;
+ break;
+
+ case 2:
+ m_channels[ch].pan = data;
+ break;
+
+ case 3:
+ if (data & 6)
+ {
+ UINT8 phrase = m_channels[ch].phrase;
+ m_channels[ch].atbl = m_rom_base[(4*phrase)+0] >> 4 & 7;
+ m_channels[ch].pptr = 8*(m_rom_base[(4*phrase)+1]<<16 | m_rom_base[(4*phrase)+2]<<8 | m_rom_base[(4*phrase)+3]);
+ m_channels[ch].last_block = false;
+
+ m_channels[ch].is_playing = true;
+ }
+ else
+ {
+ m_channels[ch].is_playing = false;
+ }
+
+ m_channels[ch].control = data;
+ break;
+ }
+ }
+
+ // sequencer registers
+ else
+ {
+ int ch = reg >> 4 & 0x07;
+
+ switch (reg & 0x0f)
+ {
+ case 0:
+ m_channels[ch].sequence = data;
+ break;
+ case 1:
+ if (data & 6)
+ {
+ UINT8 sqn = m_channels[ch].sequence;
+ UINT32 pptr = m_rom_base[(4*sqn)+1+0x400]<<16 | m_rom_base[(4*sqn)+2+0x400]<<8 | m_rom_base[(4*sqn)+3+0x400];
+ m_channels[ch].seqdata = &m_rom_base[pptr];
+ m_channels[ch].seqdelay = 0;
+ m_channels[ch].is_seq_playing = true;
+ }
+ else
+ {
+ m_channels[ch].is_seq_playing = false;
+ }
+
+ m_channels[ch].seqcontrol = data;
+ break;
+
+ default:
+ break;
+ }
+ }
+}