// 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. #ifndef YMFM_ADPCM_H #define YMFM_ADPCM_H #pragma once #include "ymfm.h" namespace ymfm { //********************************************************* // INTERFACE CLASSES //********************************************************* // forward declarations class adpcm_a_engine; class adpcm_b_engine; // ======================> adpcm_a_registers // // ADPCM-A register map: // // System-wide registers: // 00 x------- Dump (disable=1) or keyon (0) control // --xxxxxx Mask of channels to dump or keyon // 01 --xxxxxx Total level // 02 xxxxxxxx Test register // 08-0D x------- Pan left // -x------ Pan right // ---xxxxx Instrument level // 10-15 xxxxxxxx Start address (low) // 18-1D xxxxxxxx Start address (high) // 20-25 xxxxxxxx End address (low) // 28-2D xxxxxxxx End address (high) // class adpcm_a_registers { public: // constants static constexpr uint32_t OUTPUTS = 2; static constexpr uint32_t CHANNELS = 6; static constexpr uint32_t REGISTERS = 0x30; static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; // constructor adpcm_a_registers() { } // reset to initial state void reset(); // save/restore void save_restore(ymfm_saved_state &state); // map channel number to register offset static constexpr uint32_t channel_offset(uint32_t chnum) { assert(chnum < CHANNELS); return chnum; } // direct read/write access void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } // system-wide registers uint32_t dump() const { return bitfield(m_regdata[0x00], 7); } uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); } uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); } uint32_t test() const { return m_regdata[0x02]; } // per-channel registers uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); } uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); } uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); } uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); } uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); } // per-channel writes void write_start(uint32_t choffs, uint32_t address) { write(choffs + 0x10, address); write(choffs + 0x18, address >> 8); } void write_end(uint32_t choffs, uint32_t address) { write(choffs + 0x20, address); write(choffs + 0x28, address >> 8); } private: // internal state uint8_t m_regdata[REGISTERS]; // register data }; // ======================> adpcm_a_channel class adpcm_a_channel { public: // constructor adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift); // reset the channel state void reset(); // save/restore void save_restore(ymfm_saved_state &state); // signal key on/off void keyonoff(bool on); // master clockingfunction bool clock(); // return the computed output value, with panning applied template void output(ymfm_output &output) const; private: // internal state uint32_t const m_choffs; // channel offset uint32_t const m_address_shift; // address bits shift-left uint32_t m_playing; // currently playing? uint32_t m_curnibble; // index of the current nibble uint32_t m_curbyte; // current byte of data uint32_t m_curaddress; // current address int32_t m_accumulator; // accumulator int32_t m_step_index; // index in the stepping table adpcm_a_registers &m_regs; // reference to registers adpcm_a_engine &m_owner; // reference to our owner }; // ======================> adpcm_a_engine class adpcm_a_engine { public: static constexpr int CHANNELS = adpcm_a_registers::CHANNELS; // constructor adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift); // reset our status void reset(); // save/restore void save_restore(ymfm_saved_state &state); // master clocking function uint32_t clock(uint32_t chanmask); // compute sum of channel outputs template void output(ymfm_output &output, uint32_t chanmask); // write to the ADPCM-A registers void write(uint32_t regnum, uint8_t data); // set the start/end address for a channel (for hardcoded YM2608 percussion) void set_start_end(uint8_t chnum, uint16_t start, uint16_t end) { uint32_t choffs = adpcm_a_registers::channel_offset(chnum); m_regs.write_start(choffs, start); m_regs.write_end(choffs, end); } // return a reference to our interface ymfm_interface &intf() { return m_intf; } // return a reference to our registers adpcm_a_registers ®s() { return m_regs; } private: // internal state ymfm_interface &m_intf; // reference to the interface std::unique_ptr m_channel[CHANNELS]; // array of channels adpcm_a_registers m_regs; // registers }; // ======================> adpcm_b_registers // // ADPCM-B register map: // // System-wide registers: // 00 x------- Start of synthesis/analysis // -x------ Record // --x----- External/manual driving // ---x---- Repeat playback // ----x--- Speaker off // -------x Reset // 01 x------- Pan left // -x------ Pan right // ----x--- Start conversion // -----x-- DAC enable // ------x- DRAM access (1=8-bit granularity; 0=1-bit) // -------x RAM/ROM (1=ROM, 0=RAM) // 02 xxxxxxxx Start address (low) // 03 xxxxxxxx Start address (high) // 04 xxxxxxxx End address (low) // 05 xxxxxxxx End address (high) // 06 xxxxxxxx Prescale value (low) // 07 -----xxx Prescale value (high) // 08 xxxxxxxx CPU data/buffer // 09 xxxxxxxx Delta-N frequency scale (low) // 0a xxxxxxxx Delta-N frequency scale (high) // 0b xxxxxxxx Level control // 0c xxxxxxxx Limit address (low) // 0d xxxxxxxx Limit address (high) // 0e xxxxxxxx DAC data [YM2608/10] // 0f xxxxxxxx PCM data [YM2608/10] // 0e xxxxxxxx DAC data high [Y8950] // 0f xx------ DAC data low [Y8950] // 10 -----xxx DAC data exponent [Y8950] // class adpcm_b_registers { public: // constants static constexpr uint32_t REGISTERS = 0x11; // constructor adpcm_b_registers() { } // reset to initial state void reset(); // save/restore void save_restore(ymfm_saved_state &state); // direct read/write access void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } // system-wide registers uint32_t execute() const { return bitfield(m_regdata[0x00], 7); } uint32_t record() const { return bitfield(m_regdata[0x00], 6); } uint32_t external() const { return bitfield(m_regdata[0x00], 5); } uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); } uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); } uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); } uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); } uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); } uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); } uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); } uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); } uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); } uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); } uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); } uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); } uint32_t cpudata() const { return m_regdata[0x08]; } uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); } uint32_t level() const { return m_regdata[0x0b]; } uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); } uint32_t dac() const { return m_regdata[0x0e]; } uint32_t pcm() const { return m_regdata[0x0f]; } private: // internal state uint8_t m_regdata[REGISTERS]; // register data }; // ======================> adpcm_b_channel class adpcm_b_channel { static constexpr int32_t STEP_MIN = 127; static constexpr int32_t STEP_MAX = 24576; public: static constexpr uint8_t STATUS_EOS = 0x01; static constexpr uint8_t STATUS_BRDY = 0x02; static constexpr uint8_t STATUS_PLAYING = 0x04; // constructor adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift); // reset the channel state void reset(); // save/restore void save_restore(ymfm_saved_state &state); // signal key on/off void keyonoff(bool on); // master clocking function void clock(); // return the computed output value, with panning applied template void output(ymfm_output &output, uint32_t rshift) const; // return the status register uint8_t status() const { return m_status; } // handle special register reads uint8_t read(uint32_t regnum); // handle special register writes void write(uint32_t regnum, uint8_t value); private: // helper - return the current address shift uint32_t address_shift() const; // load the start address void load_start(); // limit checker; stops at the last byte of the chunk described by address_shift() bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); } // end checker; stops at the last byte of the chunk described by address_shift() bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); } // internal state uint32_t const m_address_shift; // address bits shift-left uint32_t m_status; // currently playing? uint32_t m_curnibble; // index of the current nibble uint32_t m_curbyte; // current byte of data uint32_t m_dummy_read; // dummy read tracker uint32_t m_position; // current fractional position uint32_t m_curaddress; // current address int32_t m_accumulator; // accumulator int32_t m_prev_accum; // previous accumulator (for linear interp) int32_t m_adpcm_step; // next forecast adpcm_b_registers &m_regs; // reference to registers adpcm_b_engine &m_owner; // reference to our owner }; // ======================> adpcm_b_engine class adpcm_b_engine { public: // constructor adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0); // reset our status void reset(); // save/restore void save_restore(ymfm_saved_state &state); // master clocking function void clock(); // compute sum of channel outputs template void output(ymfm_output &output, uint32_t rshift); // read from the ADPCM-B registers uint32_t read(uint32_t regnum) { return m_channel->read(regnum); } // write to the ADPCM-B registers void write(uint32_t regnum, uint8_t data); // status uint8_t status() const { return m_channel->status(); } // return a reference to our interface ymfm_interface &intf() { return m_intf; } // return a reference to our registers adpcm_b_registers ®s() { return m_regs; } private: // internal state ymfm_interface &m_intf; // reference to our interface std::unique_ptr m_channel; // channel pointer adpcm_b_registers m_regs; // registers }; } #endif // YMFM_ADPCM_H