A rust port of vgm2mid by Paul Jensen and Valley Bell
use crate::config::Config;
use crate::midi_shim::{
	db_to_midi_vol, MIDIShim, MIDI_MODULATOR_WHEEL, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT,
	MIDI_PAN_RIGHT, MIDI_PITCHWHEEL_CENTER, MIDI_VOLUME,
};
use crate::strict;
use crate::utils::shift;
use crate::utils::{hz_to_note, FactoredState};
use crate::vgm2mid::{CHN_DAC, OPN_TYPE_YM2608, OPN_TYPE_YM2610, OPN_TYPE_YM2612};
use crate::ym3812::ym3812_vol_to_db;
use anyhow::{bail, Result};

// YM2612 Register Select Constants
#[allow(dead_code)]
pub(crate) const YM2610_DELTAT_ADPCM: u8 = 0x10; // DELTA-T/ADPCM
pub(crate) const YM2612_TEST: u8 = 0x21; // LSI Test Data
pub(crate) const YM2612_LFO_EN_LFO_FREQ: u8 = 0x22;
#[allow(dead_code)]
pub(crate) const YM2612_LFO_EN: u8 = 8;
#[allow(dead_code)]
pub(crate) const YM2612_LFO_FREQ: u8 = 7;
pub(crate) const YM2612_TIMER_A_MSB: u8 = 0x24;
pub(crate) const YM2612_TIMER_A_LSB: u8 = 0x25;
pub(crate) const YM2612_TIMER_B: u8 = 0x26;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_MODE_RESET_ENABLE_LOAD: u8 = 0x27;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_MODE: u8 = 192;
#[allow(dead_code)]
pub(crate) const YM2612_TIMER_RESET: u8 = 48;
#[allow(dead_code)]
pub(crate) const YM2612_TIMER_ENABLE: u8 = 12;
#[allow(dead_code)]
pub(crate) const YM2612_TIMER_LOAD: u8 = 3;
pub(crate) const YM2612_SLOT_CH: u8 = 0x28; // Key On/Off
pub(crate) const YM2612_SLOT: u8 = 240;
pub(crate) const YM2612_CH: u8 = 7;
pub(crate) const YM2612_DAC: u8 = 0x2A; // DAC Data
#[allow(dead_code)]
pub(crate) const YM2612_DAC_DATA: u8 = 255;
pub(crate) const YM2612_DAC_EN: u8 = 0x2B; // DAC Data
#[allow(dead_code)]
pub(crate) const YM2612_DAC_EN_BIT: u8 = 127;
#[allow(dead_code)]
pub(crate) const YM2612_DT_MULTI: u8 = 0x30; // Detune/Multiple
#[allow(dead_code)]
pub(crate) const YM2612_DT: u8 = 112;
#[allow(dead_code)]
pub(crate) const YM2612_MULTI: u8 = 15;
pub(crate) const YM2612_TL: u8 = 0x40; // Total Level
pub(crate) const YM2612_TL_END: u8 = YM2612_TL + 0xF;
pub(crate) const YM2612_KS_AR: u8 = 0x50; // Key Scale/Attack Rate
#[allow(dead_code)]
pub(crate) const YM2612_KS: u8 = 192;
#[allow(dead_code)]
pub(crate) const YM2612_AR: u8 = 31;
pub(crate) const YM2612_DR: u8 = 0x60; // Decay Rate
pub(crate) const YM2612_SR: u8 = 0x70; // Sustain Rate
pub(crate) const YM2612_SL_RR: u8 = 0x80; // Sustain Level/Release Rate
#[allow(dead_code)]
pub(crate) const YM2612_SL: u8 = 240;
#[allow(dead_code)]
pub(crate) const YM2612_RR: u8 = 15;
pub(crate) const YM2612_SSG_EG: u8 = 0x90; // SSG-Type Envelope Control
#[allow(dead_code)]
pub(crate) const YM2612_FNUM_LSB: u8 = 0xA0;
#[allow(dead_code)]
pub(crate) const YM2612_BLOCK_FNUM_MSB: u8 = 0xA4;
pub(crate) const YM2612_BLOCK: u8 = 56;
#[allow(dead_code)]
pub(crate) const YM2612_FNUM_MSB: u8 = 7;
pub(crate) const YM2612_CH3_SPECIAL_FNUM_MSB: u8 = 0xA8;
pub(crate) const YM2612_CH3_SPECIAL_FNUM_MSB_END: u8 = YM2612_CH3_SPECIAL_FNUM_MSB + 2;
pub(crate) const YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB: u8 = 0xAC;
pub(crate) const YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB_END: u8 = YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB + 2;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_SPECIAL_BLOCK: u8 = 56;
#[allow(dead_code)]
pub(crate) const YM2612_CH3_SPECIAL_FNUM_LSB: u8 = 7;
pub(crate) const YM2612_FB_CONNECTION: u8 = 0xB0; // Self-Feedback/Connection
pub(crate) const YM2612_FB_CONNECTION_END: u8 = YM2612_FB_CONNECTION + 2;
#[allow(dead_code)]
pub(crate) const YM2612_FB: u8 = 56;
pub(crate) const YM2612_CONNECTION: u8 = 7;
pub(crate) const YM2612_L_R: u8 = 0xB4; // Pan Select
pub(crate) const YM2612_L_R_END: u8 = YM2612_L_R + 2;
pub(crate) const YM2612_L: u8 = 0x80;
pub(crate) const YM2612_R: u8 = 0x40;
pub(crate) const YM2612_BOTH_L_R: u8 = 192;

