411 lines
13 KiB
C++
411 lines
13 KiB
C++
// 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<int NumOutputs>
|
|
void output(ymfm_output<NumOutputs> &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<int NumOutputs>
|
|
void output(ymfm_output<NumOutputs> &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<adpcm_a_channel> 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<int NumOutputs>
|
|
void output(ymfm_output<NumOutputs> &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<int NumOutputs>
|
|
void output(ymfm_output<NumOutputs> &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<adpcm_b_channel> m_channel; // channel pointer
|
|
adpcm_b_registers m_regs; // registers
|
|
};
|
|
|
|
}
|
|
|
|
#endif // YMFM_ADPCM_H
|