539 lines
17 KiB
C++
539 lines
17 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.
|
|
|
|
#include "ymfm_opm.h"
|
|
#include "ymfm_fm.ipp"
|
|
|
|
namespace ymfm
|
|
{
|
|
|
|
//*********************************************************
|
|
// OPM REGISTERS
|
|
//*********************************************************
|
|
|
|
//-------------------------------------------------
|
|
// opm_registers - constructor
|
|
//-------------------------------------------------
|
|
|
|
opm_registers::opm_registers() :
|
|
m_lfo_counter(0),
|
|
m_noise_lfsr(1),
|
|
m_noise_counter(0),
|
|
m_noise_state(0),
|
|
m_noise_lfo(0),
|
|
m_lfo_am(0)
|
|
{
|
|
// create the waveforms
|
|
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
|
|
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
|
|
|
|
// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
|
|
// waveforms are adjusted to match the pictures in the application manual
|
|
for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
|
|
{
|
|
// waveform 0 is a sawtooth
|
|
uint8_t am = index ^ 0xff;
|
|
int8_t pm = int8_t(index);
|
|
m_lfo_waveform[0][index] = am | (pm << 8);
|
|
|
|
// waveform 1 is a square wave
|
|
am = bitfield(index, 7) ? 0 : 0xff;
|
|
pm = int8_t(am ^ 0x80);
|
|
m_lfo_waveform[1][index] = am | (pm << 8);
|
|
|
|
// waveform 2 is a triangle wave
|
|
am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
|
|
pm = int8_t(bitfield(index, 6) ? am : ~am);
|
|
m_lfo_waveform[2][index] = am | (pm << 8);
|
|
|
|
// waveform 3 is noise; it is filled in dynamically
|
|
m_lfo_waveform[3][index] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// reset - reset to initial state
|
|
//-------------------------------------------------
|
|
|
|
void opm_registers::reset()
|
|
{
|
|
std::fill_n(&m_regdata[0], REGISTERS, 0);
|
|
|
|
// enable output on both channels by default
|
|
m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0;
|
|
m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// save_restore - save or restore the data
|
|
//-------------------------------------------------
|
|
|
|
void opm_registers::save_restore(ymfm_saved_state &state)
|
|
{
|
|
state.save_restore(m_lfo_counter);
|
|
state.save_restore(m_lfo_am);
|
|
state.save_restore(m_noise_lfsr);
|
|
state.save_restore(m_noise_counter);
|
|
state.save_restore(m_noise_state);
|
|
state.save_restore(m_noise_lfo);
|
|
state.save_restore(m_regdata);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// operator_map - return an array of operator
|
|
// indices for each channel; for OPM this is fixed
|
|
//-------------------------------------------------
|
|
|
|
void opm_registers::operator_map(operator_mapping &dest) const
|
|
{
|
|
// Note that the channel index order is 0,2,1,3, so we bitswap the index.
|
|
//
|
|
// This is because the order in the map is:
|
|
// carrier 1, carrier 2, modulator 1, modulator 2
|
|
//
|
|
// But when wiring up the connections, the more natural order is:
|
|
// carrier 1, modulator 1, carrier 2, modulator 2
|
|
static const operator_mapping s_fixed_map =
|
|
{ {
|
|
operator_list( 0, 16, 8, 24 ), // Channel 0 operators
|
|
operator_list( 1, 17, 9, 25 ), // Channel 1 operators
|
|
operator_list( 2, 18, 10, 26 ), // Channel 2 operators
|
|
operator_list( 3, 19, 11, 27 ), // Channel 3 operators
|
|
operator_list( 4, 20, 12, 28 ), // Channel 4 operators
|
|
operator_list( 5, 21, 13, 29 ), // Channel 5 operators
|
|
operator_list( 6, 22, 14, 30 ), // Channel 6 operators
|
|
operator_list( 7, 23, 15, 31 ), // Channel 7 operators
|
|
} };
|
|
dest = s_fixed_map;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// write - handle writes to the register array
|
|
//-------------------------------------------------
|
|
|
|
bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
|
|
{
|
|
assert(index < REGISTERS);
|
|
|
|
// LFO AM/PM depth are written to the same register (0x19);
|
|
// redirect the PM depth to an unused neighbor (0x1a)
|
|
if (index == 0x19)
|
|
m_regdata[index + bitfield(data, 7)] = data;
|
|
else if (index != 0x1a)
|
|
m_regdata[index] = data;
|
|
|
|
// handle writes to the key on index
|
|
if (index == 0x08)
|
|
{
|
|
channel = bitfield(data, 0, 3);
|
|
opmask = bitfield(data, 3, 4);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// clock_noise_and_lfo - clock the noise and LFO,
|
|
// handling clock division, depth, and waveform
|
|
// computations
|
|
//-------------------------------------------------
|
|
|
|
int32_t opm_registers::clock_noise_and_lfo()
|
|
{
|
|
// base noise frequency is measured at 2x 1/2 FM frequency; this
|
|
// means each tick counts as two steps against the noise counter
|
|
uint32_t freq = noise_frequency();
|
|
for (int rep = 0; rep < 2; rep++)
|
|
{
|
|
// evidence seems to suggest the LFSR is clocked continually and just
|
|
// sampled at the noise frequency for output purposes; note that the
|
|
// low 8 bits are the most recent 8 bits of history while bits 8-24
|
|
// contain the 17 bit LFSR state
|
|
m_noise_lfsr <<= 1;
|
|
m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
|
|
|
|
// compare against the frequency and latch when we exceed it
|
|
if (m_noise_counter++ >= freq)
|
|
{
|
|
m_noise_counter = 0;
|
|
m_noise_state = bitfield(m_noise_lfsr, 17);
|
|
}
|
|
}
|
|
|
|
// treat the rate as a 4.4 floating-point step value with implied
|
|
// leading 1; this matches exactly the frequencies in the application
|
|
// manual, though it might not be implemented exactly this way on chip
|
|
uint32_t rate = lfo_rate();
|
|
m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4);
|
|
|
|
// bit 1 of the test register is officially undocumented but has been
|
|
// discovered to hold the LFO in reset while active
|
|
if (lfo_reset())
|
|
m_lfo_counter = 0;
|
|
|
|
// now pull out the non-fractional LFO value
|
|
uint32_t lfo = bitfield(m_lfo_counter, 22, 8);
|
|
|
|
// fill in the noise entry 1 ahead of our current position; this
|
|
// ensures the current value remains stable for a full LFO clock
|
|
// and effectively latches the running value when the LFO advances
|
|
uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
|
|
m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
|
|
|
|
// fetch the AM/PM values based on the waveform; AM is unsigned and
|
|
// encoded in the low 8 bits, while PM signed and encoded in the upper
|
|
// 8 bits
|
|
int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo];
|
|
|
|
// apply depth to the AM value and store for later
|
|
m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7;
|
|
|
|
// apply depth to the PM value and return it
|
|
return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// lfo_am_offset - return the AM offset from LFO
|
|
// for the given channel
|
|
//-------------------------------------------------
|
|
|
|
uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const
|
|
{
|
|
// OPM maps AM quite differently from OPN
|
|
|
|
// shift value for AM sensitivity is [*, 0, 1, 2],
|
|
// mapping to values of [0, 23.9, 47.8, and 95.6dB]
|
|
uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
|
|
if (am_sensitivity == 0)
|
|
return 0;
|
|
|
|
// QUESTION: see OPN note below for the dB range mapping; it applies
|
|
// here as well
|
|
|
|
// raw LFO AM value on OPM is 0-FF, which is already a factor of 2
|
|
// larger than the OPN below, putting our staring point at 2x theirs;
|
|
// this works out since our minimum is 2x their maximum
|
|
return m_lfo_am << (am_sensitivity - 1);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// cache_operator_data - fill the operator cache
|
|
// with prefetched data
|
|
//-------------------------------------------------
|
|
|
|
void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
|
|
{
|
|
// set up the easy stuff
|
|
cache.waveform = &m_waveform[0][0];
|
|
|
|
// get frequency from the channel
|
|
uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
|
|
|
|
// compute the keycode: block_freq is:
|
|
//
|
|
// BBBCCCCFFFFFF
|
|
// ^^^^^
|
|
//
|
|
// the 5-bit keycode is just the top 5 bits (block + top 2 bits
|
|
// of the key code)
|
|
uint32_t keycode = bitfield(block_freq, 8, 5);
|
|
|
|
// detune adjustment
|
|
cache.detune = detune_adjustment(op_detune(opoffs), keycode);
|
|
|
|
// multiple value, as an x.1 value (0 means 0.5)
|
|
cache.multiple = op_multiple(opoffs) * 2;
|
|
if (cache.multiple == 0)
|
|
cache.multiple = 1;
|
|
|
|
// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
|
|
// block_freq, detune, and multiple, so compute it after we've done those
|
|
if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0)
|
|
cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
|
|
else
|
|
cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
|
|
|
|
// total level, scaled by 8
|
|
cache.total_level = op_total_level(opoffs) << 3;
|
|
|
|
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
|
|
cache.eg_sustain = op_sustain_level(opoffs);
|
|
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
|
|
cache.eg_sustain <<= 5;
|
|
|
|
// determine KSR adjustment for enevlope rates
|
|
uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
|
|
cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
|
|
cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
|
|
cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
|
|
cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// compute_phase_step - compute the phase step
|
|
//-------------------------------------------------
|
|
|
|
uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
|
|
{
|
|
// OPM logic is rather unique here, due to extra detune
|
|
// and the use of key codes (not to be confused with keycode)
|
|
|
|
// start with coarse detune delta; table uses cents value from
|
|
// manual, converted into 1/64ths
|
|
static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
|
|
int32_t delta = s_detune2_delta[op_detune2(opoffs)];
|
|
|
|
// add in the PM delta
|
|
uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
|
|
if (pm_sensitivity != 0)
|
|
{
|
|
// raw PM value is -127..128 which is +/- 200 cents
|
|
// manual gives these magnitudes in cents:
|
|
// 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
|
|
// this roughly corresponds to shifting the 200-cent value:
|
|
// 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
|
|
if (pm_sensitivity < 6)
|
|
delta += lfo_raw_pm >> (6 - pm_sensitivity);
|
|
else
|
|
delta += lfo_raw_pm << (pm_sensitivity - 5);
|
|
}
|
|
|
|
// apply delta and convert to a frequency number
|
|
uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
|
|
|
|
// apply detune based on the keycode
|
|
phase_step += cache.detune;
|
|
|
|
// apply frequency multiplier (which is cached as an x.1 value)
|
|
return (phase_step * cache.multiple) >> 1;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// log_keyon - log a key-on event
|
|
//-------------------------------------------------
|
|
|
|
std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
|
|
{
|
|
uint32_t chnum = choffs;
|
|
uint32_t opnum = opoffs;
|
|
|
|
char buffer[256];
|
|
char *end = &buffer[0];
|
|
|
|
end += sprintf(end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
|
|
chnum, opnum,
|
|
ch_block_freq(choffs),
|
|
op_detune2(opoffs),
|
|
op_detune(opoffs),
|
|
ch_feedback(choffs),
|
|
ch_algorithm(choffs),
|
|
op_multiple(opoffs),
|
|
op_total_level(opoffs),
|
|
op_ksr(opoffs),
|
|
op_attack_rate(opoffs),
|
|
op_decay_rate(opoffs),
|
|
op_sustain_rate(opoffs),
|
|
op_release_rate(opoffs),
|
|
op_sustain_level(opoffs),
|
|
ch_output_0(choffs) ? 'L' : '-',
|
|
ch_output_1(choffs) ? 'R' : '-');
|
|
|
|
bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
|
|
if (am)
|
|
end += sprintf(end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
|
|
bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
|
|
if (pm)
|
|
end += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
|
|
if (am || pm)
|
|
end += sprintf(end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
|
|
if (noise_enable() && opoffs == 31)
|
|
end += sprintf(end, " noise=1");
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
|
|
//*********************************************************
|
|
// YM2151
|
|
//*********************************************************
|
|
|
|
//-------------------------------------------------
|
|
// ym2151 - constructor
|
|
//-------------------------------------------------
|
|
|
|
ym2151::ym2151(ymfm_interface &intf, opm_variant variant) :
|
|
m_variant(variant),
|
|
m_address(0),
|
|
m_fm(intf)
|
|
{
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// reset - reset the system
|
|
//-------------------------------------------------
|
|
|
|
void ym2151::reset()
|
|
{
|
|
// reset the engines
|
|
m_fm.reset();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// save_restore - save or restore the data
|
|
//-------------------------------------------------
|
|
|
|
void ym2151::save_restore(ymfm_saved_state &state)
|
|
{
|
|
m_fm.save_restore(state);
|
|
state.save_restore(m_address);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// read_status - read the status register
|
|
//-------------------------------------------------
|
|
|
|
uint8_t ym2151::read_status()
|
|
{
|
|
uint8_t result = m_fm.status();
|
|
if (m_fm.intf().ymfm_is_busy())
|
|
result |= fm_engine::STATUS_BUSY;
|
|
return result;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// read - handle a read from the device
|
|
//-------------------------------------------------
|
|
|
|
uint8_t ym2151::read(uint32_t offset)
|
|
{
|
|
uint8_t result = 0xff;
|
|
switch (offset & 1)
|
|
{
|
|
case 0: // data port (unused)
|
|
debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3);
|
|
break;
|
|
|
|
case 1: // status port, YM2203 compatible
|
|
result = read_status();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// write_address - handle a write to the address
|
|
// register
|
|
//-------------------------------------------------
|
|
|
|
void ym2151::write_address(uint8_t data)
|
|
{
|
|
// just set the address
|
|
m_address = data;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// write - handle a write to the register
|
|
// interface
|
|
//-------------------------------------------------
|
|
|
|
void ym2151::write_data(uint8_t data)
|
|
{
|
|
// write the FM register
|
|
m_fm.write(m_address, data);
|
|
|
|
// special cases
|
|
if (m_address == 0x1b)
|
|
{
|
|
// writes to register 0x1B send the upper 2 bits to the output lines
|
|
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
|
|
}
|
|
|
|
// mark busy for a bit
|
|
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// write - handle a write to the register
|
|
// interface
|
|
//-------------------------------------------------
|
|
|
|
void ym2151::write(uint32_t offset, uint8_t data)
|
|
{
|
|
switch (offset & 1)
|
|
{
|
|
case 0: // address port
|
|
write_address(data);
|
|
break;
|
|
|
|
case 1: // data port
|
|
write_data(data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// generate - generate one sample of sound
|
|
//-------------------------------------------------
|
|
|
|
void ym2151::generate(output_data *output, uint32_t numsamples)
|
|
{
|
|
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
|
|
{
|
|
// clock the system
|
|
m_fm.clock(fm_engine::ALL_CHANNELS);
|
|
|
|
// update the FM content; OPM is full 14-bit with no intermediate clipping
|
|
m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
|
|
|
|
// YM2151 uses an external DAC (YM3012) with mantissa/exponent format
|
|
// convert to 10.3 floating point value and back to simulate truncation
|
|
output->roundtrip_fp();
|
|
}
|
|
}
|
|
|
|
}
|