summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/ymfm/src/ymfm_pcm.cpp
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/ymfm/src/ymfm_pcm.cpp')
-rw-r--r--3rdparty/ymfm/src/ymfm_pcm.cpp715
1 files changed, 715 insertions, 0 deletions
diff --git a/3rdparty/ymfm/src/ymfm_pcm.cpp b/3rdparty/ymfm/src/ymfm_pcm.cpp
new file mode 100644
index 00000000000..50595133bf7
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_pcm.cpp
@@ -0,0 +1,715 @@
+// 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_pcm.h"
+#include "ymfm_fm.h"
+#include "ymfm_fm.ipp"
+
+namespace ymfm
+{
+
+//*********************************************************
+// PCM REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void pcm_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+ m_regdata[0x02] = 0x20;
+ m_regdata[0xf8] = 0x1b;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void pcm_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// cache_channel_data - update the cache with
+// data from the registers
+//-------------------------------------------------
+
+void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
+{
+ // compute step from octave and fnumber; the math here implies
+ // a .18 fraction but .16 should be perfectly fine
+ int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
+ uint32_t fnum = ch_fnumber(choffs);
+ cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
+
+ // total level is computed as a .10 value for interpolation
+ cache.total_level = ch_total_level(choffs) << 10;
+
+ // compute panning values in terms of envelope attenuation
+ int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
+ if (panpot >= 0)
+ {
+ cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot;
+ cache.pan_right = 0;
+ }
+ else if (panpot >= -7)
+ {
+ cache.pan_left = 0;
+ cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot;
+ }
+ else
+ cache.pan_left = cache.pan_right = 0x3ff;
+
+ // determine the LFO stepping value; this how much to add to a running
+ // x.18 value for the LFO; steps were derived from frequencies in the
+ // manual and come out very close with these values
+ static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
+ cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
+
+ // AM LFO depth values, derived from the manual; note each has at most
+ // 2 bits to make the "multiply" easy in hardware
+ static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
+ cache.am_depth = s_am_depth[ch_am_depth(choffs)];
+
+ // PM LFO depth values; these are converted from the manual's cents values
+ // into f-numbers; the computations come out quite cleanly so pretty sure
+ // these are correct
+ static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
+ cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = ch_sustain_level(choffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // compute the key scaling correction factor; 15 means don't do any correction
+ int32_t correction = ch_rate_correction(choffs);
+ if (correction == 15)
+ correction = 0;
+ else
+ correction = (octave + correction) * 2 + bitfield(fnum, 9);
+
+ // compute the envelope generator rates
+ cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
+ cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
+ cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
+ cache.eg_rate[EG_REVERB] = 5;
+
+ // if damping is on, override some things; essentially decay at a hardcoded
+ // rate of 48 until -12db (0x80), then at maximum rate for the rest
+ if (ch_damp(choffs) != 0)
+ {
+ cache.eg_rate[EG_DECAY] = 48;
+ cache.eg_rate[EG_SUSTAIN] = 63;
+ cache.eg_rate[EG_RELEASE] = 63;
+ cache.eg_sustain = 0x80;
+ }
+}
+
+
+//-------------------------------------------------
+// effective_rate - return the effective rate,
+// clamping and applying corrections as needed
+//-------------------------------------------------
+
+uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
+{
+ // raw rates of 0 and 15 just pin to min/max
+ if (raw == 0)
+ return 0;
+ if (raw == 15)
+ return 63;
+
+ // otherwise add the correction and clamp to range
+ return clamp(raw * 4 + correction, 0, 63);
+}
+
+
+
+//*********************************************************
+// PCM CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// pcm_channel - constructor
+//-------------------------------------------------
+
+pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
+ m_choffs(choffs),
+ m_baseaddr(0),
+ m_endpos(0),
+ m_looppos(0),
+ m_curpos(0),
+ m_nextpos(0),
+ m_lfo_counter(0),
+ m_eg_state(EG_RELEASE),
+ m_env_attenuation(0x3ff),
+ m_total_level(0x7f << 10),
+ m_format(0),
+ m_key_state(0),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+void pcm_channel::reset()
+{
+ m_baseaddr = 0;
+ m_endpos = 0;
+ m_looppos = 0;
+ m_curpos = 0;
+ m_nextpos = 0;
+ m_lfo_counter = 0;
+ m_eg_state = EG_RELEASE;
+ m_env_attenuation = 0x3ff;
+ m_total_level = 0x7f << 10;
+ m_format = 0;
+ m_key_state = 0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void pcm_channel::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_baseaddr);
+ state.save_restore(m_endpos);
+ state.save_restore(m_looppos);
+ state.save_restore(m_curpos);
+ state.save_restore(m_nextpos);
+ state.save_restore(m_lfo_counter);
+ state.save_restore(m_eg_state);
+ state.save_restore(m_env_attenuation);
+ state.save_restore(m_total_level);
+ state.save_restore(m_format);
+ state.save_restore(m_key_state);
+}
+
+
+//-------------------------------------------------
+// prepare - prepare for clocking
+//-------------------------------------------------
+
+bool pcm_channel::prepare()
+{
+ // cache the data
+ m_regs.cache_channel_data(m_choffs, m_cache);
+
+ // clock the key state
+ if ((m_key_state & KEY_PENDING) != 0)
+ {
+ uint8_t oldstate = m_key_state;
+ m_key_state = (m_key_state >> 1) & KEY_ON;
+ if (((oldstate ^ m_key_state) & KEY_ON) != 0)
+ {
+ if ((m_key_state & KEY_ON) != 0)
+ start_attack();
+ else
+ start_release();
+ }
+ }
+
+ // set the total level directly if not interpolating
+ if (m_regs.ch_level_direct(m_choffs))
+ m_total_level = m_cache.total_level;
+
+ // we're active until we're quiet after the release
+ return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void pcm_channel::clock(uint32_t env_counter)
+{
+ // clock the LFO, which is an x.18 value incremented based on the
+ // LFO speed value
+ m_lfo_counter += m_cache.lfo_step;
+
+ // clock the envelope
+ clock_envelope(env_counter);
+
+ // determine the step after applying vibrato
+ uint32_t step = m_cache.step;
+ if (m_cache.pm_depth != 0)
+ {
+ // shift the LFO by 1/4 cycle for PM so that it starts at 0
+ uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
+ int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
+ if (bitfield(lfo_shifted, 17) != 0)
+ lfo_value ^= 0x7f;
+ lfo_value -= 0x40;
+ step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
+ }
+
+ // advance the sample step and loop as needed
+ m_curpos = m_nextpos;
+ m_nextpos = m_curpos + step;
+ if (m_nextpos >= m_endpos)
+ m_nextpos += m_looppos - m_endpos;
+
+ // interpolate total level if needed
+ if (m_total_level != m_cache.total_level)
+ {
+ // max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
+ // min->max volume is half that, so advance by 38/1024 per sample
+ if (m_total_level < m_cache.total_level)
+ m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
+ else
+ m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
+ }
+}
+
+
+//-------------------------------------------------
+// output - return the computed output value, with
+// panning applied
+//-------------------------------------------------
+
+void pcm_channel::output(output_data &output) const
+{
+ // early out if the envelope is effectively off
+ uint32_t envelope = m_env_attenuation;
+ if (envelope > EG_QUIET)
+ return;
+
+ // add in LFO AM modulation
+ if (m_cache.am_depth != 0)
+ {
+ uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
+ if (bitfield(m_lfo_counter, 17) != 0)
+ lfo_value ^= 0x7f;
+ envelope += (lfo_value * m_cache.am_depth) >> 7;
+ }
+
+ // add in the current interpolated total level value, which is a .10
+ // value shifted left by 2
+ envelope += m_total_level >> 8;
+
+ // add in panning effect and clamp
+ uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
+ uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
+
+ // convert to volume as a .11 fraction
+ int32_t lvol = attenuation_to_volume(lenv << 2);
+ int32_t rvol = attenuation_to_volume(renv << 2);
+
+ // fetch current sample and add
+ int16_t sample = fetch_sample();
+ uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
+ output.data[outnum + 0] += (lvol * sample) >> 15;
+ output.data[outnum + 1] += (rvol * sample) >> 15;
+}
+
+
+//-------------------------------------------------
+// keyonoff - signal key on/off
+//-------------------------------------------------
+
+void pcm_channel::keyonoff(bool on)
+{
+ // mark the key state as pending
+ m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
+
+ // don't log masked channels
+ if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
+ {
+ debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
+ m_choffs,
+ m_regs.ch_wave_table_num(m_choffs),
+ int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
+ m_regs.ch_fnumber(m_choffs),
+ m_regs.ch_total_level(m_choffs),
+ m_regs.ch_level_direct(m_choffs) ? '!' : '/',
+ m_regs.ch_attack_rate(m_choffs),
+ m_regs.ch_decay_rate(m_choffs),
+ m_regs.ch_sustain_rate(m_choffs),
+ m_regs.ch_release_rate(m_choffs),
+ m_regs.ch_sustain_level(m_choffs));
+
+ if (m_regs.ch_rate_correction(m_choffs) != 15)
+ debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
+
+ if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
+ debug::log_keyon(" %s", "REV");
+ if (m_regs.ch_damp(m_choffs) != 0)
+ debug::log_keyon(" %s", "DAMP");
+
+ if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
+ {
+ if (m_regs.ch_vibrato(m_choffs) != 0)
+ debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
+ if (m_regs.ch_am_depth(m_choffs) != 0)
+ debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
+ debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
+ }
+ debug::log_keyon("%s", "\n");
+ }
+}
+
+
+//-------------------------------------------------
+// load_wavetable - load a wavetable by fetching
+// its data from external memory
+//-------------------------------------------------
+
+void pcm_channel::load_wavetable()
+{
+ // determine the address of the wave table header
+ uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
+ uint32_t wavheader = 12 * wavnum;
+
+ // above 384 it may be in a different bank
+ if (wavnum >= 384)
+ {
+ uint32_t bank = m_regs.wave_table_header();
+ if (bank != 0)
+ wavheader = 512*1024 * bank + (wavnum - 384) * 12;
+ }
+
+ // fetch the 22-bit base address and 2-bit format
+ uint8_t byte = read_pcm(wavheader + 0);
+ m_format = bitfield(byte, 6, 2);
+ m_baseaddr = bitfield(byte, 0, 6) << 16;
+ m_baseaddr |= read_pcm(wavheader + 1) << 8;
+ m_baseaddr |= read_pcm(wavheader + 2) << 0;
+
+ // fetch the 16-bit loop position
+ m_looppos = read_pcm(wavheader + 3) << 8;
+ m_looppos |= read_pcm(wavheader + 4);
+ m_looppos <<= 16;
+
+ // fetch the 16-bit end position, which is stored as a negative value
+ // for some reason that is unclear
+ m_endpos = read_pcm(wavheader + 5) << 8;
+ m_endpos |= read_pcm(wavheader + 6);
+ m_endpos = -int32_t(m_endpos) << 16;
+
+ // remaining data values set registers
+ m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
+ m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
+ m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
+ m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
+ m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
+
+ // reset the envelope so we don't continue playing mid-sample from previous key ons
+ m_env_attenuation = 0x3ff;
+}
+
+
+//-------------------------------------------------
+// read_pcm - read a byte from the external PCM
+// memory interface
+//-------------------------------------------------
+
+uint8_t pcm_channel::read_pcm(uint32_t address) const
+{
+ return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
+}
+
+
+//-------------------------------------------------
+// start_attack - start the attack phase
+//-------------------------------------------------
+
+void pcm_channel::start_attack()
+{
+ // don't change anything if already in attack state
+ if (m_eg_state == EG_ATTACK)
+ return;
+ m_eg_state = EG_ATTACK;
+
+ // reset the LFO if requested
+ if (m_regs.ch_lfo_reset(m_choffs))
+ m_lfo_counter = 0;
+
+ // if the attack rate == 63 then immediately go to max attenuation
+ if (m_cache.eg_rate[EG_ATTACK] == 63)
+ m_env_attenuation = 0;
+
+ // reset the positions
+ m_curpos = m_nextpos = 0;
+}
+
+
+//-------------------------------------------------
+// start_release - start the release phase
+//-------------------------------------------------
+
+void pcm_channel::start_release()
+{
+ // don't change anything if already in release or reverb state
+ if (m_eg_state >= EG_RELEASE)
+ return;
+ m_eg_state = EG_RELEASE;
+}
+
+
+//-------------------------------------------------
+// clock_envelope - clock the envelope generator
+//-------------------------------------------------
+
+void pcm_channel::clock_envelope(uint32_t env_counter)
+{
+ // handle attack->decay transitions
+ if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
+ m_eg_state = EG_DECAY;
+
+ // handle decay->sustain transitions
+ if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
+ m_eg_state = EG_SUSTAIN;
+
+ // fetch the appropriate 6-bit rate value from the cache
+ uint32_t rate = m_cache.eg_rate[m_eg_state];
+
+ // compute the rate shift value; this is the shift needed to
+ // apply to the env_counter such that it becomes a 5.11 fixed
+ // point number
+ uint32_t rate_shift = rate >> 2;
+ env_counter <<= rate_shift;
+
+ // see if the fractional part is 0; if not, it's not time to clock
+ if (bitfield(env_counter, 0, 11) != 0)
+ return;
+
+ // determine the increment based on the non-fractional part of env_counter
+ uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
+ uint32_t increment = attenuation_increment(rate, relevant_bits);
+
+ // attack is the only one that increases
+ if (m_eg_state == EG_ATTACK)
+ m_env_attenuation += (~m_env_attenuation * increment) >> 4;
+
+ // all other cases are similar
+ else
+ {
+ // apply the increment
+ m_env_attenuation += increment;
+
+ // clamp the final attenuation
+ if (m_env_attenuation >= 0x400)
+ m_env_attenuation = 0x3ff;
+
+ // transition to reverb at -18dB if enabled
+ if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
+ m_eg_state = EG_REVERB;
+ }
+}
+
+
+//-------------------------------------------------
+// fetch_sample - fetch a sample at the current
+// position
+//-------------------------------------------------
+
+int16_t pcm_channel::fetch_sample() const
+{
+ uint32_t addr = m_baseaddr;
+ uint32_t pos = m_curpos >> 16;
+
+ // 8-bit PCM: shift up by 8
+ if (m_format == 0)
+ return read_pcm(addr + pos) << 8;
+
+ // 16-bit PCM: assemble from 2 halves
+ if (m_format == 2)
+ {
+ addr += pos * 2;
+ return (read_pcm(addr) << 8) | read_pcm(addr + 1);
+ }
+
+ // 12-bit PCM: assemble out of half of 3 bytes
+ addr += (pos / 2) * 3;
+ if ((pos & 1) == 0)
+ return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
+ else
+ return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
+}
+
+
+
+//*********************************************************
+// PCM ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// pcm_engine - constructor
+//-------------------------------------------------
+
+pcm_engine::pcm_engine(ymfm_interface &intf) :
+ m_intf(intf),
+ m_env_counter(0),
+ m_modified_channels(ALL_CHANNELS),
+ m_active_channels(ALL_CHANNELS)
+{
+ // create the channels
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void pcm_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 pcm_engine::save_restore(ymfm_saved_state &state)
+{
+ // save our data
+ state.save_restore(m_env_counter);
+
+ // save channel state
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum]->save_restore(state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void pcm_engine::clock(uint32_t chanmask)
+{
+ // if something was modified, prepare
+ // also prepare every 4k samples to catch ending notes
+ if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
+ {
+ // call each channel to prepare
+ m_active_channels = 0;
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ if (m_channel[chnum]->prepare())
+ m_active_channels |= 1 << chnum;
+
+ // reset the modified channels and prepare count
+ m_modified_channels = m_prepare_count = 0;
+ }
+
+ // increment the envelope counter; the envelope generator
+ // only clocks every other sample in order to make the PCM
+ // envelopes line up with the FM envelopes (after taking into
+ // account the different FM sampling rate)
+ m_env_counter++;
+
+ // now update the state of all the channels and operators
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->clock(m_env_counter >> 1);
+}
+
+
+//-------------------------------------------------
+// update - master update function
+//-------------------------------------------------
+
+void pcm_engine::output(output_data &output, uint32_t chanmask)
+{
+ // mask out some channels for debug purposes
+ chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
+
+ // compute the output of each channel
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->output(output);
+}
+
+
+//-------------------------------------------------
+// read - handle reads from the PCM registers
+//-------------------------------------------------
+
+uint8_t pcm_engine::read(uint32_t regnum)
+{
+ // handle reads from the data register
+ if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
+ return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
+
+ return m_regs.read(regnum);
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the PCM registers
+//-------------------------------------------------
+
+void pcm_engine::write(uint32_t regnum, uint8_t data)
+{
+ // handle reads to the data register
+ if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
+ {
+ m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
+ return;
+ }
+
+ // for now just mark all channels as modified
+ m_modified_channels = ALL_CHANNELS;
+
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // however, process keyons immediately
+ if (regnum >= 0x68 && regnum <= 0x7f)
+ m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
+
+ // and also wavetable writes
+ else if (regnum >= 0x08 && regnum <= 0x1f)
+ m_channel[regnum - 0x08]->load_wavetable();
+}
+
+}