A rust port of vgm2mid by Paul Jensen and Valley Bell
use crate::ay8910::AY8910;
use crate::config::Config;
use crate::gameboy::GameBoy;
use crate::gd3::{get_gd3_header, Gd3Header};
use crate::midi_shim::{MIDIShim, TEXT_LOOP_END, TEXT_LOOP_START};
use crate::nesapu::NESAPU;
use crate::pcm::SegaPCM;
use crate::sn76489::SN76489;
use crate::vgm2mid::{
	OPL_TYPE_Y8950, OPL_TYPE_YM3526, OPL_TYPE_YM3812, OPL_TYPE_YMF262, OPN_TYPE_YM2203,
	OPN_TYPE_YM2608, OPN_TYPE_YM2610, OPN_TYPE_YM2612,
};
use crate::ym2151::YM2151;
use crate::ym2413::YM2413;
use crate::ym2612::{YM2612, YM2612_DAC};
use crate::ym3812::YM3812;
use crate::ymf278::YMF278;
use crate::{strict, verbose};
use anyhow::{bail, Result};
use midly::MetaMessage::Marker;
use midly::TrackEventKind::Meta;

#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct VgmHeader {
	str_vgm: [u8; 4], // 0x00
	eof_offset: u32,
	version: u32,
	hz_sn76489: u32,
	hz_ym2413: u32, // 0x10
	gd3_offset: u32,
	total_samples: u32,
	loop_offset: u32,
	loop_samples: u32, // 0x20
	rate: u32,
	sn_feedback: u16,
	sn_stwidth: u8,
	sn_flags: u8,
	hz_ym2612: u32,
	hz_ym2151: u32, // 0x30
	data_offset: u32,
	hz_spcm: u32,
	spcm_intf: u32,
	hz_rf5c68: u32, // 0x40
	hz_ym2203: u32,
	hz_ym2608: u32,
	hz_ym2610b: u32,
	hz_ym3812: u32, // 0x50
	hz_ym3526: u32,
	hz_y8950: u32,
	hz_ymf262: u32,
	hz_ymf278b: u32, // 0x60
	hz_ymf271: u32,
	hz_ymz280b: u32,
	hz_rf5c164: u32,
	hz_pwm: u32, // 0x70
	hz_ay8910: u32,
	ay_type: u8,
	ay_flags: u8,
	ay_flags2203: u8,
	ay_flags2608: u8,
	volume_modifier: u8,
	reserved_1: u8,
	loop_base: u8,
	loop_modifier: u8,
	hz_gb_dmg: u32, // 0x80
	hz_nes_apu: u32,
	hz_multipcm: u32,
	hz_upd7759: u32,
	hz_okim6258: u32, // 0x90
	okim_flags: u8,
	k0_flags: u8,
	c140_type: u8,
	reserved_2: u8,
	hz_okim5295: u32,
	hz_k051649: u32,
	hz_k054539: u32, // 0xA0
	hz_huc6280: u32,
	hz_c140: u32,
	k053260: u32,
	hz_pokey: u32, // 0xB0
	hz_qsound: u32,
	hz_scsp: u32,
	extra_header_offset: u32,
}

struct Vgm {
	file_header: VgmHeader,
	data_bank: Vec<u8>,
	data_block_count: u32,
	data_block_pos: [usize; 0xFF],
	gt_gd3_tag: Gd3Header,
	conversion_status_current: u32,
	conversion_status_total: u32,
	clock_sn76489: u32,
	clock_ym2151: u32,
	clock_ym2413: u32,
	clock_ym2612: u32,
	clock_ay8910: u32,
}

