use crate::config::Config;
use crate::midi_shim::{
db_to_midi_vol, MIDIShim, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT, MIDI_PAN_RIGHT,
MIDI_VOLUME,
};
use crate::strict;
use crate::utils::{hz_to_note, FactoredState};
use crate::vgm2mid::{CHN_DAC, OPL_TYPE_YMF262};
use anyhow::Result;
use midly::num::{u4, u7};
const YM3812_REG_TEST_WAVESEL_EN: u16 = 0x1;
const YM3812_REG_CSW_NOTESEL: u16 = 0x8;
const YM3812_REG_MOD_VIB_EG_KS_MULT: u16 = 0x20;
const YM3812_REG_MOD_VIB_EG_KS_MULT_END: u16 = YM3812_REG_MOD_VIB_EG_KS_MULT + 0x15;
const YM3812_REG_KSL_TL: u16 = 0x40;
const YM3812_REG_KSL_TL_END: u16 = YM3812_REG_KSL_TL + 0x17;
#[allow(dead_code)]
const YM3812_REG_AR_DR: u16 = 0x60;
#[allow(dead_code)]
const YM3812_REG_SL_RR: u16 = 0x80;
const YM3812_REG_FNUM_LSB: u16 = 0xA0;
const YM3812_REG_FNUM_LSB_END: u16 = YM3812_REG_FNUM_LSB + 0x8;
const YM3812_REG_KEY_BLOCK_FNUM_MSB: u16 = 0xB0;
const YM3812_REG_KEY_BLOCK_FNUM_MSB_END: u16 = YM3812_REG_KEY_BLOCK_FNUM_MSB + 0x8;
const YM3812_REG_RHYTHM: u16 = 0xBD;
const YM3812_FB_CONNECTION_PAN: u16 = 0xC0;
const YM3812_FB_CONNECTION_PAN_END: u16 = YM3812_FB_CONNECTION_PAN + 0x8;
#[allow(dead_code)]
const YM3812_WAVESEL: u16 = 0xE0;
const YMF262_4OP_EN: u16 = 0x104;
const YMF262_OPL3_EN: u16 = 0x105;
const YM3812_REG_KEY: u8 = 0x20;
const YM3812_REG_BLOCK: u8 = 0x1C;
const YM3812_REG_FNUM_MSB: u8 = 0x3;
#[allow(dead_code)]
const YM3812_REG_RHYTHM_MODE: u16 = 0x20;
const YM3812_REG_BD: u8 = 0x10;
const YM3812_REG_SD: u8 = 0x8;
const YM3812_REG_TOM: u8 = 0x4;
const YM3812_REG_TCT: u8 = 0x2;
const YM3812_REG_HH: u8 = 0x1;
pub(crate) fn hz_ym3812(fnum: f64, opt_block: Option<u8>, fsam_3812: f64) -> f64 {
let block = opt_block.unwrap_or(0);
if block == 0 && fnum == 0.0 {
0.0
} else {
fnum * fsam_3812 * f64::powi(2.0, block as i32 - 20)
}
}
pub(crate) struct YM3812State {
key_on: [bool; 8],
block: [u8; 8],
volume: [u8; 8],
bd_vol: u7,
hh_vol: u7,
sd_vol: u7,
tom_vol: u7,
tct_vol: u7,
percussion_on: [bool; 5],
opl3_mode: u8,
pub opl_type: u8,
factored: FactoredState,
}
pub(crate) struct YM3812<'config> {
pub state: YM3812State,
config: &'config Config, }
impl<'config> YM3812<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<YM3812State>) -> Self {
YM3812 {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn command_handle(
&mut self,
register: u16,
data: u8,
fsam_3812: f64,
midi: &mut MIDIShim,
) -> Result<()> {
match register {
YM3812_REG_TEST_WAVESEL_EN => (),
YMF262_4OP_EN => (),
YMF262_OPL3_EN => self.state.opl3_mode = data & 0x1,
YM3812_REG_CSW_NOTESEL => (),
YM3812_REG_MOD_VIB_EG_KS_MULT..=YM3812_REG_MOD_VIB_EG_KS_MULT_END => {
process_mod_vib_eg_ks_mult(register, data, self.config, midi)
},
YM3812_REG_KSL_TL..=YM3812_REG_KSL_TL_END => {
ksl_tl(self, register, data, midi)
},
YM3812_REG_FNUM_LSB..=YM3812_REG_FNUM_LSB_END
| YM3812_REG_KEY_BLOCK_FNUM_MSB..=YM3812_REG_KEY_BLOCK_FNUM_MSB_END => {
let channel = (register & 0xF) as u8;
if channel > 0x8 {
return Ok(());
}
if self.config.ym2413_ch_disabled[channel as usize] {
return Ok(());
}
if (register & 0xF0) == YM3812_REG_FNUM_LSB {
self.state.factored.fnum_lsb[channel as usize] = data;
} else if (register & 0xF0) == YM3812_REG_KEY_BLOCK_FNUM_MSB {
self.state.key_on[channel as usize] =
(data & YM3812_REG_KEY) / YM3812_REG_KEY != 0;
self.state.block[channel as usize] =
(data & YM3812_REG_BLOCK) / 0x4;
self.state.factored.fnum_msb[channel as usize] =
data & YM3812_REG_FNUM_MSB;
}
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
* 256) + self.state.factored.fnum_lsb[channel as usize]
as u32;
self.state.factored.hz_1[channel as usize] =
self.state.factored.hz_2[channel as usize];
self.state.factored.hz_2[channel as usize] = hz_ym3812(
self.state.factored.fnum_2[channel as usize] as f64,
Some(self.state.block[channel as usize]),
fsam_3812,
);
self.state.factored.note_1[channel as usize] =
self.state.factored.note_2[channel as usize];
self.state.factored.note_2[channel as usize] =
hz_to_note(self.state.factored.hz_2[channel as usize]);
self.state.factored.note_on_1[channel as usize] =
self.state.factored.note_on_2[channel as usize];
if (register & 0xF0) == YM3812_REG_KEY_BLOCK_FNUM_MSB {
self.state.factored.note_on_2[channel as usize] =
self.state.key_on[channel as usize]
}
if !self.state.key_on[channel as usize] {
if self.state.factored.note_on_1[channel as usize]
&& self.state.factored.note_1[channel as usize]
!= 0.0
{
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();
}
} else if self.state.key_on[channel as usize] {
if self.state.factored.note_on_2[channel as usize]
!= self.state.factored.note_on_1[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,
)?;
} else {
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,
)?;
}
}
},
YM3812_REG_RHYTHM => {
process_reg_rhythm(data, &mut self.state, self.config, midi)
},
YM3812_FB_CONNECTION_PAN..=YM3812_FB_CONNECTION_PAN_END => {
process_fb_connection_pan(
register,
data,
&mut self.state,
self.config,
midi,
);
},
_ => {
strict!("Error when processing ym3812: {}, {}", register, data);
},
}
Ok(())
}
}
impl Default for YM3812State {
fn default() -> Self {
YM3812State {
key_on: [false; 8],
block: [0; 8],
volume: [0; 8],
bd_vol: 0.into(),
hh_vol: 0.into(),
sd_vol: 0.into(),
tom_vol: 0.into(),
tct_vol: 0.into(),
percussion_on: [false; 5],
opl3_mode: 0,
opl_type: 0,
factored: Default::default(),
}
}
}
fn ksl_tl(me: &mut YM3812, register: u16, data: u8, midi: &mut MIDIShim) {
let operation = (register & 0x7) / 0x3;
let channel = (((register & 0x18) / 0x8 * 0x3) + ((register & 0x7) % 0x3)) as u8;
if me.config.ym2413_ch_disabled[channel as usize] {
return;
}
match (channel, operation) {
(0x6, 0x1) =>
{
me.state.bd_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x7, 0x0) =>
{
me.state.hh_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x7, 0x1) =>
{
me.state.sd_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x8, 0x0) =>
{
me.state.tom_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x8, 0x1) =>
{
me.state.tct_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(_, _) => (),
}
if me.config.ym2413_vol_disabled[channel as usize] || operation == 0x0 {
return;
}
let temp_byte = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F));
if me.state.volume[channel as usize] != temp_byte {
me.state.volume[channel as usize] = temp_byte.into();
midi.controller_write(
channel.into(),
MIDI_VOLUME,
me.state.volume[channel as usize].into(),
);
}
}
fn process_fb_connection_pan(
register: u16,
data: u8,
state: &mut YM3812State,
config: &Config,
midi: &mut MIDIShim,
) {
let channel = register as u8 & 0xF;
if config.ym2413_ch_disabled[channel as usize] {
return;
}
if config.ym2413_vol_disabled[channel as usize] {
return;
}
if state.opl_type != OPL_TYPE_YMF262 {
return;
}
let temp_pan = match (state.opl3_mode, (data & 0x30) / 0x10) {
(_, 0x0) => MIDI_PAN_CENTER, (_, 0x1) => MIDI_PAN_LEFT,
(_, 0x2) => MIDI_PAN_CENTER,
(_, 0x3) => MIDI_PAN_RIGHT,
(0x0, _) => MIDI_PAN_CENTER,
(_, _) => MIDI_PAN_CENTER, };
state.factored.midi_pan[channel as usize] = temp_pan;
midi.controller_write(
channel.into(),
MIDI_PAN,
state.factored.midi_pan[channel as usize],
);
}
fn process_reg_rhythm(data: u8, state: &mut YM3812State, config: &Config, midi: &mut MIDIShim) {
if config.ym2413_percussion_disabled {
return;
}
let channel = CHN_DAC;
toggle_drum(
data,
YM3812_REG_BD,
&mut state.percussion_on[0],
midi,
channel,
0x23.into(), state.bd_vol,
);
toggle_drum(
data,
YM3812_REG_SD,
&mut state.percussion_on[1],
midi,
channel,
0x26.into(),
state.sd_vol,
);
toggle_drum(
data,
YM3812_REG_TOM,
&mut state.percussion_on[2],
midi,
channel,
0x2D.into(),
state.tom_vol,
);
toggle_drum(
data,
YM3812_REG_TCT,
&mut state.percussion_on[3],
midi,
channel,
0x33.into(),
state.tct_vol,
);
toggle_drum(
data,
YM3812_REG_HH,
&mut state.percussion_on[4],
midi,
channel,
0x2A.into(),
state.hh_vol,
);
}
pub(crate) fn toggle_drum(
data: u8,
reg: u8,
percussion_on: &mut bool,
midi: &mut MIDIShim,
channel: u4,
key: u7,
drum_vol: u7,
) {
if (data & reg) == 0 {
if *percussion_on {
midi.note_off_write(channel, key, 0x00.into());
*percussion_on = false;
}
} else if !*percussion_on {
midi.note_on_write(channel, key, drum_vol);
*percussion_on = true;
}
}
fn process_mod_vib_eg_ks_mult(register: u16, data: u8, config: &Config, midi: &mut MIDIShim) {
if (register & 0x7) > 0x5 {
return;
}
let operation = (register & 0x7) / 0x3;
let channel = ((register & 0x18) / 0x8 * 0x3 + (register & 0x7) % 0x3) as u8;
let temp_byte = (data & 0xFE) / 0x2;
if config.ym2413_ch_disabled[channel as usize]
|| config.ym2413_vol_disabled[channel as usize]
|| operation == 0x00
{
return;
}
midi.program_change_write(channel.into(), temp_byte.into());
}
pub(crate) fn ym3812_vol_to_db(tl: u8) -> f64 {
-f64::from(tl) * 4.0 / 3.0
}