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.cpp406
1 files changed, 296 insertions, 110 deletions
diff --git a/src/devices/sound/ymz770.cpp b/src/devices/sound/ymz770.cpp
index 11b30d08eb1..c7a7a91a114 100644
--- a/src/devices/sound/ymz770.cpp
+++ b/src/devices/sound/ymz770.cpp
@@ -1,21 +1,23 @@
// license:BSD-3-Clause
-// copyright-holders:Olivier Galibert, R. Belmont
+// copyright-holders:Olivier Galibert, R. Belmont, MetalliC
/***************************************************************************
- ymz770.c
+ Yamaha YMZ770C and YMZ774
- Emulation by R. Belmont
+ Emulation by R. Belmont and MetalliC
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?
+ 770:
+- verify if pan 100% correct
+- sequencer timers and triggers not implemented (seems used in Deathsmiles ending tune)
+ 774:
+- find out how volume/pan delayed transition works (used few times in orleg2 attract mode)
+- 4 channel output
+- Equalizer
+- Sequencer (not used)
***************************************************************************/
@@ -24,20 +26,28 @@ TODO:
#include "mpeg_audio.h"
// device type definition
-DEFINE_DEVICE_TYPE(YMZ770, ymz770_device, "ymz770", "Yamaha YMZ770 AMMS-A")
+DEFINE_DEVICE_TYPE(YMZ770, ymz770_device, "ymz770", "Yamaha YMZ770C-F AMMS-A")
+DEFINE_DEVICE_TYPE(YMZ774, ymz774_device, "ymz774", "Yamaha YMZ774-S AMMS2")
//-------------------------------------------------
// ymz770_device - constructor
//-------------------------------------------------
ymz770_device::ymz770_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : device_t(mconfig, YMZ770, tag, owner, clock)
+ : ymz770_device(mconfig, YMZ770, tag, owner, clock, 16000)
+{
+}
+
+ymz770_device::ymz770_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t sclock)
+ : device_t(mconfig, type, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, m_stream(nullptr)
+ , m_sclock(sclock)
, m_cur_reg(0)
, m_mute(0)
, m_doen(0)
, m_vlma(0)
+ , m_vlma1(0)
, m_bsl(0)
, m_cpl(0)
, m_rom(*this, DEVICE_SELF)
@@ -52,41 +62,51 @@ ymz770_device::ymz770_device(const machine_config &mconfig, const char *tag, dev
void ymz770_device::device_start()
{
// create the stream
- m_stream = machine().sound().stream_alloc(*this, 0, 2, 16000);
+ m_stream = machine().sound().stream_alloc(*this, 0, 2, m_sclock);
- for (auto & elem : m_channels)
+ for (auto & channel : m_channels)
{
- elem.is_playing = false;
- elem.is_seq_playing = false;
- elem.decoder = new mpeg_audio(&m_rom[0], mpeg_audio::AMM, false, 0);
+ channel.is_playing = false;
+ channel.decoder = new mpeg_audio(&m_rom[0], mpeg_audio::AMM, false, 0);
}
+ for (auto & sequence : m_sequences)
+ sequence.is_playing = false;
// 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_vlma1));
save_item(NAME(m_bsl));
save_item(NAME(m_cpl));
- for (int ch = 0; ch < 8; ch++)
+ for (int ch = 0; ch < 16; ch++) // TODO array size
{
save_item(NAME(m_channels[ch].phrase), ch);
save_item(NAME(m_channels[ch].pan), ch);
+ save_item(NAME(m_channels[ch].pan_delay), ch);
+ save_item(NAME(m_channels[ch].pan1), ch);
+ save_item(NAME(m_channels[ch].pan1_delay), ch);
save_item(NAME(m_channels[ch].volume), ch);
- save_item(NAME(m_channels[ch].control), ch);
+ save_item(NAME(m_channels[ch].volume_delay), ch);
+ save_item(NAME(m_channels[ch].volume2), ch);
+ save_item(NAME(m_channels[ch].loop), 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);
}
+ for (int ch = 0; ch < 8; ch++)
+ {
+ save_item(NAME(m_sequences[ch].sequence), ch);
+ save_item(NAME(m_sequences[ch].control), ch);
+ save_item(NAME(m_sequences[ch].delay), ch);
+ save_item(NAME(m_sequences[ch].is_playing), ch);
+ }
}
@@ -96,19 +116,27 @@ void ymz770_device::device_start()
void ymz770_device::device_reset()
{
- for (auto & elem : m_channels)
+ for (auto & channel : m_channels)
{
- elem.phrase = 0;
- elem.pan = 8;
- elem.volume = 0;
- elem.control = 0;
- elem.sequence = 0;
- elem.seqcontrol = 0;
- elem.seqdelay = 0;
- elem.is_playing = false;
- elem.is_seq_playing = false;
- elem.output_remaining = 0;
- elem.decoder->clear();
+ channel.phrase = 0;
+ channel.pan = 64;
+ channel.pan_delay = 0;
+ channel.pan1 = 64;
+ channel.pan1_delay = 0;
+ channel.volume = 0;
+ channel.volume_delay = 0;
+ channel.volume2 = 0;
+ channel.loop = 0;
+ channel.is_playing = false;
+ channel.output_remaining = 0;
+ channel.decoder->clear();
+ }
+ for (auto & sequence : m_sequences)
+ {
+ sequence.sequence = 0;
+ sequence.control = 0;
+ sequence.delay = 0;
+ sequence.is_playing = false;
}
}
@@ -127,105 +155,142 @@ void ymz770_device::sound_stream_update(sound_stream &stream, stream_sample_t **
for (int i = 0; i < samples; i++)
{
- // run sequencers (should probably be in separate timer callbacks)
- for (auto & elem : m_channels)
- {
- if (elem.is_seq_playing)
- {
- if (elem.seqdelay > 0)
- {
- elem.seqdelay--;
- }
- else
- {
- int reg = *elem.seqdata++;
- uint8_t data = *elem.seqdata++;
- switch (reg)
- {
- case 0x0f:
- if (elem.seqcontrol & 1)
- {
- // loop sequence
- uint8_t sqn = elem.sequence;
- uint32_t pptr = m_rom[(4*sqn)+1+0x400]<<16 | m_rom[(4*sqn)+2+0x400]<<8 | m_rom[(4*sqn)+3+0x400];
- elem.seqdata = &m_rom[pptr];
- }
- else
- {
- elem.is_seq_playing = false;
- }
- break;
- case 0x0e:
- elem.seqdelay = 32 - 1;
- break;
- default:
- internal_reg_write(reg, data);
- break;
- }
- }
- }
- }
+ sequencer();
// process channels
- int32_t mix = 0;
+ int32_t mixl = 0;
+ int32_t mixr = 0;
- for (auto & elem : m_channels)
+ for (auto & channel : m_channels)
{
- if (elem.output_remaining > 0)
+ if (channel.output_remaining > 0)
{
// force finish current block
- mix += (elem.output_data[elem.output_ptr++]*elem.volume);
- elem.output_remaining--;
-
- if (elem.output_remaining == 0 && !elem.is_playing)
- elem.decoder->clear();
+ int32_t smpl = (channel.output_data[channel.output_ptr++] * channel.volume) >> 7; // volume is linear, 0 - 128 (100%)
+ smpl = (smpl * channel.volume2) >> 7;
+ mixr += (smpl * channel.pan) >> 7; // pan seems linear, 0 - 128, where 0 = 100% left, 128 = 100% right, 64 = 50% left 50% right
+ mixl += (smpl * (128 - channel.pan)) >> 7;
+ channel.output_remaining--;
+
+ if (channel.output_remaining == 0 && !channel.is_playing)
+ channel.decoder->clear();
}
- else if (elem.is_playing)
+ else if (channel.is_playing)
{
retry:
- if (elem.last_block)
+ if (channel.last_block)
{
- if (elem.control & 1)
+ if (channel.loop)
{
+ if (channel.loop != 255)
+ --channel.loop;
// loop sample
- uint8_t phrase = elem.phrase;
- elem.atbl = m_rom[(4*phrase)+0] >> 4 & 7;
- elem.pptr = 8*(m_rom[(4*phrase)+1]<<16 | m_rom[(4*phrase)+2]<<8 | m_rom[(4*phrase)+3]);
+ int phrase = channel.phrase;
+ channel.atbl = m_rom[(4*phrase)+0] >> 4 & 7;
+ channel.pptr = 8 * get_phrase_offs(phrase);
}
else
{
- elem.is_playing = false;
- elem.output_remaining = 0;
- elem.decoder->clear();
+ channel.is_playing = false;
+ channel.output_remaining = 0;
+ channel.decoder->clear();
}
}
- if (elem.is_playing)
+ if (channel.is_playing)
{
// next block
int sample_rate, channel_count;
- if (!elem.decoder->decode_buffer(elem.pptr, m_rom.bytes()*8, elem.output_data, elem.output_remaining, sample_rate, channel_count) || elem.output_remaining == 0)
+ if (!channel.decoder->decode_buffer(channel.pptr, m_rom.bytes()*8, channel.output_data, channel.output_remaining, sample_rate, channel_count) || channel.output_remaining == 0)
{
- elem.is_playing = !elem.last_block; // detect infinite retry loop
- elem.last_block = true;
- elem.output_remaining = 0;
+ channel.is_playing = !channel.last_block; // detect infinite retry loop
+ channel.last_block = true;
+ channel.output_remaining = 0;
goto retry;
}
- elem.last_block = elem.output_remaining < 1152;
- elem.output_remaining--;
- elem.output_ptr = 1;
+ channel.last_block = channel.output_remaining < 1152;
+ channel.output_remaining--;
+ channel.output_ptr = 1;
- mix += (elem.output_data[0]*elem.volume);
+ int32_t smpl = (channel.output_data[0] * channel.volume) >> 7;
+ smpl = (smpl * channel.volume2) >> 7;
+ mixr += (smpl * channel.pan) >> 7;
+ mixl += (smpl * (128 - channel.pan)) >> 7;
}
}
}
- outL[i] = outR[i] = mix>>8;
+ mixr *= m_vlma; // main volume is linear, 0 - 255, where 128 = 100%
+ mixl *= m_vlma;
+ mixr >>= 7 - m_bsl;
+ mixl >>= 7 - m_bsl;
+ // Clip limiter: 0 - off, 1 - 6.02 dB (100%), 2 - 4.86 dB (87.5%), 3 - 3.52 dB (75%). values taken from YMZ773 docs, might be incorrect for YMZ770.
+ constexpr int32_t ClipMax3 = 32768 * 75 / 100;
+ constexpr int32_t ClipMax2 = 32768 * 875 / 1000;
+ switch (m_cpl)
+ {
+ case 3:
+ mixl = (mixl > ClipMax3) ? ClipMax3 : (mixl < -ClipMax3) ? -ClipMax3 : mixl;
+ mixr = (mixr > ClipMax3) ? ClipMax3 : (mixr < -ClipMax3) ? -ClipMax3 : mixr;
+ break;
+ case 2:
+ mixl = (mixl > ClipMax2) ? ClipMax2 : (mixl < -ClipMax2) ? -ClipMax2 : mixl;
+ mixr = (mixr > ClipMax2) ? ClipMax2 : (mixr < -ClipMax2) ? -ClipMax2 : mixr;
+ break;
+ case 1:
+ mixl = (mixl > 32767) ? 32767 : (mixl < -32768) ? -32768 : mixl;
+ mixr = (mixr > 32767) ? 32767 : (mixr < -32768) ? -32768 : mixr;
+ break;
+ }
+ if (m_mute)
+ mixr = mixl = 0;
+ outL[i] = mixl;
+ outR[i] = mixr;
}
}
+void ymz770_device::sequencer()
+{
+ for (auto & sequence : m_sequences)
+ {
+ if (sequence.is_playing)
+ {
+ if (sequence.delay > 0)
+ {
+ sequence.delay--;
+ }
+ else
+ {
+ int reg = *sequence.data++;
+ uint8_t data = *sequence.data++;
+ switch (reg)
+ {
+ case 0x0f:
+ if (sequence.control & 1)
+ {
+ // loop sequence
+ uint8_t sqn = sequence.sequence;
+ uint32_t pptr = get_seq_offs(sqn);
+ sequence.data = &m_rom[pptr];
+ }
+ else
+ {
+ sequence.is_playing = false;
+ }
+ break;
+ case 0x0e:
+ sequence.delay = 32 - 1;
+ break;
+ default:
+ internal_reg_write(reg, data);
+ break;
+ }
+ }
+ }
+ }
+}
//-------------------------------------------------
// write - write to the chip's registers
@@ -285,10 +350,11 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
case 1:
m_channels[ch].volume = data;
+ m_channels[ch].volume2 = 128;
break;
case 2:
- m_channels[ch].pan = data;
+ m_channels[ch].pan = data << 3;
break;
case 3:
@@ -296,7 +362,7 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
{
uint8_t phrase = m_channels[ch].phrase;
m_channels[ch].atbl = m_rom[(4*phrase)+0] >> 4 & 7;
- m_channels[ch].pptr = 8*(m_rom[(4*phrase)+1]<<16 | m_rom[(4*phrase)+2]<<8 | m_rom[(4*phrase)+3]);
+ m_channels[ch].pptr = 8 * get_phrase_offs(phrase);
m_channels[ch].last_block = false;
m_channels[ch].is_playing = true;
@@ -306,7 +372,7 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
m_channels[ch].is_playing = false;
}
- m_channels[ch].control = data;
+ m_channels[ch].loop = (data & 1) ? 255 : 0;
break;
}
}
@@ -319,23 +385,23 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
switch (reg & 0x0f)
{
case 0:
- m_channels[ch].sequence = data;
+ m_sequences[ch].sequence = data;
break;
case 1:
if (data & 6)
{
- uint8_t sqn = m_channels[ch].sequence;
- uint32_t pptr = m_rom[(4*sqn)+1+0x400]<<16 | m_rom[(4*sqn)+2+0x400]<<8 | m_rom[(4*sqn)+3+0x400];
- m_channels[ch].seqdata = &m_rom[pptr];
- m_channels[ch].seqdelay = 0;
- m_channels[ch].is_seq_playing = true;
+ uint8_t sqn = m_sequences[ch].sequence;
+ uint32_t pptr = get_seq_offs(sqn);
+ m_sequences[ch].data = &m_rom[pptr];
+ m_sequences[ch].delay = 0;
+ m_sequences[ch].is_playing = true;
}
else
{
- m_channels[ch].is_seq_playing = false;
+ m_sequences[ch].is_playing = false;
}
- m_channels[ch].seqcontrol = data;
+ m_sequences[ch].control = data;
break;
default:
@@ -343,3 +409,123 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
}
}
}
+
+//-------------------------------------------------
+// ymz774_device
+//-------------------------------------------------
+
+ymz774_device::ymz774_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : ymz770_device(mconfig, YMZ774, tag, owner, clock, 44100)
+{
+}
+
+READ8_MEMBER(ymz774_device::read)
+{
+ if (offset & 1)
+ {
+ if (m_cur_reg == 0xe3 || m_cur_reg == 0xe4)
+ {
+ m_stream->update();
+ uint8_t res = 0;
+ int bank = (m_cur_reg == 0xe3) ? 8 : 0;
+ for (int i = 0; i < 8; i++)
+ if (m_channels[i + bank].is_playing)
+ res |= 1 << i;
+ return res;
+ }
+ }
+ logerror("unimplemented read %02X\n", m_cur_reg);
+ return 0;
+}
+
+void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
+{
+ // playback registers
+ if (reg < 0x10) // phrase num H and L
+ {
+ int ch = ((reg >> 1) & 7) + m_bank * 8;
+ if (reg & 1)
+ m_channels[ch].phrase = (m_channels[ch].phrase & 0xff00) | data;
+ else
+ m_channels[ch].phrase = (m_channels[ch].phrase & 0x00ff) | ((data & 7) << 8);
+ }
+ else if (reg < 0x60)
+ {
+ int ch = (reg & 7) + m_bank * 8;
+ switch (reg & 0xf8)
+ {
+ case 0x10: // Volume 1
+ m_channels[ch].volume = data;
+ break;
+ case 0x18: // Volume 1 delayed transition
+ if (data) logerror("unimplemented write %02X %02X\n", reg, data);
+ m_channels[ch].volume_delay = data;
+ break;
+ case 0x20: // Volume 2
+ m_channels[ch].volume2 = data;
+ break;
+ case 0x28: // Pan L/R
+ m_channels[ch].pan = data;
+ break;
+ case 0x30: // Pan L/R delayed transition
+ if (data) logerror("unimplemented write %02X %02X\n", reg, data);
+ m_channels[ch].pan_delay = data;
+ break;
+ case 0x38: // Pan T/B
+ m_channels[ch].pan1 = data;
+ break;
+ case 0x40: // Pan T/B delayed transition
+ if (data) logerror("unimplemented write %02X %02X\n", reg, data);
+ m_channels[ch].pan1_delay = data;
+ break;
+ case 0x48: // Loop
+ m_channels[ch].loop = data;
+ break;
+ case 0x50: // Start / Stop
+ if (data)
+ {
+ int phrase = m_channels[ch].phrase;
+ m_channels[ch].atbl = m_rom[(4 * phrase) + 0] >> 4 & 7;
+ m_channels[ch].pptr = 8 * get_phrase_offs(phrase);
+ m_channels[ch].last_block = false;
+
+ m_channels[ch].is_playing = true;
+ }
+ else
+ {
+ m_channels[ch].is_playing = false;
+ }
+ break;
+ case 0x58: // Pause / Resume
+ if (data) logerror("pause/resume unimplemented %02X %02X\n", reg, data);
+ break;
+ }
+ }
+ else if (reg < 0xd0)
+ {
+ if (m_bank == 0) // Sequencer, in PGM2 games not used at all
+ {
+ if (data) logerror("sequencer unimplemented %02X %02X\n", reg, data);
+ }
+ // else bank1 - Equalizer control
+ }
+ // global registers
+ else
+ {
+ switch (reg) {
+ case 0xd0:
+ m_vlma = data;
+ break;
+ case 0xd1:
+ m_vlma1 = data;
+ break;
+ case 0xd2:
+ m_cpl = data;
+ break;
+ case 0xf0:
+ m_bank = data & 1;
+ if (data > 1) logerror("Set bank %02X!\n", data);
+ break;
+ }
+ }
+}