// VGM Command Constants
const GG_STEREO: u8 = 0x4F;
const SN76489: u8 = 0x50;
const YM2413: u8 = 0x51;
const YM2612_P0: u8 = 0x52;
const YM2612_P1: u8 = 0x53;
const YM2151: u8 = 0x54;
const YM2203: u8 = 0x55;
const YM2608_P0: u8 = 0x56;
const YM2608_P1: u8 = 0x57;
const YM2610_P0: u8 = 0x58;
const YM2610_P1: u8 = 0x59;
const YM3812: u8 = 0x5A;
const YM3526: u8 = 0x5B;
const Y8950: u8 = 0x5C;
const YMF262_P0: u8 = 0x5E;
const YMF262_P1: u8 = 0x5F;
const RF5C68_REG: u8 = 0xB0;
const SPCM_MEM: u8 = 0xC0;
const RF5C68_MEM: u8 = 0xC1;
const DATABNK_SEEK: u8 = 0xE0;
const AY8910: u8 = 0xA0;

const WAIT_N_SAMPLES: u8 = 0x61;
const WAIT_735_SAMPLES: u8 = 0x62;
const WAIT_882_SAMPLES: u8 = 0x63;
const END_OF_SOUND_DATA: u8 = 0x66;
const DATA_BLOCK: u8 = 0x67;

fn correct_header_for_version(mut header: VgmHeader) -> VgmHeader {
	// correct the header for old files and calculate absolute offsets
	header.eof_offset += 0x4;
	if header.gd3_offset > 0x0 {
		header.gd3_offset += 0x14;
	}
	if header.loop_offset > 0x0 {
		header.loop_offset += 0x1C;
	}
	if header.version < 0x101 {
		header.rate = 0x0;
	}
	if header.version < 0x110 {
		header.sn_feedback = 0x9;
		header.sn_stwidth = 16;
		header.hz_ym2612 = header.hz_ym2413;
		header.hz_ym2612 = header.hz_ym2413;
	}
	if header.version < 0x150 || header.data_offset == 0x0 {
		header.data_offset = 0xC;
	}
	if header.version < 0x151 || header.data_offset <= 0xC {
		header.hz_spcm = 0x00;
		header.spcm_intf = 0x00;
		header.hz_rf5c68 = 0x00;
		header.hz_ym2203 = 0x00;
		header.hz_ym2608 = 0x00;
		header.hz_ym2610b = 0x00;
		header.hz_ym3812 = 0x00;
		header.hz_ym3526 = 0x00;
		header.hz_y8950 = 0x00;
		header.hz_ymf262 = 0x00;
		header.hz_ymf278b = 0x00;
		header.hz_ymf271 = 0x00;
		header.hz_ymz280b = 0x00;
		header.hz_rf5c164 = 0x00;
		header.hz_pwm = 0x00;
		header.hz_ay8910 = 0x00;
		header.ay_type = 0x00;
		header.ay_flags = 0x00;
		header.ay_flags2203 = 0x00;
		header.ay_flags2608 = 0x00;
	}
	header.data_offset += 0x34;
	header
}