#[test]
fn test_hz() {
	let res = hz_ym2612(20, None, 3579545.0 / 72.0);
	assert!((res - 0.23706389798058403).abs() < 0.000001);
}

pub(crate) fn hz_ym2612(fnum: u32, opt_block: Option<u8>, fsam_2612: f64) -> f64 {
	let block = opt_block.unwrap_or(0);
	fnum as f64 * fsam_2612 * f64::powf(2.0, block as f64 - 22.0)
}

pub(crate) struct YM2612State {
	last_dac_note: u8,
	dac_en: bool,
	slot: [u8; 6],
	//	DT(5, 3) As Byte, MULTI(5, 3) As Byte
	tl: [[u8; 4]; 6],
	volume: [u8; 6],
	//KF As Byte
	//	KS(5, 3) As Byte, AR(5, 3) As Byte
	//	DR: [u8; 6, 3],
	//	SR: [u8; 6, 3],
	//	SL(5, 3) As Byte, RR(5, 3) As Byte
	block: [u8; 6],
	connection: [u8; 6],
	fmopn_adpcm_state: FMOPNADPCMState,
	ym_adpcm_state: YMADPCMState,
	pub opn_type: u8,
	factored: FactoredState,
}

impl Default for YM2612State {
	fn default() -> Self {
		YM2612State {
			last_dac_note: 0,
			dac_en: false,
			slot: [0; 6],
			tl: [[0; 4]; 6],
			volume: [0xFF; 6],
			block: [0; 6],
			connection: [0; 6],
			fmopn_adpcm_state: Default::default(),
			ym_adpcm_state: Default::default(),
			opn_type: 0,
			factored: Default::default(),
		}
	}
}

pub(crate) struct YM2612<'config> {
	pub(crate) state: YM2612State,
	config: &'config Config,
}

