A rust port of vgm2mid by Paul Jensen and Valley Bell
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
	}
}