fn parse_vgm_header(data: &[u8]) -> Result<VgmHeader> {
	let mut header = VgmHeader {
		str_vgm: [0; 4],
		eof_offset: 0,
		version: 0,
		hz_sn76489: 0,
		hz_ym2413: 0,
		gd3_offset: 0,
		total_samples: 0,
		loop_offset: 0,
		loop_samples: 0,
		rate: 0,
		sn_feedback: 0,
		sn_stwidth: 0,
		sn_flags: 0,
		hz_ym2612: 0,
		hz_ym2151: 0,
		data_offset: 0,
		hz_spcm: 0,
		spcm_intf: 0,
		hz_rf5c68: 0,
		hz_ym2203: 0,
		hz_ym2608: 0,
		hz_ym2610b: 0,
		hz_ym3812: 0,
		hz_ym3526: 0,
		hz_y8950: 0,
		hz_ymf262: 0,
		hz_ymf278b: 0,
		hz_ymf271: 0,
		hz_ymz280b: 0,
		hz_rf5c164: 0,
		hz_pwm: 0,
		hz_ay8910: 0,
		ay_type: 0,
		ay_flags: 0,
		ay_flags2203: 0,
		ay_flags2608: 0,
		volume_modifier: 0,
		reserved_1: 0,
		loop_base: 0,
		loop_modifier: 0,
		hz_gb_dmg: 0,
		hz_nes_apu: 0,
		hz_multipcm: 0,
		hz_upd7759: 0,
		hz_okim6258: 0,
		okim_flags: 0,
		k0_flags: 0,
		c140_type: 0,
		reserved_2: 0,
		hz_okim5295: 0,
		hz_k051649: 0,
		hz_k054539: 0,
		hz_huc6280: 0,
		hz_c140: 0,
		k053260: 0,
		hz_pokey: 0,
		hz_qsound: 0,
		hz_scsp: 0,
		extra_header_offset: 0,
	};

	let mut pos = 0;

	header.str_vgm.clone_from_slice(&data[0..=3]);
	if header.str_vgm != "Vgm ".as_bytes() {
		verbose!("VGM Header is incorrect.");
		strict!();
	}
	pos += 4;

	header.eof_offset = extract_u32(data, &mut pos);
	if data.len() < header.eof_offset as usize {
		verbose!("VGM EOF is incorrect.");
		strict!();
	}

	header.version = extract_u32(data, &mut pos);
	header.hz_sn76489 = extract_u32(data, &mut pos);
	header.hz_ym2413 = extract_u32(data, &mut pos);
	header.gd3_offset = extract_u32(data, &mut pos);
	header.total_samples = extract_u32(data, &mut pos);
	header.loop_offset = extract_u32(data, &mut pos);
	header.loop_samples = extract_u32(data, &mut pos);
	header.rate = extract_u32(data, &mut pos);
	pos += 4; //FIXME: extract others
	header.hz_ym2612 = extract_u32(data, &mut pos);
	header.hz_ym2151 = extract_u32(data, &mut pos);
	header.data_offset = extract_u32(data, &mut pos);
	header.hz_spcm = extract_u32(data, &mut pos);
	header.spcm_intf = extract_u32(data, &mut pos);
	header.hz_rf5c68 = extract_u32(data, &mut pos);
	header.hz_ym2203 = extract_u32(data, &mut pos);
	header.hz_ym2608 = extract_u32(data, &mut pos);
	header.hz_ym2610b = extract_u32(data, &mut pos);
	header.hz_ym3812 = extract_u32(data, &mut pos);
	header.hz_ym3526 = extract_u32(data, &mut pos);
	header.hz_y8950 = extract_u32(data, &mut pos);
	header.hz_ymf262 = extract_u32(data, &mut pos);
	header.hz_ymf278b = extract_u32(data, &mut pos);

	header = correct_header_for_version(header);
	Ok(header)
}

fn extract_u32(data: &[u8], pos: &mut usize) -> u32 {
	let mut dst = [0u8; 4];
	dst.clone_from_slice(&data[*pos..=*pos + 3]);
	*pos += 4;
	u32::from_le_bytes(dst)
}

fn parse_vgm_data(data: &[u8]) -> Result<Vgm> {
	Ok(Vgm {
		file_header: parse_vgm_header(data)?,
		data_bank: Vec::new(),
		data_block_count: 0,
		data_block_pos: [0; 0xFF],
		gt_gd3_tag: get_gd3_header(None)?,
		conversion_status_current: 0,
		conversion_status_total: 0,
		clock_sn76489: 0,
		clock_ym2151: 0,
		clock_ym2413: 0,
		clock_ym2612: 0,
		clock_ay8910: 0,
	})
}