impl<'config> YM2612<'config> {
	pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<YM2612State>) -> Self {
		YM2612 {
			state: opt_state.unwrap_or_default(),
			config,
		}
	}

	pub(crate) fn command_handle(
		&mut self,
		port: u8,
		register: u8,
		data: u8,
		fsam_2612: f64,
		midi: &mut MIDIShim,
	) -> Result<()> {
		match register {
			0x0..=0x1F => {
				self.opn_pcm_write(port, register, data, midi)?;
			},
			YM2612_TEST => (),
			YM2612_LFO_EN_LFO_FREQ => (),
			YM2612_TIMER_A_MSB => (),
			YM2612_TIMER_A_LSB => (),
			YM2612_TIMER_B => (),

			//  YM2612_CH3_MODE_RESET_ENABLE_LOAD => {
			//		CH3_Mode = (Data & YM2612_CH3_MODE) / 96
			//  },
			YM2612_SLOT_CH => self.process_slot(data, midi)?,
			YM2612_DAC => self.process_dac(data, midi),
			YM2612_DAC_EN => {
				self.state.dac_en = (register & YM2612_DAC_EN) == YM2612_DAC_EN;

				//	} YM2612_DT_MULTI => {
			},
			YM2612_TL..=YM2612_TL_END => {
				self.process_total_level(register, port, data, midi)?
			},
			YM2612_KS_AR => (),
			YM2612_DR => (),
			YM2612_SR => (),
			YM2612_SL_RR => (),
			YM2612_SSG_EG => (),
			0xA4..=0xA6 => {
				let channel = (register ^ 0xA4) + port;

				if self.config.ym2612_ch_disabled[channel as usize] {
					return Ok(());
				}

				self.state.block[channel as usize] = (data & YM2612_BLOCK) >> 3;
				self.state.factored.fnum_msb[channel as usize] = data & 7;
			},
			0xA0..=0xA2 => {
				self.fun_name(register, port, data, fsam_2612, midi)?;
			},
			YM2612_CH3_SPECIAL_FNUM_MSB..=YM2612_CH3_SPECIAL_FNUM_MSB_END => (),
			YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB
				..=YM2612_CH3_SPECIAL_BLOCK_FNUM_LSB_END => (),
			YM2612_FB_CONNECTION..=YM2612_FB_CONNECTION_END => {
				let channel = (register ^ YM2612_FB_CONNECTION) + port;
				self.state.connection[channel as usize] = data & YM2612_CONNECTION;

				if self.config.ym2612_ch_disabled[channel as usize] {
					return Ok(());
				}
				if self.config.ym2612_prog_disabled[channel as usize] {
					return Ok(());
				}

				midi.program_change_write(channel.into(), (data & 0x7F).into());
				//midi.event_write(MIDI_PROGRAM_CHANGE, CH, (Data & 0xF8) / 0x8);
			},
			YM2612_L_R..=YM2612_L_R_END => {
				let channel = (register ^ YM2612_L_R) + port;

				if self.config.ym2612_ch_disabled[channel as usize] {
					return Ok(());
				}
				if self.config.ym2612_pan_disabled[channel as usize] {
					return Ok(());
				}

				if channel == 0x5 && self.state.dac_en {
					self.state.factored.midi_pan[channel as usize] = 0xFF.into()
				}
				let temp_pan = get_pan_from_data(data)?;
				if self.state.factored.midi_pan[channel as usize] != temp_pan {
					self.state.factored.midi_pan[channel as usize] = temp_pan;
					midi.controller_write(
						channel.into(),
						MIDI_PAN,
						self.state.factored.midi_pan[channel as usize],
					);
				}

				let temp_double = (data & 0x07) as f64 / 0x07 as f64;
				let temp = (temp_double * temp_double * 0x7F as f64).round() as u8;
				if self.state.factored.midi_mod[channel as usize] != temp {
					self.state.factored.midi_mod[channel as usize] =
						temp.into();
					midi.controller_write(
						channel.into(),
						MIDI_MODULATOR_WHEEL,
						self.state.factored.midi_mod[channel as usize],
					);
				}

				if self.config.ym2612_dac_disabled {
					return Ok(());
				}

				//if CH = 5 & DAC_EN = 1 {
				//	midi.event_write(Midi { channel: CHN_DAC, message: Controller { controller: MIDI_PAN, value: self.state.factored.midi_pan[channel as usize] }});
				//	midi.event_write(Midi { channel: CHN_DAC, message: NoteOff { key: 35, vel: 0 }});
				//	midi.event_write(Midi { channel: CHN_DAC, message: NoteOn { key: 35, vel: MIDI_VOLUME_MAX * 0.8 }});
				//}
			},
			_ => {
				/*Exit Sub
				Debug.Print "Register " & Hex$(Register) & ": ";
				match Register
				} YM2612_TEST => {
					Debug.Print "TEST - LSI Test Data"
				} YM2612_LFO_EN_LFO_FREQ => {
					Debug.Print "LFO_EN_LFO_FREQ"
				} YM2612_TIMER_A_MSB => {
					Debug.Print "TIMER_A_MSB"
				} YM2612_TIMER_A_LSB => {
					Debug.Print "TIMER_A_LSB"
				} YM2612_TIMER_B => {
					Debug.Print "TIMER_B"
				} YM2612_CH3_MODE_RESET_ENABLE_LOAD => {
					Debug.Print "CH3_MODE_RESET_ENABLE_LOAD"
				} YM2612_SLOT_CH => {
					Debug.Print "SLOT_CH - Key On/Off"
				} YM2612_DAC => {
					Debug.Print "DAC - DAC Data"
				} YM2612_DAC_DATA => {
					Debug.Print "DAC_DATA"
				} YM2612_DAC_EN => {
					Debug.Print "DAC_EN - DAC Data"
				} YM2612_DT_MULTI => {
					Debug.Print "DT_MULTI - Detune/Multiple"
				} YM2612_TL => {
					Debug.Print "TL - Total Level"
				} YM2612_KS_AR => {
					Debug.Print "KS_AR - Key Scale/Attack Rate"
				} YM2612_DR => {
					Debug.Print "DR - Decay Rate"
				} YM2612_SR => {
					Debug.Print "SR - Sustain Rate"
				} YM2612_SL_RR => {
					Debug.Print "SL_RR - Sustain Level/Release Rate"
				} YM2612_SSG_EG => {
					Debug.Print "SSG_EG - SSG-Type Envelope Control"
				} YM2612_FNum_LSB => {
					Debug.Print "FNum_LSB"
				} YM2612_Block_FNum_MSB => {
					Debug.Print "Block_FNum_MSB"
				} YM2612_CH3_SPECIAL_FNum_MSB => {
					Debug.Print "CH3_SPECIAL_FNum_MSB"
				} YM2612_CH3_SPECIAL_BLOCK_FNum_LSB => {
					Debug.Print "CH3_SPECIAL_BLOCK_FNum_LSB"
				} YM2612_CH3_SPECIAL_FNum_LSB => {
					Debug.Print "CH3_SPECIAL_FNum_LSB"
				} YM2612_FB_CONNECTION => {
					Debug.Print "FB_CONNECTION - Self-Feedback/Connection"
				} YM2612_L_R => {
					Debug.Print "L_R - Pan Select"
				_ => {
					Debug.Print "Unknown"
				},*/
			}, //Stop
		}
		Ok(())
	}

	fn fun_name(
		&mut self,
		register: u8,
		port: u8,
		data: u8,
		fsam_2612: f64,
		midi: &mut MIDIShim<'_, '_>,
	) -> Result<(), anyhow::Error> {
		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] = shift(
			&mut 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,
			)?;
		}
		Ok(())
	}

	fn process_slot(&mut self, data: u8, midi: &mut MIDIShim) -> Result<()> {
		let mut channel = data & YM2612_CH;
		channel -= if channel > 3 { 1 } else { 0 };
		channel = channel.clamp(0, 5);

		if self.config.ym2612_ch_disabled[channel as usize] {
			return Ok(());
		}

		self.state.slot[channel as usize] = (data & YM2612_SLOT) / 8;

		if self.state.slot[channel as usize] == 0
			&& self.state.factored.note_on_2[channel as usize]
		{
			self.state.factored.midi_wheel[channel as usize] = MIDI_PITCHWHEEL_CENTER;
			//midi.event_write(MIDI_PITCHWHEEL, CH, self.state.factored.midi_wheel[channel as usize]);
			midi.note_off_write(
				channel.into(),
				self.state.factored.midi_note[channel as usize],
				0x00.into(),
			);
			self.state.factored.midi_note[channel as usize] = 0xFF.into();

			self.state.factored.note_on_1[channel as usize] =
				shift(&mut self.state.factored.note_on_2[channel as usize], false);
		} else if self.state.slot[channel as usize] != 0
			&& !self.state.factored.note_on_2[channel as usize]
		{
			midi.do_note_on(
				self.state.factored.note_1[channel as usize],
				self.state.factored.note_2[channel as usize],
				channel.into(),
				&mut self.state.factored.midi_note[channel as usize],
				&mut self.state.factored.midi_wheel[channel as usize],
				Some(255),
				None,
			)?;
			self.state.factored.note_on_1[channel as usize] =
				self.state.factored.note_on_2[channel as usize];
			self.state.factored.note_on_2[channel as usize] = true;
		}
		Ok(())
	}

	fn opn_pcm_write(
		&mut self,
		port: u8,
		register: u8,
		data: u8,
		midi: &mut MIDIShim,
	) -> Result<()> {
		match (port, self.state.opn_type) {
			(0x00, OPN_TYPE_YM2608) if (register & 0x10) != 0 => fmopn_adpcm_write(
				register & 0xF,
				data,
				&mut self.state.fmopn_adpcm_state,
				midi,
			)?,
			(0x00, OPN_TYPE_YM2610) if (register & 0x10) == 0 => ym_adpcm_write(
				register & 0xF,
				data,
				&mut self.state.ym_adpcm_state,
				midi,
			)?,
			(0x01, OPN_TYPE_YM2608) if (register & 0x10) == 0 => ym_adpcm_write(
				register & 0xF,
				data,
				&mut self.state.ym_adpcm_state,
				midi,
			)?,
			(0x01, OPN_TYPE_YM2610) if register > 0x30 => fmopn_adpcm_write(
				register & 0xF,
				data,
				&mut self.state.fmopn_adpcm_state,
				midi,
			)?,
			_ => strict!(),
		}

		Ok(())
	}

	fn process_total_level(
		&mut self,
		register: u8,
		port: u8,
		data: u8,
		midi: &mut MIDIShim,
	) -> Result<()> {
		let mut channel = ((register ^ YM2612_TL) % 4) + port;
		if self.config.ym2612_ch_disabled[channel as usize] {
			return Ok(());
		}
		if self.config.ym2612_vol_disabled[channel as usize] {
			return Ok(());
		}
		let operation = (register ^ YM2612_TL) / 4;
		channel = if channel > 5 { 5 } else { channel };
		self.state.tl[channel as usize][operation as usize] = data & 0x7F;
		if operation < 3 {
			return Ok(());
		}
		let temp_byte = self.state.factored.midi_volume[channel as usize];
		self.state.volume[channel as usize] = self.volume_from_connection(channel)?;
		self.state.factored.midi_volume[channel as usize] =
			db_to_midi_vol(ym3812_vol_to_db(self.state.volume[channel as usize]) / 4.0);

		//Const PI = 3.14159265358979
		////Const Phase = PI / (MIDI_VOLUME_MAX * 2)
		////self.state.factored.midi_volume[channel as usize] = MIDI_VOLUME_MAX * (1 - (Sin(self.state.factored.midi_volume[channel as usize] * Phase)))
		//self.state.factored.midi_volume[channel as usize] = 0x80 * (1# - Sin(self.state.volume[channel as usize] / 0x80 * (PI / 2)))
		//if self.state.factored.midi_volume[channel as usize] = 0x80 {
		//	self.state.factored.midi_volume[channel as usize] = 0x7F
		//}

		if temp_byte != self.state.factored.midi_volume[channel as usize] {
			midi.controller_write(
				channel.into(),
				MIDI_VOLUME,
				self.state.factored.midi_volume[channel as usize],
			);
		}
		Ok(())
	}

	fn volume_from_connection(&mut self, channel: u8) -> Result<u8> {
		Ok((match self.state.connection[channel as usize] {
			0..=3 => self.state.tl[channel as usize][3] as u16,
			4 => {
				(self.state.tl[channel as usize][1] as u16
					+ self.state.tl[channel as usize][3] as u16)
					/ 2
			},
			5 | 6 => {
				(self.state.tl[channel as usize][1] as u16
					+ self.state.tl[channel as usize][2] as u16
					+ self.state.tl[channel as usize][3] as u16)
					/ 3
			},
			7 => {
				(self.state.tl[channel as usize][0] as u16
					+ self.state.tl[channel as usize][1] as u16
					+ self.state.tl[channel as usize][2] as u16
					+ self.state.tl[channel as usize][3] as u16)
					/ 4
			},
			_ => bail!("Invalid connection state"),
		}) as u8)
	}

	fn process_dac(&mut self, data: u8, midi: &mut MIDIShim) {
		if self.config.ym2612_dac_disabled {
			return;
		}
		if self.state.opn_type != OPN_TYPE_YM2612 {
			return;
		}

		//if Data = 0xF {
		//	Data = 0x14	// Sonic 3K Bass Drum
		//} else if Data = 0x10 {
		//	Data = 0x16	// Sonic 3K Snare Drum
		//} else {
		//	Data = 0xFF
		//}
		//if Data = 0x22 {
		//	Data = 0x23	 // Sonic Crackers Bass Drum
		//} else if Data = 0x23 {
		//	Data = 0x26	 // Sonic Crackers Snare Drum
		//} else if Data = 0xC4 {
		//	Data = 0x2D	 // Sonic Crackers Tom Tom
		//} else {
		//	Data = 0xFF
		//}
		//if Data = 0x1A {
		//	Data = 0x23	 // Sonic 1 Bass Drum
		//} else if Data = 0xB {
		//	Data = 0x26	 // Sonic 1 Snare Drum
		//} else {
		//	Data = 0xFF
		//}
		let should_write = data < 0x20;

		if data != 0xFF && should_write {
			midi.note_off_write(CHN_DAC, self.state.last_dac_note.into(), 0x00.into());
			//midi.event_write(Midi { channel: CHN_DAC, message: NoteOn { key: 35, vel: Data }});
			midi.note_on_write(CHN_DAC, data.into(), 0x7F.into());
			self.state.last_dac_note = data
		}
	}
}

