looper/backends/playback/zsm/x16emu/ymfm/src/ymfm_pcm.h
2024-09-06 10:06:32 -07:00

347 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 &regs() { 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