use midly::num::u7;
use crate::config::Config;
use crate::midi_shim::{
MIDIShim, MIDI_DATA_ENTRY_MSB, MIDI_MODULATOR_WHEEL, MIDI_NRPN_LSB, MIDI_NRPN_MSB,
MIDI_PAN, MIDI_VOLUME, NRPN_DRUM_PAN, NRPN_DRUM_PITCH_COARSE,
};
use crate::strict;
use crate::utils::FactoredState;
use crate::vgm2mid::CHN_DAC;
use crate::ym3812::YM3812;
use anyhow::{anyhow, Result};
use midly::num::u4;
#[allow(dead_code)]
const YM278_REG_LSI_TEST: u8 = 0x00;
#[allow(dead_code)]
const YM278_REG_MEMORY_ACCESS_MODE: u8 = 0x02;
#[allow(dead_code)]
const YM278_REG_MEMORY_TYPE: u8 = 0x02;
#[allow(dead_code)]
const YM278_REG_WAVE_TABLE_HEADER: u8 = 0x02;
struct Opl4Tone {
instrument: u8,
pitch: u16,
#[allow(dead_code)]
key_scl: u8,
}
const XG_MODE: bool = false;
static OPL4_TONES: Vec<Opl4Tone> = Vec::new();
fn note_ymf278(tone: u16, fnum: u32, oct: i16) -> f64 {
if oct == -8 && fnum == 0 {
return 0xFF.into();
}
let mut pitch = (1.0 + fnum as f64 / 1024.0).log2();
let octave = oct + 8;
pitch -= OPL4_TONES[tone as usize].pitch as f64 / 0x600 as f64;
(octave as f64 * 12.0) + pitch * 12.0
}
fn channel_select(snum: u8, tone_ins: u8) -> (u4, bool) {
let mut select_channel: u4;
let drum_mode: bool;
if XG_MODE {
select_channel = (snum & 0xF).into();
drum_mode = false;
} else if tone_ins & 0x80 == 0 {
select_channel = (snum & 0xF).into();
if select_channel == CHN_DAC {
select_channel = 0xF.into();
}
drum_mode = false;
} else {
select_channel = CHN_DAC;
drum_mode = true;
}
(select_channel, drum_mode)
}
pub(crate) struct YMF278State {
tone_wave: [u16; 0x18],
tone_ins: [u8; 0x18],
oct: [i16; 0x18],
fnum_o: [u32; 0x18],
fnum_n: [u32; 0x18],
note_o: [f64; 0x18],
note_n: [f64; 0x18],
note_on_o: [u8; 0x18],
note_on_n: [u8; 0x18],
slot_volume: [u8; 0x18],
slot_pan: [u8; 0x18],
damper_on_o: [u8; 0x18],
damper_on_n: [u8; 0x18],
vib: [u8; 0x18],
drum_pan: [[u8; 0x80]; 0x10],
drum_pitch: [[u8; 0x80]; 0x10],
drum_nrpn_m: [u7; 0x10],
drum_nrpn_l: [u8; 0x10],
opl4_tones: Vec<Opl4Tone>,
factored: FactoredState,
}
pub(crate) struct YMF278<'config> {
pub state: YMF278State,
config: &'config Config,
}
impl<'config> YMF278<'config> {
pub(crate) fn new<'c: 'config>(
config: &'c Config,
opt_state: Option<YMF278State>,
) -> YMF278 {
YMF278 {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn command_handle(
&mut self,
port: u8,
register: u8,
data: u8,
ym3812: &mut YM3812,
fsam_3812: f64,
midi: &mut MIDIShim,
) -> Result<()> {
if port < 0x2 {
ym3812.command_handle(
(port as u16) << 8 | register as u16,
data,
fsam_3812,
midi,
)?;
if port == 0x1 && register == 0x5 {
self.state = Default::default();
}
return Ok(());
}
if (0x08..=0xF7).contains(®ister) {
let slot_number = (register - 0x8) % 24;
let (channel, drum_mode) = channel_select(
slot_number,
self.state.tone_ins[slot_number as usize],
);
match (register - 0x8) / 24 {
0x0 => {
wave_table_number(
&mut self.state,
slot_number,
data,
channel.into(),
midi,
)?;
},
0x1 => {
f_number(&mut self.state, slot_number, data);
},
0x2 => {
octave(
&mut self.state,
slot_number,
data,
channel.into(),
midi,
)?;
},
0x3 => {
total_level(
&mut self.state,
slot_number,
data,
drum_mode,
channel.into(),
midi,
);
},
0x4 => {
key_on(
data,
&mut self.state,
slot_number,
drum_mode,
channel.into(),
midi,
)?;
},
0x5 => {
lfo(
&mut self.state,
slot_number,
data,
drum_mode,
channel.into(),
midi,
);
},
0x6 => {
ar(
&mut self.state,
slot_number,
data,
channel.into(),
midi,
)?;
},
0x7 => (), 0x8 => (), 0x9 => (), _ => strict!(),
}
} else {
match register {
0x0 => (), 0x1 => (),
0x2 => (),
0x3 => (),
0x4 => (),
0x5 => (),
0x6 => (), 0xF8 => (),
0xF9 => (),
_ => (),
}
}
Ok(())
}
pub(crate) fn load_opl4_instrument_set(&mut self) -> Result<()> {
const INSSET_SIG: &[u8] = b"INSSET";
const COUNT_PTR: usize = INSSET_SIG.len();
let bytes = std::fs::read("yrw801.ins").map_err(|err| anyhow!(err))?;
if bytes.len() <= INSSET_SIG.len() {
strict!("OPL4 Instrument file is too short!");
}
if &bytes[0..INSSET_SIG.len()] != INSSET_SIG {
strict!("Invalid OPL4 instrument set");
}
let tone_count = bytes[COUNT_PTR];
for tone in 0..tone_count {
let instrument = bytes[COUNT_PTR + 1 + (tone * 4) as usize];
let pitch = u16::from_le_bytes([
bytes[COUNT_PTR + 1 + (1 + tone * 4) as usize],
bytes[COUNT_PTR + 1 + (2 + tone * 4) as usize],
]);
let key_scl = bytes[COUNT_PTR + 1 + (3 + tone * 4) as usize];
self.state.opl4_tones.push(Opl4Tone {
instrument,
pitch,
key_scl,
});
}
Ok(())
}
}
impl Default for YMF278State {
fn default() -> Self {
YMF278State {
tone_wave: [0; 0x18],
tone_ins: [0; 0x18],
oct: [0; 0x18],
fnum_o: [0; 0x18],
fnum_n: [0; 0x18],
note_o: [0xFF.into(); 0x18],
note_n: [0xFF.into(); 0x18],
note_on_o: [0; 0x18],
note_on_n: [0; 0x18],
slot_volume: [0; 0x18],
slot_pan: [0; 0x18],
damper_on_o: [0; 0x18],
damper_on_n: [0; 0x18],
vib: [0; 0x18],
drum_pan: [[0xFF; 0x80]; 0x10],
drum_pitch: [[0xFF; 0x80]; 0x10],
drum_nrpn_m: [0xFF.into(); 0x10],
drum_nrpn_l: [0xFF; 0x10],
opl4_tones: Vec::new(),
factored: Default::default(),
}
}
}
fn ar(
state: &mut YMF278State,
slot_number: u8,
data: u8,
channel: u8,
midi: &mut MIDIShim,
) -> Result<()> {
if state.tone_ins[slot_number as usize] == 0x77 {
let temp_byte = data / 0x10;
if temp_byte >= 0xE {
if state.note_on_o[slot_number as usize] != 0 {
midi.do_note_on(
state.note_o[slot_number as usize],
0xFF.into(),
channel.into(),
&mut state.factored.midi_note[slot_number as usize],
&mut state.factored.midi_wheel[slot_number as usize],
Some(0xFF),
Some(temp_byte.into()),
)?;
state.note_on_o[slot_number as usize] = 0;
}
state.tone_wave[slot_number as usize] = 0x180;
state.tone_ins[slot_number as usize] = OPL4_TONES
[state.tone_wave[slot_number as usize] as usize]
.instrument;
let (channel, _drum_mode) =
channel_select(slot_number, state.tone_ins[slot_number as usize]);
let channel_ptr = channel.as_int() as usize;
if state.tone_ins[slot_number as usize] & 0x80 != 0 {
let temp_byte = state.tone_ins[slot_number as usize] & 0x7F;
let old_val = state.drum_pitch[channel_ptr][temp_byte as usize];
state.drum_pitch[channel_ptr][temp_byte as usize] =
state.note_n[slot_number as usize] as u8; if state.drum_pitch[channel_ptr][temp_byte as usize] != old_val {
if state.drum_nrpn_m[channel_ptr] != NRPN_DRUM_PITCH_COARSE
|| state.drum_nrpn_l[channel_ptr] != temp_byte
{
state.drum_nrpn_m[channel_ptr] =
NRPN_DRUM_PITCH_COARSE;
state.drum_nrpn_l[channel_ptr] = temp_byte;
midi.controller_write(
channel,
MIDI_NRPN_MSB,
state.drum_nrpn_m[channel_ptr],
);
midi.controller_write(
channel,
MIDI_NRPN_LSB,
state.drum_nrpn_l[channel_ptr].into(),
);
}
midi.controller_write(
channel,
MIDI_DATA_ENTRY_MSB,
state.drum_pitch[channel_ptr][temp_byte as usize]
.into(),
);
}
state.note_n[slot_number as usize] =
(state.tone_ins[slot_number as usize] & 0x7F).into();
}
}
}
Ok(())
}
fn lfo(
state: &mut YMF278State,
slot_number: u8,
data: u8,
drum_mode: bool,
channel: u8,
midi: &mut MIDIShim,
) {
let old_val = state.vib[slot_number as usize];
state.vib[slot_number as usize] = (data & 0x7) * 0x10;
if !drum_mode && state.vib[slot_number as usize] != old_val {
midi.controller_write(
channel.into(),
MIDI_MODULATOR_WHEEL,
state.vib[slot_number as usize].into(),
);
}
}
fn key_on(
data: u8,
state: &mut YMF278State,
slot_number: u8,
drum_mode: bool,
channel: u8,
midi: &mut MIDIShim,
) -> Result<()> {
if data & 0x80 != 0 {
let temp_byte = data & 0xF;
state.slot_pan[slot_number as usize] = match temp_byte.cmp(&0x08) {
std::cmp::Ordering::Less => 0x40 - temp_byte / 0x7 * 0x40,
std::cmp::Ordering::Equal => 0x40,
std::cmp::Ordering::Greater => {
let temp = 0x40 + (0x10 - temp_byte) / 0x7 * 0x40;
if temp == 0x80 {
0x7F
} else {
temp
}
},
};
if !drum_mode {
let old_val = state.factored.midi_pan[channel as usize];
state.factored.midi_pan[channel as usize] =
state.slot_pan[slot_number as usize].into();
if state.factored.midi_pan[channel as usize] != old_val {
midi.controller_write(
channel.into(),
MIDI_PAN,
state.factored.midi_pan[channel as usize],
);
}
} else {
let temp_byte = state.tone_ins[slot_number as usize] & 0x7F;
let old_val = state.drum_pan[channel as usize][temp_byte as usize];
state.drum_pan[channel as usize][temp_byte as usize] =
state.slot_pan[slot_number as usize];
if state.drum_pan[channel as usize][temp_byte as usize] != old_val {
if state.drum_nrpn_m[channel as usize] != NRPN_DRUM_PAN
|| state.drum_nrpn_l[channel as usize] != temp_byte
{
state.drum_nrpn_m[channel as usize] = NRPN_DRUM_PAN;
state.drum_nrpn_l[channel as usize] = temp_byte;
midi.controller_write(
channel.into(),
MIDI_NRPN_MSB,
state.drum_nrpn_m[channel as usize],
);
midi.controller_write(
channel.into(),
MIDI_NRPN_LSB,
state.drum_nrpn_l[channel as usize].into(),
);
}
midi.controller_write(
channel.into(),
MIDI_DATA_ENTRY_MSB,
state.drum_pan[channel as usize][temp_byte as usize].into(),
);
}
}
}
state.damper_on_n[slot_number as usize] = (data & 0x40) / 0x40;
state.note_on_n[slot_number as usize] = (data & 0x80) / 0x80;
if state.damper_on_n[slot_number as usize] != state.damper_on_o[slot_number as usize] {
state.damper_on_o[slot_number as usize] = state.damper_on_n[slot_number as usize]
}
if state.note_on_n[slot_number as usize] != state.note_on_o[slot_number as usize] {
let temp_byte = if !drum_mode {
0x7F
} else {
state.slot_volume[slot_number as usize]
};
if state.note_on_n[slot_number as usize] != 0 {
midi.do_note_on(
state.note_o[slot_number as usize],
state.note_n[slot_number as usize],
channel.into(),
&mut state.factored.midi_note[slot_number as usize],
&mut state.factored.midi_wheel[slot_number as usize],
Some(0xFF),
Some(temp_byte.into()),
)?;
} else {
midi.do_note_on(
state.note_o[slot_number as usize],
0xFF.into(),
channel.into(),
&mut state.factored.midi_note[slot_number as usize],
&mut state.factored.midi_wheel[slot_number as usize],
Some(0xFF),
Some(temp_byte.into()),
)?;
}
state.note_on_o[slot_number as usize] = state.note_on_n[slot_number as usize];
}
Ok(())
}
fn total_level(
state: &mut YMF278State,
slot_number: u8,
data: u8,
drum_mode: bool,
channel: u8,
midi: &mut MIDIShim,
) {
let old_val = state.slot_volume[slot_number as usize];
state.slot_volume[slot_number as usize] = 0x7F - data / 0x2;
if state.slot_volume[slot_number as usize] == 0x0
&& state.note_on_n[slot_number as usize] == 0
{
state.slot_volume[slot_number as usize] = old_val;
}
if !drum_mode {
let old_val = state.factored.midi_volume[channel as usize];
state.factored.midi_volume[channel as usize] =
state.slot_volume[slot_number as usize].into();
if state.factored.midi_volume[channel as usize] != old_val {
midi.controller_write(
channel.into(),
MIDI_VOLUME,
state.factored.midi_volume[channel as usize],
);
}
}
}
fn octave(
state: &mut YMF278State,
slot_number: u8,
data: u8,
channel: u8,
midi: &mut MIDIShim,
) -> Result<()> {
state.factored.fnum_msb[slot_number as usize] = data & 0x7;
state.oct[slot_number as usize] = ((data & 0xF0) / 0x10).into();
if state.oct[slot_number as usize] & 0x8 != 0 {
state.oct[slot_number as usize] |= -8;
}
state.fnum_o[slot_number as usize] = state.fnum_n[slot_number as usize];
state.fnum_n[slot_number as usize] = ((state.factored.fnum_msb[slot_number as usize]
* 0x80) | state.factored.fnum_lsb
[slot_number as usize])
.into();
state.note_o[slot_number as usize] = state.note_n[slot_number as usize];
state.note_n[slot_number as usize] = note_ymf278(
state.tone_wave[slot_number as usize],
state.fnum_n[slot_number as usize],
state.oct[slot_number as usize],
);
if state.tone_ins[slot_number as usize] & 0x80 != 0 {
let temp_byte = state.tone_ins[slot_number as usize] & 0x7F;
let old_val = state.drum_pitch[channel as usize][temp_byte as usize];
state.drum_pitch[channel as usize][temp_byte as usize] =
state.note_n[slot_number as usize] as u8; if state.drum_pitch[channel as usize][temp_byte as usize] != old_val {
if state.drum_nrpn_m[channel as usize] != NRPN_DRUM_PITCH_COARSE
|| state.drum_nrpn_l[channel as usize] != temp_byte
{
state.drum_nrpn_m[channel as usize] = NRPN_DRUM_PITCH_COARSE;
state.drum_nrpn_l[channel as usize] = temp_byte;
midi.controller_write(
channel.into(),
MIDI_NRPN_MSB,
state.drum_nrpn_m[channel as usize],
);
midi.controller_write(
channel.into(),
MIDI_NRPN_LSB,
state.drum_nrpn_l[channel as usize].into(),
);
}
midi.controller_write(
channel.into(),
MIDI_DATA_ENTRY_MSB,
state.drum_pitch[channel as usize][temp_byte as usize].into(),
);
}
state.note_n[slot_number as usize] =
(state.tone_ins[slot_number as usize] & 0x7F).into();
}
if state.note_on_n[slot_number as usize] != 0 {
midi.do_note_on(
state.note_o[slot_number as usize],
state.note_n[slot_number as usize],
channel.into(),
&mut state.factored.midi_note[slot_number as usize],
&mut state.factored.midi_wheel[slot_number as usize],
None,
None,
)?;
}
Ok(())
}
fn f_number(state: &mut YMF278State, slot_number: u8, data: u8) {
state.tone_wave[slot_number as usize] =
(state.tone_wave[slot_number as usize] & 0xFF) | ((data as u16 & 0x1) * 0x100);
state.factored.fnum_lsb[slot_number as usize] = data / 0x2;
}
fn wave_table_number(
state: &mut YMF278State,
slot_number: u8,
data: u8,
channel: u8,
midi: &mut MIDIShim,
) -> Result<()> {
state.tone_wave[slot_number as usize] =
(state.tone_wave[slot_number as usize] & 0x100) | data as u16;
state.tone_ins[slot_number as usize] =
OPL4_TONES[state.tone_wave[slot_number as usize] as usize].instrument;
let old_val = channel;
let (channel, _drum_mode) =
channel_select(slot_number, state.tone_ins[slot_number as usize]);
let channel_ptr = channel.as_int() as usize;
if channel != old_val & state.note_on_o[slot_number as usize] {
midi.do_note_on(
state.note_o[slot_number as usize],
0xFF.into(),
channel,
&mut state.factored.midi_note[slot_number as usize],
&mut state.factored.midi_wheel[slot_number as usize],
Some(0xFF),
Some(0x00.into()), )?;
state.note_on_o[slot_number as usize] = 0
}
let old_instrument = state.factored.midi_instrument[channel_ptr];
state.factored.midi_instrument[channel_ptr] =
if state.tone_ins[slot_number as usize] & 0x80 != 0 {
0x80.into()
} else {
(state.tone_ins[slot_number as usize] & 0x7F).into()
};
if state.factored.midi_instrument[channel_ptr] != old_instrument {
if XG_MODE {
if (state.factored.midi_instrument[channel_ptr].as_int() & 0x80)
!= (old_instrument.as_int() & 0x80)
{
let temp_byte = if state.factored.midi_instrument[channel_ptr]
.as_int() & 0x80 != 0
{
0x7F
} else {
0x0
};
midi.controller_write(channel, 0x00.into(), temp_byte.into());
}
if (state.factored.midi_instrument[channel_ptr].as_int() & 0x80)
& (old_instrument.as_int() & 0x80)
== 0
{
midi.program_change_write(
channel,
state.factored.midi_instrument[channel_ptr],
);
}
} else if state.factored.midi_instrument[channel_ptr].as_int() & 0x80 == 0 {
midi.program_change_write(
channel,
state.factored.midi_instrument[channel_ptr],
);
}
}
Ok(())
}