use crate::config::Config;
use crate::midi_shim::{
db_to_midi_vol, MIDIShim, MIDI_MODULATOR_WHEEL, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT,
MIDI_PAN_RIGHT, MIDI_PITCHWHEEL_CENTER, MIDI_VOLUME,
};
use crate::strict;
use crate::utils::shift;
use crate::utils::{hz_to_note, FactoredState};
use crate::vgm2mid::{CHN_DAC, OPN_TYPE_YM2608, OPN_TYPE_YM2610, OPN_TYPE_YM2612};
use crate::ym3812::ym3812_vol_to_db;
use anyhow::{bail, Result};
#[allow(dead_code)]
pub(crate) const YM2610_DELTAT_ADPCM: u8 = 0x10; pub(crate) const YM2612_TEST: u8 = 0x21; pub(crate) const YM2612_LFO_EN_LFO_FREQ: u8 = 0x22;
#[allow(dead_code)]
pub(crate) const YM2612_LFO_EN: u8 = 8;
#[allow(dead_code)]
pub(crate) const YM2612_LFO_FREQ: u8 = 7;
pub(crate) const YM2612_TIMER_A_MSB: u8 = 0x24;
pub(crate) const YM2612_TIMER_A_LSB: u8 = 0x25;
pub(crate) const YM2612_TIMER_B: u8 = 0x26;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_MODE_RESET_ENABLE_LOAD: u8 = 0x27;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_MODE: u8 = 192;
#[allow(dead_code)]
pub(crate) const YM2612_TIMER_RESET: u8 = 48;
#[allow(dead_code)]
pub(crate) const YM2612_TIMER_ENABLE: u8 = 12;
#[allow(dead_code)]
pub(crate) const YM2612_TIMER_LOAD: u8 = 3;
pub(crate) const YM2612_SLOT_CH: u8 = 0x28; pub(crate) const YM2612_SLOT: u8 = 240;
pub(crate) const YM2612_CH: u8 = 7;
pub(crate) const YM2612_DAC: u8 = 0x2A; #[allow(dead_code)]
pub(crate) const YM2612_DAC_DATA: u8 = 255;
pub(crate) const YM2612_DAC_EN: u8 = 0x2B; #[allow(dead_code)]
pub(crate) const YM2612_DAC_EN_BIT: u8 = 127;
#[allow(dead_code)]
pub(crate) const YM2612_DT_MULTI: u8 = 0x30; #[allow(dead_code)]
pub(crate) const YM2612_DT: u8 = 112;
#[allow(dead_code)]
pub(crate) const YM2612_MULTI: u8 = 15;
pub(crate) const YM2612_TL: u8 = 0x40; pub(crate) const YM2612_TL_END: u8 = YM2612_TL + 0xF;
pub(crate) const YM2612_KS_AR: u8 = 0x50; #[allow(dead_code)]
pub(crate) const YM2612_KS: u8 = 192;
#[allow(dead_code)]
pub(crate) const YM2612_AR: u8 = 31;
pub(crate) const YM2612_DR: u8 = 0x60; pub(crate) const YM2612_SR: u8 = 0x70; pub(crate) const YM2612_SL_RR: u8 = 0x80; #[allow(dead_code)]
pub(crate) const YM2612_SL: u8 = 240;
#[allow(dead_code)]
pub(crate) const YM2612_RR: u8 = 15;
pub(crate) const YM2612_SSG_EG: u8 = 0x90; #[allow(dead_code)]
pub(crate) const YM2612_FNUM_LSB: u8 = 0xA0;
#[allow(dead_code)]
pub(crate) const YM2612_BLOCK_FNUM_MSB: u8 = 0xA4;
pub(crate) const YM2612_BLOCK: u8 = 56;
#[allow(dead_code)]
pub(crate) const YM2612_FNUM_MSB: u8 = 7;
pub(crate) const YM2612_CH3_SPECIAL_FNUM_MSB: u8 = 0xA8;
pub(crate) const YM2612_CH3_SPECIAL_FNUM_MSB_END: u8 = YM2612_CH3_SPECIAL_FNUM_MSB + 2;
pub(crate) const YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB: u8 = 0xAC;
pub(crate) const YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB_END: u8 = YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB + 2;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_SPECIAL_BLOCK: u8 = 56;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_SPECIAL_FNUM_LSB: u8 = 7;
pub(crate) const YM2612_FB_CONNECTION: u8 = 0xB0; pub(crate) const YM2612_FB_CONNECTION_END: u8 = YM2612_FB_CONNECTION + 2;
#[allow(dead_code)]
pub(crate) const YM2612_FB: u8 = 56;
pub(crate) const YM2612_CONNECTION: u8 = 7;
pub(crate) const YM2612_L_R: u8 = 0xB4; pub(crate) const YM2612_L_R_END: u8 = YM2612_L_R + 2;
pub(crate) const YM2612_L: u8 = 0x80;
pub(crate) const YM2612_R: u8 = 0x40;
pub(crate) const YM2612_BOTH_L_R: u8 = 192;
#[test]
fn test_hz() {
let res = hz_ym2612(20, None, 3579545.0 / 72.0);
assert!((res - 0.23706389798058403).abs() < 0.000001);
}
pub(crate) fn hz_ym2612(fnum: u32, opt_block: Option<u8>, fsam_2612: f64) -> f64 {
let block = opt_block.unwrap_or(0);
fnum as f64 * fsam_2612 * f64::powf(2.0, block as f64 - 22.0)
}
pub(crate) struct YM2612State {
last_dac_note: u8,
dac_en: bool,
slot: [u8; 6],
tl: [[u8; 4]; 6],
volume: [u8; 6],
block: [u8; 6],
connection: [u8; 6],
fmopn_adpcm_state: FMOPNADPCMState,
ym_adpcm_state: YMADPCMState,
pub opn_type: u8,
factored: FactoredState,
}
impl Default for YM2612State {
fn default() -> Self {
YM2612State {
last_dac_note: 0,
dac_en: false,
slot: [0; 6],
tl: [[0; 4]; 6],
volume: [0xFF; 6],
block: [0; 6],
connection: [0; 6],
fmopn_adpcm_state: Default::default(),
ym_adpcm_state: Default::default(),
opn_type: 0,
factored: Default::default(),
}
}
}
pub(crate) struct YM2612<'config> {
pub(crate) state: YM2612State,
config: &'config Config,
}
impl<'config> YM2612<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<YM2612State>) -> Self {
YM2612 {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn command_handle(
&mut self,
port: u8,
register: u8,
data: u8,
fsam_2612: f64,
midi: &mut MIDIShim,
) -> Result<()> {
match register {
0x0..=0x1F => {
self.opn_pcm_write(port, register, data, midi)?;
},
YM2612_TEST => (),
YM2612_LFO_EN_LFO_FREQ => (),
YM2612_TIMER_A_MSB => (),
YM2612_TIMER_A_LSB => (),
YM2612_TIMER_B => (),
YM2612_SLOT_CH => self.process_slot(data, midi)?,
YM2612_DAC => self.process_dac(data, midi),
YM2612_DAC_EN => {
self.state.dac_en = (register & YM2612_DAC_EN) == YM2612_DAC_EN;
},
YM2612_TL..=YM2612_TL_END => {
self.process_total_level(register, port, data, midi)?
},
YM2612_KS_AR => (),
YM2612_DR => (),
YM2612_SR => (),
YM2612_SL_RR => (),
YM2612_SSG_EG => (),
0xA4..=0xA6 => {
let channel = (register ^ 0xA4) + port;
if self.config.ym2612_ch_disabled[channel as usize] {
return Ok(());
}
self.state.block[channel as usize] = (data & YM2612_BLOCK) >> 3;
self.state.factored.fnum_msb[channel as usize] = data & 7;
},
0xA0..=0xA2 => {
self.fun_name(register, port, data, fsam_2612, midi)?;
},
YM2612_CH3_SPECIAL_FNUM_MSB..=YM2612_CH3_SPECIAL_FNUM_MSB_END => (),
YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB
..=YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB_END => (),
YM2612_FB_CONNECTION..=YM2612_FB_CONNECTION_END => {
let channel = (register ^ YM2612_FB_CONNECTION) + port;
self.state.connection[channel as usize] = data & YM2612_CONNECTION;
if self.config.ym2612_ch_disabled[channel as usize] {
return Ok(());
}
if self.config.ym2612_prog_disabled[channel as usize] {
return Ok(());
}
midi.program_change_write(channel.into(), (data & 0x7F).into());
},
YM2612_L_R..=YM2612_L_R_END => {
let channel = (register ^ YM2612_L_R) + port;
if self.config.ym2612_ch_disabled[channel as usize] {
return Ok(());
}
if self.config.ym2612_pan_disabled[channel as usize] {
return Ok(());
}
if channel == 0x5 && self.state.dac_en {
self.state.factored.midi_pan[channel as usize] = 0xFF.into()
}
let temp_pan = get_pan_from_data(data)?;
if self.state.factored.midi_pan[channel as usize] != temp_pan {
self.state.factored.midi_pan[channel as usize] = temp_pan;
midi.controller_write(
channel.into(),
MIDI_PAN,
self.state.factored.midi_pan[channel as usize],
);
}
let temp_double = (data & 0x07) as f64 / 0x07 as f64;
let temp = (temp_double * temp_double * 0x7F as f64).round() as u8;
if self.state.factored.midi_mod[channel as usize] != temp {
self.state.factored.midi_mod[channel as usize] =
temp.into();
midi.controller_write(
channel.into(),
MIDI_MODULATOR_WHEEL,
self.state.factored.midi_mod[channel as usize],
);
}
if self.config.ym2612_dac_disabled {
return Ok(());
}
},
_ => {
}, }
Ok(())
}
fn fun_name(
&mut self,
register: u8,
port: u8,
data: u8,
fsam_2612: f64,
midi: &mut MIDIShim<'_, '_>,
) -> Result<(), anyhow::Error> {
let channel = (register ^ 0xA0) + port;
if self.config.ym2612_ch_disabled[channel as usize] {
return Ok(());
}
self.state.factored.fnum_lsb[channel as usize] = data;
self.state.factored.fnum_1[channel as usize] =
self.state.factored.fnum_2[channel as usize];
self.state.factored.fnum_2[channel as usize] =
((self.state.factored.fnum_msb[channel as usize] as u32) << 8)
+ self.state.factored.fnum_lsb[channel as usize] as u32;
self.state.factored.hz_1[channel as usize] = shift(
&mut self.state.factored.hz_2[channel as usize],
hz_ym2612(
self.state.factored.fnum_2[channel as usize],
Some(self.state.block[channel as usize]),
fsam_2612,
),
);
self.state.factored.note_1[channel as usize] = shift(
&mut self.state.factored.note_2[channel as usize],
hz_to_note(self.state.factored.hz_2[channel as usize]),
);
if self.state.factored.note_on_2[channel as usize]
&& self.state.factored.note_1[channel as usize]
!= self.state.factored.note_2[channel as usize]
{
midi.do_note_on(
self.state.factored.note_1[channel as usize],
self.state.factored.note_2[channel as usize],
channel.into(),
&mut self.state.factored.midi_note[channel as usize],
&mut self.state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
Ok(())
}
fn process_slot(&mut self, data: u8, midi: &mut MIDIShim) -> Result<()> {
let mut channel = data & YM2612_CH;
channel -= if channel > 3 { 1 } else { 0 };
channel = channel.clamp(0, 5);
if self.config.ym2612_ch_disabled[channel as usize] {
return Ok(());
}
self.state.slot[channel as usize] = (data & YM2612_SLOT) / 8;
if self.state.slot[channel as usize] == 0
&& self.state.factored.note_on_2[channel as usize]
{
self.state.factored.midi_wheel[channel as usize] = MIDI_PITCHWHEEL_CENTER;
midi.note_off_write(
channel.into(),
self.state.factored.midi_note[channel as usize],
0x00.into(),
);
self.state.factored.midi_note[channel as usize] = 0xFF.into();
self.state.factored.note_on_1[channel as usize] =
shift(&mut self.state.factored.note_on_2[channel as usize], false);
} else if self.state.slot[channel as usize] != 0
&& !self.state.factored.note_on_2[channel as usize]
{
midi.do_note_on(
self.state.factored.note_1[channel as usize],
self.state.factored.note_2[channel as usize],
channel.into(),
&mut self.state.factored.midi_note[channel as usize],
&mut self.state.factored.midi_wheel[channel as usize],
Some(255),
None,
)?;
self.state.factored.note_on_1[channel as usize] =
self.state.factored.note_on_2[channel as usize];
self.state.factored.note_on_2[channel as usize] = true;
}
Ok(())
}
fn opn_pcm_write(
&mut self,
port: u8,
register: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
match (port, self.state.opn_type) {
(0x00, OPN_TYPE_YM2608) if (register & 0x10) != 0 => fmopn_adpcm_write(
register & 0xF,
data,
&mut self.state.fmopn_adpcm_state,
midi,
)?,
(0x00, OPN_TYPE_YM2610) if (register & 0x10) == 0 => ym_adpcm_write(
register & 0xF,
data,
&mut self.state.ym_adpcm_state,
midi,
)?,
(0x01, OPN_TYPE_YM2608) if (register & 0x10) == 0 => ym_adpcm_write(
register & 0xF,
data,
&mut self.state.ym_adpcm_state,
midi,
)?,
(0x01, OPN_TYPE_YM2610) if register > 0x30 => fmopn_adpcm_write(
register & 0xF,
data,
&mut self.state.fmopn_adpcm_state,
midi,
)?,
_ => strict!(),
}
Ok(())
}
fn process_total_level(
&mut self,
register: u8,
port: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let mut channel = ((register ^ YM2612_TL) % 4) + port;
if self.config.ym2612_ch_disabled[channel as usize] {
return Ok(());
}
if self.config.ym2612_vol_disabled[channel as usize] {
return Ok(());
}
let operation = (register ^ YM2612_TL) / 4;
channel = if channel > 5 { 5 } else { channel };
self.state.tl[channel as usize][operation as usize] = data & 0x7F;
if operation < 3 {
return Ok(());
}
let temp_byte = self.state.factored.midi_volume[channel as usize];
self.state.volume[channel as usize] = self.volume_from_connection(channel)?;
self.state.factored.midi_volume[channel as usize] =
db_to_midi_vol(ym3812_vol_to_db(self.state.volume[channel as usize]) / 4.0);
if temp_byte != self.state.factored.midi_volume[channel as usize] {
midi.controller_write(
channel.into(),
MIDI_VOLUME,
self.state.factored.midi_volume[channel as usize],
);
}
Ok(())
}
fn volume_from_connection(&mut self, channel: u8) -> Result<u8> {
Ok((match self.state.connection[channel as usize] {
0..=3 => self.state.tl[channel as usize][3] as u16,
4 => {
(self.state.tl[channel as usize][1] as u16
+ self.state.tl[channel as usize][3] as u16)
/ 2
},
5 | 6 => {
(self.state.tl[channel as usize][1] as u16
+ self.state.tl[channel as usize][2] as u16
+ self.state.tl[channel as usize][3] as u16)
/ 3
},
7 => {
(self.state.tl[channel as usize][0] as u16
+ self.state.tl[channel as usize][1] as u16
+ self.state.tl[channel as usize][2] as u16
+ self.state.tl[channel as usize][3] as u16)
/ 4
},
_ => bail!("Invalid connection state"),
}) as u8)
}
fn process_dac(&mut self, data: u8, midi: &mut MIDIShim) {
if self.config.ym2612_dac_disabled {
return;
}
if self.state.opn_type != OPN_TYPE_YM2612 {
return;
}
let should_write = data < 0x20;
if data != 0xFF && should_write {
midi.note_off_write(CHN_DAC, self.state.last_dac_note.into(), 0x00.into());
midi.note_on_write(CHN_DAC, data.into(), 0x7F.into());
self.state.last_dac_note = data
}
}
}
fn get_pan_from_data(data: u8) -> Result<midly::num::u7> {
Ok(match data & YM2612_BOTH_L_R {
YM2612_L => MIDI_PAN_LEFT,
YM2612_R => MIDI_PAN_RIGHT,
YM2612_BOTH_L_R | 0 => MIDI_PAN_CENTER,
_ => {
strict!();
MIDI_PAN_CENTER
},
})
}
struct FMOPNADPCMState {
tl: u8,
il: [u8; 5],
ins_vol: [u8; 5],
note_on: [u8; 5],
vol_max: u8,
adpcm_drum_notes: [u8; 6],
}
impl Default for FMOPNADPCMState {
fn default() -> Self {
FMOPNADPCMState {
tl: 0,
il: [0; 5],
ins_vol: [0; 5],
note_on: [0; 5],
vol_max: 0,
adpcm_drum_notes: [
0x23, 0x26, 0x33, 0x2A, 0x2D, 0x25, ],
}
}
}
fn fmopn_adpcm_write(
register: u8,
data: u8,
state: &mut FMOPNADPCMState,
midi: &mut MIDIShim,
) -> Result<()> {
let channel: u8;
let mut volume: u8;
let vol_mul: u8;
let vol_shift: u8;
match register {
0x0 => {
fmopn_dm(data, state, midi);
},
0x1 => {
fnopn_tl(state, data);
},
_ => {
channel = register & 0x7;
if channel >= 0x6 {
return Ok(());
}
match register & 0x38 {
0x8 => {
state.il[channel as usize] = (!data) & 0x3F;
volume = state.tl + state.il[channel as usize];
if volume < state.vol_max {
state.vol_max = volume
}
if volume >= 0x3F {
vol_mul = 0x00;
vol_shift = 0x00;
} else {
vol_mul = 0x0F - (volume & 0x7);
vol_shift = 0x01 + ((volume & 0x38) / 0x08);
}
let _temp_byte = get_pan_from_data(register);
let mut temp_volume =
((vol_mul as u32) << 8) >> vol_shift as u32; temp_volume = if temp_volume == 0x80 {
0x7F
} else {
temp_volume
};
volume = if temp_volume >= 0x100 {
temp_volume as u8 | 0x80
} else {
temp_volume as u8
};
volume = if volume == 0x0 { 0x1 } else { volume };
state.ins_vol[channel as usize] = volume;
},
0x10 | 0x18 => {
if (register & 0x38) == 0x18 {
}
},
0x20 | 0x28 => (), _ => strict!(),
}
},
}
Ok(())
}
fn fnopn_tl(state: &mut FMOPNADPCMState, data: u8) {
state.tl = (!data) & 0x3F;
if state.vol_max == 0 {
state.vol_max = 0xFF
}
}
fn fmopn_dm(data: u8, state: &mut FMOPNADPCMState, midi: &mut MIDIShim) {
if (data & 0x80) == 0x0 {
for channel in 0x0..=0x5 {
if data & (1 << channel) != 0 {
if channel != 0 {
midi.note_off_write(
0x09.into(),
state.adpcm_drum_notes[channel as usize].into(),
0x00.into(),
);
}
midi.note_on_write(
0x09.into(),
state.adpcm_drum_notes[channel as usize].into(),
state.ins_vol[channel as usize].into(),
);
state.note_on[channel as usize] = 1
} else {
if state.note_on[channel as usize] != 0 {
midi.note_off_write(
0x09.into(),
state.adpcm_drum_notes[channel as usize].into(),
0x00.into(),
);
}
state.note_on[channel as usize] = 0
}
}
} else {
for channel in 0x0..=0x5 {
if state.note_on[channel as usize] != 0 {
midi.note_off_write(
0x09.into(),
state.adpcm_drum_notes[channel as usize].into(),
0x00.into(),
);
}
state.note_on[channel as usize] = 0
}
}
}
#[derive(Default)]
struct YMADPCMState {
ins_vol: u8,
}
fn ym_adpcm_write(
register: u8,
data: u8,
state: &mut YMADPCMState,
midi: &mut MIDIShim,
) -> Result<()> {
match register & 0x0F {
0x00 => {
if data & 0x80 != 0 {
midi.note_off_write(0x09.into(), 0x7F.into(), 0x00.into());
midi.note_on_write(0x09.into(), 0x7F.into(), state.ins_vol.into());
} else {
midi.note_off_write(0x09.into(), 0x7F.into(), 0x00.into());
}
},
0x01 => {
match (data & 0xC0) / 0x40 {
0x1 => (), 0x2 => (), 0x3 | 0x0 => (), _ => strict!(),
}
},
0x02 => (), 0x03 => (), 0x04 => (), 0x05 => (), 0x06 => (), 0x07 => (), 0x08 => (), 0x09 => (), 0x0A => (), 0x0B => state.ins_vol = data / 0x2, 0x0C => (), _ => (),
}
Ok(())
}