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 { Stoppitch -= 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 constantsif 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 = 0x8drum_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 & 0xF0x8 => (), // Reg 0xC8 - 0xDF//slot.RC = Int(Data / 0x10)//slot.RR = Data & 0xF0x9 => (), // Reg 0xE0 - 0xF7//slot.AM = Data & 0x7_ => strict!(),}} else {// All non-slot registersmatch register {0x0 => (), // TEST0x1 => (),0x2 => (),//wavetblhdr = (Data >> 2) & 0x7//memmode = Data & 10x3 => (),//memadr = (memadr & 0xFFFF&) | (Data * 0x10000)0x4 => (),//memadr = (memadr & 0xFF00FF) | (Data * 0x100&)0x5 => (),//memadr = (memadr & 0xFFFF00) | Data0x6 => (), // memory data//Call writeMem(memadr, Data)//memadr = (memadr + 1) & 0xFFFFFF0xF8 => (),//fm_l = Data & 0x7//fm_r = (Data >> 3) & 0x70xF9 => (),//pcm_l = Data & 0x7//pcm_r = (Data >> 3) & 0x7_ => (),}}//regs [Register] = DataOk(())}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 & 0xFif state.tone_ins[slot_number as usize] == 0x77 {// Reverse Cymballet temp_byte = data / 0x10;if temp_byte >= 0xE {// -> Crash Cymbalif 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 - 0x97let 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 - 0x7Fif 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 - 0x67let 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) Moduleuse 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 Constantsconst 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) / 0x20YMF262_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 numbersstate.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/ADPCMpub(crate) const YM2612_TEST: u8 = 0x21; // LSI Test Datapub(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/Offpub(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 Levelpub(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 Ratepub(crate) const YM2612_SR: u8 = 0x70; // Sustain Ratepub(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/Connectionpub(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 Selectpub(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 Bytetl: [[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 Byteblock: [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 SubDebug.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 Drum0x26, // Snare Drum0x33, // Top Cymbal0x2A, // High Hat0x2D, // Tom Tom0x25, // 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 Levelstate.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 ^ VolShiftlet mut temp_volume =((vol_mul as u32) << 8) >> vol_shift as u32; //FIXME: this is messytemp_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 Addressif (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 Levelstate.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, C0if (data & 0x80) == 0x0 {// Key Onfor 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 Offfor 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_RIGHT0x2 => (), //state.factored.midi_pan[channel as usize] = MIDI_PAN_LEFT0x3 | 0x0 => (), //state.factored.midi_pan[channel as usize] = MIDI_PAN_CENTER_ => strict!(),}},0x02 => (), // Start Address Low0x03 => (), // Start Address High0x04 => (), // Stop Address Low0x05 => (), // Stop Address High0x06 => (), // Prescale Low0x07 => (), // Prescale High0x08 => (), // ADPCM Data0x09 => (), // Delta-N Low0x0A => (), // Delta-N High0x0B => state.ins_vol = data / 0x2, // Volume0x0C => (), // 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) Constantspub(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 = 0Erase state.FNum_MSBErase 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_2Erase 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 = 0x7FErase state.MIDINote: Erase state.MIDIWheel: Erase state.factored.midi_volume[0]: Erase state.NoteOn_1: Erase state.NoteOn_2: Erase state.Percussion_onFor CH = 0x0..=0x8state.InsVol[CH as usize] = 0x0state.Instrument[CH as usize] = 0xFFstate.Volume[CH as usize] = 0xFFstate.MIDINote[CH as usize] = 0xFFstate.MIDIWheel[CH as usize] = 0x8000state.factored.midi_volume[CH as usize] = 0xFFNext CHVariables_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 = 0x7Fself.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 = 0x7Fself.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 = 0x7Fself.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 = 0x7Fself.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 = 0x7Fself.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] = 0x7Fself.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_MSBif !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 numberstoggle_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 Bytetl: [[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 Byteblock: [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 & 0x31YM2151_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 - noiseYM2151_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 + 0x1Fchannel = Fix((register & 0x3F) / 0x8)//KS = Fix(Data / 0x40) & 0x3//AR = Data & 0x1FYM2151_LFO_AM_EN_D1R..=YM2151_LFO_AM_EN_D1R + 0x1Fchannel = Fix((register & 0x3F) / 0x8)//AMS_EN = if(Data & 0x80, 1, 0)//D1R = Data & 0x1FYM2151_DT2_D2R..=YM2151_DT2_D2R + 0x1Fchannel = Fix((register & 0x3F) / 0x8)//DT2 = Fix(Data / 0x40) & 0x3//D2R = Data & 0x1FYM2151_D1L_RR..=YM2151_D1L_RR + 0x1Fchannel = 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 & 0x3let 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 / 4pub(crate) const PITCHWHEEL_SENSITIVITY_DEFAULT: u7 = u7::new(0x02); //+/- n Semitonespub(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], // 0x00eof_offset: u32,version: u32,hz_sn76489: u32,hz_ym2413: u32, // 0x10gd3_offset: u32,total_samples: u32,loop_offset: u32,loop_samples: u32, // 0x20rate: u32,sn_feedback: u16,sn_stwidth: u8,sn_flags: u8,hz_ym2612: u32,hz_ym2151: u32, // 0x30data_offset: u32,hz_spcm: u32,spcm_intf: u32,hz_rf5c68: u32, // 0x40hz_ym2203: u32,hz_ym2608: u32,hz_ym2610b: u32,hz_ym3812: u32, // 0x50hz_ym3526: u32,hz_y8950: u32,hz_ymf262: u32,hz_ymf278b: u32, // 0x60hz_ymf271: u32,hz_ymz280b: u32,hz_rf5c164: u32,hz_pwm: u32, // 0x70hz_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, // 0x80hz_nes_apu: u32,hz_multipcm: u32,hz_upd7759: u32,hz_okim6258: u32, // 0x90okim_flags: u8,k0_flags: u8,c140_type: u8,reserved_2: u8,hz_okim5295: u32,hz_k051649: u32,hz_k054539: u32, // 0xA0hz_huc6280: u32,hz_c140: u32,k053260: u32,hz_pokey: u32, // 0xB0hz_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 Constantsconst 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 offsetsheader.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 othersheader.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_WriteOpenlast_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 Writewait = (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 Streamdata_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 Streamif 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 slowdownsCall frmMain.tmrConversionStatus_TimerConversion_Status_Current = file_pos - vhFileHeader.lngDataOffsetDoEventsDETimer = 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_WriteCloseif 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; //128pub(crate) const SN76489_CHANNEL_SELECT: u8 = SN76489_LATCH | 0x70; //240#[allow(clippy::identity_op)]pub(crate) const SN76489_TONE_1: u8 = SN76489_LATCH | 0x00; //128pub(crate) const SN76489_TONE_2: u8 = SN76489_LATCH | 0x20; //160pub(crate) const SN76489_TONE_3: u8 = SN76489_LATCH | 0x40; //192pub(crate) const SN76489_NOISE: u8 = SN76489_LATCH | 0x60; //224const 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; //144const SN76489_ATTENUATOR_2: u8 = SN76489_LATCH | 0x30; //176const SN76489_ATTENUATOR_3: u8 = SN76489_LATCH | 0x50; //208const 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 Bytepub(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 commandSN76489_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 writeself.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::Lessif 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 Octavesif 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] = 0x7Fself.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/Offif 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: 39if 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-Frequencylet 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 Low0x85 => (), // Set Loop Address High0x6 => (), // Set End Address0x7 => {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 Rstate.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 Lstate.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 Timemidi.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 Highstate.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 - Channelif 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 Lowstate.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 & 0x1if 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 / 16const 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 Cycleif (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 * envelopeself.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 = 0midi.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 vgm2midAPU_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 = 0midi.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 notesmidi.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 Freqself.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 = 0midi.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 notesmidi.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, Frequencyself.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 Maskmidi.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 DACHeaderDataRIFF As String * 4HeaderLen As LongWAVEfmt As String * 8HeaderSize As LongTag: u16Channels: u16SampleRate As LongBytespSecond As LongBytespSample: u16SampelBits: u16Data As String * 4DataLen As LongEnd 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 MIDIControllersmcModulatorWheel = 1mcDataEntry = 6mcVolume = 7mcPan = 10mcSustain = 64mcLegatoPedal = 68mcHold2Pedal = 69mcSoundTimbre = 71mcSoundReleaseTime = 72mcSoundAttackTime = 73mcSoundBrightness = 74mcRPNFine = 100mcRPNCoarse = 101mcAllControllersOff = 123mcAllNotesOff = 123End 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 MIDIMetaEventmmeTrackEnd = 0x2FEnd Enum*///RPNspub(crate) const RPN_PITCH_BEND_RANGE_M: u7 = u7::new(0x0);pub(crate) const RPN_PITCH_BEND_RANGE_L: u7 = u7::new(0x0);// NRPNspub(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//VolumemcvVolumeMin = 0x0mcvVolumeMax = 0x7F//PanmcvPanLeft = 0x0mcvPanRight = 0x7FmcvPanCenter = 0x40//PitchWheelmcvPitchWheelCenter = 0x2000End Enum*///General MIDI Patch Namespub(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 namingcurrent_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 unnecessarycontinue;}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 Channelself.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 CHfor 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 CHfor 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 CHfor 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 saneself.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 VBpub(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 Constantsconst 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 11bail!("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_2Erase Envelope_1: Erase Envelope_2: Erase state.Duty1: Erase Duty_2: Erase NoteOn_1: Erase NoteOn_2Erase MIDINote: Erase MIDIWheel: Erase NoteOn_1: Erase NoteOn_2: MIDIVolume = 0for CH in 0x0..=0x3 {Envelope_2[channel as usize] = 0xFFDuty_2[channel as usize] = 0xFFstate.factored.note_1[channel as usize] = 0xFFstate.factored.note_2[channel as usize] = 0xFFstate.factored.note_on_2[channel as usize] = 0x0state.factored.midi_note[channel as usize] = 0xFFstate.factored.midi_wheel[channel as usize] = 0x8000}for CH in 0x0..=0x3 {GB_Pan[channel as usize] = 0x0}GB_Pan(0x4) = 0xFFVariables_Clear_YM2612 = 0midi.event_write(MIDI_PROGRAM_CHANGE, MIDI_CHANNEL_PSG_BASE + 0x2, 0x50);midi.event_write(MIDI_PROGRAM_CHANGE, MIDI_CHANNEL_PSG_BASE + 0x3, 0x7F);}*/// Wave RAMif 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 Onself.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;// Envelopestate.envelope_1[channel_ptr] = state.envelope_2[channel_ptr];if register != NR32 {// output is 1 * envelopestate.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 dbstate.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, Highif (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 channelstate.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,// Optionspub 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 Bytefn 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//0000000111s11111112222222222333333333344444444445555555555666666666666777777778888888888pub(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_2Erase Attenuation_1: Erase Attenuation_2Erase MIDINote: Erase MIDIWheel: Erase NoteOn_1: Erase NoteOn_2: MIDIVolume = 0for CH in 0x0..=0x7 {state.attenuation_2[channel as usize] = 0xFFSN76489_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 FrequencyAY_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 datastate.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_xCOARSEstate.factored.fnum_2[channel_ptr] =(state.factored.fnum_2[channel_ptr] & 0xFF)| ((data as u32 & 0xF) << 8);} else {// AY_xFINEstate.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",]