fn get_pan_from_data(data: u8) -> Result<midly::num::u7> {
	Ok(match data & YM2612_BOTH_L_R {
		YM2612_L => MIDI_PAN_LEFT,
		YM2612_R => MIDI_PAN_RIGHT,
		YM2612_BOTH_L_R | 0 => MIDI_PAN_CENTER,
		_ => {
			strict!();
			MIDI_PAN_CENTER
		},
	})
}

struct FMOPNADPCMState {
	tl: u8,
	il: [u8; 5],
	ins_vol: [u8; 5],
	note_on: [u8; 5],
	vol_max: u8,
	adpcm_drum_notes: [u8; 6],
}

impl Default for FMOPNADPCMState {
	fn default() -> Self {
		FMOPNADPCMState {
			tl: 0,
			il: [0; 5],
			ins_vol: [0; 5],
			note_on: [0; 5],
			vol_max: 0,
			adpcm_drum_notes: [
				0x23, // Bass Drum
				0x26, // Snare Drum
				0x33, // Top Cymbal
				0x2A, // High Hat
				0x2D, // Tom Tom
				0x25, // Rim Shot
			],
		}
	}
}

fn fmopn_adpcm_write(
	register: u8,
	data: u8,
	state: &mut FMOPNADPCMState,
	midi: &mut MIDIShim,
) -> Result<()> {
	let channel: u8;
	let mut volume: u8;
	let vol_mul: u8;
	let vol_shift: u8;

	match register {
		0x0 => {
			fmopn_dm(data, state, midi);
		},
		0x1 => {
			fnopn_tl(state, data);
		},
		_ => {
			channel = register & 0x7;
			if channel >= 0x6 {
				return Ok(());
			}

			match register & 0x38 {
				0x8 => {
					// Bit 7 = L, Bit 6 = R, Bit 4-0 = Instrument Level
					state.il[channel as usize] = (!data) & 0x3F;
					volume = state.tl + state.il[channel as usize];
					if volume < state.vol_max {
						state.vol_max = volume
					}
					if volume >= 0x3F {
						vol_mul = 0x00;
						vol_shift = 0x00;
					} else {
						vol_mul = 0x0F - (volume & 0x7);
						vol_shift = 0x01 + ((volume & 0x38) / 0x08);
					}

					let _temp_byte = get_pan_from_data(register);
					//midi.event_write(Midi { channel: channel, message: Controller { controller: MIDI_PAN, value: state.factored.midi_pan[channel as usize] }});

					//Volume = (AdPcm_Acc * VolMul) / 2 ^ VolShift
					let mut temp_volume =
						((vol_mul as u32) << 8) >> vol_shift as u32; //FIXME: this is messy
					temp_volume = if temp_volume == 0x80 {
						0x7F
					} else {
						temp_volume
					};
					volume = if temp_volume >= 0x100 {
						temp_volume as u8 | 0x80
					} else {
						temp_volume as u8
					};
					volume = if volume == 0x0 { 0x1 } else { volume };
					state.ins_vol[channel as usize] = volume;
					//midi.event_write(MIDI_CONTROLLER_channelANGE, channel, MIDI_VOLUME, Volume);
				},
				0x10 | 0x18 => {
					// Start Address
					if (register & 0x38) == 0x18 {
						//Debug.Print Hex(Data)
						//Stop
					}
				},
				0x20 | 0x28 => (), // End Address
				_ => strict!(),
			}
		},
	}
	Ok(())
}

