// license:BSD-3-Clause // copyright-holders:Olivier Galibert, R. Belmont, MetalliC /*************************************************************************** Yamaha YMZ770C "AMMS-A" and YMZ774 "AMMS2C" Emulation by R. Belmont and MetalliC AMM decode by Olivier Galibert ----- TODO: - What does channel ATBL mean? - Simple Access mode. SACs is register / data lists same as SEQ. in 770C, when both /SEL and /CS pins goes low - will be run SAC with number set at data bus. can not be used in CV1K (/SEL pin is NC, internally pulled to VCC), probably not used in PGM2 too. 770: - sequencer timers implemented but seems unused, presumably because of design flaws or bugs, likely due to lack of automatic adding of sequencer # to register offset. in result sequences uses very long chains of 32-sample wait commands instead, wasting a lot of ROM space. - sequencer triggers not implemented, not sure how they works (Deathsmiles ending tune starts sequence with TGST = 01h, likely a bug and don't affect tune playback) 774: - 4 channel output - Equalizer - pan delayed transition (not used in games) - sequencer off trigger (not used in games) known SPUs in this series: YMZ770B AMMSL Capcom medal hardware (alien.cpp), sample format is not AMM, in other parts looks like 770C YMZ770C AMMS-A Cave CV1000 YMZ771 SSGS3 YMZ773 AMMS2 Cron corp. video slots YMZ775 AMMS2B YMZ774 AMMS2C IGS PGM2 YMZ776 AMMS3 uses AM3 sample format (modified Ogg?) YMZ778 AMMS3S YMZ779 AMMS3D YMZ870 AMMS3EX ***************************************************************************/ #include "emu.h" #include "ymz770.h" #include "mpeg_audio.h" // device type definition DEFINE_DEVICE_TYPE(YMZ770, ymz770_device, "ymz770", "Yamaha YMZ770C AMMS-A") DEFINE_DEVICE_TYPE(YMZ774, ymz774_device, "ymz774", "Yamaha YMZ774 AMMS2C") //------------------------------------------------- // ymz770_device - constructor //------------------------------------------------- ymz770_device::ymz770_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t 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) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void ymz770_device::device_start() { // create the stream m_stream = machine().sound().stream_alloc(*this, 0, 2, m_sclock); for (auto & channel : m_channels) { 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; for (auto & sqc : m_sqcs) sqc.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 < 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].volume_target), 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].is_paused), 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].output_data), ch); } for (int ch = 0; ch < 8; ch++) { save_item(NAME(m_sequences[ch].delay), ch); save_item(NAME(m_sequences[ch].sequence), ch); save_item(NAME(m_sequences[ch].timer), ch); save_item(NAME(m_sequences[ch].stopchan), ch); save_item(NAME(m_sequences[ch].loop), ch); save_item(NAME(m_sequences[ch].bank), ch); save_item(NAME(m_sequences[ch].is_playing), ch); save_item(NAME(m_sequences[ch].is_paused), ch); save_item(NAME(m_sequences[ch].offset), ch); } for (int ch = 0; ch < 8; ch++) { save_item(NAME(m_sqcs[ch].sqc), ch); save_item(NAME(m_sqcs[ch].loop), ch); save_item(NAME(m_sqcs[ch].is_playing), ch); save_item(NAME(m_sqcs[ch].is_waiting), ch); save_item(NAME(m_sqcs[ch].offset), ch); } } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void ymz770_device::device_reset() { for (auto & channel : m_channels) { channel.phrase = 0; channel.pan = 64; channel.pan_delay = 0; channel.pan1 = 64; channel.pan1_delay = 0; channel.volume = 0; channel.volume_target = 0; channel.volume_delay = 0; channel.volume2 = 0; channel.loop = 0; channel.is_playing = false; channel.is_paused = false; channel.output_remaining = 0; channel.decoder->clear(); } for (auto & sequence : m_sequences) { sequence.delay = 0; sequence.sequence = 0; sequence.timer = 0; sequence.stopchan = 0; sequence.loop = 0; sequence.bank = 0; sequence.is_playing = false; sequence.is_paused = false; } for (auto & sqc : m_sqcs) { sqc.is_playing = false; sqc.loop = 0; } } //------------------------------------------------- // 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++) { sequencer(); // process channels int32_t mixl = 0; int32_t mixr = 0; for (auto & channel : m_channels) { if (channel.output_remaining > 0) { // force finish current block int32_t smpl = ((int32_t)channel.output_data[channel.output_ptr++] * (channel.volume >> 17)) >> 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 (channel.is_playing && !channel.is_paused) { retry: if (channel.last_block) { if (channel.loop) { if (channel.loop != 255) --channel.loop; // loop sample int phrase = channel.phrase; channel.atbl = m_rom[(4*phrase)+0] >> 4 & 7; channel.pptr = 8 * get_phrase_offs(phrase); } else { channel.is_playing = false; channel.output_remaining = 0; channel.decoder->clear(); } } if (channel.is_playing) { // next block int sample_rate, channel_count; 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) { channel.is_playing = !channel.last_block; // detect infinite retry loop channel.last_block = true; channel.output_remaining = 0; goto retry; } channel.last_block = channel.output_remaining < 1152; channel.output_remaining--; channel.output_ptr = 1; int32_t smpl = ((int32_t)channel.output_data[0] * (channel.volume >> 17)) >> 7; smpl = (smpl * channel.volume2) >> 7; mixr += (smpl * channel.pan) >> 7; mixl += (smpl * (128 - channel.pan)) >> 7; } } } 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%) 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 = get_rom_byte(sequence.offset++); uint8_t data = get_rom_byte(sequence.offset++); switch (reg) { case 0x0f: for (int ch = 0; ch < 8; ch++) // might be wrong, ie not needed in case of loop if (sequence.stopchan & (1 << ch)) m_channels[ch].is_playing = false; if (sequence.loop) sequence.offset = get_seq_offs(sequence.sequence); // loop sequence else sequence.is_playing = false; break; case 0x0e: sequence.delay = sequence.timer * 32 + 32 - 1; break; default: internal_reg_write(reg, data); break; } } } } } //------------------------------------------------- // 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_t reg, uint8_t 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: logerror("unimplemented write %02X %02X\n", reg, data); 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].volume2 = data; m_channels[ch].volume = 128 << 17; break; case 2: m_channels[ch].pan = data << 3; break; case 3: if ((data & 6) == 2 || ((data & 6) == 6 && !m_channels[ch].is_playing)) // both KON bits is 1 = "Keep Playing", do not restart channel in this case { uint8_t 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 if ((data & 6) == 0) m_channels[ch].is_playing = false; m_channels[ch].loop = (data & 1) ? 255 : 0; break; } } // sequencer registers else if (reg >= 0x80) { int ch = reg >> 4 & 0x07; switch (reg & 0x0f) { case 0: // SQSN m_sequences[ch].sequence = data; break; case 1: // SQON SQLP if ((data & 6) == 2 || ((data & 6) == 6 && !m_sequences[ch].is_playing)) // both KON bits 1 is "Keep Playing" { m_sequences[ch].offset = get_seq_offs(m_sequences[ch].sequence); m_sequences[ch].delay = 0; m_sequences[ch].is_playing = true; } else if ((data & 6) == 0 && m_sequences[ch].is_playing) { m_sequences[ch].is_playing = false; for (int i = 0; i < 8; i++) if (m_sequences[ch].stopchan & (1 << i)) m_channels[i].is_playing = false; } m_sequences[ch].loop = data & 1; break; case 2: // TMRH m_sequences[ch].timer = (m_sequences[ch].timer & 0x00ff) | (data << 8); break; case 3: // TMRL m_sequences[ch].timer = (m_sequences[ch].timer & 0xff00) | data; break; case 6: // SQOF m_sequences[ch].stopchan = data; break; //case 4: // TGST "ON trigger playback channel selection bitmask" //case 5: // TGEN "OFF trigger playback channel selection bitmask" //case 7: // YMZ770: unused, but CV1K games write 1 here, must be game bugs or YMZ770C datasheet is wrong and have messed 7 and 8 registers ? ; YMZ771: SQOF_SSG //case 8: // YMZ770: docs said "set to 01h" but CV1K games never write it, see above. ; YMZ771: TEMPO (wait timer speed control) default: if (data) logerror("unimplemented write %02X %02X\n", reg, data); break; } } else logerror("unimplemented write %02X %02X\n", reg, 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) { // calculate volume increments, fixed point values, fractions of 0x20000 for (u32 i = 0; i < 256; i++) { if (i < 0x20) volinc[i] = i; else volinc[i] = (0x20 | (i & 0x1f)) << ((i >> 5) - 1); } } 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_target = data; break; case 0x18: // Volume 1 delayed transition 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; m_channels[ch].is_paused = false; // checkme, might be not needed } else m_channels[ch].is_playing = false; break; case 0x58: // Pause / Resume m_channels[ch].is_paused = data ? true : false; if (data) logerror("CHECKME: CHAN pause/resume %02X %02X\n", reg, data); break; } } else if (reg < 0xd0) { if (m_bank == 0) { int sq = reg & 7; switch (reg & 0xf8) { case 0x60: // sequence num H and L case 0x68: sq = (reg >> 1) & 7; if (reg & 1) m_sequences[sq].sequence = (m_sequences[sq].sequence & 0xff00) | data; else m_sequences[sq].sequence = (m_sequences[sq].sequence & 0x00ff) | ((data & 0x07) << 8); break; case 0x70: // Start / Stop if (data) { //logerror("SEQ %d start (%s)\n", sq, m_sequences[sq].is_playing ? "playing":"stopped"); m_sequences[sq].offset = get_seq_offs(m_sequences[sq].sequence); m_sequences[sq].delay = 0; m_sequences[sq].is_playing = true; m_sequences[sq].is_paused = false; // checkme, might be not needed } else { //logerror("SEQ %d stop (%s)\n", sq, m_sequences[sq].is_playing ? "playing" : "stopped"); if (m_sequences[sq].is_playing) for (int ch = 0; ch < 16; ch++) if (m_sequences[sq].stopchan & (1 << ch)) m_channels[ch].is_playing = false; m_sequences[sq].is_playing = false; } break; case 0x78: // Pause / Resume m_sequences[sq].is_paused = data ? true : false; if (data) logerror("CHECKME: SEQ pause/resume %02X %02X\n", reg, data); break; case 0x80: // Loop count, 0 = off, 255 - infinite m_sequences[sq].loop = data; break; case 0x88: // timer H and L case 0x90: sq = (reg - 0x88) >> 1; if (reg & 1) m_sequences[sq].timer = (m_sequences[sq].timer & 0xff00) | data; else m_sequences[sq].timer = (m_sequences[sq].timer & 0x00ff) | (data << 8); break; case 0x98: // Off trigger, bit4 = on/off, bits0-3 channel (end sequence when channel playback ends) if (data) logerror("SEQ Off trigger unimplemented %02X %02X\n", reg, data); break; case 0xa0: // stop channel mask H and L (when sequence stopped) case 0xa8: sq = (reg >> 1) & 7; if (reg & 1) m_sequences[sq].stopchan = (m_sequences[sq].stopchan & 0xff00) | data; else m_sequences[sq].stopchan = (m_sequences[sq].stopchan & 0x00ff) | (data << 8); break; case 0xb0: // SQC number m_sqcs[sq].sqc = data; break; case 0xb8: // SQC start / stop if (data) { //logerror("SQC %d start (%s)\n", sq, m_sqcs[sq].is_playing ? "playing" : "stopped"); m_sqcs[sq].offset = get_sqc_offs(m_sqcs[sq].sqc); m_sqcs[sq].is_playing = true; m_sqcs[sq].is_waiting = false; } else { //logerror("SQC %d stop (%s)\n", sq, m_sqcs[sq].is_playing ? "playing" : "stopped"); m_sqcs[sq].is_playing = false; // stop SEQ too, and stop channels if (m_sequences[sq].is_playing) for (int ch = 0; ch < 16; ch++) if (m_sequences[sq].stopchan & (1 << ch)) m_channels[ch].is_playing = false; m_sequences[sq].is_playing = false; } break; case 0xc0: // SQC loop (255 = infinite) m_sqcs[sq].loop = data; break; default: logerror("unimplemented write %02X %02X\n", reg, data); break; } } // else bank1 - Equalizer control } // global registers else { switch (reg) { case 0xd0: // Total Volume L0/R0 m_vlma = data; break; case 0xd1: // Total Volume L1/R1 m_vlma1 = data; break; case 0xd2: // Clip limit m_cpl = data; break; //case 0xd3: // Digital/PWM output //case 0xd4: // Digital audio IF/IS L0/R0 //case 0xd5: // Digital audio IF/IS L1/R1 //case 0xd8: // GPIO A //case 0xdd: // GPIO B //case 0xde: // GPIO C case 0xf0: m_bank = data & 1; if (data > 1) logerror("Set bank %02X!\n", data); break; default: logerror("unimplemented write %02X %02X\n", reg, data); break; } } } void ymz774_device::sequencer() { for (auto & chan : m_channels) { if (chan.is_playing && !chan.is_paused && (chan.volume >> 17) != chan.volume_target) { if (chan.volume_delay) { if ((chan.volume >> 17) < chan.volume_target) chan.volume += volinc[chan.volume_delay]; else chan.volume -= volinc[chan.volume_delay]; } else chan.volume = chan.volume_target << 17; } } for (int i = 0; i < 8; i++) { auto & sqc = m_sqcs[i]; auto & sequence = m_sequences[i]; if (sqc.is_playing && !sqc.is_waiting) { // SQC consists of 4 byte records: SEQ num H, SEQ num L, SEQ Loop count, End flag (0xff) sequence.sequence = ((get_rom_byte(sqc.offset) << 8) | get_rom_byte(sqc.offset + 1)) & 0x7ff; sqc.offset += 2; sequence.loop = get_rom_byte(sqc.offset++); sequence.offset = get_seq_offs(sequence.sequence); sequence.delay = 0; sequence.is_playing = true; sequence.is_paused = false; // checkme, might be not needed sqc.is_waiting = true; if (get_rom_byte(sqc.offset++) == 0xff) { if (sqc.loop) { if (sqc.loop != 255) --sqc.loop; sqc.offset = get_sqc_offs(sqc.sqc); } else sqc.is_playing = false; } } if (sequence.is_playing && !sequence.is_paused) { if (sequence.delay > 0) --sequence.delay; else { int reg = get_rom_byte(sequence.offset++); uint8_t data = get_rom_byte(sequence.offset++); switch (reg) { case 0xff: // end for (int ch = 0; ch < 16; ch++) // might be wrong, ie not needed in case of loop if (sequence.stopchan & (1 << ch)) m_channels[ch].is_playing = false; if (sequence.loop) { if (sequence.loop != 255) --sequence.loop; sequence.offset = get_seq_offs(sequence.sequence); } else { sequence.is_playing = false; sqc.is_waiting = false; } break; case 0xfe: // timer delay sequence.delay = sequence.timer * 32 + 32 - 1; break; case 0xf0: sequence.bank = data & 1; break; default: { uint8_t temp = m_bank; m_bank = sequence.bank; if (m_bank == 0 && reg >= 0x60 && reg < 0xb0) // if we hit SEQ registers need to add this sequence offset { int sqn = i; if (reg < 0x70 || (reg >= 0x88 && reg < 0x98) || reg >= 0xa0) sqn = i * 2; internal_reg_write(reg + sqn, data); } else internal_reg_write(reg, data); m_bank = temp; } break; } } } } }