R5MU66IEYJHSQ6XEUE2F5FTPQCVK3HCN7ENKJMQB62EUBGC5X44QC
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();
}
// Note2FNum from Linux// OPL4 Driver
//pitch = (note - 60) * voice->sound->key_scaling / 100 + 60;
//pitch = pitch << 7; -- 1 Halftone = 128 pitch --
//pitch += voice->sound->pitch_offset;
//octave = pitch / 0x600 - 8;
//fnum = pitch2fnum(pitch % 0x600);
let mut pitch = (1.0 + fnum as f64 / 1024.0).log2();
let octave = oct + 8;
//if OPL4Tones(Tone).Pitch = 0x1000 { Stop
pitch -= OPL4_TONES[tone as usize].pitch as f64 / 0x600 as f64;
// Formula from YMF278B Manual: 1024 + F_NUMBER
// F(c) = 1200 x (Octave - 1) + 1200 x log2 ---------------
// 1 octave = 1200c 1024
//if OPL4Tones(Tone).KeyScl > 0 & OPL4Tones(Tone).KeyScl != 100 {
// Note = (Note - 60) * 100 / OPL4Tones(Tone).KeyScl + 60
//}
(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;
//FIXME: more nonsense constants
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();
}
//if SNum <= 0x4 { SelChn = 0x8
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],
);
//FIXME: this is dumb, make this more sensible.
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 => (), // Reg 0xB0 - 0xC7
//slot.DL = dl_tab(Int(Data / 0x10))
//slot.D2R = Data & 0xF
0x8 => (), // Reg 0xC8 - 0xDF
//slot.RC = Int(Data / 0x10)
//slot.RR = Data & 0xF
0x9 => (), // Reg 0xE0 - 0xF7
//slot.AM = Data & 0x7
_ => strict!(),
}
} else {
// All non-slot registers
match register {
0x0 => (), // TEST
0x1 => (),
0x2 => (),
//wavetblhdr = (Data >> 2) & 0x7
//memmode = Data & 1
0x3 => (),
//memadr = (memadr & 0xFFFF&) | (Data * 0x10000)
0x4 => (),
//memadr = (memadr & 0xFF00FF) | (Data * 0x100&)
0x5 => (),
//memadr = (memadr & 0xFFFF00) | Data
0x6 => (), // memory data
//Call writeMem(memadr, Data)
//memadr = (memadr + 1) & 0xFFFFFF
0xF8 => (),
//fm_l = Data & 0x7
//fm_r = (Data >> 3) & 0x7
0xF9 => (),
//pcm_l = Data & 0x7
//pcm_r = (Data >> 3) & 0x7
_ => (),
}
}
//regs [Register] = Data
Ok(())
}
pub(crate) fn load_opl4_instrument_set(&mut self) -> Result<()> {
const INSSET_SIG: &[u8] = b"INSSET";
const COUNT_PTR: usize = INSSET_SIG.len(); //This should resolve to the subsequent byte.
//NOTE: Consider adding this as a static resource?
//There is at most 1 KiB of data. Even on an embedded device it would be reasonable to
//just statically allocate this.
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<()> {
// Reg 0x98 - 0xAF
//slot.AR = Int(Data / 0x10)
//slot.D1R = Data & 0xF
if state.tone_ins[slot_number as usize] == 0x77 {
// Reverse Cymbal
let temp_byte = data / 0x10;
if temp_byte >= 0xE {
// -> Crash Cymbal
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; //FIXME: This is impossible to understand;
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,
) {
// Reg 0x80 - 0x97
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(),
);
}
//slot.set_lfo(Int(Data / 0x8) & 0x7)
}
fn key_on(
data: u8,
state: &mut YMF278State,
slot_number: u8,
drum_mode: bool,
channel: u8,
midi: &mut MIDIShim,
) -> Result<()> {
// Reg 0x68 - 0x7F
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] {
// Damping increases Decay rate
//midi.event_write(MIDI_CONTROLLER_CHANGE, CH, MIDI_SOFT, DamperOn_N[SNum]) & 0x7F != 0;
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,
) {
// Reg 0x50 - 0x67
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;
}
//LD = Data & 0x1
//if LD {
// // directly change volume
//} else {
// // interpolate volume
//}
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<()> {
// Reg 0x38 - 0x4F
//slot.FN = (slot.FN & 0x07F) | ((Data & 0x07) << 7)
state.factored.fnum_msb[slot_number as usize] = data & 0x7;
//slot.PRVB = ((data & 0x08) >> 3)
//slot.OCT = ((data & 0xF0) >> 4)
state.oct[slot_number as usize] = ((data & 0xF0) / 0x10).into();
//int oct = slot.OCT;
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; //FIXME wtf is this shit.
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) {
// Reg 0x20 - 0x37
//slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8);
state.tone_wave[slot_number as usize] =
(state.tone_wave[slot_number as usize] & 0xFF) | ((data as u16 & 0x1) * 0x100);
//slot.FN = (slot.FN & 0x380) | (data >> 1);
state.factored.fnum_lsb[slot_number as usize] = data / 0x2;
// usually, FNum MSB is sent after FNum LSB
}
fn wave_table_number(
state: &mut YMF278State,
slot_number: u8,
data: u8,
channel: u8,
midi: &mut MIDIShim,
) -> Result<()> {
// Reg 0x8 - 0x1F
//loadTime = time + LOAD_DELAY;
//
//slot.wave = (slot.wave & 0x100) | data;
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()), //FIXME: This was temp_byte, though it was never assigned...
)?;
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],
);
}
//if CH < 8 & MIDIIns[channel_ptr] = 0x77 { Stop
}
//int base = (slot.wave < 384 | !wavetblhdr) ?
// (slot.wave * 12) :
// (wavetblhdr * 0x80000 + ((slot.wave - 384) * 12));
//byte buf[12];
//for (int i = 0; i < 12; ++i) {
// buf[i] = readMem(base + i);
//}
//slot.bits = (buf[0] & 0xC0) >> 6;
//slot.set_lfo((buf[7] >> 3) & 7);
//slot.vib = buf[7] & 7;
//slot.AR = buf[8] >> 4;
//slot.D1R = buf[8] & 0xF;
//slot.DL = dl_tab[buf[9] >> 4];
//slot.D2R = buf[9] & 0xF;
//slot.RC = buf[10] >> 4;
//slot.RR = buf[10] & 0xF;
//slot.AM = buf[11] & 7;
//slot.startaddr = buf[2] | (buf[1] << 8) |
// ((buf[0] & 0x3F) << 16);
//slot.loopaddr = buf[4] + (buf[3] << 8);
//slot.endaddr = (((buf[6] + (buf[5] << 8)) ^ 0xFFFF) + 1);
//if ((regs[Register + 4] & 0x080)) {
// keyOnHelper(slot);
//}
Ok(())
}
// OPL Module for YM3812, YM3526, Y8950 and YMF262, based on YM2413 (OPLL) Module
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};
// YM3812/YMF262 Register Constants
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, //FIXME: This should be able to be a reference. It's basically static anyway though...
}
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 => (),
//WaveselEnable = (Data & 0x20) / 0x20
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;
//Exit Sub
} 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 {
//if (Register & 0xF0) = YM3812_REG_KEY_BLOCK_FNum_MSB & _
//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,
)?;
}
}
},
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) {
//if (register & 0x7) > 0x5 {}
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 = OPL_TL2Vol(Data & 0x3F)
{
me.state.bd_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x7, 0x0) =>
//me.state.hh_vol = OPL_TL2Vol(Data & 0x3F)
{
me.state.hh_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x7, 0x1) =>
//me.state.sd_vol = OPL_TL2Vol(Data & 0x3F)
{
me.state.sd_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x8, 0x0) =>
//me.state.tom_vol = OPL_TL2Vol(Data & 0x3F)
{
me.state.tom_vol = db_to_midi_vol(ym3812_vol_to_db(data & 0x3F))
},
(0x8, 0x1) =>
//me.state.tct_vol = OPL_TL2Vol(Data & 0x3F)
{
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));
//TempByt = OPL_TL2Vol(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, // should actually be complete silence
(_, 0x1) => MIDI_PAN_LEFT,
(_, 0x2) => MIDI_PAN_CENTER,
(_, 0x3) => MIDI_PAN_RIGHT,
(0x0, _) => MIDI_PAN_CENTER,
(_, _) => MIDI_PAN_CENTER, //FIXME: this is likely incorrect
};
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(), //FIXME: Magic numbers
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.event_write(Midi { channel: CH, message: NoteOff { key: 35, vel: 0x0 }});
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
}
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};
// YM2612 Register Select Constants
#[allow(dead_code)]
pub(crate) const YM2610_DELTAT_ADPCM: u8 = 0x10; // DELTA-T/ADPCM
pub(crate) const YM2612_TEST: u8 = 0x21; // LSI Test Data
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; // Key On/Off
pub(crate) const YM2612_SLOT: u8 = 240;
pub(crate) const YM2612_CH: u8 = 7;
pub(crate) const YM2612_DAC: u8 = 0x2A; // DAC Data
#[allow(dead_code)]
pub(crate) const YM2612_DAC_DATA: u8 = 255;
pub(crate) const YM2612_DAC_EN: u8 = 0x2B; // DAC Data
#[allow(dead_code)]
pub(crate) const YM2612_DAC_EN_BIT: u8 = 127;
#[allow(dead_code)]
pub(crate) const YM2612_DT_MULTI: u8 = 0x30; // Detune/Multiple
#[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; // Total Level
pub(crate) const YM2612_TL_END: u8 = YM2612_TL + 0xF;
pub(crate) const YM2612_KS_AR: u8 = 0x50; // Key Scale/Attack Rate
#[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; // Decay Rate
pub(crate) const YM2612_SR: u8 = 0x70; // Sustain Rate
pub(crate) const YM2612_SL_RR: u8 = 0x80; // Sustain Level/Release Rate
#[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; // SSG-Type Envelope Control
#[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; // Self-Feedback/Connection
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; // Pan Select
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],
// DT(5, 3) As Byte, MULTI(5, 3) As Byte
tl: [[u8; 4]; 6],
volume: [u8; 6],
//KF As Byte
// KS(5, 3) As Byte, AR(5, 3) As Byte
// DR: [u8; 6, 3],
// SR: [u8; 6, 3],
// SL(5, 3) As Byte, RR(5, 3) As Byte
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_CH3_MODE_RESET_ENABLE_LOAD => {
// CH3_Mode = (Data & YM2612_CH3_MODE) / 96
// },
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_DT_MULTI => {
},
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) / 8;
self.state.factored.fnum_msb[channel as usize] = data & 7;
},
0xA0..=0xA2 => {
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] =
self.state.factored.hz_2[channel as usize];
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,
)?;
}
},
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());
//midi.event_write(MIDI_PROGRAM_CHANGE, CH, (Data & 0xF8) / 0x8);
},
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(());
}
//if CH = 5 & DAC_EN = 1 {
// midi.event_write(Midi { channel: CHN_DAC, message: Controller { controller: MIDI_PAN, value: self.state.factored.midi_pan[channel as usize] }});
// midi.event_write(Midi { channel: CHN_DAC, message: NoteOff { key: 35, vel: 0 }});
// midi.event_write(Midi { channel: CHN_DAC, message: NoteOn { key: 35, vel: MIDI_VOLUME_MAX * 0.8 }});
//}
},
_ => {
/*Exit Sub
Debug.Print "Register " & Hex$(Register) & ": ";
match Register
} YM2612_TEST => {
Debug.Print "TEST - LSI Test Data"
} YM2612_LFO_EN_LFO_FREQ => {
Debug.Print "LFO_EN_LFO_FREQ"
} YM2612_TIMER_A_MSB => {
Debug.Print "TIMER_A_MSB"
} YM2612_TIMER_A_LSB => {
Debug.Print "TIMER_A_LSB"
} YM2612_TIMER_B => {
Debug.Print "TIMER_B"
} YM2612_CH3_MODE_RESET_ENABLE_LOAD => {
Debug.Print "CH3_MODE_RESET_ENABLE_LOAD"
} YM2612_SLOT_CH => {
Debug.Print "SLOT_CH - Key On/Off"
} YM2612_DAC => {
Debug.Print "DAC - DAC Data"
} YM2612_DAC_DATA => {
Debug.Print "DAC_DATA"
} YM2612_DAC_EN => {
Debug.Print "DAC_EN - DAC Data"
} YM2612_DT_MULTI => {
Debug.Print "DT_MULTI - Detune/Multiple"
} YM2612_TL => {
Debug.Print "TL - Total Level"
} YM2612_KS_AR => {
Debug.Print "KS_AR - Key Scale/Attack Rate"
} YM2612_DR => {
Debug.Print "DR - Decay Rate"
} YM2612_SR => {
Debug.Print "SR - Sustain Rate"
} YM2612_SL_RR => {
Debug.Print "SL_RR - Sustain Level/Release Rate"
} YM2612_SSG_EG => {
Debug.Print "SSG_EG - SSG-Type Envelope Control"
} YM2612_FNum_LSB => {
Debug.Print "FNum_LSB"
} YM2612_Block_FNum_MSB => {
Debug.Print "Block_FNum_MSB"
} YM2612_CH3_SPECIAL_FNum_MSB => {
Debug.Print "CH3_SPECIAL_FNum_MSB"
} YM2612_CH3_SPECIAL_BLOCK_FNum_LSB => {
Debug.Print "CH3_SPECIAL_BLOCK_FNum_LSB"
} YM2612_CH3_SPECIAL_FNum_LSB => {
Debug.Print "CH3_SPECIAL_FNum_LSB"
} YM2612_FB_CONNECTION => {
Debug.Print "FB_CONNECTION - Self-Feedback/Connection"
} YM2612_L_R => {
Debug.Print "L_R - Pan Select"
_ => {
Debug.Print "Unknown"
},*/
}, //Stop
}
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.event_write(MIDI_PITCHWHEEL, CH, self.state.factored.midi_wheel[channel as usize]);
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);
//Const PI = 3.14159265358979
////Const Phase = PI / (MIDI_VOLUME_MAX * 2)
////self.state.factored.midi_volume[channel as usize] = MIDI_VOLUME_MAX * (1 - (Sin(self.state.factored.midi_volume[channel as usize] * Phase)))
//self.state.factored.midi_volume[channel as usize] = 0x80 * (1# - Sin(self.state.volume[channel as usize] / 0x80 * (PI / 2)))
//if self.state.factored.midi_volume[channel as usize] = 0x80 {
// self.state.factored.midi_volume[channel as usize] = 0x7F
//}
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;
}
//if Data = 0xF {
// Data = 0x14 // Sonic 3K Bass Drum
//} else if Data = 0x10 {
// Data = 0x16 // Sonic 3K Snare Drum
//} else {
// Data = 0xFF
//}
//if Data = 0x22 {
// Data = 0x23 // Sonic Crackers Bass Drum
//} else if Data = 0x23 {
// Data = 0x26 // Sonic Crackers Snare Drum
//} else if Data = 0xC4 {
// Data = 0x2D // Sonic Crackers Tom Tom
//} else {
// Data = 0xFF
//}
//if Data = 0x1A {
// Data = 0x23 // Sonic 1 Bass Drum
//} else if Data = 0xB {
// Data = 0x26 // Sonic 1 Snare Drum
//} else {
// Data = 0xFF
//}
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.event_write(Midi { channel: CHN_DAC, message: NoteOn { key: 35, vel: Data }});
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, // Bass Drum
0x26, // Snare Drum
0x33, // Top Cymbal
0x2A, // High Hat
0x2D, // Tom Tom
0x25, // Rim Shot
],
}
}
}
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 => {
// Bit 7 = L, Bit 6 = R, Bit 4-0 = Instrument Level
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);
//midi.event_write(Midi { channel: channel, message: Controller { controller: MIDI_PAN, value: state.factored.midi_pan[channel as usize] }});
//Volume = (AdPcm_Acc * VolMul) / 2 ^ VolShift
let mut temp_volume =
((vol_mul as u32) << 8) >> vol_shift as u32; //FIXME: this is messy
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;
//midi.event_write(MIDI_CONTROLLER_channelANGE, channel, MIDI_VOLUME, Volume);
},
0x10 | 0x18 => {
// Start Address
if (register & 0x38) == 0x18 {
//Debug.Print Hex(Data)
//Stop
}
},
0x20 | 0x28 => (), // End Address
_ => strict!(),
}
},
}
Ok(())
}
fn fnopn_tl(state: &mut FMOPNADPCMState, data: u8) {
// Bit 0-5 = Total Level
state.tl = (!data) & 0x3F;
if state.vol_max == 0 {
state.vol_max = 0xFF
}
//TempByt = (!Data) & 0x3F
//for channel in 0x0..=0x5 {
// Volume = TL + IL[channel as usize]
// if Volume >= 0x3F {
// VolMul = 0x0
// VolShift = 0x0
// } else {
// VolMul = 0xF - (Volume & 0x7)
// VolShift = 0x1 + ((Volume & 0x38) / 0x8)
// }
// //Volume = (AdPcm_Acc * VolMul) / 1 << VolShift
// Volume = 0x100 * VolMul / 1 << VolShift
//}
}
fn fmopn_dm(data: u8, state: &mut FMOPNADPCMState, midi: &mut MIDIShim) {
// DM, --, C5, C4, C3, C2, C1, C0
if (data & 0x80) == 0x0 {
// Key On
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 {
// All Keys Off
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 => {
// Control 1: Start, Rec, MemMode, Repeat, SpOff, --, --, Reset => {
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 => {
// Control 2: L, R, -, -, Sample, DA/AD, RAMTYPE, ROM => {
match (data & 0xC0) / 0x40 {
0x1 => (), //state.factored.midi_pan[channel as usize] = MIDI_PAN_RIGHT
0x2 => (), //state.factored.midi_pan[channel as usize] = MIDI_PAN_LEFT
0x3 | 0x0 => (), //state.factored.midi_pan[channel as usize] = MIDI_PAN_CENTER
_ => strict!(),
}
},
0x02 => (), // Start Address Low
0x03 => (), // Start Address High
0x04 => (), // Stop Address Low
0x05 => (), // Stop Address High
0x06 => (), // Prescale Low
0x07 => (), // Prescale High
0x08 => (), // ADPCM Data
0x09 => (), // Delta-N Low
0x0A => (), // Delta-N High
0x0B => state.ins_vol = data / 0x2, // Volume
0x0C => (), // Flag Control: Extend Status Clear/Mask
_ => (),
}
Ok(())
}
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;
// YM2413 Register Constants
#[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;
// Tone Data (INST) Constants
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<()> {
/*if Variables_Clear_YM2413 = 1 {
CH = 0
Erase state.FNum_MSB
Erase state.SUS_ON: Erase state.KEY_ON: Erase block: Erase state.FNum_LSB: Erase state.FNum_1: Erase state.FNum_2: Erase state.Hz_1: Erase state.Hz_2: Erase state.Note_1: Erase state.Note_2
Erase state.InsVol: Erase state.Instrument: Erase state.Volume: state.BD_VOL = 0x7F: state.HH_VOL = 0x7F: state.SD_VOL = 0x7F: state.TOM_VOL = 0x7F: state.TCT_VOL = 0x7F
Erase state.MIDINote: Erase state.MIDIWheel: Erase state.factored.midi_volume[0]: Erase state.NoteOn_1: Erase state.NoteOn_2: Erase state.Percussion_on
For CH = 0x0..=0x8
state.InsVol[CH as usize] = 0x0
state.Instrument[CH as usize] = 0xFF
state.Volume[CH as usize] = 0xFF
state.MIDINote[CH as usize] = 0xFF
state.MIDIWheel[CH as usize] = 0x8000
state.factored.midi_volume[CH as usize] = 0xFF
Next CH
Variables_Clear_YM2413 = 0
}*/
match register {
// Case YM2413_REG_USER..=(YM2413_REG_USER + 7)
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 = (MIDI_VOLUME_MAX - ((Data & 15) * 8)) * 0.8
//self.state.BD_VOL = (MIDI_VOLUME_MAX - ((Data & 0xF) * 0x8)) + 1
//if self.state.BD_VOL = 0x80 { self.state.BD_VOL = 0x7F
self.state.bd_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
},
7 => {
//self.state.HH_VOL = MIDI_VOLUME_MAX - (((Data & 0xF0) / 0x10) * 8) + 1
//if self.state.HH_VOL = 0x80 { self.state.HH_VOL = 0x7F
self.state.hh_vol =
db_to_midi_vol(ym2413_vol_to_db((data & 0xF0) / 0x10));
////self.state.SD_VOL = (MIDI_VOLUME_MAX - ((Data & 15) * 8)) * 0.8
//self.state.SD_VOL = (MIDI_VOLUME_MAX - ((Data & 0xF) * 0x8)) + 1
//if self.state.SD_VOL = 0x80 { self.state.SD_VOL = 0x7F
self.state.sd_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
},
8 => {
////self.state.TOM_VOL = (MIDI_VOLUME_MAX - (((Data & 240) / 16) * 8)) * 0.6
//self.state.TOM_VOL = (MIDI_VOLUME_MAX - (((Data & 0xF0) / 0x10) * 8)) + 1
//if self.state.TOM_VOL = 0x80 { self.state.TOM_VOL = 0x7F
self.state.tom_vol =
db_to_midi_vol(ym2413_vol_to_db((data & 0xF0) / 0x10));
//self.state.TCT_VOL = MIDI_VOLUME_MAX - ((Data & 0xF) * 0x8) + 1
//if self.state.TCT_VOL = 0x80 { self.state.TCT_VOL = 0x7F
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.Volume[CH as usize] = MIDI_VOLUME_MAX - ((Data & YM2413_REG_VOL) * 8) + 1
//if self.state.Volume[CH as usize] = 0x80 { self.state.Volume[CH as usize] = 0x7F
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];
}
//CH = Register - YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB
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 {
//if (Register & 0xF0) = YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB & _
//self.state.Note_1[CH as usize] != self.state.Note_2[CH 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 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;
}
//TODO: Move this into utils or something. Also, fix magic key numbers
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,
);
// Note Value 46 (Open HiHat) replaced with 51 (Ride Cymbal 1),
// because REG_TCT is often used with REG_HH at the same time and
// prevents the Cymbal from playing
// Case YM2413_REG_TEST
//Case YM2413_REG_self.state.FNum_LSB..=(YM2413_REG_self.state.FNum_LSB + 8)
//CH = Register - YM2413_REG_self.state.FNum_LSB
//
//if self.config.ym2413_ch_disabled[CH as usize] = 1 { return }
//
//self.state.FNum_LSB[CH as usize] = Data
//Case YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB..=(YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB + 8)
}
}
pub(crate) fn ym2413_vol_to_db(tl: u8) -> f64 {
-f64::from(tl) * 3.0
}
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;
// YM2151 Register Constants
#[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())
//if YM2151FNumToMidi = 0x0 { Stop
}
}
#[derive(Default)]
pub(crate) struct YM2151State {
slot: [u8; 8],
// DT(5, 3) As Byte, MULTI(5, 3) As Byte
tl: [[u8; 4]; 8],
//KF As Byte
// KS(5, 3) As Byte, AR(5, 3) As Byte
// DR: [u8; 5, 3],
// SR: [u8; 5, 3],
// SL(5, 3) As Byte, RR(5, 3) As Byte
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,
}
}
//FIXME: This subroutine used a lot of static state. It needs to be re-written to something more sane.
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 => (), // NOISE: NE, NFRQ
//nen = if(Data & 0x80, 1, 0)
//nfrq = Data & 0x31
YM2151_CLK_A_MSB => (),
YM2151_CLK_A_LSB => (),
YM2151_CLK_B => (),
YM2151_CSM_IRQ_RESET_EN_TIMER => (),
YM2151_LFO_FREQ => (),
YM2151_PMD_AMD => (),
//depth = Data & 0x7F
//ispm = if(Data & 0x80, 1, 0)
YM2151_CT1_CT2_WAVEFORM => (),
//w = Data & 0x3 // W: 0 - ramp, 1 - sq, 2 - tri, 3 - noise
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 => {
//let channel = (register & 0x3F) / 0x8;
//DT1 = Fix(Data / 0x10) & 0x7
//MUl = Data & 0xF
},
YM2151_TL..=YM2151_TL_END => {
process_total_level(
register,
&mut self.state,
data,
self.config,
midi,
);
},
/*YM2151_KS_AR..=YM2151_KS_AR + 0x1F
channel = Fix((register & 0x3F) / 0x8)
//KS = Fix(Data / 0x40) & 0x3
//AR = Data & 0x1F
YM2151_LFO_AM_EN_D1R..=YM2151_LFO_AM_EN_D1R + 0x1F
channel = Fix((register & 0x3F) / 0x8)
//AMS_EN = if(Data & 0x80, 1, 0)
//D1R = Data & 0x1F
YM2151_DT2_D2R..=YM2151_DT2_D2R + 0x1F
channel = Fix((register & 0x3F) / 0x8)
//DT2 = Fix(Data / 0x40) & 0x3
//D2R = Data & 0x1F
YM2151_D1L_RR..=YM2151_D1L_RR + 0x1F
channel = Fix((register & 0x1F) / 0x4)
//D1L = Fix(Data / 0x10) & 0xF
//RR = Data & 0xF
//Abort("YM2151 ADSR - SL " & D1L & " RR " & RR);*/
_ => 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);
//match state.connection[channel as usize]
//0, 1, 2, 3
//4
// TempLng = (CLng(state.tl[channel as usize][1]) + state.tl[channel as usize][3]) / 2
//5, 6
// TempLng = (CLng(state.tl[channel as usize][1]) + state.tl[channel as usize][2] + state.tl[channel as usize][3]) / 3
//7
// TempLng = (CLng(state.tl[channel as usize][0]) + state.tl[channel as usize][1] + state.tl[channel as usize][2] + state.tl[channel as usize][3]) / 4
//}
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;
//PMS = Fix(Data / 0x10) & 0x7
//AMS = Data & 0x3
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);
// Key Fraction goes from 0x00 to 0xFC
// 0x00 is no change, 0x100 would be a full semitone
//TempLng = MIDI_PITCHWHEEL_CENTER + state.factored.fnum_lsb[channel as usize] * 0x40 //(0x1000 / 0x40)
//if TempLng != state.factored.midi_wheel[channel as usize] {
// state.factored.midi_wheel[channel as usize] = TempLng
// midi.event_write(Midi { channel: channel, message: PitchBend { bend: state.factored.midi_wheel[channel as usize] }});
//}
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);
//FIXME: fnum_2 really shouldn't be a long
//if state.factored.note_on_2[channel as usize] = 1 | NOTE_ON_MODE & 0x2 != 0 {
// if state.factored.note_2[channel as usize] != state.factored.note_1[channel as usize] | (NOTE_ON_MODE & 0x2) = 0x2 {
// midi.event_write(Midi { channel: channel, message: NoteOff { key: state.factored.note_1[channel as usize], vel: 0x0 }});
// midi.event_write(Midi { channel: channel, message: NoteOn { key: state.factored.note_2[channel as usize], vel: 0x7F }});
// }
//}
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
/*NOTE_ON_MODE & 0x1*/
true {
//FIXME: wtf?
// This is a const. wtf?
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.event_write(Midi { channel: channel, message: NoteOff { key: state.factored.note_2[channel as usize], vel: 0x0 }});
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.event_write(Midi { channel: channel, message: NoteOn { key: state.factored.note_2[channel as usize], vel: 0x7F }});
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(())
}
#[allow(unused_imports)]
use crate::midi_shim::{
MIDIShim, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT, MIDI_PAN_RIGHT, MIDI_PATCH_GUNSHOT,
MIDI_PATCH_LEAD_1_SQUARE, MIDI_PITCHWHEEL_CENTER, MIDI_PITCHWHEEL_MAX, MIDI_PITCHWHEEL_MIN,
MIDI_RPN_LSB, MIDI_RPN_MSB, MIDI_VOLUME, RPN_PITCH_BEND_RANGE_L, RPN_PITCH_BEND_RANGE_M,
};
use midly::num::{u14, u7, u4};
pub(crate) const CHN_DAC: u4 = u4::new(9);
#[allow(dead_code)]
pub(crate) const MIDI_NOTE_STEPS: u16 = 12;
//const PITCHWHEEL_SENSITIVITY = 64 //+/- n Semitones
//const PITCHWHEEL_STEPS_REAL = 8192 / PITCHWHEEL_SENSITIVITY
//const PITCHWHEEL_STEPS_DEFAULT = 4096 //16384 / 4
pub(crate) const PITCHWHEEL_SENSITIVITY_DEFAULT: u7 = u7::new(0x02); //+/- n Semitones
pub(crate) const PITCHWHEEL_STEPS_DEFAULT: u14 = u14::new(4096); //16384 / 4
//const MIDI_NOTE_CURVE = 2 ^ (1 / MIDI_NOTE_STEPS)
//const PITCHWHEEL_CURVE = MIDI_NOTE_CURVE ^ (1 / PITCHWHEEL_STEPS)
// Timing
//FIXME: Ensure that floating point numbers are the correct representation.
#[allow(dead_code)]
pub(crate) const OSC1: f64 = 53693100.0;
#[allow(dead_code)]
pub(crate) const FM_YM2413_SN76489: f64 = OSC1 / 15.0;
#[allow(dead_code)]
pub(crate) const FSAM_YM2413_SN76489: f64 = FM_YM2413_SN76489 / 72.0;
#[allow(dead_code)]
pub(crate) const FM_YM2612: f64 = OSC1 / 14.0;
#[allow(dead_code)]
pub(crate) const FSAM_YM2612: f64 = FM_YM2612 / 72.0;
pub(crate) const OPL_TYPE_YM3526: u8 = 0x1;
pub(crate) const OPL_TYPE_YM3812: u8 = 0x2;
pub(crate) const OPL_TYPE_YMF262: u8 = 0x3;
pub(crate) const OPL_TYPE_Y8950: u8 = 0x8;
pub(crate) const OPN_TYPE_YM2203: u8 = 0x1;
pub(crate) const OPN_TYPE_YM2608: u8 = 0x2;
pub(crate) const OPN_TYPE_YM2610: u8 = 0x3;
pub(crate) const OPN_TYPE_YM2612: u8 = 0x4;
use crate::ay8910::AY8910;
use crate::config::Config;
use crate::gameboy::GameBoy;
use crate::gd3::{get_gd3_header, Gd3Header};
use crate::midi_shim::{MIDIShim, TEXT_LOOP_END, TEXT_LOOP_START};
use crate::nesapu::NESAPU;
use crate::pcm::SegaPCM;
use crate::sn76489::SN76489;
use crate::vgm2mid::{
OPL_TYPE_Y8950, OPL_TYPE_YM3526, OPL_TYPE_YM3812, OPL_TYPE_YMF262, OPN_TYPE_YM2203,
OPN_TYPE_YM2608, OPN_TYPE_YM2610, OPN_TYPE_YM2612,
};
use crate::ym2151::YM2151;
use crate::ym2413::YM2413;
use crate::ym2612::{YM2612, YM2612_DAC};
use crate::ym3812::YM3812;
use crate::ymf278::YMF278;
use crate::{strict, verbose};
use anyhow::{bail, Result};
use midly::MetaMessage::Marker;
use midly::TrackEventKind::Meta;
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct VgmHeader {
str_vgm: [u8; 4], // 0x00
eof_offset: u32,
version: u32,
hz_sn76489: u32,
hz_ym2413: u32, // 0x10
gd3_offset: u32,
total_samples: u32,
loop_offset: u32,
loop_samples: u32, // 0x20
rate: u32,
sn_feedback: u16,
sn_stwidth: u8,
sn_flags: u8,
hz_ym2612: u32,
hz_ym2151: u32, // 0x30
data_offset: u32,
hz_spcm: u32,
spcm_intf: u32,
hz_rf5c68: u32, // 0x40
hz_ym2203: u32,
hz_ym2608: u32,
hz_ym2610b: u32,
hz_ym3812: u32, // 0x50
hz_ym3526: u32,
hz_y8950: u32,
hz_ymf262: u32,
hz_ymf278b: u32, // 0x60
hz_ymf271: u32,
hz_ymz280b: u32,
hz_rf5c164: u32,
hz_pwm: u32, // 0x70
hz_ay8910: u32,
ay_type: u8,
ay_flags: u8,
ay_flags2203: u8,
ay_flags2608: u8,
volume_modifier: u8,
reserved_1: u8,
loop_base: u8,
loop_modifier: u8,
hz_gb_dmg: u32, // 0x80
hz_nes_apu: u32,
hz_multipcm: u32,
hz_upd7759: u32,
hz_okim6258: u32, // 0x90
okim_flags: u8,
k0_flags: u8,
c140_type: u8,
reserved_2: u8,
hz_okim5295: u32,
hz_k051649: u32,
hz_k054539: u32, // 0xA0
hz_huc6280: u32,
hz_c140: u32,
k053260: u32,
hz_pokey: u32, // 0xB0
hz_qsound: u32,
hz_scsp: u32,
extra_header_offset: u32,
}
struct Vgm {
file_header: VgmHeader,
data_bank: Vec<u8>,
data_block_count: u32,
data_block_pos: [usize; 0xFF],
gt_gd3_tag: Gd3Header,
conversion_status_current: u32,
conversion_status_total: u32,
clock_sn76489: u32,
clock_ym2151: u32,
clock_ym2413: u32,
clock_ym2612: u32,
clock_ay8910: u32,
}
// VGM Command Constants
const GG_STEREO: u8 = 0x4F;
const SN76489: u8 = 0x50;
const YM2413: u8 = 0x51;
const YM2612_P0: u8 = 0x52;
const YM2612_P1: u8 = 0x53;
const YM2151: u8 = 0x54;
const YM2203: u8 = 0x55;
const YM2608_P0: u8 = 0x56;
const YM2608_P1: u8 = 0x57;
const YM2610_P0: u8 = 0x58;
const YM2610_P1: u8 = 0x59;
const YM3812: u8 = 0x5A;
const YM3526: u8 = 0x5B;
const Y8950: u8 = 0x5C;
const YMF262_P0: u8 = 0x5E;
const YMF262_P1: u8 = 0x5F;
const RF5C68_REG: u8 = 0xB0;
const SPCM_MEM: u8 = 0xC0;
const RF5C68_MEM: u8 = 0xC1;
const DATABNK_SEEK: u8 = 0xE0;
const AY8910: u8 = 0xA0;
const WAIT_N_SAMPLES: u8 = 0x61;
const WAIT_735_SAMPLES: u8 = 0x62;
const WAIT_882_SAMPLES: u8 = 0x63;
const END_OF_SOUND_DATA: u8 = 0x66;
const DATA_BLOCK: u8 = 0x67;
fn correct_header_for_version(mut header: VgmHeader) -> VgmHeader {
// correct the header for old files and calculate absolute offsets
header.eof_offset += 0x4;
if header.gd3_offset > 0x0 {
header.gd3_offset += 0x14;
}
if header.loop_offset > 0x0 {
header.loop_offset += 0x1C;
}
if header.version < 0x101 {
header.rate = 0x0;
}
if header.version < 0x110 {
header.sn_feedback = 0x9;
header.sn_stwidth = 16;
header.hz_ym2612 = header.hz_ym2413;
header.hz_ym2612 = header.hz_ym2413;
}
if header.version < 0x150 || header.data_offset == 0x0 {
header.data_offset = 0xC;
}
if header.version < 0x151 || header.data_offset <= 0xC {
header.hz_spcm = 0x00;
header.spcm_intf = 0x00;
header.hz_rf5c68 = 0x00;
header.hz_ym2203 = 0x00;
header.hz_ym2608 = 0x00;
header.hz_ym2610b = 0x00;
header.hz_ym3812 = 0x00;
header.hz_ym3526 = 0x00;
header.hz_y8950 = 0x00;
header.hz_ymf262 = 0x00;
header.hz_ymf278b = 0x00;
header.hz_ymf271 = 0x00;
header.hz_ymz280b = 0x00;
header.hz_rf5c164 = 0x00;
header.hz_pwm = 0x00;
header.hz_ay8910 = 0x00;
header.ay_type = 0x00;
header.ay_flags = 0x00;
header.ay_flags2203 = 0x00;
header.ay_flags2608 = 0x00;
}
header.data_offset += 0x34;
header
}
fn parse_vgm_header(data: &[u8]) -> Result<VgmHeader> {
let mut header = VgmHeader {
str_vgm: [0; 4],
eof_offset: 0,
version: 0,
hz_sn76489: 0,
hz_ym2413: 0,
gd3_offset: 0,
total_samples: 0,
loop_offset: 0,
loop_samples: 0,
rate: 0,
sn_feedback: 0,
sn_stwidth: 0,
sn_flags: 0,
hz_ym2612: 0,
hz_ym2151: 0,
data_offset: 0,
hz_spcm: 0,
spcm_intf: 0,
hz_rf5c68: 0,
hz_ym2203: 0,
hz_ym2608: 0,
hz_ym2610b: 0,
hz_ym3812: 0,
hz_ym3526: 0,
hz_y8950: 0,
hz_ymf262: 0,
hz_ymf278b: 0,
hz_ymf271: 0,
hz_ymz280b: 0,
hz_rf5c164: 0,
hz_pwm: 0,
hz_ay8910: 0,
ay_type: 0,
ay_flags: 0,
ay_flags2203: 0,
ay_flags2608: 0,
volume_modifier: 0,
reserved_1: 0,
loop_base: 0,
loop_modifier: 0,
hz_gb_dmg: 0,
hz_nes_apu: 0,
hz_multipcm: 0,
hz_upd7759: 0,
hz_okim6258: 0,
okim_flags: 0,
k0_flags: 0,
c140_type: 0,
reserved_2: 0,
hz_okim5295: 0,
hz_k051649: 0,
hz_k054539: 0,
hz_huc6280: 0,
hz_c140: 0,
k053260: 0,
hz_pokey: 0,
hz_qsound: 0,
hz_scsp: 0,
extra_header_offset: 0,
};
let mut pos = 0;
header.str_vgm.clone_from_slice(&data[0..=3]);
if header.str_vgm != "Vgm ".as_bytes() {
verbose!("VGM Header is incorrect.");
strict!();
}
pos += 4;
header.eof_offset = extract_u32(data, &mut pos);
if data.len() < header.eof_offset as usize {
verbose!("VGM EOF is incorrect.");
strict!();
}
header.version = extract_u32(data, &mut pos);
header.hz_sn76489 = extract_u32(data, &mut pos);
header.hz_ym2413 = extract_u32(data, &mut pos);
header.gd3_offset = extract_u32(data, &mut pos);
header.total_samples = extract_u32(data, &mut pos);
header.loop_offset = extract_u32(data, &mut pos);
header.loop_samples = extract_u32(data, &mut pos);
header.rate = extract_u32(data, &mut pos);
pos += 4; //FIXME: extract others
header.hz_ym2612 = extract_u32(data, &mut pos);
header.hz_ym2151 = extract_u32(data, &mut pos);
header.data_offset = extract_u32(data, &mut pos);
header.hz_spcm = extract_u32(data, &mut pos);
header.spcm_intf = extract_u32(data, &mut pos);
header.hz_rf5c68 = extract_u32(data, &mut pos);
header.hz_ym2203 = extract_u32(data, &mut pos);
header.hz_ym2608 = extract_u32(data, &mut pos);
header.hz_ym2610b = extract_u32(data, &mut pos);
header.hz_ym3812 = extract_u32(data, &mut pos);
header.hz_ym3526 = extract_u32(data, &mut pos);
header.hz_y8950 = extract_u32(data, &mut pos);
header.hz_ymf262 = extract_u32(data, &mut pos);
header.hz_ymf278b = extract_u32(data, &mut pos);
header = correct_header_for_version(header);
Ok(header)
}
fn extract_u32(data: &[u8], pos: &mut usize) -> u32 {
let mut dst = [0u8; 4];
dst.clone_from_slice(&data[*pos..=*pos + 3]);
*pos += 4;
u32::from_le_bytes(dst)
}
fn parse_vgm_data(data: &[u8]) -> Result<Vgm> {
Ok(Vgm {
file_header: parse_vgm_header(data)?,
data_bank: Vec::new(),
data_block_count: 0,
data_block_pos: [0; 0xFF],
gt_gd3_tag: get_gd3_header(None)?,
conversion_status_current: 0,
conversion_status_total: 0,
clock_sn76489: 0,
clock_ym2151: 0,
clock_ym2413: 0,
clock_ym2612: 0,
clock_ay8910: 0,
})
}
pub(crate) fn convert_vgm_to_mid(file_data: &[u8], config: &Config, midi: &mut MIDIShim) -> Result<()> {
let mut vgm = parse_vgm_data(file_data)?;
dbg!(&vgm.file_header);
let mut file_pos: usize;
let (mut command, mut port, mut register, mut data): (u8, u8, u8, u8);
let _detimer: f32;
let mut last_data_note: u8;
let mut data_bank_pos: usize = 0;
let temp_long: u32 = 0;
let mut last_sn76489_cmd: [u8; 2] = [0; 2];
let mut stop_vgm: bool = false;
let mut loop_pos: usize = 0;
let mut dac_wrt: bool;
let mut cur_loop: u16 = 0;
vgm.conversion_status_current = 0x0;
vgm.conversion_status_total = vgm.file_header.eof_offset - vgm.file_header.data_offset;
let clock_sn76489 = vgm.file_header.hz_sn76489 & 0x3FFFFFFF;
vgm.clock_ym2413 = vgm.file_header.hz_ym2413 & 0x3FFFFFFF;
vgm.clock_ym2612 = vgm.file_header.hz_ym2612 & 0x3FFFFFFF;
vgm.clock_ym2151 = vgm.file_header.hz_ym2151 & 0x3FFFFFFF;
let mut fsam_2612 = f64::from(vgm.clock_ym2612) / 72.0;
let mut fsam_3812 = 0.0;
vgm.clock_ay8910 = vgm.file_header.hz_ay8910;
let t6w28_sn76489 = (vgm.file_header.hz_sn76489 & 0xC0000000) == 0xC0000000;
let mut ay8910 = AY8910::new(config, None);
let mut sn76489 = SN76489::new(t6w28_sn76489, clock_sn76489, config, None);
let mut segapcm = SegaPCM::new(None);
let mut gameboy = GameBoy::new(config, None);
let mut nes_apu = NESAPU::new(config, None);
let mut ym2151 = YM2151::new(config, None);
let mut ym2413 = YM2413::new(config, None);
let mut ym2612 = YM2612::new(config, None);
let mut ym3812 = YM3812::new(config, None);
let mut ymf278 = YMF278::new(config, None);
gameboy.init(midi);
if vgm.file_header.hz_ymf278b != 0 {
ymf278.load_opl4_instrument_set()?;
}
let loop_available = vgm.file_header.loop_offset > 0;
if loop_available { loop_pos = vgm.file_header.loop_offset as usize }
//detimer = Timer;
//Call DAC_WriteOpen
last_sn76489_cmd[0x0] = 0x0;
last_sn76489_cmd[0x1] = 0x0;
dac_wrt = false;
let mut _dac_data_byte = 0x80;
last_data_note = 0xFF;
//FIXME: Erase MID_Trackdata;
//FIXME: ReDim MID_Trackdata(0): u8;
//WriteMidiTag();
midi.data_init(
vgm.clock_sn76489 != 0 || vgm.clock_ay8910 != 0,
t6w28_sn76489,
);
file_pos = vgm.file_header.data_offset as usize;
loop {
let mut wait = 0;
let mut cur_chip = 0x00u8;
command = file_data[file_pos];
if config.dualchips {
match command {
0x30 => {
cur_chip = 0x1;
command += 0x20;
},
0x3F => {
cur_chip = 0x1;
command += 0x10;
},
0xA1..=0xAC => {
cur_chip = 0x1;
command -= 0x50;
},
_ => verbose!("Invalid dual-chip command: {}", command),
}
}
match command {
GG_STEREO => {
data = file_data[file_pos + 1];
sn76489.gg_stereo_handle(data, cur_chip, midi)?;
file_pos += 2;
},
SN76489 => {
data = file_data[file_pos + 1];
sn76489.command_handle(data, cur_chip, midi)?;
file_pos += 2
},
YM2413 => {
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
ym2413.command_handle(
register,
data,
vgm.clock_ym2413,
midi,
)?;
file_pos += 3;
},
YM2612_P0 | YM2612_P1 | YM2203..=YM2610_P1 => {
//if command = YM2612_P0 {
// port = 0
//} else if command = YM2612_P1 {
// port = 3
//}
(port, fsam_2612) = match command {
YM2612_P0 | YM2612_P1 => {
ym2612.state.opn_type = OPN_TYPE_YM2612;
(command & 0x1,
f64::from(
vgm.file_header.hz_ym2612 & 0x3FFFFFFF,
) / 72.0)
},
YM2203 => {
ym2612.state.opn_type = OPN_TYPE_YM2203;
(0x0,
f64::from(
vgm.file_header.hz_ym2203 & 0x3FFFFFFF,
) / 72.0)
},
YM2608_P0 | YM2608_P1 => {
ym2612.state.opn_type = OPN_TYPE_YM2608;
(command & 0x1,
f64::from(
vgm.file_header.hz_ym2608 & 0x3FFFFFFF,
) / 72.0)
},
YM2610_P0 | YM2610_P1 => {
ym2612.state.opn_type = OPN_TYPE_YM2610;
(command & 0x1,
f64::from(
vgm.file_header.hz_ym2610b & 0x3FFFFFFF,
) / 72.0)
},
_ => bail!("Invalid register"),
};
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
ym2612.command_handle(
port * 0x3,
register,
data,
fsam_2612,
midi,
)?;
if register == YM2612_DAC {
_dac_data_byte = data;
dac_wrt = true;
}
file_pos += 3;
},
YM2151 => {
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
ym2151.command_handle(
register,
data,
midi,
)?;
file_pos += 3;
},
YM3812..=Y8950 => {
match command {
YM3812 => {
ym3812.state.opl_type = OPL_TYPE_YM3812;
fsam_3812 = f64::from(
vgm.file_header.hz_ym3812 & 0x3FFFFFFF,
) / 72.0;
},
YM3526 => {
ym3812.state.opl_type = OPL_TYPE_YM3526;
fsam_3812 = f64::from(
vgm.file_header.hz_ym3526 & 0x3FFFFFFF,
) / 72.0;
},
Y8950 => {
ym3812.state.opl_type = OPL_TYPE_Y8950;
fsam_3812 = f64::from(
vgm.file_header.hz_y8950 & 0x3FFFFFFF,
) / 72.0;
},
_ => (),
}
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
ym3812.command_handle(register as u16, data, fsam_3812, midi)?;
file_pos += 3;
},
YMF262_P0..=YMF262_P1 => {
ym3812.state.opl_type = OPL_TYPE_YMF262;
fsam_3812 =
f64::from(vgm.file_header.hz_ymf262 & 0x3FFFFFFF) / 288.0;
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
ym3812.command_handle(
(command as u16 & 0x1) << 8 | register as u16,
data,
fsam_3812,
midi,
)?;
file_pos += 3;
},
0xD0 => {
port = file_data[file_pos + 1];
register = file_data[file_pos + 2];
data = file_data[file_pos + 3];
ymf278.command_handle(
port,
register,
data,
&mut ym3812,
fsam_3812,
midi,
)?;
file_pos += 4;
},
0xB3 => {
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
gameboy.command_handle(
register,
data,
midi,
)?;
file_pos += 3;
},
0xB4 => {
if vgm.clock_sn76489 == 0 {
vgm.clock_sn76489 = 1
}
register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
nes_apu.command_handle(
register,
data,
midi,
)?;
file_pos += 3;
},
WAIT_N_SAMPLES => {
wait = (file_data[file_pos + 2] as u32) << 8;
wait |= file_data[file_pos + 1] as u32;
file_pos += 3
},
WAIT_735_SAMPLES => {
wait = 735;
file_pos += 1
},
WAIT_882_SAMPLES => {
wait = 882;
file_pos += 1;
},
END_OF_SOUND_DATA => stop_vgm = true,
DATA_BLOCK => {
//FIXME Why is this an unused assignment?
//register = file_data[file_pos + 1];
data = file_data[file_pos + 2];
wait = file_data[file_pos + 3] as u32
| (file_data[file_pos + 4] as u32) << 0x08
| (file_data[file_pos + 5] as u32) << 0x10
| (file_data[file_pos + 6] as u32) << 0x18;
file_pos += 7;
if data == 0x00 {
let previous_length = vgm.data_bank.len();
vgm.data_bank
.resize(vgm.data_bank.len() + wait as usize, 0);
for offset in 0x0..wait {
vgm.data_bank[previous_length + offset as usize] =
file_data[file_pos + offset as usize]
}
}
file_pos += TryInto::<usize>::try_into(wait).unwrap();
wait = 0;
},
0x70..=0x7F => {
wait = ((command & 0x0F) + 1).into();
file_pos += 1;
},
0x80..=0x8F => {
wait = (command & 0x0F).into();
port = 0x00;
register = YM2612_DAC;
data = vgm.data_bank[data_bank_pos];
ym2612.command_handle(port, register, data, fsam_2612, midi)?;
_dac_data_byte = data;
dac_wrt = true;
data_bank_pos += 1;
file_pos += 1;
},
AY8910 => {
port = file_data[file_pos + 1] / 0x80;
register = file_data[file_pos + 1] & 0xF;
data = file_data[file_pos + 2];
ay8910.command_handle(
port,
register,
data,
vgm.clock_ay8910,
midi,
)?;
file_pos += 3;
},
RF5C68_REG =>
// RF5C68 Register Write
{
file_pos += 3
},
SPCM_MEM => {
//FIXME: wtf?
// SegaPCM Memory Write
wait = (file_data[file_pos + 2] as u32) << 8
| file_data[file_pos + 1] as u32;
data = file_data[file_pos + 3];
segapcm.mem_write(
wait.try_into().unwrap(),
data,
midi,
)?;
wait = 0;
file_pos += 4;
},
RF5C68_MEM =>
// RF5C68 Memory Write
{
file_pos += 4
},
DATABNK_SEEK => {
data_bank_pos = file_data[file_pos + 1] as usize
| (file_data[file_pos + 2] as usize) << 0x08
| (file_data[file_pos + 3] as usize) << 0x10
| (file_data[file_pos + 4] as usize) << 0x18;
for block in 0x0..vgm.data_block_count {
if vgm.data_block_pos[block as usize] == data_bank_pos {
break;
}
}
if temp_long >= vgm.data_block_count {
vgm.data_block_pos[vgm.data_block_count as usize] =
data_bank_pos;
vgm.data_block_count += 0x01;
}
file_pos += 5;
},
0x30..=0x4E => file_pos += 2,
0x55..=0x5F => file_pos += 3,
0xA0..=0xBF => file_pos += 3,
0xC0..=0xDF => file_pos += 4,
0xE1..=0xFF => file_pos += 5,
0x90 =>
// DAC Stream Setup
{
file_pos += 5
},
0x91 =>
// Set Stream Data
{
file_pos += 5
},
0x92 =>
// Set Stream Frequency
{
file_pos += 6
},
0x93 => {
// Start Stream
data_bank_pos = file_data[file_pos + 2] as usize
| (file_data[file_pos + 3] as usize) << 8
| (file_data[file_pos + 4] as usize) << 16
| (file_data[file_pos + 5] as usize) << 24;
for data_block_pointer in 0x0..(vgm.data_block_count as usize)
{
if vgm.data_block_pos[data_block_pointer] == data_bank_pos {
break;
}
}
if temp_long >= vgm.data_block_count {
vgm.data_block_pos[vgm.data_block_count as usize] =
data_bank_pos;
vgm.data_block_count += 0x1;
}
dac_wrt = true;
file_pos += 11;
},
0x94 => {
// Stop Stream
if last_data_note < 0x80 {
midi.note_off_write(0x0F.into(), last_data_note.into(), 0x00.into());
}
last_data_note = 0xFF;
file_pos += 2
},
0x95 => {
// Start Stream (fast call)
let temp = file_data[file_pos + 2] & 0x7F;
if last_data_note < 0x80 {
midi.note_off_write(0x0F.into(), last_data_note.into(), 0x00.into());
}
midi.note_on_write(0x0F.into(), temp.into(), 0x7F.into());
last_data_note = temp;
file_pos += 5;
},
_ => {
file_pos += 1;
verbose!("Unrecognized command {} at {}", command, file_pos);
//FIXME: unrecoverable? Stop
},
}
midi.delta_time += wait;
if loop_available {
if stop_vgm {
cur_loop += 0x01;
if cur_loop < config.vgm_loops {
midi.event_write(Meta(Marker(TEXT_LOOP_END)));
stop_vgm = false;
file_pos = loop_pos;
}
}
//FIXME: If this is not aligned with an instruction, it won't loop.
if file_pos == loop_pos {
midi.event_write(Meta(Marker(TEXT_LOOP_START)));
//Call SegaPCM_Dump
}
}
if vgm.clock_sn76489 != 0 {
for counter in 0..sn76489.state.note_delay.len() {
sn76489.state.note_delay[counter] += wait;
}
}
//if config.ym2612_dac_disabled = 1 { DACWrt = false
//if vgm.clock_2612 {
//for TempLng = 0x0..=wait - 1
// Put #2, , DAC_DATA_BYTE
//}
//}
if dac_wrt {
for counter in 0x0..vgm.data_block_count {
if vgm.data_block_pos[counter as usize] == data_bank_pos - 1 {
if last_data_note < 0x80 {
midi.note_off_write(0x0F.into(), last_data_note.into(), 0x00.into());
}
midi.note_on_write(0x0F.into(), (counter as u8).into(), 0x7F.into());
last_data_note = counter.try_into().unwrap(); //FIXME: This is something fucky. Check if VB allows overflow or not.
}
}
dac_wrt = false
}
/* FIXME: if Timer >= DETimer {
// Calling DoEvents too often causes huge slowdowns
Call frmMain.tmrConversionStatus_Timer
Conversion_Status_Current = file_pos - vhFileHeader.lngDataOffset
DoEvents
DETimer = Timer + 0.2
}*/
if file_pos >= vgm.file_header.eof_offset as usize || stop_vgm {
break;
}
}
if loop_available {
midi.event_write(Meta(Marker(TEXT_LOOP_END)));
}
//Call SegaPCM_Dump
//Call DAC_WriteClose
if vgm.clock_sn76489 != 0 {
port = if vgm.file_header.hz_sn76489 & 0x40000000 != 0 {
0x1
} else {
0x0
};
for chip_number in 0x0..=port {
for command in 0x0..=0x3 {
data = (1 << 7) | (command << 5);
sn76489.command_handle(
data,
chip_number,
midi,
)?;
sn76489.command_handle(
0x00,
chip_number,
midi,
)?;
}
}
}
/*if vgm.clock_ym2413 != 0 && false {
// TODO ...
for _command in 0x0..=0x7 {
register = 0x0;
data = 0x0;
ym2413.command_handle(
register,
data,
vgm.clock_ym2413,
midi,
)?;
}
}
if vgm.clock_ym2612 != 0 && false {
// TODO ...
for command in 0x0..=0x5 {
port = command / 0x3;
register = command % 0x3;
data = 0x0;
ym2612.command_handle(port, register, data, fsam_2612, midi)?;
}
}*/
if vgm.clock_ym2151 != 0 {
for command in 0x0..=0x7 {
register = 0x8;
data = command;
ym2151.command_handle(register, data, midi)?;
}
}
Ok(())
}
use midly::num::u14;
use midly::num::u7;
pub(crate) struct FactoredState {
pub(crate) fnum_msb: [u8; 0x10],
pub(crate) fnum_lsb: [u8; 0x10],
pub(crate) fnum_1: [u32; 0x10],
pub(crate) fnum_2: [u32; 0x10],
pub(crate) hz_1: [f64; 0x10],
pub(crate) hz_2: [f64; 0x10],
pub(crate) note_1: [f64; 0x10],
pub(crate) note_2: [f64; 0x10],
pub(crate) midi_instrument: [u7; 0x10],
pub(crate) midi_note: [u7; 0x10],
pub(crate) midi_wheel: [u14; 0x10],
pub(crate) midi_volume: [u7; 0x10],
pub(crate) midi_pan: [u7; 0x10],
pub(crate) midi_mod: [u7; 0x10],
pub(crate) note_on_1: [bool; 0x10],
pub(crate) note_on_2: [bool; 0x10],
}
impl Default for FactoredState {
fn default() -> Self {
FactoredState {
fnum_msb: [0; 0x10],
fnum_lsb: [0; 0x10],
fnum_1: [0; 0x10],
fnum_2: [0; 0x10],
hz_1: [0.0; 0x10],
hz_2: [0.0; 0x10],
note_1: [0xFF.into(); 0x10],
note_2: [0xFF.into(); 0x10],
midi_instrument: [0xFF.into(); 0x10],
midi_note: [0xFF.into(); 0x10],
midi_wheel: [0x8000.into(); 0x10],
midi_volume: [0xFF.into(); 0x10],
midi_pan: [0.into(); 0x10],
midi_mod: [0xFF.into(); 0x10],
note_on_1: [false; 0x10],
note_on_2: [false; 0x10],
}
}
}
pub(crate) fn shift<T: Copy>(buf: &mut T, new: T) -> T {
let old = *buf;
*buf = new;
old
}
pub(crate) fn midi_from_note(note: f64) -> u7 {
(note.round() as u8).into()
}
#[test]
fn test_hz_to_note() {
assert!((hz_to_note(110.0) - 45.0).abs() < 0.01);
assert!((hz_to_note(220.0) - 57.0).abs() < 0.01);
assert!((hz_to_note(261.63) - 60.0).abs() < 0.01);
assert!((hz_to_note(440.0) - 69.0).abs() < 0.01);
assert!((hz_to_note(523.25) - 72.0).abs() < 0.01);
assert!((hz_to_note(880.0) - 81.0).abs() < 0.01);
assert!((hz_to_note(1760.0) - 93.0).abs() < 0.01);
}
//FIXME: Frequencies close to zero will not behave well.
pub(crate) fn hz_to_note(freq: f64) -> f64 {
if freq == 0.into() {
0xFF.into()
} else {
f64::log(freq / 440.0, f64::powf(2.0, 1.0 / 12.0)) + 69.0
}
}
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, MIDI_VOLUME_MAX,
};
use crate::strict;
use crate::utils::{hz_to_note, midi_from_note, shift, FactoredState};
use anyhow::{bail, Result};
use midly::num::u4;
pub(crate) const SN76489_LATCH: u8 = 0x80; //128
pub(crate) const SN76489_CHANNEL_SELECT: u8 = SN76489_LATCH | 0x70; //240
#[allow(clippy::identity_op)]
pub(crate) const SN76489_TONE_1: u8 = SN76489_LATCH | 0x00; //128
pub(crate) const SN76489_TONE_2: u8 = SN76489_LATCH | 0x20; //160
pub(crate) const SN76489_TONE_3: u8 = SN76489_LATCH | 0x40; //192
pub(crate) const SN76489_NOISE: u8 = SN76489_LATCH | 0x60; //224
const SN76489_CHN_MASK: u8 = 0x60; //112
#[allow(dead_code)]
const SN76489_FB_PERIODIC: u8 = 0x00;
#[allow(dead_code)]
const SN76489_FB_WHITE: u8 = 0x01;
#[allow(dead_code)]
const SN76489_CLOCK_SOURCE_HALF: u8 = 0x00;
#[allow(dead_code)]
const SN76489_CLOCK_SOURCE_FOURTH: u8 = 0x01;
#[allow(dead_code)]
const SN76489_CLOCK_SOURCE_EIGHTH: u8 = 0x02;
#[allow(dead_code)]
const SN76489_CLOCK_SOURCE_TONE_3: u8 = 0x03;
const SN76489_ATTENUATOR_1: u8 = SN76489_LATCH | 0x10; //144
const SN76489_ATTENUATOR_2: u8 = SN76489_LATCH | 0x30; //176
const SN76489_ATTENUATOR_3: u8 = SN76489_LATCH | 0x50; //208
const SN76489_ATTENUATOR_NOISE: u8 = SN76489_LATCH | 0x70; //240
#[allow(dead_code)]
const SN76489_ATTENUATION_MIN: u8 = 0x00;
const SN76489_ATTENUATION_MAX: u8 = 0x0F;
pub(crate) const MIDI_CHANNEL_SN76489_BASE: u4 = u4::new(0x0A);
// used for Note On/Off-Detection
//Public SN76489_LastVol(0x0..=0x7) As Byte
pub(crate) struct SN76489State {
feedback: [u8; 2],
clock_source: [u8; 2],
attenuation_1: [u8; 8],
attenuation_2: [u8; 8],
gg_pan: [[u8; 5]; 2],
chip_num: u8,
pub note_delay: [u32; 8],
factored: FactoredState,
last_command: [u8; 2],
}
impl Default for SN76489State {
fn default() -> Self {
SN76489State {
feedback: [0; 2],
clock_source: [0; 2],
attenuation_1: [0; 8],
attenuation_2: [0xFF; 8],
gg_pan: [[0, 0, 0, 0, 0xFF]; 2],
chip_num: 0,
note_delay: [0; 8],
factored: Default::default(),
last_command: [0x00; 2],
}
}
}
pub(crate) struct SN76489<'config> {
pub(crate) state: SN76489State,
t6w28: bool, //FIXME: dumb naming. This should probably be refactored into its own class anyway.
clock: u32,
config: &'config Config,
}
impl<'config> SN76489<'config> {
//WARNING: This won't behave well if fnum is small.
fn hz(fnum: u32, clock: u32) -> f64 {
if fnum == 0 {
0.0
} else {
(clock as f64 / 32.0) / fnum as f64
}
}
pub(crate) fn new<'c: 'config>(
t6w28: bool,
clock: u32,
config: &'c Config,
opt_state: Option<SN76489State>,
) -> Self {
SN76489 {
state: opt_state.unwrap_or_default(),
t6w28,
clock,
config,
}
}
pub(crate) fn command_handle(
&mut self,
data: u8,
chip_num: u8,
midi: &mut MIDIShim,
) -> Result<()> {
self.state.chip_num = validate_chip_number(chip_num)?;
if data & 0x80 != 0 {
let ret = match data & SN76489_CHANNEL_SELECT {
// skip and wait for data command
SN76489_TONE_1 | SN76489_TONE_2 | SN76489_TONE_3 => Ok(()),
_ => self.command_handle_internal(data, data, midi), // Volume Changes use LSB
};
self.state.last_command[self.state.chip_num as usize] = data;
ret
} else {
// do data write
self.command_handle_internal(
self.state.last_command[self.state.chip_num as usize],
data,
midi,
)
}
}
fn command_handle_internal(&mut self, msb: u8, lsb: u8, midi: &mut MIDIShim) -> Result<()> {
if msb & 0x80 == 0x0 {
return Ok(());
}
let mut channel = (msb & SN76489_CHN_MASK) / 0x20;
match channel.cmp(&0x03) {
std::cmp::Ordering::Less
if self.config.sn76489_ch_disabled[channel as usize] =>
{
return Ok(())
},
std::cmp::Ordering::Equal if self.config.sn76489_noise_disabled => {
return Ok(())
},
std::cmp::Ordering::Greater => bail!("Invalid midi channel"),
_ => (),
}
channel |= self.state.chip_num << 2;
let mut midi_channel = MIDI_CHANNEL_SN76489_BASE + (channel & 0x03).into();
if self.state.chip_num == 0x01 {
midi_channel -= MIDI_CHANNEL_SN76489_BASE
}
match msb & SN76489_CHANNEL_SELECT {
SN76489_TONE_1 | SN76489_TONE_2 | SN76489_TONE_3 => self
.process_tone_channels(
msb,
lsb,
channel.into(),
midi_channel,
midi,
)?,
SN76489_NOISE => self.process_noise_channel(lsb, channel.into()),
SN76489_ATTENUATOR_1 | SN76489_ATTENUATOR_2 | SN76489_ATTENUATOR_3 => self
.process_tone_attenuation(channel.into(), lsb, midi_channel, midi)?,
SN76489_ATTENUATOR_NOISE => self.process_noise_attenuation(
channel.into(),
lsb,
midi_channel,
midi,
),
_ => strict!("Invalid channel selected"),
}
Ok(())
}
fn process_tone_channels(
&mut self,
msb: u8,
lsb: u8,
channel_ptr: usize,
midi_channel: u4,
midi: &mut MIDIShim,
) -> Result<()> {
self.state.factored.fnum_1[channel_ptr] = shift(
&mut self.state.factored.fnum_2[channel_ptr],
((lsb as u32 & 0x3F) << 4) + (msb & 0x0F) as u32,
);
self.state.factored.hz_1[channel_ptr] = shift(
&mut self.state.factored.hz_2[channel_ptr],
Self::hz(self.state.factored.fnum_2[channel_ptr], self.clock),
);
if !(self.t6w28 && self.state.chip_num > 0x00) {
let mut temp_note = hz_to_note(self.state.factored.hz_2[channel_ptr]);
if temp_note >= 0x80.into() {
//TempNote = 0x7F - self.state.factored.fnum_2[channel]
temp_note = 0xFF.into();
}
self.state.factored.note_1[channel_ptr] =
shift(&mut self.state.factored.note_2[channel_ptr], temp_note);
if self.t6w28 {
self.state.factored.note_1[0x04 + channel_ptr] = shift(
&mut self.state.factored.note_2[0x04 + channel_ptr],
temp_note,
);
}
} else {
//if T6W28_SN76489 & SN76489_NUM > 0x0{
let mut temp_note = hz_to_note(self.state.factored.hz_2[channel_ptr] / 2.0);
if (channel_ptr & 0x03) == 0x02 {
if temp_note < 0xFF.into() {
temp_note -= 24.0; // - 2 Octaves
if temp_note >= 0x80.into() {
temp_note = 0x7F.into();
}
}
self.state.factored.note_2[0x3] = temp_note;
self.state.factored.note_2[0x7] = temp_note;
}
return Ok(());
}
if self.state.factored.fnum_2[channel_ptr] == 0 {
if self.state.factored.note_on_2[channel_ptr] {
if self.state.factored.midi_note[channel_ptr] < 0xFF {
midi.do_note_on(
self.state.factored.note_1[channel_ptr],
self.state.factored.note_2[channel_ptr],
midi_channel,
&mut self.state.factored.midi_note[channel_ptr],
&mut self.state.factored.midi_wheel[channel_ptr],
None,
None,
)?;
self.state.factored.midi_note[channel_ptr] = 0xFF.into(); // TODO: Check this
}
self.state.factored.note_on_1[channel_ptr] =
shift(&mut self.state.factored.note_on_2[channel_ptr], false);
}
} else if self.state.factored.fnum_2[channel_ptr] > 0
&& self.state.factored.fnum_2[channel_ptr]
!= self.state.factored.fnum_1[channel_ptr]
{
let dn_ret = midi.do_note_on(
self.state.factored.note_1[channel_ptr],
self.state.factored.note_2[channel_ptr],
midi_channel,
&mut self.state.factored.midi_note[channel_ptr],
&mut self.state.factored.midi_wheel[channel_ptr],
Some(if self.state.factored.note_on_2[channel_ptr] {
0x0
} else {
0xFF
}),
None,
)?;
self.state.factored.note_on_1[channel_ptr] =
shift(&mut self.state.factored.note_on_2[channel_ptr], true);
if dn_ret {
self.state.note_delay[channel_ptr] = 0x0;
}
}
if self.t6w28 {
self.tone_t6w28(channel_ptr, midi_channel, midi)?;
}
if (channel_ptr & 0x3) == 0x2
&& self.state.clock_source[self.state.chip_num as usize] == 0x3
{
self.command_handle_internal(
0xE0,
(self.state.feedback[self.state.chip_num as usize] << 2)
| self.state.clock_source[self.state.chip_num as usize],
midi,
)?;
}
Ok(())
}
fn tone_t6w28(&mut self, mut channel: usize, mut midi_channel: u4, midi: &mut MIDIShim) -> Result<()> {
channel += 0x04;
midi_channel -= MIDI_CHANNEL_SN76489_BASE;
if self.state.factored.note_2[channel] == 0xFF.into() {
if self.state.factored.note_on_2[channel] {
if self.state.factored.midi_note[channel] < 0xFF {
midi.do_note_on(
self.state.factored.note_1[channel],
self.state.factored.note_2[channel],
midi_channel,
&mut self.state.factored.midi_note[channel],
&mut self.state.factored.midi_wheel[channel],
None,
None,
)?;
//self.state.factored.midi_note[channel] = 0xFF
}
self.state.factored.note_on_1[channel] =
shift(&mut self.state.factored.note_on_2[channel], false);
}
} else if self.state.factored.note_2[channel] < 0xFF.into()
&& self.state.factored.note_2[channel]
!= self.state.factored.note_1[channel]
{
let dn_ret = midi.do_note_on(
self.state.factored.note_1[channel],
self.state.factored.note_2[channel],
midi_channel,
&mut self.state.factored.midi_note[channel],
&mut self.state.factored.midi_wheel[channel],
Some(if self.state.factored.note_on_2[channel] {
0x0
} else {
0xFF
}),
None,
)?;
self.state.factored.note_on_1[channel] =
shift(&mut self.state.factored.note_on_2[channel], true);
if dn_ret {
self.state.note_delay[channel] = 0x00;
}
}
Ok(())
}
fn process_tone_attenuation(
&mut self,
channel: usize,
lsb: u8,
midi_channel: u4,
midi: &mut MIDIShim,
) -> Result<()> {
self.state.attenuation_1[channel] = shift(
&mut self.state.attenuation_2[channel],
lsb & SN76489_ATTENUATION_MAX,
);
if self.state.attenuation_2[channel] != self.state.attenuation_1[channel] {
//self.state.attenuation_2[channel] = (LSB & SN76489_ATTENUATION_MAX) * 8.45 //(127 / 15)
self.state.attenuation_2[channel] = lsb & SN76489_ATTENUATION_MAX; // I like round values
//self.state.factored.midi_volume[0] = MIDI_VOLUME_MAX + 1 - 0x8 - self.state.attenuation_2[channel] * 0x8
//if self.state.factored.midi_volume[0] > 0x7F{ self.state.factored.midi_volume[0] = 0x7F
self.state.factored.midi_volume[0] =
db_to_midi_vol(Self::vol_to_db(lsb & 0x0F));
midi.controller_write(
midi_channel,
MIDI_VOLUME,
self.state.factored.midi_volume[0],
);
// write Note On/Off
if self.config.sn76489_voldep_notes >= 1
&& self.state.factored.note_on_2[channel]
{
if self.state.factored.midi_volume[0] == 0 {
midi.do_note_on(
self.state.factored.note_1[channel],
0xFF.into(),
midi_channel,
&mut self.state.factored.midi_note[channel],
&mut self.state.factored.midi_wheel[channel],
Some(0xFF),
None,
)?;
self.state.note_delay[channel] = 44100;
//} else if (SN76489_LastVol[channel] = 0x0 & SN76489_NoteDelay[channel] >= 10) | _
// (self.config.sn76489_voldep_notes >= 0x2 & SN76489_NoteDelay[channel] >= 735 &
// SN76489_LastVol[channel] + 20 < self.state.factored.midi_volume[0]) {
} else if (self.state.attenuation_1[channel] == 0x0F
&& self.state.note_delay[channel] >= 10) || (self
.config
.sn76489_voldep_notes
>= 0x2
&& self.state.note_delay[channel] >= 735
&& self.state.attenuation_1[channel] - 2
> self.state.attenuation_2[channel])
{
midi.do_note_on(
self.state.factored.note_1[channel],
self.state.factored.note_2[channel],
midi_channel,
&mut self.state.factored.midi_note[channel],
&mut self.state.factored.midi_wheel[channel],
Some(0xFF),
None,
)?;
self.state.note_delay[channel] = 0;
}
}
//SN76489_LastVol[channel] = self.state.factored.midi_volume[0]
}
Ok(())
}
fn process_noise_attenuation(
&mut self,
channel_ptr: usize,
lsb: u8,
midi_channel: u4,
midi: &mut MIDIShim,
) {
self.state.attenuation_1[channel_ptr] = shift(
&mut self.state.attenuation_2[channel_ptr],
lsb & SN76489_ATTENUATION_MAX,
);
self.state.factored.midi_volume[0] = self.calculate_noise_volume(channel_ptr);
if self.state.attenuation_2[channel_ptr]
!= self.state.attenuation_1[channel_ptr]
|| self.state.note_delay[channel_ptr] >= 735
{
if self.state.factored.midi_volume[0] > 0 {
// old Note-Height: 39
if self.state.factored.note_1[channel_ptr] < 0xFF.into() {
midi.note_off_write(
midi_channel,
midi_from_note(
self.state.factored.note_1
[channel_ptr],
),
0x00.into(),
);
}
if self.state.factored.note_2[channel_ptr] < 0xFF.into() {
midi.note_on_write(
midi_channel,
midi_from_note(
self.state.factored.note_2
[channel_ptr],
),
self.state.factored.midi_volume[0],
);
}
self.state.factored.note_1[channel_ptr] =
self.state.factored.note_2[channel_ptr];
} else if self.state.factored.midi_volume[0] == 0 && self.state.attenuation_1[channel_ptr] < 0x0F && self.state.factored.note_1[channel_ptr]
< 0xFF.into() {
midi.note_off_write(
midi_channel,
midi_from_note(
self.state.factored.note_1
[channel_ptr],
),
0x00.into(),
);
self.state.factored.note_1[channel_ptr] = 0xFF.into();
}
self.state.note_delay[channel_ptr] = 0x0;
}
}
fn calculate_noise_volume(&mut self, channel_ptr: usize) -> midly::num::u7 {
(MIDI_VOLUME_MAX.as_int() + 1 - 8 - (self.state.attenuation_2[channel_ptr] * 8)).into()
}
fn process_noise_channel(&mut self, lsb: u8, channel_ptr: usize) {
self.state.feedback[self.state.chip_num as usize] = (lsb & 0x04) >> 2; //FIXME: Is this supposed to be a boolean?
self.state.clock_source[self.state.chip_num as usize] = lsb & 0x03;
// Noise-Frequency
let noise_frequency = self.calculate_noise_frequency(channel_ptr);
self.state.factored.fnum_1[channel_ptr] =
shift(&mut self.state.factored.fnum_2[channel_ptr], noise_frequency);
if self.state.factored.fnum_2[channel_ptr] == 0 {
self.state.factored.fnum_2[channel_ptr] = 1
}
self.state.factored.hz_1[channel_ptr] = shift(
&mut self.state.factored.hz_2[channel_ptr],
Self::hz(self.state.factored.fnum_2[channel_ptr], self.clock),
);
//if T6W28_SN76489 & SN76489_NUM > 0x0{ return }
self.state.factored.note_2[channel_ptr] =
hz_to_note(self.state.factored.hz_2[channel_ptr]) / 1.5;
//SN76489_NoteDelay[channel] = 0x0
}
fn calculate_noise_frequency(&mut self, channel: usize) -> u32 {
if self.state.clock_source[self.state.chip_num as usize] == 0x3 {
self.state.factored.fnum_2[channel - 1] << 1
} else {
1 << (5 + self.state.clock_source[self.state.chip_num as usize] as u32)
}
}
pub(crate) fn gg_stereo_handle(
&mut self,
register: u8,
chip_num: u8,
midi: &mut MIDIShim,
) -> Result<()> {
self.state.chip_num = validate_chip_number(chip_num)?;
let mut pan_val: u8;
let mut channel: u4;
for cur_bit in 0x0..=0x3 {
let channel_mask = 1 << cur_bit;
pan_val = 0x0;
if (register & (channel_mask << 4)) != 0 {
pan_val |= 0x01; // Left Channel On
}
if (register & channel_mask) != 0 {
pan_val |= 0x02; // Right Channel On
}
if self.state.gg_pan[self.state.chip_num as usize][cur_bit as usize]
!= pan_val || self.state.gg_pan[self.state.chip_num as usize][0x4]
== register
{
//if CurBit = 0x0{
// CH = CHN_DAC
//} else {
channel = MIDI_CHANNEL_SN76489_BASE + cur_bit.into();
if self.state.chip_num == 0x1 {
channel -= MIDI_CHANNEL_SN76489_BASE
}
//}
match pan_val {
0x1 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_LEFT,
),
0x2 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_RIGHT,
),
0x3 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_CENTER,
),
_ => strict!("Illegal pan_val"),
};
}
self.state.gg_pan[self.state.chip_num as usize][cur_bit as usize] = pan_val
}
self.state.gg_pan[self.state.chip_num as usize][0x4] = register;
Ok(())
}
pub(crate) fn vol_to_db(tl: u8) -> f64 {
if tl < 0x0F {
-f64::from(tl) * 2.0
} else {
-400.0 // results in volume 0
}
}
}
fn validate_chip_number(num: u8) -> Result<u8> {
match num {
0x00 => Ok(0x00),
0x01 => Ok(0x01),
_ => bail!("Illegal chip number"),
}
}
/*register_structs! {
YMF278Registers {
(0x00 => lsi_test: [ReadWrite<u8>; 0x02]),
(0x02 => header: ReadWrite<u8, >),
(0x03 => memory_access1: ReadWrite<u8, MemoryAccess1::Register>),
(0x04 => memory_access2: ReadWrite<u8>),
(0x05 => memory_access3: ReadWrite<u8>),
(0x06 => memory_data: ReadWrite<u8>),
(0x07 => reserved: ReadWrite<u8>),
(0x08 => wave_table_number: [ReadWrite<u8>; 0x18]),
(0x20 => f_number: [ReadWrite<u8, FNumber::Register>; 0x18]),
(0x38 => octave: [ReadWrite<u8, Octave::Register>; 0x18]),
(0x50 => total_level: [ReadWrite<u8, TotalLevel::Register>; 0x18]),
(0x68 => key_on: [ReadWrite<u8, KeyOn::Register>; 0x18]),
(0x80 => lfo: [ReadWrite<u8, LFO::Register>; 0x18]),
(0x98 => ar: [ReadWrite<u8, AR::Register>; 0x18]),
(0xB0 => dl: [ReadWrite<u8, DL::Register>; 0x18]),
(0xC8 => rate_correction: [ReadWrite<u8, RateCorrection::Register>; 0x18]),
(0xE0 => am: [ReadWrite<u8, AM::Register>; 0x18]),
(0xF8 => mixing_control_fm: ReadWrite<u8, MixingControlFM::Register>),
(0xF9 => mixing_control_pcm: ReadWrite<u8, MixingControlPCM::Register>),
(0xFA => @END),
}
}
register_bitfields![u8,
Status [
IRQ 7,
FT1 6,
FT2 5,
LD 1,
BUSY 0,
],
MemoryAccess1 [
RESERVED OFFSET(7) NUMBITS(2) [],
MEMORYACCESS OFFSET(0) NUMBITS(6) [],
],
FNumber [
F_NUMBER OFFSET(1) NUMBITS(7) [],
WAVETABLE OFFSET(0) NUMBITS(1) [],
],
Octave [
OCTAVE OFFSET(4) NUMBITS(4) [],
PSEUDO_REVERB OFFSET(3) NUMBITS (1) [],
F_NUMBER OFFSET(0) NUMBITS(3) [],
],
TotalLevel [
TOTAL_LEVEL OFFSET(1) NUMBITS(7) [],
LEVEL_DIRECT OFFSET(0) NUMBITS(1) [],
],
KeyOn [
KEY_ON OFFSET(7) NUMBITS(1) [],
DAMP OFFSET(6) NUMBITS(1) [],
LFO_RST OFFSET(5) NUMBITS(1) [],
CH OFFSET(4) NUMBITS(1) [],
PANPOT OFFSET(0) NUMBITS(4) [],
],
LFO [
RESERVED OFFSET(6) NUMBITS(2) [],
LFO OFFSET(3) NUMBITS(3) [],
VIB OFFSET(0) NUMBITS(3) [],
],
AR [
AR OFFSET(3) NUMBITS(4) [],
D1R OFFSET(0) NUMBITS(4) [],
],
DL [
DL OFFSET(3) NUMBITS(4) [],
D2R OFFSET(0) NUMBITS(4) [],
],
RateCorrection [
Rate_Correction OFFSET(3) NUMBITS(4) [],
RR OFFSET(3) NUMBITS(4) [],
],
AM [
RESERVED OFFSET(3) NUMBITS(5) [],
AM OFFSET(0) NUMBITS(3) [],
],
MixingControlFM [
FM_R OFFSET(3) NUMBITS(3) [],
FM_L OFFSET(0) NUMBITS(3) [],
],
MixingControlPCM [
PCM_R OFFSET(3) NUMBITS(3) [],
PCM_L OFFSET(0) NUMBITS(3) [],
],
];*/
use crate::midi_shim::MIDIShim;
use crate::midi_shim::{
MIDI_DATA_ENTRY_MSB, MIDI_NRPN_LSB, MIDI_NRPN_MSB, MIDI_PITCHWHEEL_CENTER, NRPN_DRUM_PAN,
};
use crate::strict;
use anyhow::{bail, Result};
pub(crate) struct SegaPCMState {
ram: [u8; 0x800],
chn_addr: [u32; 0x10],
chn_vol_l: [u8; 0x10],
chn_vol_r: [u8; 0x10],
chn_note: [u8; 0x10],
last_pan: [u8; 0x10],
}
impl Default for SegaPCMState {
fn default() -> Self {
SegaPCMState {
ram: [0; 0x800],
chn_addr: [0; 0x10],
chn_vol_l: [0; 0x10],
chn_vol_r: [0; 0x10],
chn_note: [0; 0x10],
last_pan: [0; 0x10],
}
}
}
pub(crate) struct SegaPCM {
state: SegaPCMState,
}
impl SegaPCM {
pub(crate) fn new(opt_state: Option<SegaPCMState>) -> Self {
Self {
state: opt_state.unwrap_or_default(),
}
}
pub(crate) fn mem_write(
&mut self,
offset: u16,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
if offset > 0x7FF {
strict!("sega_pcm_mem_write offset is larger than 0x7FF");
}
let masked_offset = offset & 0x7FF;
let channel = ((masked_offset / 0x8) & 0xF) as u8;
let rel_offset = masked_offset & (!0x78);
self.state.ram[masked_offset as usize] = data;
match rel_offset {
0x86 => {
channel_on_off(data, midi, &mut self.state, channel);
},
0x4 => {
set_audio_address_low(&mut self.state, channel, data);
},
0x5 => {
set_audio_address_time(&mut self.state, channel, data, midi)?;
},
0x84 => (), // Set Loop Address Low
0x85 => (), // Set Loop Address High
0x6 => (), // Set End Address
0x7 => {
set_sample_delta_time(midi);
},
0x2 => {
set_volume_l(&mut self.state, channel, data);
},
0x3 => {
set_volume_r(&mut self.state, channel, data);
},
_ => (), // Write Offset ##, Data ##
}
Ok(())
}
//FIXME: Not called.
pub(crate) fn dump(&mut self) -> Result<()> {
match std::fs::read("SegaPCM.dmp") {
Ok(mut vec) => {
vec.resize(self.state.ram.len(), 0);
self.state.ram.copy_from_slice(vec.as_slice());
Ok(())
},
Err(_) => bail!("Failed when attempting to read SegaPCM dump."), //FIXME: error handling
}
}
}
fn set_volume_r(state: &mut SegaPCMState, channel: u8, data: u8) {
// Set Volume R
state.chn_vol_r[channel as usize] = data
//midi.event_write(Midi { channel: Channel, message: Controller { controller: MIDI_VOLUME | 0x20, value: TempByt }});
}
fn set_volume_l(state: &mut SegaPCMState, channel: u8, data: u8) {
// Set Volume L
state.chn_vol_l[channel as usize] = data
//midi.event_write(Midi { channel: Channel, message: Controller { controller: MIDI_VOLUME, value: TempByt }});
}
fn set_sample_delta_time(midi: &mut MIDIShim) {
// Set Sample Delta Time
midi.pitch_bend_write(0x09.into(), MIDI_PITCHWHEEL_CENTER);
}
fn set_audio_address_time(
state: &mut SegaPCMState,
channel: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
// Set Audio Address High
state.chn_addr[channel as usize] =
(state.chn_addr[channel as usize] & 0xFF) | (data as u32) << 8;
let nrpn_data = match state.chn_addr[channel as usize] {
0x0 => 0x0,
0x437C => 0x38,
0x49DE => 0x1C,
0x302F => 0x2A,
0x17C0 | 0x172F => 0x2E,
0x90 => 0x26,
0xF0 => 0x2D,
0x29AB => 0x24,
0x5830 => 0x27,
0x1C03 => 0x30,
0x951 => 0x2B,
0x3BE6 => 0x31,
0x4DA0 => 0x45,
0xC1D => 0x28,
0x6002 => 0x33,
//FIXME: why was this commented 0x6700 => 0x7F,
0x82 => 0x60,
0x2600 => 0x61,
0x4A => 0x62,
0x5600 => 0x63,
0x4000 => 0x65,
0x2D6 => 0x67,
0x158A => 0x68,
0xC04 => 0x69,
0x1CD0 => 0x6A,
0x218E => 0x6B,
0x1E34 => 0x6C,
0xE31 => 0x6D,
0x89B => 0x6E,
0x27B => 0x6F,
0x4EA => 0x70,
0x3600 => 0x71,
0x1800 => 0x72,
0x18BA => 0x73,
0x173 => 0x74,
0x204F => 0x75,
0xAB => 0x76,
0x201D => 0x77,
0x20AB => 0x78,
0x2000 => 0x79,
0x2 => 0x7A,
0x1D => 0x7B,
0x2F => 0x7C,
0x26AB => 0x7D,
0x262F => 0x7E,
_ => {
strict!(
"Bad SegaPCM channel address for channel {}: {}",
channel,
state.chn_addr[channel as usize]
);
0x7F
},
};
midi.note_off_write(0x09.into(), state.chn_note[channel as usize].into(), 0x00.into());
let mut note_vol = state.chn_vol_l[channel as usize] + state.chn_vol_r[channel as usize];
let temp_byte = if note_vol > 0 {
(state.chn_vol_r[channel as usize] / (note_vol / 2)) * 0x40
} else {
0x40
};
if state.last_pan[channel as usize] != temp_byte {
midi.controller_write(0x09.into(), MIDI_NRPN_MSB, NRPN_DRUM_PAN);
midi.controller_write(0x09.into(), MIDI_NRPN_LSB, nrpn_data.into());
midi.controller_write(0x09.into(), MIDI_DATA_ENTRY_MSB, temp_byte.into());
state.last_pan[channel as usize] = temp_byte;
}
//midi.event_write(Midi { channel: 0x9, message: Controller { controller: 0x20, value: Channel }});
if note_vol == 0x0 {
note_vol = 0x1
}
//Data = Data - Channel
if note_vol <= 0x40 {
note_vol = 0x40 + note_vol / 2
}
midi.note_on_write(0x09.into(), data.into(), note_vol.into());
//midi.event_write(Midi { channel: 0x9, message: NoteOff { key: Data, vel: 0x0 }});
state.chn_note[channel as usize] = data;
Ok(())
}
fn set_audio_address_low(state: &mut SegaPCMState, channel: u8, data: u8) {
// Set Audio Address Low
state.chn_addr[channel as usize] =
(state.chn_addr[channel as usize] & 0xFF00) | data as u32;
}
fn channel_on_off(data: u8, midi: &mut MIDIShim, state: &mut SegaPCMState, channel: u8) {
// Channel On/Off
//OnOff = Data & 0x1
if data & 0x1 != 0 {
midi.note_off_write(0x09.into(), state.chn_note[channel as usize].into(), 0x00.into());
state.chn_note[channel as usize] = 0x0;
}
}
use crate::config::Config;
use crate::midi_shim::{db_to_midi_vol, lin_to_db, MIDIShim, MIDI_BANK_SELECT, MIDI_VOLUME};
use crate::sn76489::MIDI_CHANNEL_SN76489_BASE;
use crate::strict;
use crate::utils::shift;
use crate::utils::{hz_to_note, FactoredState};
use crate::vgm2mid::CHN_DAC;
use anyhow::Result;
use midly::MidiMessage::PitchBend;
use midly::TrackEventKind::Midi;
const APU_WRA0: u8 = 0x0;
const APU_WRA1: u8 = 0x1;
const APU_WRA2: u8 = 0x2;
const APU_WRA3: u8 = 0x3;
const APU_WRB0: u8 = 0x4;
const APU_WRB1: u8 = 0x5;
const APU_WRB2: u8 = 0x6;
const APU_WRB3: u8 = 0x7;
const APU_WRC0: u8 = 0x8;
const APU_WRC2: u8 = 0xA;
const APU_WRC3: u8 = 0xB;
const APU_WRD0: u8 = 0xC;
const APU_WRD2: u8 = 0xE;
const APU_WRD3: u8 = 0xF;
const APU_WRE0: u8 = 0x10;
const APU_WRE1: u8 = 0x11;
const APU_WRE2: u8 = 0x12;
#[allow(dead_code)]
const APU_WRE3: u8 = 0x13;
const APU_SMASK: u8 = 0x15;
#[allow(dead_code)]
const APU_IRQCTRL: u8 = 0x17;
// N2A03 clock: 21 477 270 / 12 = 1 789 772.5
//N2A03 clock / 16
const NES_CLK_BASE: f64 = 111860.78125;
fn hz_nes(fnum: u32) -> f64 {
NES_CLK_BASE / f64::from(fnum + 1)
}
fn hz_nesnoise(freq_mode: u32) -> f64 {
let fnum = [
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 2046,
][(1 + freq_mode) as usize];
NES_CLK_BASE / f64::from(fnum + 1)
}
pub(crate) struct NESAPUState {
envelope_1: [u8; 4],
envelope_2: [u8; 4],
vblen_1: [u8; 4],
vblen_2: [u8; 4],
hold: [u8; 4],
tri_len: u8,
duty_1: [u8; 4],
duty_2: [u8; 4],
note_en_1: [u8; 4],
note_en_2: [u8; 4],
note_delay: [u32; 8],
factored: FactoredState,
}
impl Default for NESAPUState {
fn default() -> Self {
NESAPUState {
envelope_1: [0; 4],
envelope_2: [0xFF; 4],
vblen_1: [0; 4],
vblen_2: [0; 4],
hold: [0; 4],
tri_len: 0,
duty_1: [0; 4],
duty_2: [0xFF; 4],
note_en_1: [0; 4],
note_en_2: [0, 0, 4, 0],
note_delay: [0; 8],
factored: Default::default(),
}
}
}
pub(crate) struct NESAPU<'config> {
state: NESAPUState,
config: &'config Config,
}
impl<'config> NESAPU<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<NESAPUState>) -> Self {
Self {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn init(&mut self, midi: &mut MIDIShim) {
midi.controller_write(
MIDI_CHANNEL_SN76489_BASE + 0x02.into(),
MIDI_BANK_SELECT,
0x08.into(),
);
midi.program_change_write(MIDI_CHANNEL_SN76489_BASE + 0x02.into(), 0x50.into()); //FIXME: Magic values.
midi.program_change_write(MIDI_CHANNEL_SN76489_BASE + 0x03.into(), 0x7F.into());
}
pub(crate) fn command_handle(
&mut self,
register: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = register / 0x4;
if channel < 0x3 {
if self.config.sn76489_ch_disabled[channel as usize] {
return Ok(());
}
} else if channel == 0x3 {
if self.config.sn76489_noise_disabled {
return Ok(());
}
} else if channel == 0x4 && self.config.ym2612_dac_disabled {
return Ok(());
}
let mut midi_channel = if channel == 0x4 {
CHN_DAC
} else {
MIDI_CHANNEL_SN76489_BASE + channel.into()
};
match register {
APU_WRA0 | APU_WRB0 | APU_WRD0 => {
// Volume, Envelope, Hold, Duty Cycle
if (register == APU_WRA0 || register == APU_WRB0)
&& (data & 0xF) > 0x0
{
self.state.duty_1[channel as usize] =
self.state.duty_2[channel as usize];
self.state.duty_2[channel as usize] = (data & 0xC0) / 0x40;
if self.state.duty_1[channel as usize]
!= self.state.duty_2[channel as usize]
{
let temp_byte = 0x4F
+ (!self.state.duty_2[channel as usize]
& 0x3);
midi.program_change_write(
midi_channel,
temp_byte.into(),
);
}
}
self.state.envelope_1[channel as usize] =
self.state.envelope_2[channel as usize];
// output is 1 * envelope
self.state.envelope_2[channel as usize] = data & 0xF;
self.state.hold[channel as usize] = data & 0x20;
if self.state.envelope_1[channel as usize]
!= self.state.envelope_2[channel as usize]
{
if self.state.envelope_2[channel as usize] == 0x0 {
midi.note_off_write(
midi_channel,
self.state.factored.midi_note
[channel as usize],
0x00.into(),
);
self.state.factored.midi_note[channel as usize] =
0xFF.into();
self.state.note_delay[channel as usize] = 10000;
}
self.state.factored.midi_volume[0] =
db_to_midi_vol(lin_to_db(
self.state.envelope_2[channel as usize]
as f64 / 0xF as f64,
));
midi.controller_write(
midi_channel,
MIDI_VOLUME,
self.state.factored.midi_volume[0],
);
}
},
APU_WRC0 => {
self.state.hold[channel as usize] = data & 0x80;
let temp_byte = data & 0x7F;
if temp_byte != self.state.tri_len {
self.state.tri_len = temp_byte;
self.state.note_en_1[channel as usize] =
self.state.note_en_2[channel as usize];
self.state.note_en_2[channel as usize] =
(self.state.note_en_2[channel as usize] & !0x4)
| (self.state.tri_len & 0x4);
if self.state.note_en_1[channel as usize]
!= self.state.note_en_2[channel as usize] && (self
.state
.note_en_2[channel as usize]
& 0x3)
== 0x3
{
if self.state.note_en_2[channel as usize] & 0x4 != 0
{
midi.do_note_on(
self.state.factored.note_1
[channel as usize],
self.state.factored.note_2
[channel as usize],
midi_channel,
&mut self.state.factored.midi_note
[channel as usize],
&mut self.state.factored.midi_wheel
[channel as usize],
Some(255),
None,
)?;
self.state.note_delay[channel as usize] = 0;
} else if self.state.factored.midi_note
[channel as usize] != 0xFF
{
// Note got silenced by setting TriLen = 0
midi.note_off_write(
midi_channel,
self.state.factored.midi_note
[channel as usize],
0x00.into(),
);
self.state.factored.midi_note
[channel as usize] = 0xFF.into();
self.state.note_delay[channel as usize] =
10000;
}
}
}
},
APU_WRA1 | APU_WRB1 => (), // Sweep
//FIXME: impossible to do in this version of vgm2mid
APU_WRA2 | APU_WRB2 | APU_WRC2 | APU_WRA3 | APU_WRB3 | APU_WRC3 => {
if (register & 0x3) == 0x2 {
self.state.factored.fnum_lsb[channel as usize] = data;
//Exit Sub
} else if (register & 0x3) == 0x3 {
self.state.factored.fnum_msb[channel as usize] = data;
self.state.vblen_1[channel as usize] =
self.state.vblen_2[channel as usize];
if self.state.hold[channel as usize] != 0 {
self.state.vblen_2[channel as usize] = 0xFF;
} else {
self.state.vblen_2[channel as usize] =
(data & 0xF8) / 0x8;
}
self.state.note_en_1[channel as usize] =
self.state.note_en_2[channel as usize];
self.state.note_en_2[channel as usize] =
(self.state.note_en_2[channel as usize] & !0x2)
| self.state.vblen_2[channel as usize]
& 0x2;
if self.state.note_en_1[channel as usize]
!= self.state.note_en_2[channel as usize] && (self
.state
.note_en_2[channel as usize]
& 0x5)
== 0x5 && self.state.note_en_2[channel as usize] & 0x2 == 0 && self.state.factored.midi_note
[channel as usize] != 0xFF {
// Note got silenced by setting VBLen = 0
midi.note_off_write(
midi_channel,
self.state.factored.midi_note
[channel as usize],
0x00.into(),
);
self.state.factored.midi_note
[channel as usize] = 0xFF.into();
self.state.note_delay[channel as usize] =
10000;
}
}
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
& 0x7) << 8) | 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_nes(self.state.factored.fnum_2[channel as usize]);
if channel == 0x2 {
self.state.factored.hz_2[channel as usize] /= 2.0;
}
let mut temp_note =
hz_to_note(self.state.factored.hz_2[channel as usize]);
if temp_note >= 0x80.into() {
//TempNote = 0x7F - self.state.factored.fnum_2[channel as usize]
temp_note = 0x7F.into();
}
self.state.factored.note_1[channel as usize] =
self.state.factored.note_2[channel as usize];
self.state.factored.note_2[channel as usize] = temp_note;
if (self.state.note_en_2[channel as usize] & 0x7) == 0x7 {
if (register & 0x3) == 0x3
&& self.state.note_delay[channel as usize] > 10
{
// writing to register 3 restarts the notes
midi.do_note_on(
self.state.factored.note_1
[channel as usize],
self.state.factored.note_2
[channel as usize],
midi_channel,
&mut self.state.factored.midi_note
[channel as usize],
&mut self.state.factored.midi_wheel
[channel as usize],
Some(255),
None,
)?;
self.state.note_delay[channel as usize] = 0;
} else if 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],
midi_channel,
&mut self.state.factored.midi_note
[channel as usize],
&mut self.state.factored.midi_wheel
[channel as usize],
None,
None,
)? {
self.state.note_delay[channel as usize] = 0;
}
}
},
APU_WRD2 => {
// Noise Freq
self.state.factored.fnum_lsb[channel as usize] = data;
self.state.factored.fnum_1[channel as usize] = shift(
&mut self.state.factored.fnum_2[channel as usize],
(self.state.factored.fnum_lsb[channel as usize] & 0xF)
.into(),
);
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_nesnoise(self.state.factored.fnum_2[channel as usize]);
let mut temp_note =
hz_to_note(self.state.factored.hz_2[channel as usize]);
if temp_note >= 0x80.into() {
//TempNote = 0x7F - self.state.factored.fnum_2[channel as usize]
temp_note = 0x7F.into();
}
self.state.factored.note_1[channel as usize] =
self.state.factored.note_2[channel as usize];
self.state.factored.note_2[channel as usize] = temp_note;
if (self.state.note_en_2[channel as usize] & 0x7) == 0x7 && 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],
midi_channel,
&mut self.state.factored.midi_note
[channel as usize],
&mut self.state.factored.midi_wheel
[channel as usize],
None,
None,
)? {
self.state.note_delay[channel as usize] = 0;
}
},
APU_WRD3 => {
self.state.vblen_1[channel as usize] =
self.state.vblen_2[channel as usize];
if self.state.hold[channel as usize] != 0 {
self.state.vblen_2[channel as usize] = 1;
} else {
self.state.vblen_2[channel as usize] = (data & 0xF8) / 0x8;
}
self.state.note_en_1[channel as usize] =
self.state.note_en_2[channel as usize];
self.state.note_en_2[channel as usize] =
(self.state.note_en_2[channel as usize] & !0x2)
| (self.state.vblen_2[channel as usize]) & 0x2;
if self.state.note_en_1[channel as usize]
!= self.state.note_en_2[channel as usize] && (self.state.note_en_2[channel as usize] & 0x5) == 0x5 && !self.state.note_en_2[channel as usize] & 0x2 != 0 && self.state.factored.midi_note[channel as usize]
!= 0xFF {
// Note got silenced by setting VBLen = 0
midi.note_off_write(
midi_channel,
self.state.factored.midi_note
[channel as usize],
0x00.into(),
);
self.state.factored.midi_note[channel as usize] =
0xFF.into();
self.state.note_delay[channel as usize] = 10000;
}
if (self.state.note_en_2[channel as usize] & 0x7) == 0x7
&& self.state.note_delay[channel as usize] > 10
{
// writing to register 3 restarts the notes
midi.do_note_on(
self.state.factored.note_2[channel as usize],
self.state.factored.note_2[channel as usize],
midi_channel,
&mut self.state.factored.midi_note
[channel as usize],
&mut self.state.factored.midi_wheel
[channel as usize],
Some(255),
None,
)?;
self.state.note_delay[channel as usize] = 0;
}
},
APU_WRE0 => {
// IRQ, Looping, Frequency
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_lsb[channel as usize] & 0xF)
.into();
if self.state.factored.fnum_1[channel as usize]
!= self.state.factored.fnum_2[channel as usize]
{
self.state.factored.midi_wheel[channel as usize] =
((self.state.factored.fnum_2[channel as usize]
<< 10) as u16)
.into(); //(0x4000 / 0x10)
midi.event_write(Midi {
channel: midi_channel,
message: PitchBend {
bend: midly::PitchBend(
self.state.factored.midi_wheel
[channel as usize],
),
},
});
}
},
APU_WRE1 => {
midi.note_on_write(
midi_channel,
(data & 0x7F).into(),
0x7F.into(),
);
midi.note_off_write(
midi_channel,
(data & 0x7F).into(),
0x00.into(),
);
},
APU_WRE2 => {
self.state.factored.fnum_msb[channel as usize] = data;
self.state.factored.fnum_1[channel as usize] =
self.state.factored.fnum_2[channel as usize];
self.state.factored.note_2[channel as usize] =
(self.state.factored.fnum_msb[channel as usize] & 0x7F)
.into();
// note is activated by setting the DAC bit in APU_SMASK
},
APU_SMASK => {
for channel in 0..=0x4 {
let temp_byte = 2 ^ channel;
midi_channel = if channel == 0x4 {
CHN_DAC
} else {
MIDI_CHANNEL_SN76489_BASE + channel.into()
};
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] =
(data & temp_byte) != 0;
self.state.note_en_1[channel as usize] =
self.state.note_en_2[channel as usize];
self.state.note_en_2[channel as usize] =
(self.state.note_en_2[channel as usize] & !0x1)
| self.state.factored.note_on_2
[channel as usize] as u8;
if self.state.note_en_1[channel as usize]
!= self.state.note_en_2[channel as usize]
{
if (self.state.note_en_2[channel as usize] & 0x7)
== 0x7
{
midi.do_note_on(
self.state.factored.note_2
[channel as usize],
self.state.factored.note_2
[channel as usize],
midi_channel,
&mut self.state.factored.midi_note
[channel as usize],
&mut self.state.factored.midi_wheel
[channel as usize],
Some(255),
None,
)?;
self.state.note_delay[channel as usize] = 0;
} else if self.state.factored.midi_note
[channel as usize] != 0xFF
{
// Note got silenced via Channel Mask
midi.note_off_write(
midi_channel,
self.state.factored.midi_note
[channel as usize],
0x00.into(),
);
self.state.factored.midi_note
[channel as usize] = 0xFF.into();
self.state.note_delay[channel as usize] =
10000;
}
}
}
},
_ => strict!(),
}
Ok(())
}
}
use crate::config::Config;
use crate::{strict, verbose};
use crate::vgm2mid::CHN_DAC;
use crate::vgm2mid::PITCHWHEEL_SENSITIVITY_DEFAULT;
use anyhow::{anyhow, Result};
use midly::num::{u14, u4, u7};
use midly::MetaMessage::Tempo;
use midly::MidiMessage::{Controller, NoteOff, NoteOn, PitchBend, ProgramChange};
use midly::TrackEventKind::{Meta, Midi};
use midly::{Smf, TrackEvent, TrackEventKind};
use std::path::Path;
/*Public Type DACHeaderData
RIFF As String * 4
HeaderLen As Long
WAVEfmt As String * 8
HeaderSize As Long
Tag: u16
Channels: u16
SampleRate As Long
BytespSecond As Long
BytespSample: u16
SampelBits: u16
Data As String * 4
DataLen As Long
End Type*/
//MIDI Controller Numbers
#[allow(dead_code)]
pub(crate) const MIDI_BANK_SELECT: u7 = u7::new(0x00);
pub(crate) const MIDI_MODULATOR_WHEEL: u7 = u7::new(0x01);
pub(crate) const MIDI_DATA_ENTRY_MSB: u7 = u7::new(0x06);
pub(crate) const MIDI_VOLUME: u7 = u7::new(0x07);
pub(crate) const MIDI_PAN: u7 = u7::new(0x0A);
#[allow(dead_code)]
pub(crate) const MIDI_SUSTAIN: u7 = u7::new(0x40);
#[allow(dead_code)]
pub(crate) const MIDI_SOFT: u7 = u7::new(0x43);
#[allow(dead_code)]
pub(crate) const MIDI_LEGATO_PEDAL: u7 = u7::new(0x44);
#[allow(dead_code)]
pub(crate) const MIDI_HOLD2_PEDAL: u7 = u7::new(0x45);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_TIMBRE: u7 = u7::new(0x47);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_RELEASE_TIME: u7 = u7::new(0x48);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_ATTACK_TIME: u7 = u7::new(0x49);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_BRIGHTNESS: u7 = u7::new(0x4A);
pub(crate) const MIDI_NRPN_LSB: u7 = u7::new(0x62);
pub(crate) const MIDI_NRPN_MSB: u7 = u7::new(0x63);
pub(crate) const MIDI_RPN_LSB: u7 = u7::new(0x64);
pub(crate) const MIDI_RPN_MSB: u7 = u7::new(0x65);
#[allow(dead_code)]
pub(crate) const MIDI_ALL_SOUNDS_OFF: u7 = u7::new(0x78);
#[allow(dead_code)]
pub(crate) const MIDI_RESET_ALL_CONTROLLERS: u7 = u7::new(0x79);
#[allow(dead_code)]
pub(crate) const MIDI_ALL_NOTES_OFF: u7 = u7::new(0x7B);
/*
Public Enum MIDIControllers
mcModulatorWheel = 1
mcDataEntry = 6
mcVolume = 7
mcPan = 10
mcSustain = 64
mcLegatoPedal = 68
mcHold2Pedal = 69
mcSoundTimbre = 71
mcSoundReleaseTime = 72
mcSoundAttackTime = 73
mcSoundBrightness = 74
mcRPNFine = 100
mcRPNCoarse = 101
mcAllControllersOff = 123
mcAllNotesOff = 123
End Enum*/
// Loop-Strings (compatible with WinAmp)
pub(crate) const TEXT_LOOP_START: &[u8] = "loopStart".as_bytes();
pub(crate) const TEXT_LOOP_END: &[u8] = "loopEnd".as_bytes();
// When I tested the Space Harrier BIOS-VGM, I found a bug in Winamp.
// if the MIDI has the text "loopStart", looping works correctly (jump to 0:13)
// if the text has another case (like "LoopStart" or "Loopstart") or even a
// space ("Loop Start") it jumps to 2:34. I can//t say why.
/*Public Enum MIDIMetaEvent
mmeTrackEnd = 0x2F
End Enum*/
//RPNs
pub(crate) const RPN_PITCH_BEND_RANGE_M: u7 = u7::new(0x0);
pub(crate) const RPN_PITCH_BEND_RANGE_L: u7 = u7::new(0x0);
// NRPNs
pub(crate) const NRPN_DRUM_PITCH_COARSE: u7 = u7::new(0x18);
#[allow(dead_code)]
pub(crate) const NRPN_DRUM_PITCH_FINE: u7 = u7::new(0x19);
#[allow(dead_code)]
pub(crate) const NRPN_DRUM_VOLUME: u7 = u7::new(0x1A);
pub(crate) const NRPN_DRUM_PAN: u7 = u7::new(0x1C);
////AWE32/SBLive! NRPNs
//pub(crate) const NRPN_ENV1_DELAY: u8 = 16260
//pub(crate) const NRPN_ENV1_ATTACK: u8 = 16261
//pub(crate) const NRPN_ENV1_HOLD: u8 = 16262
//pub(crate) const NRPN_ENV1_DECAY: u8 = 16263
//pub(crate) const NRPN_ENV1_SUSTAIN: u8 = 16264
//pub(crate) const NRPN_ENV1_RELEASE: u8 = 16265
//Public Enum MIDINRPN
// mnEnv1DDelay = 16260
// mnEnv1Attack = 16261
// mnEnv1Hold = 16262
// mnEnvDecay = 16263
// mnEnvSustain = 16264
// mnEnvRelease = 16265
//End Enum
// MIDI Controller Values
#[allow(dead_code)]
pub(crate) const MIDI_VOLUME_MIN: u7 = u7::new(0x00);
pub(crate) const MIDI_VOLUME_MAX: u7 = u7::new(0x7F);
#[allow(dead_code)]
pub(crate) const MIDI_VELOCITY_MIN: u7 = u7::new(0x00);
#[allow(dead_code)]
pub(crate) const MIDI_VELOCITY_MAX: u7 = u7::new(0x7F);
pub(crate) const MIDI_PAN_LEFT: u7 = u7::new(0x00);
pub(crate) const MIDI_PAN_RIGHT: u7 = u7::new(0x7F);
pub(crate) const MIDI_PAN_CENTER: u7 = u7::new(0x40);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_DOWN_ONE_SEMITONE: u14 = u14::new(0x0);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_DOWN_TWO_SEMITONES: u14 = u14::new(0x1000);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_DOWN_HALF_SEMITIONE: u14 = u14::new(0x1800);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_MIN: u14 = u14::new(0x0);
pub(crate) const MIDI_PITCHWHEEL_CENTER: u14 = u14::new(0x2000);
pub(crate) const MIDI_PITCHWHEEL_MAX: u14 = u14::new(0x3FFF);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_UP_HALF_SEMITIONE: u14 = u14::new(0x2800);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_UP_ONE_SEMITONE: u14 = u14::new(0x3000);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_UP_TWO_SEMITONES: u14 = u14::new(0x3FFF);
/*Public Enum MIDIControllerValue
//Volume
mcvVolumeMin = 0x0
mcvVolumeMax = 0x7F
//Pan
mcvPanLeft = 0x0
mcvPanRight = 0x7F
mcvPanCenter = 0x40
//PitchWheel
mcvPitchWheelCenter = 0x2000
End Enum*/
//General MIDI Patch Names
pub(crate) const MIDI_PATCH_ACOUSTIC_GRAND_PIANO: u7 = u7::new(0);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BRIGHT_ACOUSTIC_PIANO: u7 = u7::new(1);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_GRAND_PIANO: u7 = u7::new(2);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_HONKY_TONK_PIANO: u7 = u7::new(3);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_RHODES_PIANO: u7 = u7::new(4);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CHORUSED_PIANO: u7 = u7::new(5);
pub(crate) const MIDI_PATCH_HARPSICHORD: u7 = u7::new(6);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CLAVINET: u7 = u7::new(7);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CELESTA: u7 = u7::new(8);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_GLOCKENSPIEL: u7 = u7::new(9);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MUSIC_BOX: u7 = u7::new(10);
pub(crate) const MIDI_PATCH_VIBRAPHONE: u7 = u7::new(11);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MARIMBA: u7 = u7::new(12);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_XYLOPHONE: u7 = u7::new(13);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TUBULAR_BELLS: u7 = u7::new(14);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_DULCIMER: u7 = u7::new(15);
pub(crate) const MIDI_PATCH_HAMMOND_ORGAN: u7 = u7::new(16);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PERCUSSIVE_ORGAN: u7 = u7::new(17);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ROCK_ORGAN: u7 = u7::new(18);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CHURCH_ORGAN: u7 = u7::new(19);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_REED_ORGAN: u7 = u7::new(20);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ACCORDION: u7 = u7::new(21);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_HARMONICA: u7 = u7::new(22);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TANGO_ACCORDION: u7 = u7::new(23);
pub(crate) const MIDI_PATCH_ACOUSTIC_GUITAR_NYLON: u7 = u7::new(24);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ACOUSTIC_GUITAR_STEEL: u7 = u7::new(25);
pub(crate) const MIDI_PATCH_ELECTRIC_GUITAR_JAZZ: u7 = u7::new(26);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_GUITAR_CLEAN: u7 = u7::new(27);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_GUITAR_MUTED: u7 = u7::new(28);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_OVERDRIVEN_GUITAR: u7 = u7::new(29);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_DISTORTION_GUITAR: u7 = u7::new(30);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_GUITAR_HARMONICS: u7 = u7::new(31);
pub(crate) const MIDI_PATCH_ACOUSTIC_BASS: u7 = u7::new(32);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_BASS_FINGER: u7 = u7::new(33);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_BASS_PICK: u7 = u7::new(34);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FRETLESS_BASS: u7 = u7::new(35);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SLAP_BASS_1: u7 = u7::new(36);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SLAP_BASS_2: u7 = u7::new(37);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_BASS_1: u7 = u7::new(38);
pub(crate) const MIDI_PATCH_SYNTH_BASS_2: u7 = u7::new(39);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_VIOLIN: u7 = u7::new(40);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_VIOLA: u7 = u7::new(41);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CELLO: u7 = u7::new(42);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CONTRABASS: u7 = u7::new(43);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TREMOLO_STRINGS: u7 = u7::new(44);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PIZZICATO_STRINGS: u7 = u7::new(45);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ORCHESTRAL_HARP: u7 = u7::new(46);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TIMPANI: u7 = u7::new(47);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_STRING_ENSEMBLE_1: u7 = u7::new(48);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_STRING_ENSEMBLE_2: u7 = u7::new(49);
pub(crate) const MIDI_PATCH_SYNTH_STRINGS_1: u7 = u7::new(50);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_STRINGS_2: u7 = u7::new(51);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CHOIR_AHHS: u7 = u7::new(52);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_VOICE_OOHS: u7 = u7::new(53);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_VOICE: u7 = u7::new(54);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ORCHESTRA_HIT: u7 = u7::new(55);
pub(crate) const MIDI_PATCH_TRUMPET: u7 = u7::new(56);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TROMBONE: u7 = u7::new(57);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TUBA: u7 = u7::new(58);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MUTED_TRUMPET: u7 = u7::new(59);
pub(crate) const MIDI_PATCH_FRENCH_HORN: u7 = u7::new(60);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BRASS_SECTION: u7 = u7::new(61);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_BRASS_1: u7 = u7::new(62);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_BRASS_2: u7 = u7::new(63);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SOPRANO_SAX: u7 = u7::new(64);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ALTO_SAX: u7 = u7::new(65);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TENOR_SAX: u7 = u7::new(66);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BARITONE_SAX: u7 = u7::new(67);
pub(crate) const MIDI_PATCH_OBOE: u7 = u7::new(68);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ENGLISH_HORN: u7 = u7::new(69);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BASSOON: u7 = u7::new(70);
pub(crate) const MIDI_PATCH_CLARINET: u7 = u7::new(71);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PICCOLO: u7 = u7::new(72);
pub(crate) const MIDI_PATCH_FLUTE: u7 = u7::new(73);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_RECORDER: u7 = u7::new(74);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAN_FLUTE: u7 = u7::new(75);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BOTTLE_BLOW: u7 = u7::new(76);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SHAKUHACHI: u7 = u7::new(77);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_WHISTLE: u7 = u7::new(78);
pub(crate) const MIDI_PATCH_OCARINA: u7 = u7::new(79);
pub(crate) const MIDI_PATCH_LEAD_1_SQUARE: u7 = u7::new(80);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_2_SAWTOOTH: u7 = u7::new(81);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_3_CALLIOPE: u7 = u7::new(82);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_4_CHIFF: u7 = u7::new(83);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_5_CHANGARANG: u7 = u7::new(84);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_6_VOICE: u7 = u7::new(85);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_7_FIFTHS: u7 = u7::new(86);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_8_BASS_LEAD: u7 = u7::new(87);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_1_NEW_AGE: u7 = u7::new(88);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_2_WARM: u7 = u7::new(89);
pub(crate) const MIDI_PATCH_PAD_3_POLYSYNTH: u7 = u7::new(90);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_4_CHOIR: u7 = u7::new(91);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_5_BOWED: u7 = u7::new(92);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_6_METALLIC: u7 = u7::new(93);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_7_HALO: u7 = u7::new(94);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_8_SWEEP: u7 = u7::new(95);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_1_RAIN: u7 = u7::new(96);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_2_SOUNDTRACK: u7 = u7::new(97);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_3_CRYSTAL: u7 = u7::new(98);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_4_ATMOSPHERE: u7 = u7::new(99);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_5_BRIGHTNESS: u7 = u7::new(100);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_6_GOBLINS: u7 = u7::new(101);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_7_ECHOES: u7 = u7::new(102);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_8_SCI_FI: u7 = u7::new(103);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SITAR: u7 = u7::new(104);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BANJO: u7 = u7::new(105);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SHAMISEN: u7 = u7::new(106);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_KOTO: u7 = u7::new(107);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_KALIMBA: u7 = u7::new(108);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BAGPIPE: u7 = u7::new(109);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FIDDLE: u7 = u7::new(110);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SHANAI: u7 = u7::new(111);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TINKLE_BELL: u7 = u7::new(112);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_AGOGO: u7 = u7::new(113);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_STEEL_DRUMS: u7 = u7::new(114);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_WOODBLOCK: u7 = u7::new(115);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TAIKO_DRUM: u7 = u7::new(116);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MELODIC_TOM: u7 = u7::new(117);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_DRUM: u7 = u7::new(118);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_REVERSE_CYMBAL: u7 = u7::new(119);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_GUITAR_FRET_NOISE: u7 = u7::new(120);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BREATH_NOISE: u7 = u7::new(121);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SEASHORE: u7 = u7::new(122);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BIRD_TWEET: u7 = u7::new(123);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TELEPHONE_RING: u7 = u7::new(124);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_HELICOPTER: u7 = u7::new(125);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_APPLAUSE: u7 = u7::new(126);
pub(crate) const MIDI_PATCH_GUNSHOT: u7 = u7::new(127);
pub(crate) struct MIDIShim<'smf, 'config> {
delta_factor: f64,
pub delta_time: u32,
written_delay_position: u32, //FIXME: bad naming
current_sample_position: u32,
midi_file: Smf<'smf>,
config: &'config Config,
}
impl<'smf, 'config> MIDIShim<'smf, 'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config) -> Result<MIDIShim> {
let delta_fact =
22050.0 / f64::from(config.cnv_accuracy) * config.get_tempo_modifier();
let mut midi = Smf::new(midly::Header {
format: midly::Format::SingleTrack,
timing: midly::Timing::Metrical(config.cnv_accuracy.into()),
});
midi.tracks.push(Vec::new());
Ok(MIDIShim {
delta_factor: delta_fact,
delta_time: 0,
written_delay_position: 0,
current_sample_position: 0,
midi_file: midi,
config,
})
}
pub(crate) fn controller_write(&mut self, channel: u4, controller: u7, value: u7) {
self.event_write(Midi {
channel,
message: Controller { controller, value },
})
}
pub(crate) fn note_on_write(&mut self, channel: u4, note: u7, velocity: u7) {
self.event_write(Midi {
channel,
message: NoteOn {
key: note,
vel: velocity,
},
});
}
pub(crate) fn note_off_write(&mut self, channel: u4, note: u7, velocity: u7) {
self.event_write(Midi {
channel,
message: NoteOff {
key: note,
vel: velocity,
},
});
}
pub(crate) fn program_change_write(&mut self, channel: u4, program: u7) {
self.event_write(Midi {
channel,
message: ProgramChange { program },
});
}
pub(crate) fn pitch_bend_write(&mut self, channel: u4, pitchbend: u14) {
self.event_write(Midi {
channel,
message: PitchBend {
bend: midly::PitchBend(pitchbend),
},
});
}
pub(crate) fn event_write<'event: 'smf>(&mut self, event_kind: TrackEventKind<'event>) {
self.current_sample_position += self.delta_time;
self.delta_time = 0;
let temp_long = (self.current_sample_position as f64 / self.delta_factor + 0.5)
.round() as u32;
let delta_delay = temp_long - self.written_delay_position;
self.written_delay_position += delta_delay;
let event = TrackEvent {
delta: delta_delay.into(),
kind: event_kind,
};
self.midi_file.tracks[0].push(event);
}
pub(crate) fn write_smf(&mut self, out_path: &Path) -> Result<()> {
verbose!("Writing smf to {}", out_path.display());
self.event_write(midly::TrackEventKind::Meta(midly::MetaMessage::EndOfTrack));
self.midi_file.save(out_path).map_err(|err| anyhow!(err))
}
//TODO: Move initialization in to the respective modules to encapsulate their state.
pub(crate) fn data_init(&mut self, psg_on: bool, t6w28_sn76489: bool) {
let tempo_val = 500000.0 * self.config.get_tempo_modifier();
self.event_write(Meta(Tempo((tempo_val.round() as u32).into())));
// I think there's no need to init the instruments for the FM Channels.
// Most things are done before playing the first note.
// Pan (Stereo) Settings for FM CHs 1-9 (Ch 3 Special)
for channel in 0..=8 {
self.controller_write(channel.into(), MIDI_PAN, MIDI_PAN_CENTER);
}
// Program Changes for FM CHs 1-9 (Ch 3 Special)
//for CH in 0..=8 {
// midi.event_write(MIDI_PROGRAM_CHANGE, CH, MIDI_PATCH_Lead_8_Bass_Lead);
//Next
// Initial Volume Levels for FM CHs 1-9 (Ch 3 Special)
//for CH in 0..=8 {
// midi.event_write(Midi { channel: CH, message: Controller { controller: MIDI_VOLUME, value: 95 }});
//Next
// Change Pitch Wheel Sensitivity for All CHs (Ch 3 Special)
if self.config.pitchwheel_sensitivity != PITCHWHEEL_SENSITIVITY_DEFAULT {
for channel in 0x0..=0xF {
if channel == 9 {
// Setting the PB Range for Ch 9 (Drum-Channel) is unnecessary
continue;
}
self.controller_write(
channel.into(),
MIDI_RPN_MSB,
RPN_PITCH_BEND_RANGE_M,
);
self.controller_write(
channel.into(),
MIDI_RPN_LSB,
RPN_PITCH_BEND_RANGE_L,
);
self.controller_write(
channel.into(),
MIDI_RPN_MSB,
self.config.pitchwheel_sensitivity.into(),
);
}
}
// Settings for Drum Channel
self.controller_write(CHN_DAC, MIDI_PAN, MIDI_PAN_CENTER);
self.program_change_write(CHN_DAC, 0x00.into());
self.controller_write(CHN_DAC, MIDI_VOLUME, MIDI_VOLUME_MAX);
if psg_on {
// Pan (Stereo) Settings for SN76489 CHs 1-3 and Noise CH
for channel in 10..=13 {
if !t6w28_sn76489 {
self.controller_write(
channel.into(),
MIDI_PAN,
MIDI_PAN_CENTER,
);
} else {
self.controller_write(
(channel - 10).into(),
MIDI_PAN,
MIDI_PAN_RIGHT,
);
self.controller_write(
channel.into(),
MIDI_PAN,
MIDI_PAN_LEFT,
);
}
}
// Program Changes for SN76489 CHs 1-3 and Noise CH
for channel in 10..=12 {
if t6w28_sn76489 {
self.program_change_write(
(channel - 10).into(),
MIDI_PATCH_LEAD_1_SQUARE,
);
}
self.program_change_write(channel.into(), MIDI_PATCH_LEAD_1_SQUARE);
}
if t6w28_sn76489 {
self.program_change_write(0x03.into(), MIDI_PATCH_GUNSHOT);
}
self.program_change_write(0x0D.into(), MIDI_PATCH_GUNSHOT);
// Initial Volume Levels for SN76489 CHs 1-3 and Noise CH
for channel in 10..=13 {
if t6w28_sn76489 {
self.controller_write(
channel.into(),
MIDI_VOLUME,
0x7F.into(),
);
}
self.controller_write(channel.into(), MIDI_VOLUME, 0x7F.into());
}
}
}
pub(crate) fn do_note_on(
&mut self,
note_1: f64,
note_2: f64,
midi_channel: u4,
midi_note: &mut u7,
midi_wheel: &mut u14,
opt_note_type: Option<u8>,
opt_note_velocity: Option<u7>,
) -> Result<bool> {
if note_1 < 0.0 || note_2 < 0.0 {
strict!("Notes below 0") //FIXME: Handle this as an error.
}
let note_velocity = opt_note_velocity.unwrap_or(u7::new(0x7F));
let note_type = opt_note_type.unwrap_or(0);
// the returned value ist used to detect written notes
// (and avoid notes of Length 0)
if note_type == 0xFF || note_1 == 0xFF as f64 || note_2 == 0xFF as f64 {
return self.do_note_internal(
note_2,
midi_channel,
midi_note,
midi_wheel,
note_velocity,
);
}
let fuzzy_wheel = f64::from(midi_wheel.as_int())
+ ((note_2 - note_1) * self.config.pitchwheel_steps as f64);
//if Abs(Note_2 - Note_1) >= 0.5 {
// dblTestValue = -1
//}
if fuzzy_wheel >= MIDI_PITCHWHEEL_MIN.as_int() as f64
&& fuzzy_wheel <= (MIDI_PITCHWHEEL_MAX.as_int() + 1) as f64
{
*midi_wheel = (fuzzy_wheel.round() as u16)
.clamp(MIDI_PITCHWHEEL_MIN.as_int(), MIDI_PITCHWHEEL_MAX.as_int())
.into(); //FIXME: This doesn't seem sane
self.pitch_bend_write(midi_channel, *midi_wheel);
} else {
return self.do_note_internal(
note_2,
midi_channel,
midi_note,
midi_wheel,
note_velocity,
);
}
Ok(false) // only PitchBend written
}
fn do_note_internal(
&mut self,
mut note_2: f64,
midi_channel: u4,
midi_note: &mut u7,
midi_wheel: &mut u14,
note_velocity: u7,
) -> Result<bool> {
if note_2 >= f64::from(0xFF) {
if *midi_note < 0xFF {
self.note_off_write(midi_channel, *midi_note, 0x00.into());
*midi_note = (note_2.clamp(0.0, 0xFF.into()).round() as u8).into();
}
return Ok(true);
}
let mut dbl_pitch_wheel: f64 = note_2 - note_2.trunc();
note_2 = note_2.trunc().abs();
if dbl_pitch_wheel >= 0.5 {
note_2 += 1.0;
dbl_pitch_wheel -= -1.0;
}
if *midi_note < 0xFF {
self.note_off_write(midi_channel, *midi_note, 0x00.into());
}
let new_wheel = (MIDI_PITCHWHEEL_CENTER.as_int() as f64
+ dbl_pitch_wheel * f64::from(self.config.pitchwheel_steps))
.round() as u16;
if new_wheel != *midi_wheel {
*midi_wheel = new_wheel.into();
if !(midi_channel == CHN_DAC && dbl_pitch_wheel.abs() < 0.1) {
self.pitch_bend_write(midi_channel, *midi_wheel);
}
}
*midi_note = (note_2.round() as u8).into();
self.note_on_write(midi_channel, *midi_note, note_velocity);
Ok(true) // wrote new Note
}
}
//FIXME: automatic casting nonsense transliterated from VB
pub(crate) fn db_to_midi_vol(db: f64) -> u7 {
((f64::powf(10.0, db / 40.0) * (0x7F as f64)) as u8).into()
}
pub(crate) fn lin_to_db(linear_val: f64) -> f64 {
if linear_val > 0.0 {
f64::log2(linear_val) * 6.0
} else {
-400.0 // results in volume 0
}
}
mod cli;
use vgm_rs::config;
use vgm_rs::convert_to_mid;
use vgm_rs::FileType;
use anyhow::{Context, Result};
use flate2::bufread::GzDecoder;
use once_cell::sync::OnceCell;
use std::io::Read;
use std::path::Path;
fn main() -> Result<()> {
let (in_path, out_path, config_path, file_type, verbose, strict) = cli::cli()?;
let config = config::load_config_or_default(config_path.as_deref())?;
convert_to_mid(
&in_path,
file_type,
&out_path,
&config,
)
}
pub mod ay8910;
pub mod config;
pub mod gameboy;
pub mod gd3;
pub mod gym;
pub mod midi_shim;
pub mod nesapu;
pub mod pcm;
pub mod sn76489;
pub mod utils;
pub mod vgm;
pub mod vgm2mid;
pub mod ym2151;
pub mod ym2413;
pub mod ym2612;
pub mod ym3812;
pub mod ymf278;
use crate::gym::convert_gym_to_mid;
use crate::vgm::convert_vgm_to_mid;
use crate::config::Config;
use anyhow::{Context, Result};
use flate2::bufread::GzDecoder;
use std::io::Read;
use std::path::Path;
use once_cell::sync::OnceCell;
pub(crate) static STRICT: OnceCell<bool> = OnceCell::new();
pub(crate) static VERBOSE: OnceCell<bool> = OnceCell::new();
#[macro_export]
macro_rules! strict {
() => {
if $crate::STRICT.get().map(|r| *r).unwrap_or(false) {
anyhow::bail!("Strict violation.");
}
};
($($arg:tt)*) => {
{
$crate::verbose!($($arg)*);
if $crate::STRICT.get().map(|r| *r).unwrap_or(false) {
anyhow::bail!("Strict violation.");
}
}
};
}
#[macro_export]
macro_rules! verbose {
($($arg:tt)*) => {
if $crate::VERBOSE.get().map(|r| *r).unwrap_or(false) {
std::println!($($arg)*);
}
}
}
pub enum FileType {
Gym,
Vgm,
Vgz,
}
pub fn convert_to_mid(
in_path: &Path,
file_type: FileType,
out_path: &Path,
config: &Config,
) -> Result<()> {
let _metadata = std::fs::metadata(in_path)
.with_context(|| format!("Failed to load file metadata: {}", in_path.display()))?;
let data = std::fs::read(in_path)
.with_context(|| format!("Failed to load data from file: {}", in_path.display()))?;
let mut midi = midi_shim::MIDIShim::new(config)?;
match file_type {
FileType::Gym => convert_gym_to_mid(data.as_slice(), config, &mut midi),
FileType::Vgm => convert_vgm_to_mid(data.as_slice(), config, &mut midi),
FileType::Vgz => {
let mut buf = Vec::<u8>::new();
GzDecoder::new(data.as_slice()).read_to_end(&mut buf)?;
convert_vgm_to_mid(buf.as_slice(), config, &mut midi)
},
}?;
midi.write_smf(out_path)?;
Ok(())
}
use crate::midi_shim::MIDIShim;
use crate::sn76489::{SN76489, SN76489_TONE_1, SN76489_TONE_2, SN76489_TONE_3};
use crate::ym2612::YM2612;
use crate::config::Config;
use anyhow::{bail, Result};
// GYM Command Constants
const DELAY_166MS: u8 = 0;
const YM2612_PORT0: u8 = 1;
const YM2612_PORT1: u8 = 2;
const YM2612_PSG: u8 = 3;
struct GYMHeader {
str_gymx: [u8; 4],
}
fn parse_gym_file_header(file_data: &[u8]) -> Result<Option<GYMHeader>> {
if file_data.len() < 428 {
return Ok(None);
}
let mut header = GYMHeader { str_gymx: [0; 4] };
if &file_data[0..4] != "GYMX".as_bytes() {
return Ok(None);
} else {
header.str_gymx.clone_from_slice(&file_data[0..4]);
}
Ok(Some(header))
}
pub(crate) fn convert_gym_to_mid(
file_data: &[u8],
config: &Config,
midi: &mut MIDIShim,
) -> Result<()> {
let _file_header = parse_gym_file_header(file_data)?;
let mut file_pos = 0_usize;
let register = 0_u8;
let mut _conversion_status_current = 0;
let _conversion_status_total = file_data.len();
let _mid_trackdata: Vec<u8> = Vec::new();
let _mid_file_pos = 0;
let clock_sn76489 = 3579545;
let clock_ym2612 = 3579545;
let fsam_2612 = clock_ym2612 as f64 / 72.0;
let mut ym2612 = YM2612::new(config, None);
let mut sn76489 = SN76489::new(false, clock_sn76489, config, None);
let t6w28_sn76489 = false;
midi.data_init(true, t6w28_sn76489);
//DAC_Pos = 0
//ReDim DAC_Data(0)
//let DelayTime: i64
//let Ttime: f64;
//let Ttime: f64 = 0;
loop {
let switch = file_data[file_pos];
match switch {
DELAY_166MS => {
process_delay(midi, &mut sn76489, &mut file_pos);
},
YM2612_PORT0 | YM2612_PORT1 => {
process_ym2612(
switch,
file_data,
&mut file_pos,
&mut ym2612,
register,
fsam_2612,
midi,
)?;
},
YM2612_PSG => {
process_sn76489(file_data, &mut file_pos, &mut sn76489, midi)?;
},
_ => file_pos += 1,
}
_conversion_status_current = file_pos;
if file_pos >= file_data.len() {
break;
}
}
Ok(())
}
fn process_delay(midi: &mut MIDIShim, sn76489: &mut SN76489, file_pos: &mut usize) {
midi.delta_time += 735;
for channel in 0x0..=0x2 {
sn76489.state.note_delay[channel] += 735;
}
*file_pos += 1;
}
fn process_ym2612(
switch: u8,
file_data: &[u8],
file_pos: &mut usize,
ym2612: &mut YM2612,
register: u8,
fsam_2612: f64,
midi: &mut MIDIShim,
) -> Result<()> {
let port = match switch {
YM2612_PORT0 => 0,
YM2612_PORT1 => 3,
_ => bail!("Pattern matched to a number other YM2612_PORT0 or YM2612_PORT1. This should never occur.")
};
let data = file_data[*file_pos + 2];
//if bytRegister != YM2612_DAC { _
//
ym2612.command_handle(port, register, data, fsam_2612, midi)?;
*file_pos += 3;
//if bytRegister = YM2612_DAC {
// ReDim Preserve DAC_Data(DAC_Pos)
// DAC_Data(DAC_Pos) = bytData
// DAC_Pos = DAC_Pos + 1
// DelayTime = 0
//}
Ok(())
}
fn process_sn76489(
file_data: &[u8],
file_pos: &mut usize,
sn76489: &mut SN76489,
midi: &mut MIDIShim,
) -> Result<()> {
let sn76489_msb = file_data[*file_pos + 1];
let mut sn76489_lsb = 0;
match sn76489_msb & 240 {
SN76489_TONE_1 | SN76489_TONE_2 | SN76489_TONE_3 => {
sn76489_lsb = file_data[*file_pos + 3];
*file_pos += 4;
},
_ => *file_pos += 2,
}
sn76489.command_handle(sn76489_msb, 0, midi)?;
sn76489.command_handle(sn76489_lsb, 0, midi)?;
Ok(())
}
use crate::{strict, verbose};
use anyhow::{bail, Result};
use utf16string::{WString, LE};
#[allow(dead_code)]
pub(crate) struct Gd3Header {
str_gd3: [u8; 4],
version: u32,
tag_length: u32,
track_name_e: WString<LE>,
track_name_j: WString<LE>,
game_name_e: WString<LE>,
game_name_j: WString<LE>,
system_name_e: WString<LE>,
system_name_j: WString<LE>,
author_name_e: WString<LE>,
author_name_j: WString<LE>,
release_date: WString<LE>,
creator: WString<LE>,
notes: WString<LE>,
}
pub(crate) fn get_gd3_header(opt_data: Option<&[u8]>) -> Result<Gd3Header> {
let mut gd3_header = Gd3Header {
str_gd3: [0; 4],
version: 0,
tag_length: 0,
track_name_e: "".into(),
track_name_j: "".into(),
game_name_e: "".into(),
game_name_j: "".into(),
system_name_e: "".into(),
system_name_j: "".into(),
author_name_e: "".into(),
author_name_j: "".into(),
release_date: "".into(),
creator: "".into(),
notes: "".into(),
};
let data = match opt_data {
Some(d) => d,
None => {
return Ok(gd3_header);
},
};
if data.len() <= 0x22 {
// 4 x 3 + 2 x 11
bail!("GD3 data is too small.");
}
let mut _cur_pos = 0;
gd3_header.str_gd3.clone_from_slice(&data[0..3]);
if gd3_header.str_gd3 != "gd3 ".as_bytes() {
verbose!("Invalid GD3 header.");
strict!();
}
gd3_header.version = u32::from_le_bytes(data[4..7].try_into()?);
gd3_header.tag_length = u32::from_le_bytes(data[8..11].try_into()?);
//gd3_header.track_name_e = WString::from_utf16(data.chunks(2).take_while(|&slice| slice != [0, 0]).copied().collect())?;
Ok(gd3_header)
}
use crate::ay8910::MIDI_CHANNEL_PSG_BASE;
use crate::config::Config;
use crate::midi_shim::{
db_to_midi_vol, lin_to_db, MIDIShim, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT,
MIDI_PAN_RIGHT, MIDI_VOLUME,
};
use crate::strict;
use crate::utils::{hz_to_note, shift, FactoredState};
use anyhow::Result;
use midly::num::u4;
#[allow(dead_code)]
const NR10: u8 = 0x0;
const NR11: u8 = 0x1;
const NR12: u8 = 0x2;
const NR13: u8 = 0x3;
const NR14: u8 = 0x4;
const NR21: u8 = 0x6;
const NR22: u8 = 0x7;
const NR23: u8 = 0x8;
const NR24: u8 = 0x9;
const NR30: u8 = 0xA;
const NR31: u8 = 0xB;
const NR32: u8 = 0xC;
const NR33: u8 = 0xD;
const NR34: u8 = 0xE;
const NR41: u8 = 0x10;
const NR42: u8 = 0x11;
const NR43: u8 = 0x12;
const NR44: u8 = 0x13;
#[allow(dead_code)]
const NR50: u8 = 0x14;
const NR51: u8 = 0x15;
#[allow(dead_code)]
const NR52: u8 = 0x16;
fn hz_game_boy(fnum: u32) -> f64 {
131072.0 / f64::from(2048 - fnum)
}
fn hz_game_boy_noise(poly_cntr: u8) -> f64 {
let mut freq_div = f64::from(poly_cntr & 0x7); // Division Ratio of Freq
// Shift Clock Freq (poly cntr)
if freq_div == 0.0 {
freq_div = 0.5
}
let shft_frq: u16 = ((poly_cntr & 0xF0) / 0x10).into();
524288.0 / freq_div / (2 ^ (shft_frq + 0x1)) as f64
}
pub(crate) struct GameBoyState {
envelope_1: [u8; 4],
envelope_2: [u8; 4],
duty_1: [u8; 4],
duty_2: [u8; 4],
gb_pan: [u8; 5],
factored: FactoredState,
}
impl Default for GameBoyState {
fn default() -> Self {
GameBoyState {
envelope_1: [0; 4],
envelope_2: [0xFF; 4],
duty_1: [0; 4],
duty_2: [0xFF; 4],
gb_pan: [0, 0, 0, 0, 0xFF],
factored: Default::default(),
}
}
}
pub(crate) struct GameBoy<'config> {
state: GameBoyState,
config: &'config Config,
}
impl<'config> GameBoy<'config> {
pub(crate) fn new<'c: 'config>(
config: &'c Config,
opt_state: Option<GameBoyState>,
) -> Self {
Self {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn init(&mut self, midi: &mut MIDIShim) {
midi.program_change_write(MIDI_CHANNEL_PSG_BASE + 0x02.into(), 0x50.into());
midi.program_change_write(MIDI_CHANNEL_PSG_BASE + 0x03.into(), 0x7F.into());
}
pub(crate) fn command_handle(
&mut self,
register: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let temp_byte: u8;
/*if Variables_Clear_YM2612 = 1 {
Erase FNum_1: Erase FNum_2: Erase Hz_1: Erase Hz_2: Erase Note_1: Erase Note_2
Erase Envelope_1: Erase Envelope_2: Erase state.Duty1: Erase Duty_2: Erase NoteOn_1: Erase NoteOn_2
Erase MIDINote: Erase MIDIWheel: Erase NoteOn_1: Erase NoteOn_2: MIDIVolume = 0
for CH in 0x0..=0x3 {
Envelope_2[channel as usize] = 0xFF
Duty_2[channel as usize] = 0xFF
state.factored.note_1[channel as usize] = 0xFF
state.factored.note_2[channel as usize] = 0xFF
state.factored.note_on_2[channel as usize] = 0x0
state.factored.midi_note[channel as usize] = 0xFF
state.factored.midi_wheel[channel as usize] = 0x8000
}
for CH in 0x0..=0x3 {
GB_Pan[channel as usize] = 0x0
}
GB_Pan(0x4) = 0xFF
Variables_Clear_YM2612 = 0
midi.event_write(MIDI_PROGRAM_CHANGE, MIDI_CHANNEL_PSG_BASE + 0x2, 0x50);
midi.event_write(MIDI_PROGRAM_CHANGE, MIDI_CHANNEL_PSG_BASE + 0x3, 0x7F);
}*/
// Wave RAM
if register >= 0x20 {
return Ok(()); //FIXME: Should this be a strict?
}
let channel = register / 0x5;
let channel_ptr = channel as usize;
if (channel < 0x3 && self.config.sn76489_ch_disabled[channel_ptr])
|| (channel == 0x3 && self.config.sn76489_noise_disabled)
{
return Ok(());
}
let midi_channel = MIDI_CHANNEL_PSG_BASE + channel.into();
match register {
NR30 => {
// Wave Channel - Note On
self.state.factored.note_on_1[channel_ptr] = shift(
&mut self.state.factored.note_on_2[channel_ptr],
(data & 0x80) != 0,
);
if self.state.factored.note_on_1[channel_ptr]
!= self.state.factored.note_on_2[channel_ptr]
{
if self.state.factored.note_on_2[channel_ptr] {
midi.do_note_on(
self.state.factored.note_2[channel_ptr],
self.state.factored.note_2[channel_ptr],
midi_channel,
&mut self.state.factored.midi_note
[channel_ptr],
&mut self.state.factored.midi_wheel
[channel_ptr],
Some(255),
None,
)?;
} else if self.state.factored.midi_note[channel_ptr] != 0xFF
{
midi.note_off_write(
midi_channel,
self.state.factored.midi_note[channel_ptr],
0x00.into(),
);
self.state.factored.midi_note[channel_ptr] =
0xFF.into()
}
}
},
NR11 | NR21 | NR31 | NR41 => {
// Sound Length, Wave pattern duty
// how do I do this?
if register == NR11 || register == NR21 {
// Wave duties are: 12.5%, 25%, 50%, 75%
self.state.duty_1[channel_ptr] =
self.state.duty_2[channel_ptr];
self.state.duty_2[channel_ptr] = (data & 0xC0) / 0x40;
if self.state.duty_1[channel_ptr]
!= self.state.duty_2[channel_ptr]
{
temp_byte = 0x4F
+ (!self.state.duty_2[channel_ptr] & 0x3);
midi.program_change_write(
midi_channel,
temp_byte.into(),
);
}
}
},
NR12 | NR22 | NR32 | NR42 => {
envelope(
&mut self.state,
channel,
register,
data,
midi,
midi_channel,
);
},
NR13 | NR23 | NR33 | NR43 | NR14 | NR24 | NR34 | NR44 => {
noise(
register,
&mut self.state,
channel,
data,
midi_channel,
midi,
)?;
},
NR51 => gameboy_stereo(data, &mut self.state, midi)?,
_ => strict!("Invalid register"),
}
Ok(())
}
}
fn envelope(
state: &mut GameBoyState,
channel: u8,
register: u8,
data: u8,
midi: &mut MIDIShim,
midi_channel: u4,
) {
let channel_ptr = channel as usize;
// Envelope
state.envelope_1[channel_ptr] = state.envelope_2[channel_ptr];
if register != NR32 {
// output is 1 * envelope
state.envelope_2[channel_ptr] = (data & 0xF0) / 0x10;
state.factored.midi_volume[0] = db_to_midi_vol(lin_to_db(
f64::from(state.envelope_2[channel_ptr]) / 0x0F as f64,
));
} else {
// output is 0xF >> (envelope - 1)
// >> 1 = 6 db
state.envelope_2[channel_ptr] = (data & 0x60) / 0x20;
state.factored.midi_volume[0] =
db_to_midi_vol(-f64::from(state.envelope_2[channel_ptr]) * 6.0);
}
if state.envelope_1[channel_ptr] != state.envelope_2[channel_ptr] {
midi.controller_write(
midi_channel,
MIDI_VOLUME,
state.factored.midi_volume[0],
);
}
}
fn noise(
register: u8,
state: &mut GameBoyState,
channel: u8,
data: u8,
midi_channel: u4,
midi: &mut MIDIShim,
) -> Result<()> {
// Frequency Low, High
if (register % 0x5) == 0x3 {
state.factored.fnum_lsb[channel as usize] = data;
//Exit Sub
} else if (register % 0x5) == 0x4 {
state.factored.fnum_msb[channel as usize] = data;
}
state.factored.fnum_1[channel as usize] = state.factored.fnum_2[channel as usize];
state.factored.fnum_2[channel as usize] = ((state.factored.fnum_msb[channel as usize]
as u32 & 0x7) << 8)
| state.factored.fnum_lsb[channel as usize] as u32;
state.factored.hz_1[channel as usize] = state.factored.hz_2[channel as usize];
if channel <= 0x1 {
state.factored.hz_2[channel as usize] =
hz_game_boy(state.factored.fnum_2[channel as usize]);
} else if channel == 0x2 {
state.factored.hz_2[channel as usize] =
hz_game_boy(state.factored.fnum_2[channel as usize]) / 2.0;
} else if channel == 0x3 {
state.factored.hz_2[channel as usize] =
hz_game_boy_noise(state.factored.fnum_lsb[channel as usize]) / 32.0;
}
state.factored.note_1[channel as usize] = state.factored.note_2[channel as usize];
let mut temp_note = hz_to_note(state.factored.hz_2[channel as usize]);
if temp_note >= 0x80.into() {
//TempNote = 0x7F - state.factored.fnum_2[channel as usize]
temp_note = 0x7F.into();
}
state.factored.note_2[channel as usize] = temp_note;
if (register % 0x5) == 0x4 {
if channel == 0x2 {
state.factored.note_on_1[channel as usize] =
state.factored.note_on_2[channel as usize]
} else {
// force NoteOn for all channels but the Wave channel
state.factored.note_on_1[channel as usize] = false
}
state.factored.note_on_2[channel as usize] =
state.factored.note_on_1[channel as usize] || (data & 0x80) != 0;
if state.factored.note_on_1[channel as usize]
!= state.factored.note_on_2[channel as usize]
&& 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],
midi_channel,
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
Some(255),
None,
)?;
} else if state.factored.note_on_2[channel as usize]
&& state.factored.note_1[channel as usize]
!= state.factored.note_2[channel as usize]
{
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
midi_channel,
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
} else if state.factored.note_1[channel as usize] != state.factored.note_2[channel as usize]
{
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
midi_channel,
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
Ok(())
}
fn gameboy_stereo(data: u8, state: &mut GameBoyState, midi: &mut MIDIShim) -> Result<()> {
let mut pan_value = 0x00;
if state.gb_pan[0x4] == data {
return Ok(());
}
for current_bit in 0x0..=0x3 {
let channel_mask = 2 ^ current_bit; // replaces "1 << CurBit"
if data & (channel_mask * 0x10) != 0 {
pan_value |= 0x1 // Left Channel On
}
if data & channel_mask != 0 {
pan_value |= 0x2 // Right Channel On
}
if state.gb_pan[current_bit as usize] != pan_value || state.gb_pan[0x4] == data {
let channel = MIDI_CHANNEL_PSG_BASE + current_bit.into();
match pan_value {
0x1 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_LEFT,
),
0x2 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_RIGHT,
),
0x3 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_CENTER,
),
_ => strict!(),
};
}
state.gb_pan[current_bit as usize] = pan_value;
}
state.gb_pan[0x4] = data;
Ok(())
}
use crate::midi_shim::{
MIDI_PATCH_ACOUSTIC_BASS, MIDI_PATCH_ACOUSTIC_GRAND_PIANO,
MIDI_PATCH_ACOUSTIC_GUITAR_NYLON, MIDI_PATCH_CLARINET, MIDI_PATCH_ELECTRIC_GUITAR_JAZZ,
MIDI_PATCH_FLUTE, MIDI_PATCH_FRENCH_HORN, MIDI_PATCH_HAMMOND_ORGAN, MIDI_PATCH_HARPSICHORD,
MIDI_PATCH_OBOE, MIDI_PATCH_OCARINA, MIDI_PATCH_PAD_3_POLYSYNTH, MIDI_PATCH_SYNTH_BASS_2,
MIDI_PATCH_SYNTH_STRINGS_1, MIDI_PATCH_TRUMPET, MIDI_PATCH_VIBRAPHONE,
};
use crate::vgm2mid::{PITCHWHEEL_SENSITIVITY_DEFAULT, PITCHWHEEL_STEPS_DEFAULT};
use crate::{strict, verbose};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize)]
pub struct Config {
pub strict: bool,
pub verbose: bool,
pub dualchips: bool,
pub pitchwheel_sensitivity: u8,
pub pitchwheel_steps: u16,
pub cnv_accuracy: u16,
pub vgm_loops: u16,
pub tempo_mod: bool,
pub tempo_bpm: f64,
pub tempo_mult: f64,
pub tempo_div: f64,
// Options
pub sn76489_ch_disabled: [bool; 4],
pub sn76489_noise_disabled: bool,
pub sn76489_vol_disabled: [bool; 4],
pub sn76489_voldep_notes: u8,
pub ym2413_ch_disabled: [bool; 9],
pub ym2413_vol_disabled: [bool; 9],
pub ym2413_prog_disabled: [bool; 9],
pub ym2413_percussion_disabled: bool,
pub ym2413_optimized_vgms: bool,
pub ym2413_midi_patch: [u8; 16],
pub ym2612_ch_disabled: [bool; 6],
pub ym2612_vol_disabled: [bool; 6],
pub ym2612_prog_disabled: [bool; 6],
pub ym2612_pan_disabled: [bool; 6],
pub ym2612_midi_patch: [u8; 6],
pub ym2612_dac_disabled: bool,
pub ym2151_ch_disabled: [bool; 8],
pub ym2151_vol_disabled: [bool; 8],
pub ym2151_prog_disabled: [bool; 8],
pub ym2151_pan_disabled: [bool; 8],
}
impl Default for Config {
fn default() -> Self {
Config {
strict: true,
verbose: false,
dualchips: false,
pitchwheel_sensitivity: PITCHWHEEL_SENSITIVITY_DEFAULT.into(),
pitchwheel_steps: PITCHWHEEL_STEPS_DEFAULT.into(),
cnv_accuracy: 480,
vgm_loops: 0,
tempo_mod: false,
tempo_bpm: 120.0,
tempo_mult: 256.0,
tempo_div: 256.0,
sn76489_ch_disabled: [false; 4],
sn76489_noise_disabled: false,
sn76489_vol_disabled: [false; 4],
sn76489_voldep_notes: 2,
ym2413_ch_disabled: [false; 9],
ym2413_vol_disabled: [false; 9],
ym2413_prog_disabled: [false; 9],
ym2413_percussion_disabled: false,
ym2413_optimized_vgms: false,
ym2413_midi_patch: [
MIDI_PATCH_OCARINA.into(),
MIDI_PATCH_SYNTH_STRINGS_1.into(),
MIDI_PATCH_ACOUSTIC_GUITAR_NYLON.into(),
MIDI_PATCH_ACOUSTIC_GRAND_PIANO.into(),
MIDI_PATCH_FLUTE.into(),
MIDI_PATCH_CLARINET.into(),
MIDI_PATCH_OBOE.into(),
MIDI_PATCH_TRUMPET.into(),
MIDI_PATCH_HAMMOND_ORGAN.into(),
MIDI_PATCH_FRENCH_HORN.into(),
MIDI_PATCH_PAD_3_POLYSYNTH.into(),
MIDI_PATCH_HARPSICHORD.into(),
MIDI_PATCH_VIBRAPHONE.into(),
MIDI_PATCH_SYNTH_BASS_2.into(),
MIDI_PATCH_ACOUSTIC_BASS.into(),
MIDI_PATCH_ELECTRIC_GUITAR_JAZZ.into(),
],
ym2612_ch_disabled: [false; 6],
ym2612_vol_disabled: [false; 6],
ym2612_prog_disabled: [false; 6],
ym2612_pan_disabled: [false; 6],
ym2612_dac_disabled: false,
ym2612_midi_patch: [0, 0, 0, 0, 0, 0],
ym2151_ch_disabled: [false; 8],
ym2151_vol_disabled: [false; 8],
ym2151_prog_disabled: [false; 8],
ym2151_pan_disabled: [false; 8],
}
}
}
impl Config {
pub(crate) fn get_tempo_modifier(&self) -> f64 {
if self.tempo_mod {
//this was originally a comparison of TEMPO_MOD and 0x0 or 0x1.
self.tempo_mult / self.tempo_div
} else {
120.0 / self.tempo_bpm
}
}
}
pub fn load_config_or_default(file_path: Option<&Path>) -> Result<Config> {
let config_path = file_path.map_or_else(
try_get_default_config_path,
|path| Ok(Some(path.to_path_buf())),
)?;
config_path.map_or_else(use_default_config, fun_name1)
}
fn fun_name1(path: PathBuf) -> Result<Config> {
match path.try_exists() {
Ok(true) => {
verbose!("Found configuration file at {}", path.display());
fun_name(path)
},
Ok(false) => {
strict!("Could not find configuration file at {}", path.display());
use_default_config()
},
Err(error) => {
strict!("{}", error);
use_default_config()
},
}
}
fn fun_name(path: PathBuf) -> Result<Config> {
std::fs::read_to_string(path).map_or_else(
|err| {
strict!("{}", err);
use_default_config()
},
|cfg_string| {
toml::from_str(&cfg_string).or_else(|err| {
strict!("{}", err);
use_default_config()
})
},
)
}
fn use_default_config() -> Result<Config> {
verbose!("Using default configuration.");
Ok(Config::default())
}
fn try_get_default_config_path() -> Result<Option<PathBuf>> {
directories::ProjectDirs::from("net", "vgmrips", "vgm-rs").map_or_else(
|| {
strict!("Failed to load default config path.");
Ok(None)
},
|dirs| Ok(Some(dirs.config_dir().to_path_buf())),
)
}
use crate::FileType;
use anyhow::{bail, Result};
use clap::{command, Arg, ArgAction};
use std::path::PathBuf;
pub(crate) fn cli() -> Result<(PathBuf, PathBuf, Option<PathBuf>, FileType, bool, bool)> {
let matches = command!()
.arg(
Arg::new("verbose")
.action(ArgAction::SetTrue)
.long("verbose")
.short('v')
.help("Prints extra information and any recoverable errors to stderr."),
)
.arg(
Arg::new("strict")
.action(ArgAction::SetTrue)
.long("strict")
.short('s')
.help("Aborts if any unknown data are encountered during processing."),
)
.arg(
Arg::new("gym")
.action(ArgAction::SetTrue)
.long("gym")
.help("Sets the filetype to gym."),
)
.arg(
Arg::new("vgm")
.action(ArgAction::SetTrue)
.conflicts_with("gym")
.long("vgm")
.help("Sets the filetype to vgm."),
)
.arg(
Arg::new("zipped")
.action(ArgAction::SetTrue)
.requires("vgm")
.conflicts_with("gym")
.long("zipped")
.short('z')
.help("Extracts gzipped vgms (.vgz)"),
)
.arg(
Arg::new("config")
.long("config")
.help("Path to the desired config file."),
)
.arg(Arg::new("in").required(true).help(
"The file to be processed. Filetype is inferred by extension if not set with flags.",
))
.arg(
Arg::new("out")
.required(true)
.help("The path to a resulting midi file. Existing files will not be overwritten."),
)
.get_matches();
let in_path = PathBuf::from(matches.get_one::<String>("in").expect("Required"));
let out_path = PathBuf::from(matches.get_one::<String>("out").expect("Required"));
let verbose = matches.get_flag("verbose");
let strict = matches.get_flag("strict");
let config_path = matches
.get_one::<PathBuf>("config")
.map(|pb| pb.to_path_buf());
let file_type = match (
matches.get_flag("gym"),
matches.get_flag("vgm"),
matches.get_flag("zipped"),
) {
(true, false, false) => Ok(FileType::Gym),
(false, false, false) => determine_file_type(&in_path),
(false, true, false) => Ok(FileType::Vgm),
(false, true, true) => Ok(FileType::Vgz),
_ => bail!("Illegal flag combination"),
}?;
Ok((in_path, out_path, config_path, file_type, verbose, strict))
}
fn determine_file_type(in_path: &PathBuf) -> Result<FileType> {
println!("File type flag not set. Attempting to determine file type.");
match in_path.extension().and_then(|osstr| osstr.to_str()) {
Some("gym") => {
println!("Determined file type to be gym from {}", in_path.display());
Ok(FileType::Gym)
},
Some("vgm") => {
println!("Determined file type to be vgm from {}", in_path.display());
Ok(FileType::Vgm)
},
Some("vgz") => {
println!("Determined file type to be vgz from {}", in_path.display());
Ok(FileType::Vgz)
},
_ => bail!("Unrecognized file extension from {}", in_path.display()),
}
}
pub fn vgm_convert() -> i64 {
unimplemented!()
/*let (file_name, file_ext): (String, String);
let midfilename: String;
let vgm = unimplemented!();
mid_file_init();
match vgm.FileExt {
"vgm" | "vgz" | "gym" => {
vgm.ConvertToMID();
},
_ => eprintln!("Invalid file format."),
}
mid_file_write(midfilename);*/
}
/*
strFilename = Trim(File_Open(Me, "Please select a file", _
"All Supported Files", "*.vgm;*.gym;*.vgz;*.vgm.gz", _
"VGM Log (*.vgm;*.vgz;*.vgm.gz)", "*.vgm;*.vgz;*.vgm.gz", _
"GYM Log (*.gym)", "*.gym*"))
*/
use anyhow::Result;
use crate::midi_shim::{MIDIShim, MIDI_VOLUME};
use crate::utils::{hz_to_note, FactoredState};
use crate::config::Config;
use crate::strict;
use midly::num::u4;
const AY_AFINE: u8 = 0x00;
const AY_ACOARSE: u8 = 0x01;
const AY_BFINE: u8 = 0x02;
const AY_BCOARSE: u8 = 0x03;
const AY_CFINE: u8 = 0x04;
const AY_CCOARSE: u8 = 0x05;
const AY_NOISEPER: u8 = 0x06;
const AY_ENABLE: u8 = 0x07;
const AY_AVOL: u8 = 0x08;
const AY_BVOL: u8 = 0x09;
const AY_CVOL: u8 = 0x0A;
const AY_EFINE: u8 = 0x0B;
const AY_ECOARSE: u8 = 0x0C;
const AY_ESHAPE: u8 = 0x0D;
const AY_PORTA: u8 = 0x0E;
const AY_PORTB: u8 = 0x0F;
pub(crate) const MIDI_CHANNEL_PSG_BASE: u4 = u4::new(0x0A);
//Public AY_NoteDelay(0x0..=0x7) As Long // used for Note On/Off-Detection
//Public AY_LastVol(0x0..=0x7) As Byte
fn hz_ay(fnum: f64, clock: u32) -> f64 {
if fnum == 0.0 {
0.0
} else {
f64::from(clock / 16) / fnum
}
}
pub(crate) struct AY8910State {
attenuation_2: [u8; 7],
factored: FactoredState,
}
pub(crate) struct AY8910<'config> {
state: AY8910State,
config: &'config Config,
}
impl<'config> AY8910<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<AY8910State>) -> Self {
AY8910 {
state: opt_state.unwrap_or_default(),
config,
}
}
//234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
//0000000111s11111112222222222333333333344444444445555555555666666666666777777778888888888
pub(crate) fn command_handle(
&mut self,
chip_id: u8,
reg: u8,
data: u8,
clock: u32,
midi: &mut MIDIShim,
) -> Result<()> {
/*if Variables_Clear_PSG == 1 {
Erase FNum_1: Erase FNum_2: Erase Hz_1: Erase Hz_2: Erase Note_1: Erase Note_2
Erase Attenuation_1: Erase Attenuation_2
Erase MIDINote: Erase MIDIWheel: Erase NoteOn_1: Erase NoteOn_2: MIDIVolume = 0
for CH in 0x0..=0x7 {
state.attenuation_2[channel as usize] = 0xFF
SN76489_NOTE_DELAY[channel as usize] = 0
//PSG_LastVol[channel as usize] = 0x0
}
Variables_Clear_PSG = 0
}*/
//if CH < 0x3 {
// if SN76489_CH_DISABLED[channel as usize] = 1 { return }
//} else { //if CH = 0x3 {
// if SN76489_NOISE_DISABLED = 1 { return }
//}
match reg {
AY_AFINE | AY_ACOARSE | AY_BFINE | AY_BCOARSE | AY_CFINE | AY_CCOARSE => {
process_tuning(reg, chip_id, &mut self.state, data, clock, midi)?;
},
AY_AVOL | AY_BVOL | AY_CVOL => {
process_vol(reg, chip_id, data, &mut self.state, midi);
},
AY_ENABLE => {
process_enable(data, chip_id, &mut self.state, midi)?;
},
AY_NOISEPER => (),
// Noise Frequency
AY_EFINE | AY_ECOARSE => (),
AY_ESHAPE => (),
AY_PORTA | AY_PORTB => (),
// ignore
_ => strict!("Invalid Register"),
}
Ok(())
}
}
impl Default for AY8910State {
fn default() -> Self {
AY8910State {
attenuation_2: [0xFF; 7],
factored: Default::default(),
}
}
}
fn process_enable(data: u8, chip_id: u8, state: &mut AY8910State, midi: &mut MIDIShim) -> Result<()> {
let mut temp_byte = !data;
for byte_channel in 0x0..=0x5 {
let channel = u4::from(byte_channel);
let channel_ptr = (chip_id * 0x6 + channel.as_int()) as usize;
state.factored.note_on_1[channel_ptr] =
state.factored.note_on_2[channel_ptr];
state.factored.note_on_2[channel_ptr] = (temp_byte & 0x1) != 0;
temp_byte /= 0x2;
if state.factored.note_on_1[channel_ptr]
!= state.factored.note_on_2[channel_ptr]
{
let midi_channel = if chip_id != 0 {
u4::new(0x00)
} else {
MIDI_CHANNEL_PSG_BASE
} + channel;
if state.factored.note_on_2[channel_ptr] {
midi.do_note_on(
state.factored.note_2[channel_ptr],
state.factored.note_2[channel_ptr],
midi_channel,
&mut state.factored.midi_note[channel_ptr],
&mut state.factored.midi_wheel[channel_ptr],
Some(255),
None,
)?;
} else if state.factored.midi_note[channel_ptr] != 0xFF {
midi.note_off_write(
midi_channel,
state.factored.midi_note[channel_ptr],
0x00.into(),
);
state.factored.midi_note[channel_ptr] = 0xFF.into();
}
}
}
Ok(())
}
fn process_vol(reg: u8, chip_id: u8, data: u8, state: &mut AY8910State, midi: &mut MIDIShim) {
let channel = u4::from(reg - AY_AVOL);
let channel_ptr = (chip_id * 0x3 + channel.as_int()) as usize;
let midi_channel = if chip_id != 0 {
u4::from(0x00)
} else {
MIDI_CHANNEL_PSG_BASE
} + channel;
if data & 0x10 != 0 {
// use Envelope data
state.attenuation_2[channel_ptr] = 0xFF;
state.factored.midi_volume[0] = 0x7F.into();
} else {
state.attenuation_2[channel_ptr] = data & 0xF;
state.factored.midi_volume[0] = (state.attenuation_2[channel_ptr] << 3).into();
}
midi.controller_write(
midi_channel,
MIDI_VOLUME,
state.factored.midi_volume[0],
);
}
fn process_tuning(
reg: u8,
chip_id: u8,
state: &mut AY8910State,
data: u8,
clock: u32,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = u4::from(reg >> 1);
let channel_ptr = (chip_id * 0x3 + channel.as_int()) as usize;
let midi_channel = if chip_id != 0 {
u4::from(0x00)
} else {
MIDI_CHANNEL_PSG_BASE
} + channel;
state.factored.fnum_1[channel.as_int() as usize] =
state.factored.fnum_2[channel.as_int() as usize];
if reg & 0x1 != 0 {
// AY_xCOARSE
state.factored.fnum_2[channel_ptr] =
(state.factored.fnum_2[channel_ptr] & 0xFF)
| ((data as u32 & 0xF) << 8);
} else {
// AY_xFINE
state.factored.fnum_2[channel_ptr] = (state.factored.fnum_2
[channel_ptr]
& 0xF00) | data as u32;
}
state.factored.hz_1[channel_ptr] =
state.factored.hz_2[channel_ptr];
state.factored.hz_2[channel_ptr] = hz_ay(
state.factored.fnum_2[channel_ptr].into(),
clock,
);
state.factored.note_1[channel_ptr] =
state.factored.note_2[channel_ptr];
let mut temp_note = hz_to_note(state.factored.hz_2[channel_ptr]);
if temp_note >= 0x80.into() {
//TempNote = 0x7F - state.factored.fnum_2[channel as usize]
temp_note = 0x7F.into()
}
state.factored.note_2[channel_ptr] = temp_note;
if state.factored.note_1[channel_ptr]
!= state.factored.note_2[channel_ptr]
{
midi.do_note_on(
state.factored.note_1[channel_ptr],
state.factored.note_2[channel_ptr],
midi_channel,
&mut state.factored.midi_note[channel_ptr],
&mut state.factored.midi_wheel[channel_ptr],
None,
None,
)?;
}
Ok(())
}
pub(crate) fn ay_vol_to_db(tl: u8) -> f32 {
if tl > 0x0 {
(tl - 0xF).into()
} else {
-400.0 // results in volume 0
}
}
[package]
name = "vgm-rs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.71"
clap = { version="4.2.5", features = ['cargo'] }
directories = "5.0.1"
dirs = "5.0.1"
flate2 = "1.0.26"
hound = "3.5.0"
midly = { version = "0.5.3", default-features = false, features = ['std', 'alloc'] }
once_cell = "1.17.1"
serde = { version = "1.0.160", features = ['derive'] }
tock-registers = "0.8.1"
toml = { version = "0.7.3", features = ['parse'] }
utf16string = "0.2.0"
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anstream"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"clap_lex",
"once_cell",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
]
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hound"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "linux-raw-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8776872cdc2f073ccaab02e336fa321328c1e02646ebcb9d2108d0baab480d"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "midly"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "rustix"
version = "0.37.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tock-registers"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c"
[[package]]
name = "toml"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "utf16string"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b62a1e85e12d5d712bf47a85f426b73d303e2d00a90de5f3004df3596e9d216"
dependencies = [
"byteorder",
]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "vgm-rs"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"directories",
"dirs",
"flate2",
"hound",
"midly",
"once_cell",
"serde",
"tock-registers",
"toml",
"utf16string",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69af645a61644c6dd379ade8b77cc87efb5393c988707bad12d3c8e00c50f669"
dependencies = [
"memchr",
]