fn fnopn_tl(state: &mut FMOPNADPCMState, data: u8) {
	// Bit 0-5 = Total Level
	state.tl = (!data) & 0x3F;
	if state.vol_max == 0 {
		state.vol_max = 0xFF
	}
	//TempByt = (!Data) & 0x3F
	//for channel in 0x0..=0x5 {
	//	Volume = TL + IL[channel as usize]
	//	if Volume >= 0x3F {
	//		VolMul = 0x0
	//		VolShift = 0x0
	//	} else {
	//		VolMul = 0xF - (Volume & 0x7)
	//		VolShift = 0x1 + ((Volume & 0x38) / 0x8)
	//	}
	//	//Volume = (AdPcm_Acc * VolMul) / 1 << VolShift
	//	Volume = 0x100 * VolMul / 1 << VolShift
	//}
}

fn fmopn_dm(data: u8, state: &mut FMOPNADPCMState, midi: &mut MIDIShim) {
	// DM, --, C5, C4, C3, C2, C1, C0
	if (data & 0x80) == 0x0 {
		// Key On
		for channel in 0x0..=0x5 {
			if data & (1 << channel) != 0 {
				if channel != 0 {
					midi.note_off_write(
						0x09.into(),
						state.adpcm_drum_notes[channel as usize].into(),
						0x00.into(),
					);
				}
				midi.note_on_write(
					0x09.into(),
					state.adpcm_drum_notes[channel as usize].into(),
					state.ins_vol[channel as usize].into(),
				);
				state.note_on[channel as usize] = 1
			} else {
				if state.note_on[channel as usize] != 0 {
					midi.note_off_write(
						0x09.into(),
						state.adpcm_drum_notes[channel as usize].into(),
						0x00.into(),
					);
				}
				state.note_on[channel as usize] = 0
			}
		}
	} else {
		// All Keys Off
		for channel in 0x0..=0x5 {
			if state.note_on[channel as usize] != 0 {
				midi.note_off_write(
					0x09.into(),
					state.adpcm_drum_notes[channel as usize].into(),
					0x00.into(),
				);
			}
			state.note_on[channel as usize] = 0
		}
	}
}

