diff options
Diffstat (limited to 'src/devices/sound/ym2608.cpp')
-rw-r--r-- | src/devices/sound/ym2608.cpp | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/src/devices/sound/ym2608.cpp b/src/devices/sound/ym2608.cpp new file mode 100644 index 00000000000..76937a84ad9 --- /dev/null +++ b/src/devices/sound/ym2608.cpp @@ -0,0 +1,415 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles + +#include "emu.h" +#include "ym2608.h" + + +DEFINE_DEVICE_TYPE(YM2608, ym2608_device, "ym2608", "YM2608 OPNA") + + +//********************************************************* +// CONSTANTS +//********************************************************* + +enum : u8 +{ + STATUS_ADPCM_B_EOS = 0x04, + STATUS_ADPCM_B_BRDY = 0x08, + STATUS_ADPCM_B_ZERO = 0x10, + STATUS_ADPCM_B_PLAYING = 0x20 +}; + + +//********************************************************* +// YM2608 DEVICE +//********************************************************* + +//------------------------------------------------- +// ym2608_device - constructor +//------------------------------------------------- + +ym2608_device::ym2608_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + ay8910_device(mconfig, YM2608, tag, owner, clock, PSG_TYPE_YM, 1, 2), + device_rom_interface(mconfig, *this), + m_internal(*this, "internal"), + m_opn(*this), + m_adpcm_a(*this, read8sm_delegate(*this, FUNC(ym2608_device::adpcm_a_read)), 0), + m_adpcm_b(*this, read8sm_delegate(*this, FUNC(ym2608_device::adpcm_b_read)), write8sm_delegate(*this, FUNC(ym2608_device::adpcm_b_write))), + m_stream(nullptr), + m_busy_duration(m_opn.compute_busy_duration()), + m_address(0), + m_irq_enable(0x1f), + m_flag_control(0x1c) +{ +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +u8 ym2608_device::read(offs_t offset) +{ + u8 result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = m_opn.status() & (ymopna_engine::STATUS_TIMERA | ymopna_engine::STATUS_TIMERB | ymopna_engine::STATUS_BUSY); + break; + + case 1: // data port (only SSG) + if (m_address < 0x10) + result = ay8910_read_ym(); + else if (m_address == 0xff) + result = 1; // ID code + break; + + case 2: // status port, extended + result = combine_status(); + break; + + case 3: // ADPCM-B data + if (m_address < 0x10) + result = m_adpcm_b.read(m_address); + break; + } + return result; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2608_device::write(offs_t offset, u8 value) +{ + switch (offset & 3) + { + case 0: // address port + m_address = value; + if (m_address < 0x10) + { + // write register to SSG emulator + ay8910_write_ym(0, m_address); + } + else if (m_address >= 0x2d && m_address <= 0x2f) + { + // prescaler select : 2d,2e,2f + if (m_address == 0x2d) + update_prescale(6); + else if (m_address == 0x2e && m_opn.clock_prescale() == 6) + update_prescale(3); + else if (m_address == 0x2f) + update_prescale(2); + } + break; + + case 1: // data port + + // ignore if paired with upper address + if (BIT(m_address, 8)) + break; + + if (m_address < 0x10) + { + // write to SSG + ay8910_write_ym(1, value); + } + else if (m_address < 0x20) + { + // write to ADPCM-A + m_stream->update(); + m_adpcm_a.write(m_address & 0x0f, value); + } + else if (m_address == 0x29) + { + // special IRQ mask register + m_stream->update(); + m_irq_enable = value; + m_opn.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f); + } + else + { + // write to OPN + m_stream->update(); + m_opn.write(m_address, value); + } + + // mark busy for a bit + m_opn.set_busy_end(machine().time() + m_busy_duration); + break; + + case 2: // upper address port + m_address = 0x100 | value; + break; + + case 3: // upper data port + + // ignore if paired with lower address + if (!BIT(m_address, 8)) + break; + + if (m_address < 0x110) + { + // write to ADPCM-B + m_stream->update(); + m_adpcm_b.write(m_address & 0x0f, value); + } + else if (m_address == 0x110) + { + // IRQ flag control + m_stream->update(); + if (BIT(value, 7)) + m_opn.set_reset_status(0, 0xff); + else + { + m_flag_control = value; + m_opn.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f); + } + } + else + { + // write to OPN + m_stream->update(); + m_opn.write(m_address, value); + } + + // mark busy for a bit + m_opn.set_busy_end(machine().time() + m_busy_duration); + break; + } +} + + +//------------------------------------------------- +// device_start - start of emulation +//------------------------------------------------- + +void ym2608_device::device_start() +{ + // call our parent + ay8910_device::device_start(); + + // create our stream + m_stream = stream_alloc(0, 2, clock() / (4 * 6 * 6)); + + // save our data + save_item(YMFM_NAME(m_address)); + save_item(YMFM_NAME(m_irq_enable)); + save_item(YMFM_NAME(m_flag_control)); + + // save the engines + m_opn.save(*this); + m_adpcm_a.save(*this); + m_adpcm_b.save(*this); + + // configure ADPCM percussion sounds + m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum + m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum + m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal + m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat + m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom + m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot +} + + +//------------------------------------------------- +// device_reset - start of emulation +//------------------------------------------------- + +void ym2608_device::device_reset() +{ + // call our parent + ay8910_device::device_reset(); + + // reset the engines + m_opn.reset(); + m_adpcm_a.reset(); + m_adpcm_b.reset(); + + // initialize our special interrupt states + m_irq_enable = 0x1f; + m_flag_control = 0x1c; + combine_status(); +} + + +//------------------------------------------------- +// device_clock_changed - update if clock changes +//------------------------------------------------- + +void ym2608_device::device_clock_changed() +{ + // refresh via prescale + update_prescale(m_opn.clock_prescale()); +} + + +//------------------------------------------------- +// device_rom_region - return a pointer to our +// ROM region +//------------------------------------------------- + +ROM_START( ym2608 ) + ROM_REGION( 0x2000, "internal", 0 ) + // + // While this rom was dumped by output analysis, not decap, it was tested + // by playing it back into the chip as an external adpcm sample and produced + // an identical dac result. a decap would be nice to verify things 100%, + // but there is currently no reason to think this rom dump is incorrect. + // + // offset 0x0000: Source: 01BD.ROM Length: 448 / 0x000001C0 + // offset 0x01C0: Source: 02SD.ROM Length: 640 / 0x00000280 + // offset 0x0440: Source: 04TOP.ROM Length: 5952 / 0x00001740 + // offset 0x1B80: Source: 08HH.ROM Length: 384 / 0x00000180 + // offset 0x1D00: Source: 10TOM.ROM Length: 640 / 0x00000280 + // offset 0x1F80: Source: 20RIM.ROM Length: 128 / 0x00000080 + // + ROM_LOAD16_WORD( "ym2608_adpcm_rom.bin", 0x0000, 0x2000, CRC(23c9e0d8) SHA1(50b6c3e288eaa12ad275d4f323267bb72b0445df) ) +ROM_END + +const tiny_rom_entry *ym2608_device::device_rom_region() const +{ + return ROM_NAME( ym2608 ); +} + + +//------------------------------------------------- +// rom_bank_updated - refresh the stream if the +// ROM banking changes +//------------------------------------------------- + +void ym2608_device::rom_bank_updated() +{ + m_stream->update(); +} + + +//------------------------------------------------- +// sound_stream_update - update the sound stream +//------------------------------------------------- + +void ym2608_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +{ + // if this is not our stream, pass it on + if (&stream != m_stream) + { + ay8910_device::sound_stream_update(stream, inputs, outputs); + return; + } + + // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode + u8 opnmask = BIT(m_irq_enable, 7) ? 0x3f : 0x07; + + // iterate over all target samples + for (int sampindex = 0; sampindex < outputs[0].samples(); sampindex++) + { + // clock the OPN + u32 env_counter = m_opn.clock(opnmask); + + // clock the ADPCM-A engine on every envelope cycle + // (channels 4 and 5 clock every 2 envelope clocks) + if (BIT(env_counter, 0, 2) == 0) + m_adpcm_a.clock(BIT(env_counter, 2) ? 0x0f : 0x3f); + + // clock the ADPCM-B engine every cycle + m_adpcm_b.clock(0x01); + + // update the OPN content; OPNA is 13-bit with no intermediate clipping + s32 lsum = 0, rsum = 0; + m_opn.output(lsum, rsum, 1, 32767, opnmask); + lsum <<= 1; + rsum <<= 1; + + // mix in the ADPCM + m_adpcm_a.output(lsum, rsum, 0x3f); + m_adpcm_b.output(lsum, rsum, 2, 0x01); + + // YM2608 is stereo + outputs[0].put_int_clamp(sampindex, lsum, 32768); + outputs[1].put_int_clamp(sampindex, rsum, 32768); + } +} + + +//------------------------------------------------- +// update_prescale - set a new prescale value and +// update clocks as needed +//------------------------------------------------- + +void ym2608_device::update_prescale(u8 newval) +{ + // inform the OPN engine and refresh our clock rate + m_opn.set_clock_prescale(newval); + m_stream->set_sample_rate(clock() / (4 * 6 * newval)); + logerror("Prescale = %d; sample_rate = %d\n", newval, clock() / (4 * 6 * newval)); + + // also scale the SSG streams + // mapping is (OPN->SSG): 6->4, 3->2, 2->1 + u8 ssg_scale = 2 * newval / 3; + // QUESTION: where does the *2 come from?? + ay_set_clock(clock() / ssg_scale); + + // recompute the busy duration + m_busy_duration = m_opn.compute_busy_duration(); +} + + +//------------------------------------------------- +// combine_status - combine status flags from +// OPN and ADPCM-B, masking out any indicated by +// the flag control register +//------------------------------------------------- + +u8 ym2608_device::combine_status() +{ + u8 status = m_opn.status(); + u8 adpcm_status = m_adpcm_b.status(0); + if ((adpcm_status & ymadpcm_b_channel::STATUS_EOS) != 0) + status |= STATUS_ADPCM_B_EOS; + if ((adpcm_status & ymadpcm_b_channel::STATUS_BRDY) != 0) + status |= STATUS_ADPCM_B_BRDY; + if ((adpcm_status & ymadpcm_b_channel::STATUS_PLAYING) != 0) + status |= STATUS_ADPCM_B_PLAYING; + status &= ~(m_flag_control & 0x1f); + m_opn.set_reset_status(status, ~status); + return status; +} + + +//------------------------------------------------- +// adpcm_a_read - callback to read data for the +// ADPCM-A engine; in this case, from the internal +// ROM containing drum samples +//------------------------------------------------- + +u8 ym2608_device::adpcm_a_read(offs_t offset) +{ + return m_internal->as_u8(offset % m_internal->bytes()); +} + + +//------------------------------------------------- +// adpcm_b_read - callback to read data for the +// ADPCM-B engine; in this case, from our default +// address space +//------------------------------------------------- + +u8 ym2608_device::adpcm_b_read(offs_t offset) +{ + return space(0).read_byte(offset); +} + + +//------------------------------------------------- +// adpcm_b_write - callback to write data to the +// ADPCM-B engine; in this case, to our default +// address space +//------------------------------------------------- + +void ym2608_device::adpcm_b_write(offs_t offset, u8 data) +{ + space(0).write_byte(offset, data); +} |