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_VOLUME,
};
use crate::strict;
use crate::utils::shift;
use crate::utils::FactoredState;
use crate::ym3812::ym3812_vol_to_db;
use anyhow::{bail, Result};
use midly::num::u7;
#[allow(dead_code)]
const YM2151_TEST: u8 = 0x01;
const YM2151_KEY_ON: u8 = 0x08;
const YM2151_NOISE: u8 = 0x0F;
const YM2151_CLK_A_MSB: u8 = 0x10;
const YM2151_CLK_A_LSB: u8 = 0x11;
const YM2151_CLK_B: u8 = 0x12;
const YM2151_CSM_IRQ_RESET_EN_TIMER: u8 = 0x14;
const YM2151_LFO_FREQ: u8 = 0x18;
const YM2151_PMD_AMD: u8 = 0x19;
const YM2151_CT1_CT2_WAVEFORM: u8 = 0x1B;
const YM2151_PAN_FB_CONNECTION: u8 = 0x20;
const YM2151_PAN_FB_CONNECTION_END: u8 = YM2151_PAN_FB_CONNECTION + 0x07;
const YM2151_BLK_FNUM: u8 = 0x28;
const YM2151_BLK_FNUM_END: u8 = YM2151_BLK_FNUM + 0x07;
const YM2151_KF: u8 = 0x30;
const YM2151_KF_END: u8 = YM2151_KF + 0x07;
const YM2151_PMS_AMS: u8 = 0x38;
const YM2151_PMS_AMS_END: u8 = YM2151_PMS_AMS + 0x07;
const YM2151_DT1_MUL: u8 = 0x40;
const YM2151_DT1_MUL_END: u8 = YM2151_DT1_MUL + 0x1F;
const YM2151_TL: u8 = 0x60;
const YM2151_TL_END: u8 = YM2151_TL + 0x1F;
#[allow(dead_code)]
const YM2151_KS_AR: u8 = 0x80;
#[allow(dead_code)]
const YM2151_LFO_AM_EN_D1R: u8 = 0xA0;
#[allow(dead_code)]
const YM2151_DT2_D2R: u8 = 0xC0;
#[allow(dead_code)]
const YM2151_D1L_RR: u8 = 0xE0;
fn ym2151_fnum_to_midi(mut fnum: u8, mut block: u8) -> Result<u7> {
let mut note_val: u8;
if fnum > 0x0F {
strict!("Invalid fnum {}", fnum);
}
block = block.clamp(0, 0x07);
fnum &= 0x0F;
if (fnum & 0x03) != 0x03 {
note_val = (fnum & 0xC) >> 2;
note_val = note_val * 3 + (fnum & 0x3);
note_val += 61;
} else {
note_val = 0xFF
}
if note_val == 0xFF {
Ok(0xFF.into())
} else {
Ok((note_val + (block - 0x4) * 12).into())
}
}
#[derive(Default)]
pub(crate) struct YM2151State {
slot: [u8; 8],
tl: [[u8; 4]; 8],
block: [u8; 8],
feedback: [u8; 8],
connection: [u8; 8],
factored: FactoredState,
}
pub(crate) struct YM2151<'config> {
state: YM2151State,
config: &'config Config,
}
impl<'config> YM2151<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<YM2151State>) -> Self {
YM2151 {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn command_handle(
&mut self,
register: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
match register {
YM2151_KEY_ON => {
process_key_on(data, self.config, &mut self.state, midi)?;
},
YM2151_NOISE => (), YM2151_CLK_A_MSB => (),
YM2151_CLK_A_LSB => (),
YM2151_CLK_B => (),
YM2151_CSM_IRQ_RESET_EN_TIMER => (),
YM2151_LFO_FREQ => (),
YM2151_PMD_AMD => (),
YM2151_CT1_CT2_WAVEFORM => (),
YM2151_PAN_FB_CONNECTION..=YM2151_PAN_FB_CONNECTION_END => {
process_pan_feedback(
register,
self.config,
data,
&mut self.state,
midi,
)?;
},
YM2151_BLK_FNUM..=YM2151_BLK_FNUM_END => {
process_fnum(register, self.config, &mut self.state, data, midi)?;
},
YM2151_KF..=YM2151_KF_END => {
process_kf(register, self.config, &mut self.state, data, midi)?;
},
YM2151_PMS_AMS..=YM2151_PMS_AMS_END => {
process_modulation(register, data, midi);
},
YM2151_DT1_MUL..=YM2151_DT1_MUL_END => {
},
YM2151_TL..=YM2151_TL_END => {
process_total_level(
register,
&mut self.state,
data,
self.config,
midi,
);
},
_ => strict!(),
}
Ok(())
}
}
fn process_total_level(
register: u8,
state: &mut YM2151State,
data: u8,
config: &Config,
midi: &mut MIDIShim,
) {
let channel = register & 0x7;
let op = (register & 0x1F) / 0x8;
state.tl[channel as usize][op as usize] = data & 0x7F;
if op < 3 {
return;
}
if config.ym2151_vol_disabled[channel as usize] {
return;
}
let temp_vol = db_to_midi_vol(ym3812_vol_to_db(state.tl[channel as usize][3]) / 4.0);
if temp_vol != state.factored.midi_volume[channel as usize] {
state.factored.midi_volume[channel as usize] = temp_vol;
midi.controller_write(
channel.into(),
MIDI_VOLUME,
state.factored.midi_volume[channel as usize],
);
}
}
fn process_modulation(register: u8, data: u8, midi: &mut MIDIShim) {
let channel = register & 0x7;
let temp_double = ((data & 0x70) as f32) / (0x70 as f32);
let temp = (temp_double * temp_double * (0x7F as f32)) as u8;
midi.controller_write(channel.into(), MIDI_MODULATOR_WHEEL, temp.into());
}
fn process_kf(
register: u8,
config: &Config,
state: &mut YM2151State,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = register & 0x7;
if config.ym2151_ch_disabled[channel as usize] {
return Ok(());
}
state.factored.fnum_lsb[channel as usize] = (data >> 2) & 0x3F;
state.factored.note_1[channel as usize] = state.factored.note_2[channel as usize];
state.factored.note_2[channel as usize] =
ym2151_fnum_to_midi(
state.factored.fnum_2[channel as usize] as u8,
state.block[channel as usize],
)?
.as_int() as f64 + (state.factored.fnum_lsb[channel as usize] as f64 / 0x40 as f64);
if state.factored.note_on_2[channel as usize] {
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
channel.into(),
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
Ok(())
}
fn process_fnum(
register: u8,
config: &Config,
state: &mut YM2151State,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = register & 0x7;
if config.ym2151_ch_disabled[channel as usize] {
return Ok(());
}
state.block[channel as usize] = (data / 0x10) & 0x7;
state.factored.fnum_msb[channel as usize] = data & 0xF;
state.factored.fnum_1[channel as usize] = shift(
&mut state.factored.fnum_2[channel as usize],
state.factored.fnum_msb[channel as usize] as u32,
);
state.factored.note_1[channel as usize] = state.factored.note_2[channel as usize];
state.factored.note_2[channel as usize] =
((ym2151_fnum_to_midi(
state.factored.fnum_2[channel as usize] as u8,
state.block[channel as usize],
)? + state.factored.fnum_lsb[channel as usize].into())
.as_int() as f64) / (0x40 as f64);
if state.factored.note_on_2[channel as usize] {
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
channel.into(),
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
Ok(())
}
fn process_pan_feedback(
register: u8,
config: &Config,
data: u8,
state: &mut YM2151State,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = register & 0x7;
if config.ym2151_ch_disabled[channel as usize] {
return Ok(());
}
if !config.ym2151_pan_disabled[channel as usize] {
state.factored.midi_pan[channel as usize] = match (data / 0x40) & 0x3 {
0x1 => MIDI_PAN_LEFT,
0x2 => MIDI_PAN_RIGHT,
0x3 | 0x0 => MIDI_PAN_CENTER,
_ => bail!("Invalid pan data"),
};
midi.controller_write(
channel.into(),
MIDI_PAN,
state.factored.midi_pan[channel as usize],
);
}
state.feedback[channel as usize] = (data / 0x8) & 0x7;
state.connection[channel as usize] = data & 0x7;
if !config.ym2151_prog_disabled[channel as usize] {
state.factored.midi_instrument[channel as usize] = (data & 0x3F).into();
midi.program_change_write(
channel.into(),
state.factored.midi_instrument[channel as usize],
);
}
Ok(())
}
fn process_key_on(
data: u8,
config: &Config,
state: &mut YM2151State,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = data & 0x7;
if config.ym2151_ch_disabled[channel as usize] {
return Ok(());
}
state.slot[channel as usize] = (data / 0x8) & 0xF;
state.factored.note_on_1[channel as usize] = state.factored.note_on_2[channel as usize];
state.factored.note_on_2[channel as usize] = state.slot[channel as usize] > 0;
if
true {
if (state.factored.note_on_2[channel as usize]
!= state.factored.note_on_1[channel as usize])
&& (state.factored.note_2[channel as usize] != 0.0)
{
if !state.factored.note_on_2[channel as usize] {
midi.do_note_on(
state.factored.note_1[channel as usize],
0xFF as f64,
channel.into(),
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
Some(255),
None,
)?;
} else {
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
channel.into(),
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
Some(255),
None,
)?;
}
}
}
Ok(())
}