348 lines
13 KiB
C
348 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_PCM_H
|
||
|
#define YMFM_PCM_H
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include "ymfm.h"
|
||
|
|
||
|
namespace ymfm
|
||
|
{
|
||
|
|
||
|
/*
|
||
|
Note to self: Sega "Multi-PCM" is almost identical to this
|
||
|
|
||
|
28 channels
|
||
|
|
||
|
Writes:
|
||
|
00 = data reg, causes write
|
||
|
01 = target slot = data - (data / 8)
|
||
|
02 = address (clamped to 7)
|
||
|
|
||
|
Slot data (registers with ADSR/KSR seem to be inaccessible):
|
||
|
0: xxxx---- panpot
|
||
|
1: xxxxxxxx wavetable low
|
||
|
2: xxxxxx-- pitch low
|
||
|
-------x wavetable high
|
||
|
3: xxxx---- octave
|
||
|
----xxxx pitch hi
|
||
|
4: x------- key on
|
||
|
5: xxxxxxx- total level
|
||
|
-------x level direct (0=interpolate)
|
||
|
6: --xxx--- LFO frequency
|
||
|
-----xxx PM sensitivity
|
||
|
7: -----xxx AM sensitivity
|
||
|
|
||
|
Sample data:
|
||
|
+00: start hi
|
||
|
+01: start mid
|
||
|
+02: start low
|
||
|
+03: loop hi
|
||
|
+04: loop low
|
||
|
+05: -end hi
|
||
|
+06: -end low
|
||
|
+07: vibrato (reg 6)
|
||
|
+08: attack/decay
|
||
|
+09: sustain level/rate
|
||
|
+0A: ksr/release
|
||
|
+0B: LFO amplitude (reg 7)
|
||
|
|
||
|
*/
|
||
|
|
||
|
//*********************************************************
|
||
|
// INTERFACE CLASSES
|
||
|
//*********************************************************
|
||
|
|
||
|
class pcm_engine;
|
||
|
|
||
|
|
||
|
// ======================> pcm_cache
|
||
|
|
||
|
// this class holds data that is computed once at the start of clocking
|
||
|
// and remains static during subsequent sound generation
|
||
|
struct pcm_cache
|
||
|
{
|
||
|
uint32_t step; // sample position step, as a .16 value
|
||
|
uint32_t total_level; // target total level, as a .10 value
|
||
|
uint32_t pan_left; // left panning attenuation
|
||
|
uint32_t pan_right; // right panning attenuation
|
||
|
uint32_t eg_sustain; // sustain level, shifted up to envelope values
|
||
|
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
|
||
|
uint8_t lfo_step; // stepping value for LFO
|
||
|
uint8_t am_depth; // scale value for AM LFO
|
||
|
uint8_t pm_depth; // scale value for PM LFO
|
||
|
};
|
||
|
|
||
|
|
||
|
// ======================> pcm_registers
|
||
|
|
||
|
//
|
||
|
// PCM register map:
|
||
|
//
|
||
|
// System-wide registers:
|
||
|
// 00-01 xxxxxxxx LSI Test
|
||
|
// 02 -------x Memory access mode (0=sound gen, 1=read/write)
|
||
|
// ------x- Memory type (0=ROM, 1=ROM+SRAM)
|
||
|
// ---xxx-- Wave table header
|
||
|
// xxx----- Device ID (=1 for YMF278B)
|
||
|
// 03 --xxxxxx Memory address high
|
||
|
// 04 xxxxxxxx Memory address mid
|
||
|
// 05 xxxxxxxx Memory address low
|
||
|
// 06 xxxxxxxx Memory data
|
||
|
// F8 --xxx--- Mix control (FM_R)
|
||
|
// -----xxx Mix control (FM_L)
|
||
|
// F9 --xxx--- Mix control (PCM_R)
|
||
|
// -----xxx Mix control (PCM_L)
|
||
|
//
|
||
|
// Channel-specific registers:
|
||
|
// 08-1F xxxxxxxx Wave table number low
|
||
|
// 20-37 -------x Wave table number high
|
||
|
// xxxxxxx- F-number low
|
||
|
// 38-4F -----xxx F-number high
|
||
|
// ----x--- Pseudo-reverb
|
||
|
// xxxx---- Octave
|
||
|
// 50-67 xxxxxxx- Total level
|
||
|
// -------x Level direct
|
||
|
// 68-7F x------- Key on
|
||
|
// -x------ Damp
|
||
|
// --x----- LFO reset
|
||
|
// ---x---- Output channel
|
||
|
// ----xxxx Panpot
|
||
|
// 80-97 --xxx--- LFO speed
|
||
|
// -----xxx Vibrato
|
||
|
// 98-AF xxxx---- Attack rate
|
||
|
// ----xxxx Decay rate
|
||
|
// B0-C7 xxxx---- Sustain level
|
||
|
// ----xxxx Sustain rate
|
||
|
// C8-DF xxxx---- Rate correction
|
||
|
// ----xxxx Release rate
|
||
|
// E0-F7 -----xxx AM depth
|
||
|
|
||
|
class pcm_registers
|
||
|
{
|
||
|
public:
|
||
|
// constants
|
||
|
static constexpr uint32_t OUTPUTS = 4;
|
||
|
static constexpr uint32_t CHANNELS = 24;
|
||
|
static constexpr uint32_t REGISTERS = 0x100;
|
||
|
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||
|
|
||
|
// constructor
|
||
|
pcm_registers() { }
|
||
|
|
||
|
// save/restore
|
||
|
void save_restore(ymfm_saved_state &state);
|
||
|
|
||
|
// reset to initial state
|
||
|
void reset();
|
||
|
|
||
|
// update cache information
|
||
|
void cache_channel_data(uint32_t choffs, pcm_cache &cache);
|
||
|
|
||
|
// direct read/write access
|
||
|
uint8_t read(uint32_t index ) { return m_regdata[index]; }
|
||
|
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
|
||
|
|
||
|
// system-wide registers
|
||
|
uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); }
|
||
|
uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); }
|
||
|
uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); }
|
||
|
uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); }
|
||
|
uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
|
||
|
uint32_t memory_data() const { return m_regdata[0x06]; }
|
||
|
uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); }
|
||
|
uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); }
|
||
|
uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); }
|
||
|
uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); }
|
||
|
|
||
|
// per-channel registers
|
||
|
uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
|
||
|
uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
|
||
|
uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); }
|
||
|
uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
|
||
|
uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
|
||
|
uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); }
|
||
|
uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); }
|
||
|
uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); }
|
||
|
uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); }
|
||
|
uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); }
|
||
|
uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
|
||
|
uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
|
||
|
uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
|
||
|
uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
|
||
|
uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
|
||
|
uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
|
||
|
uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
|
||
|
uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
|
||
|
uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
|
||
|
uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
|
||
|
|
||
|
// return the memory address and increment it
|
||
|
uint32_t memory_address_autoinc()
|
||
|
{
|
||
|
uint32_t result = memory_address();
|
||
|
uint32_t newval = result + 1;
|
||
|
m_regdata[0x05] = newval >> 0;
|
||
|
m_regdata[0x04] = newval >> 8;
|
||
|
m_regdata[0x03] = (newval >> 16) & 0x3f;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
// internal helpers
|
||
|
uint32_t effective_rate(uint32_t raw, uint32_t correction);
|
||
|
|
||
|
// internal state
|
||
|
uint8_t m_regdata[REGISTERS]; // register data
|
||
|
};
|
||
|
|
||
|
|
||
|
// ======================> pcm_channel
|
||
|
|
||
|
class pcm_channel
|
||
|
{
|
||
|
static constexpr uint8_t KEY_ON = 0x01;
|
||
|
static constexpr uint8_t KEY_PENDING_ON = 0x02;
|
||
|
static constexpr uint8_t KEY_PENDING = 0x04;
|
||
|
|
||
|
// "quiet" value, used to optimize when we can skip doing working
|
||
|
static constexpr uint32_t EG_QUIET = 0x200;
|
||
|
|
||
|
public:
|
||
|
using output_data = ymfm_output<pcm_registers::OUTPUTS>;
|
||
|
|
||
|
// constructor
|
||
|
pcm_channel(pcm_engine &owner, uint32_t choffs);
|
||
|
|
||
|
// save/restore
|
||
|
void save_restore(ymfm_saved_state &state);
|
||
|
|
||
|
// reset the channel state
|
||
|
void reset();
|
||
|
|
||
|
// return the channel offset
|
||
|
uint32_t choffs() const { return m_choffs; }
|
||
|
|
||
|
// prepare prior to clocking
|
||
|
bool prepare();
|
||
|
|
||
|
// master clocking function
|
||
|
void clock(uint32_t env_counter);
|
||
|
|
||
|
// return the computed output value, with panning applied
|
||
|
void output(output_data &output) const;
|
||
|
|
||
|
// signal key on/off
|
||
|
void keyonoff(bool on);
|
||
|
|
||
|
// load a new wavetable entry
|
||
|
void load_wavetable();
|
||
|
|
||
|
private:
|
||
|
// internal helpers
|
||
|
void start_attack();
|
||
|
void start_release();
|
||
|
void clock_envelope(uint32_t env_counter);
|
||
|
int16_t fetch_sample() const;
|
||
|
uint8_t read_pcm(uint32_t address) const;
|
||
|
|
||
|
// internal state
|
||
|
uint32_t const m_choffs; // channel offset
|
||
|
uint32_t m_baseaddr; // base address
|
||
|
uint32_t m_endpos; // ending position
|
||
|
uint32_t m_looppos; // loop position
|
||
|
uint32_t m_curpos; // current position
|
||
|
uint32_t m_nextpos; // next position
|
||
|
uint32_t m_lfo_counter; // LFO counter
|
||
|
envelope_state m_eg_state; // envelope state
|
||
|
uint16_t m_env_attenuation; // computed envelope attenuation
|
||
|
uint32_t m_total_level; // total level with as 7.10 for interp
|
||
|
uint8_t m_format; // sample format
|
||
|
uint8_t m_key_state; // current key state
|
||
|
pcm_cache m_cache; // cached data
|
||
|
pcm_registers &m_regs; // reference to registers
|
||
|
pcm_engine &m_owner; // reference to our owner
|
||
|
};
|
||
|
|
||
|
|
||
|
// ======================> pcm_engine
|
||
|
|
||
|
class pcm_engine
|
||
|
{
|
||
|
public:
|
||
|
static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
|
||
|
static constexpr int CHANNELS = pcm_registers::CHANNELS;
|
||
|
static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
|
||
|
using output_data = pcm_channel::output_data;
|
||
|
|
||
|
// constructor
|
||
|
pcm_engine(ymfm_interface &intf);
|
||
|
|
||
|
// reset our status
|
||
|
void reset();
|
||
|
|
||
|
// save/restore
|
||
|
void save_restore(ymfm_saved_state &state);
|
||
|
|
||
|
// master clocking function
|
||
|
void clock(uint32_t chanmask);
|
||
|
|
||
|
// compute sum of channel outputs
|
||
|
void output(output_data &output, uint32_t chanmask);
|
||
|
|
||
|
// read from the PCM registers
|
||
|
uint8_t read(uint32_t regnum);
|
||
|
|
||
|
// write to the PCM registers
|
||
|
void write(uint32_t regnum, uint8_t data);
|
||
|
|
||
|
// return a reference to our interface
|
||
|
ymfm_interface &intf() { return m_intf; }
|
||
|
|
||
|
// return a reference to our registers
|
||
|
pcm_registers ®s() { return m_regs; }
|
||
|
|
||
|
private:
|
||
|
// internal state
|
||
|
ymfm_interface &m_intf; // reference to the interface
|
||
|
uint32_t m_env_counter; // envelope counter
|
||
|
uint32_t m_modified_channels; // bitmask of modified channels
|
||
|
uint32_t m_active_channels; // bitmask of active channels
|
||
|
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
|
||
|
std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
|
||
|
pcm_registers m_regs; // registers
|
||
|
};
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif // YMFM_PCM_H
|