#[derive(Default)]
struct YMADPCMState {
	ins_vol: u8,
}

fn ym_adpcm_write(
	register: u8,
	data: u8,
	state: &mut YMADPCMState,
	midi: &mut MIDIShim,
) -> Result<()> {
	match register & 0x0F {
		0x00 => {
			// Control 1: Start, Rec, MemMode, Repeat, SpOff, --, --, Reset => {
			if data & 0x80 != 0 {
				midi.note_off_write(0x09.into(), 0x7F.into(), 0x00.into());
				midi.note_on_write(0x09.into(), 0x7F.into(), state.ins_vol.into());
			} else {
				midi.note_off_write(0x09.into(), 0x7F.into(), 0x00.into());
			}
		},
		0x01 => {
			// Control 2: L, R, -, -, Sample, DA/AD, RAMTYPE, ROM => {
			match (data & 0xC0) / 0x40 {
				0x1 => (),       //state.factored.midi_pan[channel as usize] = MIDI_PAN_RIGHT
				0x2 => (),       //state.factored.midi_pan[channel as usize] = MIDI_PAN_LEFT
				0x3 | 0x0 => (), //state.factored.midi_pan[channel as usize] = MIDI_PAN_CENTER
				_ => strict!(),
			}
		},
		0x02 => (),                         // Start Address Low
		0x03 => (),                         // Start Address High
		0x04 => (),                         // Stop Address Low
		0x05 => (),                         // Stop Address High
		0x06 => (),                         // Prescale Low
		0x07 => (),                         // Prescale High
		0x08 => (),                         // ADPCM Data
		0x09 => (),                         // Delta-N Low
		0x0A => (),                         // Delta-N High
		0x0B => state.ins_vol = data / 0x2, // Volume
		0x0C => (),                         // Flag Control: Extend Status Clear/Mask
		_ => (),
	}
	Ok(())
}