// 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[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(m_total_level + 19, m_cache.total_level); else m_total_level = std::max(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(envelope + m_cache.pan_left, 0x3ff); uint32_t renv = std::min(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(*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(); } }