summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/ymfm/src/ymfm_adpcm.cpp
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/ymfm/src/ymfm_adpcm.cpp')
-rw-r--r--3rdparty/ymfm/src/ymfm_adpcm.cpp781
1 files changed, 781 insertions, 0 deletions
diff --git a/3rdparty/ymfm/src/ymfm_adpcm.cpp b/3rdparty/ymfm/src/ymfm_adpcm.cpp
new file mode 100644
index 00000000000..bbe5bc0e200
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_adpcm.cpp
@@ -0,0 +1,781 @@
+// BSD 3-Clause License
+//
+// Copyright (c) 2021, Aaron Giles
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "ymfm_adpcm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// ADPCM "A" REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void adpcm_a_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // initialize the pans to on by default, and max instrument volume;
+ // some neogeo homebrews (for example ffeast) rely on this
+ m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] =
+ m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_a_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+//*********************************************************
+// ADPCM "A" CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_a_channel - constructor
+//-------------------------------------------------
+
+adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) :
+ m_choffs(choffs),
+ m_address_shift(addrshift),
+ m_playing(0),
+ m_curnibble(0),
+ m_curbyte(0),
+ m_curaddress(0),
+ m_accumulator(0),
+ m_step_index(0),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+void adpcm_a_channel::reset()
+{
+ m_playing = 0;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_curaddress = 0;
+ m_accumulator = 0;
+ m_step_index = 0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_a_channel::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_playing);
+ state.save_restore(m_curnibble);
+ state.save_restore(m_curbyte);
+ state.save_restore(m_curaddress);
+ state.save_restore(m_accumulator);
+ state.save_restore(m_step_index);
+}
+
+
+//-------------------------------------------------
+// keyonoff - signal key on/off
+//-------------------------------------------------
+
+void adpcm_a_channel::keyonoff(bool on)
+{
+ // QUESTION: repeated key ons restart the sample?
+ m_playing = on;
+ if (m_playing)
+ {
+ m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_accumulator = 0;
+ m_step_index = 0;
+
+ // don't log masked channels
+ if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0)
+ debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n",
+ m_choffs,
+ m_regs.ch_pan_left(m_choffs),
+ m_regs.ch_pan_right(m_choffs),
+ m_regs.ch_start(m_choffs),
+ m_regs.ch_end(m_choffs),
+ m_regs.ch_instrument_level(m_choffs));
+ }
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+bool adpcm_a_channel::clock()
+{
+ // if not playing, just output 0
+ if (m_playing == 0)
+ {
+ m_accumulator = 0;
+ return false;
+ }
+
+ // stop when we hit the end address; apparently only low 20 bits are used for
+ // comparison on the YM2610: this affects sample playback in some games, for
+ // example twinspri character select screen music will skip some samples if
+ // this is not correct
+ if (((m_curaddress ^ (m_regs.ch_end(m_choffs) << m_address_shift)) & 0xfffff) == 0)
+ {
+ m_playing = m_accumulator = 0;
+ return true;
+ }
+
+ // if we're about to read nibble 0, fetch the data
+ uint8_t data;
+ if (m_curnibble == 0)
+ {
+ m_curbyte = m_owner.intf().ymfm_adpcm_a_read(m_curaddress++);
+ data = m_curbyte >> 4;
+ m_curnibble = 1;
+ }
+
+ // otherwise just extract from the previosuly-fetched byte
+ else
+ {
+ data = m_curbyte & 0xf;
+ m_curnibble = 0;
+ }
+
+ // compute the ADPCM delta
+ static uint16_t const s_steps[49] =
+ {
+ 16, 17, 19, 21, 23, 25, 28,
+ 31, 34, 37, 41, 45, 50, 55,
+ 60, 66, 73, 80, 88, 97, 107,
+ 118, 130, 143, 157, 173, 190, 209,
+ 230, 253, 279, 307, 337, 371, 408,
+ 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552
+ };
+ int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8;
+ if (bitfield(data, 3))
+ delta = -delta;
+
+ // the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205)
+ m_accumulator = (m_accumulator + delta) & 0xfff;
+
+ // adjust ADPCM step
+ static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 };
+ m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48);
+
+ return false;
+}
+
+
+//-------------------------------------------------
+// output - return the computed output value, with
+// panning applied
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_a_channel::output(ymfm_output<NumOutputs> &output) const
+{
+ // volume combines instrument and total levels
+ int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f);
+
+ // if combined is maximum, don't add to outputs
+ if (vol >= 63)
+ return;
+
+ // convert into a shift and a multiplier
+ // QUESTION: verify this from other sources
+ int8_t mul = 15 - (vol & 7);
+ uint8_t shift = 4 + 1 + (vol >> 3);
+
+ // m_accumulator is a 12-bit value; shift up to sign-extend;
+ // the downshift is incorporated into 'shift'
+ int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3;
+
+ // apply to left/right as appropriate
+ if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs))
+ output.data[0] += value;
+ if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs))
+ output.data[1] += value;
+}
+
+
+
+//*********************************************************
+// ADPCM "A" ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_a_engine - constructor
+//-------------------------------------------------
+
+adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) :
+ m_intf(intf)
+{
+ // create the channels
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum] = std::make_unique<adpcm_a_channel>(*this, chnum, addrshift);
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void adpcm_a_engine::reset()
+{
+ // reset register state
+ m_regs.reset();
+
+ // reset each channel
+ for (auto &chan : m_channel)
+ chan->reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_a_engine::save_restore(ymfm_saved_state &state)
+{
+ // save register state
+ m_regs.save_restore(state);
+
+ // save channel state
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum]->save_restore(state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+uint32_t adpcm_a_engine::clock(uint32_t chanmask)
+{
+ // clock each channel, setting a bit in result if it finished
+ uint32_t result = 0;
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ if (m_channel[chnum]->clock())
+ result |= 1 << chnum;
+
+ // return the bitmask of completed samples
+ return result;
+}
+
+
+//-------------------------------------------------
+// update - master update function
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_a_engine::output(ymfm_output<NumOutputs> &output, uint32_t chanmask)
+{
+ // mask out some channels for debug purposes
+ chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK;
+
+ // compute the output of each channel
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->output(output);
+}
+
+template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask);
+template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask);
+
+
+//-------------------------------------------------
+// write - handle writes to the ADPCM-A registers
+//-------------------------------------------------
+
+void adpcm_a_engine::write(uint32_t regnum, uint8_t data)
+{
+ // store the raw value to the register array;
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // actively handle writes to the control register
+ if (regnum == 0x00)
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(data, chnum))
+ m_channel[chnum]->keyonoff(bitfield(~data, 7));
+}
+
+
+
+//*********************************************************
+// ADPCM "B" REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void adpcm_b_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // default limit to wide open
+ m_regdata[0x0c] = m_regdata[0x0d] = 0xff;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_b_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+
+//*********************************************************
+// ADPCM "B" CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_b_channel - constructor
+//-------------------------------------------------
+
+adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) :
+ m_address_shift(addrshift),
+ m_status(STATUS_BRDY),
+ m_curnibble(0),
+ m_curbyte(0),
+ m_dummy_read(0),
+ m_position(0),
+ m_curaddress(0),
+ m_accumulator(0),
+ m_prev_accum(0),
+ m_adpcm_step(STEP_MIN),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+void adpcm_b_channel::reset()
+{
+ m_status = STATUS_BRDY;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_dummy_read = 0;
+ m_position = 0;
+ m_curaddress = 0;
+ m_accumulator = 0;
+ m_prev_accum = 0;
+ m_adpcm_step = STEP_MIN;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_b_channel::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_status);
+ state.save_restore(m_curnibble);
+ state.save_restore(m_curbyte);
+ state.save_restore(m_dummy_read);
+ state.save_restore(m_position);
+ state.save_restore(m_curaddress);
+ state.save_restore(m_accumulator);
+ state.save_restore(m_prev_accum);
+ state.save_restore(m_adpcm_step);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void adpcm_b_channel::clock()
+{
+ // only process if active and not recording (which we don't support)
+ if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0)
+ {
+ m_status &= ~STATUS_PLAYING;
+ return;
+ }
+
+ // otherwise, advance the step
+ uint32_t position = m_position + m_regs.delta_n();
+ m_position = uint16_t(position);
+ if (position < 0x10000)
+ return;
+
+ // if playing from RAM/ROM, check the end address and process
+ if (m_regs.external())
+ {
+ // wrap at the limit address
+ if (at_limit())
+ m_curaddress = 0;
+
+ // handle the sample end, either repeating or stopping
+ if (at_end())
+ {
+ // if repeating, go back to the start
+ if (m_regs.repeat())
+ load_start();
+
+ // otherwise, done; set the EOS bit and return
+ else
+ {
+ m_accumulator = 0;
+ m_prev_accum = 0;
+ m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS;
+ debug::log_keyon("%s\n", "ADPCM EOS");
+ return;
+ }
+ }
+
+ // if we're about to process nibble 0, fetch and increment
+ if (m_curnibble == 0)
+ {
+ m_curbyte = m_owner.intf().ymfm_adpcm_b_read(m_curaddress++);
+ m_curaddress &= 0xffffff;
+ }
+ }
+
+ // extract the nibble from our current byte
+ uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4;
+ m_curnibble ^= 1;
+
+ // if CPU-driven and we just processed the last nibble, copy the next byte and request more
+ if (m_curnibble == 0 && !m_regs.external())
+ {
+ m_curbyte = m_regs.cpudata();
+ m_status |= STATUS_BRDY;
+ }
+
+ // remember previous value for interpolation
+ m_prev_accum = m_accumulator;
+
+ // forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8
+ int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8;
+ if (bitfield(data, 3))
+ delta = -delta;
+
+ // add and clamp to 16 bits
+ m_accumulator = clamp(m_accumulator + delta, -32768, 32767);
+
+ // scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4
+ static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 };
+ m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX);
+}
+
+
+//-------------------------------------------------
+// output - return the computed output value, with
+// panning applied
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_b_channel::output(ymfm_output<NumOutputs> &output, uint32_t rshift) const
+{
+ // mask out some channels for debug purposes
+ if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0)
+ return;
+
+ // do a linear interpolation between samples
+ int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16;
+
+ // apply volume (level) in a linear fashion and reduce
+ result = (result * int32_t(m_regs.level())) >> (8 + rshift);
+
+ // apply to left/right
+ if (NumOutputs == 1 || m_regs.pan_left())
+ output.data[0] += result;
+ if (NumOutputs > 1 && m_regs.pan_right())
+ output.data[1] += result;
+}
+
+
+//-------------------------------------------------
+// read - handle special register reads
+//-------------------------------------------------
+
+uint8_t adpcm_b_channel::read(uint32_t regnum)
+{
+ uint8_t result = 0;
+
+ // register 8 reads over the bus under some conditions
+ if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external())
+ {
+ // two dummy reads are consumed first
+ if (m_dummy_read != 0)
+ {
+ load_start();
+ m_dummy_read--;
+ }
+
+ // did we hit the end? if so, signal EOS
+ if (at_end())
+ {
+ m_status = STATUS_EOS | STATUS_BRDY;
+ debug::log_keyon("%s\n", "ADPCM EOS");
+ }
+
+ // otherwise, write the data and signal ready
+ else
+ {
+ result = m_owner.intf().ymfm_adpcm_b_read(m_curaddress++);
+ m_status = STATUS_BRDY;
+ }
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write - handle special register writes
+//-------------------------------------------------
+
+void adpcm_b_channel::write(uint32_t regnum, uint8_t value)
+{
+ // register 0 can do a reset; also use writes here to reset the
+ // dummy read counter
+ if (regnum == 0x00)
+ {
+ if (m_regs.execute())
+ {
+ load_start();
+
+ // don't log masked channels
+ if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0)
+ debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n",
+ m_regs.repeat(),
+ m_regs.speaker(),
+ m_regs.pan_left(),
+ m_regs.pan_right(),
+ m_regs.dac_enable(),
+ m_regs.dram_8bit(),
+ m_regs.rom_ram(),
+ m_regs.external(),
+ m_regs.record(),
+ m_regs.start(),
+ m_regs.end(),
+ m_regs.prescale(),
+ m_regs.delta_n(),
+ m_regs.level(),
+ m_regs.limit());
+ }
+ else
+ m_status &= ~STATUS_EOS;
+ if (m_regs.resetflag())
+ reset();
+ if (m_regs.external())
+ m_dummy_read = 2;
+ }
+
+ // register 8 writes over the bus under some conditions
+ else if (regnum == 0x08)
+ {
+ // if writing from the CPU during execute, clear the ready flag
+ if (m_regs.execute() && !m_regs.record() && !m_regs.external())
+ m_status &= ~STATUS_BRDY;
+
+ // if writing during "record", pass through as data
+ else if (!m_regs.execute() && m_regs.record() && m_regs.external())
+ {
+ // clear out dummy reads and set start address
+ if (m_dummy_read != 0)
+ {
+ load_start();
+ m_dummy_read = 0;
+ }
+
+ // did we hit the end? if so, signal EOS
+ if (at_end())
+ {
+ debug::log_keyon("%s\n", "ADPCM EOS");
+ m_status = STATUS_EOS | STATUS_BRDY;
+ }
+
+ // otherwise, write the data and signal ready
+ else
+ {
+ m_owner.intf().ymfm_adpcm_b_write(m_curaddress++, value);
+ m_status = STATUS_BRDY;
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// address_shift - compute the current address
+// shift amount based on register settings
+//-------------------------------------------------
+
+uint32_t adpcm_b_channel::address_shift() const
+{
+ // if a constant address shift, just provide that
+ if (m_address_shift != 0)
+ return m_address_shift;
+
+ // if ROM or 8-bit DRAM, shift is 5 bits
+ if (m_regs.rom_ram())
+ return 5;
+ if (m_regs.dram_8bit())
+ return 5;
+
+ // otherwise, shift is 2 bits
+ return 2;
+}
+
+
+//-------------------------------------------------
+// load_start - load the start address and
+// initialize the state
+//-------------------------------------------------
+
+void adpcm_b_channel::load_start()
+{
+ m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING;
+ m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_position = 0;
+ m_accumulator = 0;
+ m_prev_accum = 0;
+ m_adpcm_step = STEP_MIN;
+}
+
+
+
+//*********************************************************
+// ADPCM "B" ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_b_engine - constructor
+//-------------------------------------------------
+
+adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) :
+ m_intf(intf)
+{
+ // create the channel (only one supported for now, but leaving possibilities open)
+ m_channel = std::make_unique<adpcm_b_channel>(*this, addrshift);
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void adpcm_b_engine::reset()
+{
+ // reset registers
+ m_regs.reset();
+
+ // reset each channel
+ m_channel->reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_b_engine::save_restore(ymfm_saved_state &state)
+{
+ // save our state
+ m_regs.save_restore(state);
+
+ // save channel state
+ m_channel->save_restore(state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void adpcm_b_engine::clock()
+{
+ // clock each channel, setting a bit in result if it finished
+ m_channel->clock();
+}
+
+
+//-------------------------------------------------
+// output - master output function
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_b_engine::output(ymfm_output<NumOutputs> &output, uint32_t rshift)
+{
+ // compute the output of each channel
+ m_channel->output(output, rshift);
+}
+
+template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift);
+template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift);
+
+
+//-------------------------------------------------
+// write - handle writes to the ADPCM-B registers
+//-------------------------------------------------
+
+void adpcm_b_engine::write(uint32_t regnum, uint8_t data)
+{
+ // store the raw value to the register array;
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // let the channel handle any special writes
+ m_channel->write(regnum, data);
+}
+
+}