pub(crate) fn convert_vgm_to_mid(file_data: &[u8], config: &Config, midi: &mut MIDIShim) -> Result<()> {
	let mut vgm = parse_vgm_data(file_data)?;
	dbg!(&vgm.file_header);
	let mut file_pos: usize;
	let (mut command, mut port, mut register, mut data): (u8, u8, u8, u8);
	let _detimer: f32;
	let mut last_data_note: u8;
	let mut data_bank_pos: usize = 0;
	let temp_long: u32 = 0;
	let mut last_sn76489_cmd: [u8; 2] = [0; 2];
	let mut stop_vgm: bool = false;
	let mut loop_pos: usize = 0;
	let mut dac_wrt: bool;
	let mut cur_loop: u16 = 0;

	vgm.conversion_status_current = 0x0;
	vgm.conversion_status_total = vgm.file_header.eof_offset - vgm.file_header.data_offset;

	let clock_sn76489 = vgm.file_header.hz_sn76489 & 0x3FFFFFFF;
	vgm.clock_ym2413 = vgm.file_header.hz_ym2413 & 0x3FFFFFFF;
	vgm.clock_ym2612 = vgm.file_header.hz_ym2612 & 0x3FFFFFFF;
	vgm.clock_ym2151 = vgm.file_header.hz_ym2151 & 0x3FFFFFFF;
	let mut fsam_2612 = f64::from(vgm.clock_ym2612) / 72.0;
	let mut fsam_3812 = 0.0;
	vgm.clock_ay8910 = vgm.file_header.hz_ay8910;

	let t6w28_sn76489 = (vgm.file_header.hz_sn76489 & 0xC0000000) == 0xC0000000;

	let mut ay8910 = AY8910::new(config, None);
	let mut sn76489 = SN76489::new(t6w28_sn76489, clock_sn76489, config, None);
	let mut segapcm = SegaPCM::new(None);
	let mut gameboy = GameBoy::new(config, None);
	let mut nes_apu = NESAPU::new(config, None);
	let mut ym2151 = YM2151::new(config, None);
	let mut ym2413 = YM2413::new(config, None);
	let mut ym2612 = YM2612::new(config, None);
	let mut ym3812 = YM3812::new(config, None);
	let mut ymf278 = YMF278::new(config, None);

	gameboy.init(midi);

	if vgm.file_header.hz_ymf278b != 0 {
		ymf278.load_opl4_instrument_set()?;
	}

	let loop_available = vgm.file_header.loop_offset > 0;
	if loop_available { loop_pos = vgm.file_header.loop_offset as usize }

	//detimer = Timer;
	//Call DAC_WriteOpen
	last_sn76489_cmd[0x0] = 0x0;
	last_sn76489_cmd[0x1] = 0x0;
	dac_wrt = false;
	let mut _dac_data_byte = 0x80;
	last_data_note = 0xFF;

	//FIXME: Erase MID_Trackdata;
	//FIXME: ReDim MID_Trackdata(0): u8;
	//WriteMidiTag();
	midi.data_init(
		vgm.clock_sn76489 != 0 || vgm.clock_ay8910 != 0,
		t6w28_sn76489,
	);

	file_pos = vgm.file_header.data_offset as usize;
	loop {
		let mut wait = 0;
		let mut cur_chip = 0x00u8;
		command = file_data[file_pos];

		if config.dualchips {
			match command {
				0x30 => {
					cur_chip = 0x1;
					command += 0x20;
				},
				0x3F => {
					cur_chip = 0x1;
					command += 0x10;
				},
				0xA1..=0xAC => {
					cur_chip = 0x1;
					command -= 0x50;
				},
				_ => verbose!("Invalid dual-chip command: {}", command),
			}
		}

		match command {
			GG_STEREO => {
				data = file_data[file_pos + 1];
				sn76489.gg_stereo_handle(data, cur_chip, midi)?;
				file_pos += 2;
			},
			SN76489 => {
				data = file_data[file_pos + 1];
				sn76489.command_handle(data, cur_chip, midi)?;
				file_pos += 2
			},
			YM2413 => {
				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				ym2413.command_handle(
					register,
					data,
					vgm.clock_ym2413,
					midi,
				)?;
				file_pos += 3;
			},
			YM2612_P0 | YM2612_P1 | YM2203..=YM2610_P1 => {
				//if command = YM2612_P0 {
				//	port = 0
				//} else if command = YM2612_P1 {
				//	port = 3
				//}
				(port, fsam_2612) = match command {
					YM2612_P0 | YM2612_P1 => {
						ym2612.state.opn_type = OPN_TYPE_YM2612;
						(command & 0x1,
						f64::from(
							vgm.file_header.hz_ym2612 & 0x3FFFFFFF,
						) / 72.0)
					},
					YM2203 => {
						ym2612.state.opn_type = OPN_TYPE_YM2203;
						(0x0,
						f64::from(
							vgm.file_header.hz_ym2203 & 0x3FFFFFFF,
						) / 72.0)
					},
					YM2608_P0 | YM2608_P1 => {
						ym2612.state.opn_type = OPN_TYPE_YM2608;
						(command & 0x1,
						f64::from(
							vgm.file_header.hz_ym2608 & 0x3FFFFFFF,
						) / 72.0)
					},
					YM2610_P0 | YM2610_P1 => {
						ym2612.state.opn_type = OPN_TYPE_YM2610;
						(command & 0x1,
						f64::from(
							vgm.file_header.hz_ym2610b & 0x3FFFFFFF,
						) / 72.0)
					},
					_ => bail!("Invalid register"),
				};

				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				ym2612.command_handle(
					port * 0x3,
					register,
					data,
					fsam_2612,
					midi,
				)?;
				if register == YM2612_DAC {
					_dac_data_byte = data;
					dac_wrt = true;
				}
				file_pos += 3;
			},
			YM2151 => {
				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				ym2151.command_handle(
					register,
					data,
					midi,
				)?;
				file_pos += 3;
			},
			YM3812..=Y8950 => {
				match command {
					YM3812 => {
						ym3812.state.opl_type = OPL_TYPE_YM3812;
						fsam_3812 = f64::from(
							vgm.file_header.hz_ym3812 & 0x3FFFFFFF,
						) / 72.0;
					},
					YM3526 => {
						ym3812.state.opl_type = OPL_TYPE_YM3526;
						fsam_3812 = f64::from(
							vgm.file_header.hz_ym3526 & 0x3FFFFFFF,
						) / 72.0;
					},
					Y8950 => {
						ym3812.state.opl_type = OPL_TYPE_Y8950;
						fsam_3812 = f64::from(
							vgm.file_header.hz_y8950 & 0x3FFFFFFF,
						) / 72.0;
					},
					_ => (),
				}
				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				ym3812.command_handle(register as u16, data, fsam_3812, midi)?;
				file_pos += 3;
			},
			YMF262_P0..=YMF262_P1 => {
				ym3812.state.opl_type = OPL_TYPE_YMF262;
				fsam_3812 =
					f64::from(vgm.file_header.hz_ymf262 & 0x3FFFFFFF) / 288.0;
				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				ym3812.command_handle(
					(command as u16 & 0x1) << 8 | register as u16,
					data,
					fsam_3812,
					midi,
				)?;
				file_pos += 3;
			},
			0xD0 => {
				port = file_data[file_pos + 1];
				register = file_data[file_pos + 2];
				data = file_data[file_pos + 3];
				ymf278.command_handle(
					port,
					register,
					data,
					&mut ym3812,
					fsam_3812,
					midi,
				)?;
				file_pos += 4;
			},
			0xB3 => {
				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				gameboy.command_handle(
					register,
					data,
					midi,
				)?;
				file_pos += 3;
			},
			0xB4 => {
				if vgm.clock_sn76489 == 0 {
					vgm.clock_sn76489 = 1
				}
				register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				nes_apu.command_handle(
					register,
					data,
					midi,
				)?;
				file_pos += 3;
			},
			WAIT_N_SAMPLES => {
				wait = (file_data[file_pos + 2] as u32) << 8;
				wait |= file_data[file_pos + 1] as u32;
				file_pos += 3
			},
			WAIT_735_SAMPLES => {
				wait = 735;
				file_pos += 1
			},
			WAIT_882_SAMPLES => {
				wait = 882;
				file_pos += 1;
			},
			END_OF_SOUND_DATA => stop_vgm = true,
			DATA_BLOCK => {
				//FIXME Why is this an unused assignment?
				//register = file_data[file_pos + 1];
				data = file_data[file_pos + 2];
				wait = file_data[file_pos + 3] as u32
					| (file_data[file_pos + 4] as u32) << 0x08
					| (file_data[file_pos + 5] as u32) << 0x10
					| (file_data[file_pos + 6] as u32) << 0x18;
				file_pos += 7;

				if data == 0x00 {
					let previous_length = vgm.data_bank.len();
					vgm.data_bank
						.resize(vgm.data_bank.len() + wait as usize, 0);

					for offset in 0x0..wait {
						vgm.data_bank[previous_length + offset as usize] =
							file_data[file_pos + offset as usize]
					}
				}
				file_pos += TryInto::<usize>::try_into(wait).unwrap();
				wait = 0;
			},
			0x70..=0x7F => {
				wait = ((command & 0x0F) + 1).into();
				file_pos += 1;
			},
			0x80..=0x8F => {
				wait = (command & 0x0F).into();
				port = 0x00;
				register = YM2612_DAC;
				data = vgm.data_bank[data_bank_pos];
				ym2612.command_handle(port, register, data, fsam_2612, midi)?;
				_dac_data_byte = data;
				dac_wrt = true;
				data_bank_pos += 1;
				file_pos += 1;
			},
			AY8910 => {
				port = file_data[file_pos + 1] / 0x80;
				register = file_data[file_pos + 1] & 0xF;
				data = file_data[file_pos + 2];
				ay8910.command_handle(
					port,
					register,
					data,
					vgm.clock_ay8910,
					midi,
				)?;
				file_pos += 3;
			},
			RF5C68_REG =>
			// RF5C68 Register Write
			{
				file_pos += 3
			},
			SPCM_MEM => {
				//FIXME: wtf?
				// SegaPCM Memory Write
				wait = (file_data[file_pos + 2] as u32) << 8
					| file_data[file_pos + 1] as u32;
				data = file_data[file_pos + 3];
				segapcm.mem_write(
					wait.try_into().unwrap(),
					data,
					midi,
				)?;

				wait = 0;
				file_pos += 4;
			},
			RF5C68_MEM =>
			// RF5C68 Memory Write
			{
				file_pos += 4
			},
			DATABNK_SEEK => {
				data_bank_pos = file_data[file_pos + 1] as usize
					| (file_data[file_pos + 2] as usize) << 0x08
					| (file_data[file_pos + 3] as usize) << 0x10
					| (file_data[file_pos + 4] as usize) << 0x18;

				for block in 0x0..vgm.data_block_count {
					if vgm.data_block_pos[block as usize] == data_bank_pos {
						break;
					}
				}

				if temp_long >= vgm.data_block_count {
					vgm.data_block_pos[vgm.data_block_count as usize] =
						data_bank_pos;
					vgm.data_block_count += 0x01;
				}

				file_pos += 5;
			},
			0x30..=0x4E => file_pos += 2,
			0x55..=0x5F => file_pos += 3,
			0xA0..=0xBF => file_pos += 3,
			0xC0..=0xDF => file_pos += 4,
			0xE1..=0xFF => file_pos += 5,
			0x90 =>
			// DAC Stream Setup
			{
				file_pos += 5
			},
			0x91 =>
			// Set Stream Data
			{
				file_pos += 5
			},
			0x92 =>
			// Set Stream Frequency
			{
				file_pos += 6
			},
			0x93 => {
				// Start Stream
				data_bank_pos = file_data[file_pos + 2] as usize
					| (file_data[file_pos + 3] as usize) << 8
					| (file_data[file_pos + 4] as usize) << 16
					| (file_data[file_pos + 5] as usize) << 24;

				for data_block_pointer in 0x0..(vgm.data_block_count as usize)
				{
					if vgm.data_block_pos[data_block_pointer] == data_bank_pos {
						break;
					}
				}

				if temp_long >= vgm.data_block_count {
					vgm.data_block_pos[vgm.data_block_count as usize] =
						data_bank_pos;
					vgm.data_block_count += 0x1;
				}
				dac_wrt = true;

				file_pos += 11;
			},
			0x94 => {
				// Stop Stream
				if last_data_note < 0x80 {
					midi.note_off_write(0x0F.into(), last_data_note.into(), 0x00.into());
				}
				last_data_note = 0xFF;
				file_pos += 2
			},
			0x95 => {
				// Start Stream (fast call)
				let temp = file_data[file_pos + 2] & 0x7F;
				if last_data_note < 0x80 {
					midi.note_off_write(0x0F.into(), last_data_note.into(), 0x00.into());
				}
				midi.note_on_write(0x0F.into(), temp.into(), 0x7F.into());
				last_data_note = temp;

				file_pos += 5;
			},
			_ => {
				file_pos += 1;
				verbose!("Unrecognized command {} at {}", command, file_pos);
				//FIXME: unrecoverable? Stop
			},
		}
		midi.delta_time += wait;

		if loop_available {
			if stop_vgm {
				cur_loop += 0x01;
				if cur_loop < config.vgm_loops {
					midi.event_write(Meta(Marker(TEXT_LOOP_END)));
					stop_vgm = false;
					file_pos = loop_pos;
				}
			}
			//FIXME: If this is not aligned with an instruction, it won't loop.
			if file_pos == loop_pos {
				midi.event_write(Meta(Marker(TEXT_LOOP_START)));
				//Call SegaPCM_Dump
			}
		}

		if vgm.clock_sn76489 != 0 {
			for counter in 0..sn76489.state.note_delay.len() {
				sn76489.state.note_delay[counter] += wait;
			}
		}

		//if config.ym2612_dac_disabled = 1 { DACWrt = false
		//if vgm.clock_2612 {
		//for TempLng = 0x0..=wait - 1
		//	Put #2, , DAC_DATA_BYTE
		//}
		//}
		if dac_wrt {
			for counter in 0x0..vgm.data_block_count {
				if vgm.data_block_pos[counter as usize] == data_bank_pos - 1 {
					if last_data_note < 0x80 {
						midi.note_off_write(0x0F.into(), last_data_note.into(), 0x00.into());
					}
					midi.note_on_write(0x0F.into(), (counter as u8).into(), 0x7F.into());
					last_data_note = counter.try_into().unwrap(); //FIXME: This is something fucky. Check if VB allows overflow or not.
				}
			}
			dac_wrt = false
		}

		/* FIXME: if Timer >= DETimer {
			// Calling DoEvents too often causes huge slowdowns
			Call frmMain.tmrConversionStatus_Timer
			Conversion_Status_Current = file_pos - vhFileHeader.lngDataOffset
			DoEvents
			DETimer = Timer + 0.2
		}*/

		if file_pos >= vgm.file_header.eof_offset as usize || stop_vgm {
			break;
		}
	}

	if loop_available {
		midi.event_write(Meta(Marker(TEXT_LOOP_END)));
	}
	//Call SegaPCM_Dump
	//Call DAC_WriteClose

	if vgm.clock_sn76489 != 0 {
		port = if vgm.file_header.hz_sn76489 & 0x40000000 != 0 {
			0x1
		} else {
			0x0
		};
		for chip_number in 0x0..=port {
			for command in 0x0..=0x3 {
				data = (1 << 7) | (command << 5);
				sn76489.command_handle(
					data,
					chip_number,
					midi,
				)?;
				sn76489.command_handle(
					0x00,
					chip_number,
					midi,
				)?;
			}
		}
	}
	/*if vgm.clock_ym2413 != 0 && false {
		// TODO ...
		for _command in 0x0..=0x7 {
			register = 0x0;
			data = 0x0;
			ym2413.command_handle(
				register,
				data,
				vgm.clock_ym2413,
				midi,
			)?;
		}
	}
	if vgm.clock_ym2612 != 0 && false {
		// TODO ...
		for command in 0x0..=0x5 {
			port = command / 0x3;
			register = command % 0x3;
			data = 0x0;
			ym2612.command_handle(port, register, data, fsam_2612, midi)?;
		}
	}*/
	if vgm.clock_ym2151 != 0 {
		for command in 0x0..=0x7 {
			register = 0x8;
			data = command;
			ym2151.command_handle(register, data, midi)?;
		}
	}

	Ok(())
}