use crate::midi_shim::MIDIShim;
use crate::midi_shim::{
MIDI_DATA_ENTRY_MSB, MIDI_NRPN_LSB, MIDI_NRPN_MSB, MIDI_PITCHWHEEL_CENTER, NRPN_DRUM_PAN,
};
use crate::strict;
use anyhow::{bail, Result};
pub(crate) struct SegaPCMState {
ram: [u8; 0x800],
chn_addr: [u32; 0x10],
chn_vol_l: [u8; 0x10],
chn_vol_r: [u8; 0x10],
chn_note: [u8; 0x10],
last_pan: [u8; 0x10],
}
impl Default for SegaPCMState {
fn default() -> Self {
SegaPCMState {
ram: [0; 0x800],
chn_addr: [0; 0x10],
chn_vol_l: [0; 0x10],
chn_vol_r: [0; 0x10],
chn_note: [0; 0x10],
last_pan: [0; 0x10],
}
}
}
pub(crate) struct SegaPCM {
state: SegaPCMState,
}
impl SegaPCM {
pub(crate) fn new(opt_state: Option<SegaPCMState>) -> Self {
Self {
state: opt_state.unwrap_or_default(),
}
}
pub(crate) fn mem_write(
&mut self,
offset: u16,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
if offset > 0x7FF {
strict!("sega_pcm_mem_write offset is larger than 0x7FF");
}
let masked_offset = offset & 0x7FF;
let channel = ((masked_offset / 0x8) & 0xF) as u8;
let rel_offset = masked_offset & (!0x78);
self.state.ram[masked_offset as usize] = data;
match rel_offset {
0x86 => {
channel_on_off(data, midi, &mut self.state, channel);
},
0x4 => {
set_audio_address_low(&mut self.state, channel, data);
},
0x5 => {
set_audio_address_time(&mut self.state, channel, data, midi)?;
},
0x84 => (), 0x85 => (), 0x6 => (), 0x7 => {
set_sample_delta_time(midi);
},
0x2 => {
set_volume_l(&mut self.state, channel, data);
},
0x3 => {
set_volume_r(&mut self.state, channel, data);
},
_ => (), }
Ok(())
}
pub(crate) fn dump(&mut self) -> Result<()> {
match std::fs::read("SegaPCM.dmp") {
Ok(mut vec) => {
vec.resize(self.state.ram.len(), 0);
self.state.ram.copy_from_slice(vec.as_slice());
Ok(())
},
Err(_) => bail!("Failed when attempting to read SegaPCM dump."), }
}
}
fn set_volume_r(state: &mut SegaPCMState, channel: u8, data: u8) {
state.chn_vol_r[channel as usize] = data
}
fn set_volume_l(state: &mut SegaPCMState, channel: u8, data: u8) {
state.chn_vol_l[channel as usize] = data
}
fn set_sample_delta_time(midi: &mut MIDIShim) {
midi.pitch_bend_write(0x09.into(), MIDI_PITCHWHEEL_CENTER);
}
fn set_audio_address_time(
state: &mut SegaPCMState,
channel: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
state.chn_addr[channel as usize] =
(state.chn_addr[channel as usize] & 0xFF) | (data as u32) << 8;
let nrpn_data = match state.chn_addr[channel as usize] {
0x0 => 0x0,
0x437C => 0x38,
0x49DE => 0x1C,
0x302F => 0x2A,
0x17C0 | 0x172F => 0x2E,
0x90 => 0x26,
0xF0 => 0x2D,
0x29AB => 0x24,
0x5830 => 0x27,
0x1C03 => 0x30,
0x951 => 0x2B,
0x3BE6 => 0x31,
0x4DA0 => 0x45,
0xC1D => 0x28,
0x6002 => 0x33,
0x82 => 0x60,
0x2600 => 0x61,
0x4A => 0x62,
0x5600 => 0x63,
0x4000 => 0x65,
0x2D6 => 0x67,
0x158A => 0x68,
0xC04 => 0x69,
0x1CD0 => 0x6A,
0x218E => 0x6B,
0x1E34 => 0x6C,
0xE31 => 0x6D,
0x89B => 0x6E,
0x27B => 0x6F,
0x4EA => 0x70,
0x3600 => 0x71,
0x1800 => 0x72,
0x18BA => 0x73,
0x173 => 0x74,
0x204F => 0x75,
0xAB => 0x76,
0x201D => 0x77,
0x20AB => 0x78,
0x2000 => 0x79,
0x2 => 0x7A,
0x1D => 0x7B,
0x2F => 0x7C,
0x26AB => 0x7D,
0x262F => 0x7E,
_ => {
strict!(
"Bad SegaPCM channel address for channel {}: {}",
channel,
state.chn_addr[channel as usize]
);
0x7F
},
};
midi.note_off_write(0x09.into(), state.chn_note[channel as usize].into(), 0x00.into());
let mut note_vol = state.chn_vol_l[channel as usize] + state.chn_vol_r[channel as usize];
let temp_byte = if note_vol > 0 {
(state.chn_vol_r[channel as usize] / (note_vol / 2)) * 0x40
} else {
0x40
};
if state.last_pan[channel as usize] != temp_byte {
midi.controller_write(0x09.into(), MIDI_NRPN_MSB, NRPN_DRUM_PAN);
midi.controller_write(0x09.into(), MIDI_NRPN_LSB, nrpn_data.into());
midi.controller_write(0x09.into(), MIDI_DATA_ENTRY_MSB, temp_byte.into());
state.last_pan[channel as usize] = temp_byte;
}
if note_vol == 0x0 {
note_vol = 0x1
}
if note_vol <= 0x40 {
note_vol = 0x40 + note_vol / 2
}
midi.note_on_write(0x09.into(), data.into(), note_vol.into());
state.chn_note[channel as usize] = data;
Ok(())
}
fn set_audio_address_low(state: &mut SegaPCMState, channel: u8, data: u8) {
state.chn_addr[channel as usize] =
(state.chn_addr[channel as usize] & 0xFF00) | data as u32;
}
fn channel_on_off(data: u8, midi: &mut MIDIShim, state: &mut SegaPCMState, channel: u8) {
if data & 0x1 != 0 {
midi.note_off_write(0x09.into(), state.chn_note[channel as usize].into(), 0x00.into());
state.chn_note[channel as usize] = 0x0;
}
}