use crate::config::Config;
use crate::midi_shim::{db_to_midi_vol, MIDIShim, MIDI_VOLUME};
use crate::strict;
use crate::utils::hz_to_note;
use crate::utils::FactoredState;
use crate::vgm2mid::CHN_DAC;
use crate::ym3812::toggle_drum;
use anyhow::Result;
use midly::num::u7;
#[allow(dead_code)]
pub(crate) const YM2413_REG_USER: u8 = 0x00;
pub(crate) const YM2413_REG_RHYTHM: u8 = 0x0E;
pub(crate) const YM2413_REG_RHYTHM_MODE: u8 = 0x20;
pub(crate) const YM2413_REG_BD: u8 = 0x10;
pub(crate) const YM2413_REG_SD: u8 = 0x08;
pub(crate) const YM2413_REG_TOM: u8 = 0x04;
pub(crate) const YM2413_REG_TCT: u8 = 0x02;
pub(crate) const YM2413_REG_HH: u8 = 0x01;
#[allow(dead_code)]
pub(crate) const YM2413_REG_TEST: u8 = 0x0F;
pub(crate) const YM2413_REG_FNUM_LSB: u8 = 0x10;
pub(crate) const YM2413_REG_FNUM_LSB_END: u8 = YM2413_REG_FNUM_LSB + 8;
pub(crate) const YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB: u8 = 0x20;
pub(crate) const YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB_END: u8 = YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB + 8;
pub(crate) const YM2413_REG_SUS: u8 = 0x20;
pub(crate) const YM2413_REG_KEY: u8 = 0x10;
pub(crate) const YM2413_REG_BLOCK: u8 = 0x0E;
pub(crate) const YM2413_REG_FNUM_MSB: u8 = 0x01;
pub(crate) const YM2413_REG_INST_VOL: u8 = 0x30;
pub(crate) const YM2413_REG_INST_VOL_5: u8 = YM2413_REG_INST_VOL + 5;
pub(crate) const YM2413_REG_INST_VOL_6: u8 = YM2413_REG_INST_VOL + 6;
pub(crate) const YM2413_REG_INST_VOL_8: u8 = YM2413_REG_INST_VOL + 8;
pub(crate) const YM2413_REG_INST: u8 = 0xF0;
pub(crate) const YM2413_REG_VOL: u8 = 0x0F;
pub(crate) const INST_ORIGINAL: u8 = 0x00;
pub(crate) const INST_VIOLIN: u8 = 0x01;
pub(crate) const INST_GUITAR: u8 = 0x02;
pub(crate) const INST_PIANO: u8 = 0x03;
pub(crate) const INST_FLUTE: u8 = 0x04;
pub(crate) const INST_CLARINET: u8 = 0x05;
pub(crate) const INST_OBOE: u8 = 0x06;
pub(crate) const INST_TRUMPET: u8 = 0x07;
pub(crate) const INST_ORGAN: u8 = 0x08;
pub(crate) const INST_HORN: u8 = 0x09;
pub(crate) const INST_SYNTHESIZER: u8 = 0x0A;
pub(crate) const INST_HARPSICHORD: u8 = 0x0B;
pub(crate) const INST_VIBRAPHONE: u8 = 0x0C;
pub(crate) const INST_BASS_SYNTH: u8 = 0x0D;
pub(crate) const INST_BASS_ACOUSTIC: u8 = 0x0E;
pub(crate) const INST_GUITAR_ELECTRIC: u8 = 0x0F;
pub(crate) fn hz_ym2413(fnum: f64, opt_block: Option<u8>, clock: u32) -> f64 {
let block = opt_block.unwrap_or(0);
if block == 0 && fnum == 0.0 {
0.0
} else {
fnum * (f64::from(clock) / 72.0) * 2.0_f64.powi(block as i32 - 19)
}
}
pub(crate) struct YM2413State {
sus_on: [bool; 8],
key_on: [bool; 8],
block: [u8; 8],
ins_vol: [u8; 8],
instrument: [u8; 8],
volume: [u8; 8],
bd_vol: u7,
hh_vol: u7,
sd_vol: u7,
tom_vol: u7,
tct_vol: u7,
percussion_on: [bool; 5],
factored: FactoredState,
}
impl Default for YM2413State {
fn default() -> Self {
YM2413State {
sus_on: [false; 8],
key_on: [false; 8],
block: [0; 8],
ins_vol: [0; 8],
instrument: [0xFF; 8],
volume: [0xFF; 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],
factored: Default::default(),
}
}
}
pub(crate) struct YM2413<'config> {
state: YM2413State,
config: &'config Config,
}
impl<'config> YM2413<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<YM2413State>) -> Self {
YM2413 {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn command_handle(
&mut self,
register: u8,
data: u8,
clock: u32,
midi: &mut MIDIShim,
) -> Result<()> {
match register {
YM2413_REG_RHYTHM => {
self.rhythm(data, midi);
},
YM2413_REG_FNUM_LSB..=YM2413_REG_FNUM_LSB_END
| YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB
..=YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB_END => {
self.fnum(register, data, clock, midi)?;
},
YM2413_REG_INST_VOL..=YM2413_REG_INST_VOL_5 => {
self.instrument_volume_1_to_6(register, data, midi)?;
},
YM2413_REG_INST_VOL_6..=YM2413_REG_INST_VOL_8 => {
self.instrument_volume_6_to_8(register, data)?;
},
_ => strict!(),
}
Ok(())
}
fn instrument_volume_6_to_8(&mut self, register: u8, data: u8) -> Result<()> {
if self.config.ym2413_percussion_disabled {
return Ok(());
}
match register - YM2413_REG_INST_VOL {
6 => {
self.state.bd_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
},
7 => {
self.state.hh_vol =
db_to_midi_vol(ym2413_vol_to_db((data & 0xF0) / 0x10));
self.state.sd_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
},
8 => {
self.state.tom_vol =
db_to_midi_vol(ym2413_vol_to_db((data & 0xF0) / 0x10));
self.state.tct_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
},
_ => strict!(),
}
Ok(())
}
fn instrument_volume_1_to_6(
&mut self,
register: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = register - YM2413_REG_INST_VOL;
let mut temp_byte = self.state.instrument[channel as usize];
self.state.instrument[channel as usize] = (data & YM2413_REG_INST) / 16;
if !self.config.ym2413_prog_disabled[channel as usize]
&& (self.state.ins_vol[channel as usize] == data
|| temp_byte != self.state.instrument[channel as usize])
{
match self.state.instrument[channel as usize] {
INST_ORIGINAL => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[0].into(),
);
},
INST_VIOLIN => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[1].into(),
);
},
INST_GUITAR => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[2].into(),
);
},
INST_PIANO => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[3].into(),
);
},
INST_FLUTE => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[4].into(),
);
},
INST_CLARINET => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[5].into(),
);
},
INST_OBOE => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[6].into(),
);
},
INST_TRUMPET => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[7].into(),
);
},
INST_ORGAN => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[8].into(),
);
},
INST_HORN => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[9].into(),
);
},
INST_SYNTHESIZER => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[10].into(),
);
},
INST_HARPSICHORD => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[11].into(),
);
},
INST_VIBRAPHONE => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[12].into(),
);
},
INST_BASS_SYNTH => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[13].into(),
);
},
INST_BASS_ACOUSTIC => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[14].into(),
);
},
INST_GUITAR_ELECTRIC => {
midi.program_change_write(
channel.into(),
self.config.ym2413_midi_patch[15].into(),
);
},
_ => strict!(),
}
}
temp_byte = self.state.volume[channel as usize];
self.state.volume[channel as usize] = data & YM2413_REG_VOL;
if self.config.ym2413_vol_disabled[channel as usize]
&& (self.state.ins_vol[channel as usize] == data
|| temp_byte != self.state.volume[channel as usize])
{
self.state.factored.midi_volume[channel as usize] = db_to_midi_vol(
ym2413_vol_to_db(self.state.volume[channel as usize]),
);
midi.controller_write(
channel.into(),
MIDI_VOLUME,
self.state.factored.midi_volume[channel as usize],
);
}
self.state.ins_vol[channel as usize] = data;
Ok(())
}
fn fnum(&mut self, register: u8, data: u8, clock: u32, midi: &mut MIDIShim) -> Result<()> {
let channel = register & 0xF;
if self.config.ym2413_ch_disabled[channel as usize] {
return Ok(());
}
if (register & 0xF0) == YM2413_REG_FNUM_LSB {
self.state.factored.fnum_lsb[channel as usize] = data;
if !self.config.ym2413_optimized_vgms {
return Ok(());
}
} else if (register & 0xF0) == YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB {
self.state.sus_on[channel as usize] = data & YM2413_REG_SUS == 0;
self.state.key_on[channel as usize] = data & YM2413_REG_KEY == 0;
self.state.block[channel as usize] = (data & YM2413_REG_BLOCK) / 2;
self.state.factored.fnum_msb[channel as usize] = data & YM2413_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_ym2413(
self.state.factored.fnum_2[channel as usize] as f64,
Some(self.state.block[channel as usize]),
clock,
);
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) == YM2413_REG_SUS_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,
)?;
}
}
Ok(())
}
fn rhythm(&mut self, mut data: u8, midi: &mut MIDIShim) {
if self.config.ym2413_percussion_disabled {
return;
}
let channel = CHN_DAC;
if (data & YM2413_REG_RHYTHM_MODE) == 0 {
data = 0x0;
}
toggle_drum(
data,
YM2413_REG_BD,
&mut self.state.percussion_on[0],
midi,
channel,
0x23.into(),
self.state.bd_vol,
);
toggle_drum(
data,
YM2413_REG_SD,
&mut self.state.percussion_on[1],
midi,
channel,
0x26.into(),
self.state.sd_vol,
);
toggle_drum(
data,
YM2413_REG_TOM,
&mut self.state.percussion_on[2],
midi,
channel,
0x2D.into(),
self.state.tom_vol,
);
toggle_drum(
data,
YM2413_REG_TCT,
&mut self.state.percussion_on[3],
midi,
channel,
0x2E.into(),
self.state.tct_vol,
);
toggle_drum(
data,
YM2413_REG_HH,
&mut self.state.percussion_on[4],
midi,
channel,
0x2A.into(),
self.state.hh_vol,
);
}
}
pub(crate) fn ym2413_vol_to_db(tl: u8) -> f64 {
-f64::from(tl) * 3.0
}