A rust port of vgm2mid by Paul Jensen and Valley Bell
use crate::config::Config;
use crate::{strict, verbose};
use crate::vgm2mid::CHN_DAC;
use crate::vgm2mid::PITCHWHEEL_SENSITIVITY_DEFAULT;
use anyhow::{anyhow, Result};
use midly::num::{u14, u4, u7};
use midly::MetaMessage::Tempo;
use midly::MidiMessage::{Controller, NoteOff, NoteOn, PitchBend, ProgramChange};
use midly::TrackEventKind::{Meta, Midi};
use midly::{Smf, TrackEvent, TrackEventKind};
use std::path::Path;

/*Public Type DACHeaderData
	RIFF As String * 4
	HeaderLen As Long
	WAVEfmt As String * 8
	HeaderSize As Long
	Tag: u16
	Channels: u16
	SampleRate As Long
	BytespSecond As Long
	BytespSample: u16
	SampelBits: u16
	Data As String * 4
	DataLen As Long
End Type*/

//MIDI Controller Numbers
#[allow(dead_code)]
pub(crate) const MIDI_BANK_SELECT: u7 = u7::new(0x00);
pub(crate) const MIDI_MODULATOR_WHEEL: u7 = u7::new(0x01);
pub(crate) const MIDI_DATA_ENTRY_MSB: u7 = u7::new(0x06);
pub(crate) const MIDI_VOLUME: u7 = u7::new(0x07);
pub(crate) const MIDI_PAN: u7 = u7::new(0x0A);
#[allow(dead_code)]
pub(crate) const MIDI_SUSTAIN: u7 = u7::new(0x40);
#[allow(dead_code)]
pub(crate) const MIDI_SOFT: u7 = u7::new(0x43);
#[allow(dead_code)]
pub(crate) const MIDI_LEGATO_PEDAL: u7 = u7::new(0x44);
#[allow(dead_code)]
pub(crate) const MIDI_HOLD2_PEDAL: u7 = u7::new(0x45);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_TIMBRE: u7 = u7::new(0x47);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_RELEASE_TIME: u7 = u7::new(0x48);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_ATTACK_TIME: u7 = u7::new(0x49);
#[allow(dead_code)]
pub(crate) const MIDI_SOUND_BRIGHTNESS: u7 = u7::new(0x4A);
pub(crate) const MIDI_NRPN_LSB: u7 = u7::new(0x62);
pub(crate) const MIDI_NRPN_MSB: u7 = u7::new(0x63);
pub(crate) const MIDI_RPN_LSB: u7 = u7::new(0x64);
pub(crate) const MIDI_RPN_MSB: u7 = u7::new(0x65);
#[allow(dead_code)]
pub(crate) const MIDI_ALL_SOUNDS_OFF: u7 = u7::new(0x78);
#[allow(dead_code)]
pub(crate) const MIDI_RESET_ALL_CONTROLLERS: u7 = u7::new(0x79);
#[allow(dead_code)]
pub(crate) const MIDI_ALL_NOTES_OFF: u7 = u7::new(0x7B);

/*
Public Enum MIDIControllers
	mcModulatorWheel = 1
	mcDataEntry = 6
	mcVolume = 7
	mcPan = 10
	mcSustain = 64
	mcLegatoPedal = 68
	mcHold2Pedal = 69
	mcSoundTimbre = 71
	mcSoundReleaseTime = 72
	mcSoundAttackTime = 73
	mcSoundBrightness = 74
	mcRPNFine = 100
	mcRPNCoarse = 101
	mcAllControllersOff = 123
	mcAllNotesOff = 123
End Enum*/

// Loop-Strings (compatible with WinAmp)
pub(crate) const TEXT_LOOP_START: &[u8] = "loopStart".as_bytes();
pub(crate) const TEXT_LOOP_END: &[u8] = "loopEnd".as_bytes();
// When I tested the Space Harrier BIOS-VGM, I found a bug in Winamp.
// if the MIDI has the text "loopStart", looping works correctly (jump to 0:13)
// if the text has another case (like "LoopStart" or "Loopstart") or even a
// space ("Loop Start") it jumps to 2:34. I can//t say why.

/*Public Enum MIDIMetaEvent
	mmeTrackEnd = 0x2F
End Enum*/

//RPNs
pub(crate) const RPN_PITCH_BEND_RANGE_M: u7 = u7::new(0x0);
pub(crate) const RPN_PITCH_BEND_RANGE_L: u7 = u7::new(0x0);

// NRPNs
pub(crate) const NRPN_DRUM_PITCH_COARSE: u7 = u7::new(0x18);
#[allow(dead_code)]
pub(crate) const NRPN_DRUM_PITCH_FINE: u7 = u7::new(0x19);
#[allow(dead_code)]
pub(crate) const NRPN_DRUM_VOLUME: u7 = u7::new(0x1A);
pub(crate) const NRPN_DRUM_PAN: u7 = u7::new(0x1C);

////AWE32/SBLive! NRPNs
//pub(crate) const NRPN_ENV1_DELAY: u8 = 16260
//pub(crate) const NRPN_ENV1_ATTACK: u8 = 16261
//pub(crate) const NRPN_ENV1_HOLD: u8 = 16262
//pub(crate) const NRPN_ENV1_DECAY: u8 = 16263
//pub(crate) const NRPN_ENV1_SUSTAIN: u8 = 16264
//pub(crate) const NRPN_ENV1_RELEASE: u8 = 16265

//Public Enum MIDINRPN
//	mnEnv1DDelay = 16260
//	mnEnv1Attack = 16261
//	mnEnv1Hold = 16262
//	mnEnvDecay = 16263
//	mnEnvSustain = 16264
//	mnEnvRelease = 16265
//End Enum

// MIDI Controller Values
#[allow(dead_code)]
pub(crate) const MIDI_VOLUME_MIN: u7 = u7::new(0x00);
pub(crate) const MIDI_VOLUME_MAX: u7 = u7::new(0x7F);

#[allow(dead_code)]
pub(crate) const MIDI_VELOCITY_MIN: u7 = u7::new(0x00);
#[allow(dead_code)]
pub(crate) const MIDI_VELOCITY_MAX: u7 = u7::new(0x7F);

pub(crate) const MIDI_PAN_LEFT: u7 = u7::new(0x00);
pub(crate) const MIDI_PAN_RIGHT: u7 = u7::new(0x7F);
pub(crate) const MIDI_PAN_CENTER: u7 = u7::new(0x40);

#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_DOWN_ONE_SEMITONE: u14 = u14::new(0x0);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_DOWN_TWO_SEMITONES: u14 = u14::new(0x1000);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_DOWN_HALF_SEMITIONE: u14 = u14::new(0x1800);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_MIN: u14 = u14::new(0x0);
pub(crate) const MIDI_PITCHWHEEL_CENTER: u14 = u14::new(0x2000);
pub(crate) const MIDI_PITCHWHEEL_MAX: u14 = u14::new(0x3FFF);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_UP_HALF_SEMITIONE: u14 = u14::new(0x2800);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_UP_ONE_SEMITONE: u14 = u14::new(0x3000);
#[allow(dead_code)]
pub(crate) const MIDI_PITCHWHEEL_UP_TWO_SEMITONES: u14 = u14::new(0x3FFF);

/*Public Enum MIDIControllerValue
	//Volume
	mcvVolumeMin = 0x0
	mcvVolumeMax = 0x7F
	//Pan
	mcvPanLeft = 0x0
	mcvPanRight = 0x7F
	mcvPanCenter = 0x40
	//PitchWheel
	mcvPitchWheelCenter = 0x2000
End Enum*/

//General MIDI Patch Names
pub(crate) const MIDI_PATCH_ACOUSTIC_GRAND_PIANO: u7 = u7::new(0);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BRIGHT_ACOUSTIC_PIANO: u7 = u7::new(1);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_GRAND_PIANO: u7 = u7::new(2);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_HONKY_TONK_PIANO: u7 = u7::new(3);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_RHODES_PIANO: u7 = u7::new(4);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CHORUSED_PIANO: u7 = u7::new(5);
pub(crate) const MIDI_PATCH_HARPSICHORD: u7 = u7::new(6);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CLAVINET: u7 = u7::new(7);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CELESTA: u7 = u7::new(8);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_GLOCKENSPIEL: u7 = u7::new(9);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MUSIC_BOX: u7 = u7::new(10);
pub(crate) const MIDI_PATCH_VIBRAPHONE: u7 = u7::new(11);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MARIMBA: u7 = u7::new(12);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_XYLOPHONE: u7 = u7::new(13);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TUBULAR_BELLS: u7 = u7::new(14);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_DULCIMER: u7 = u7::new(15);
pub(crate) const MIDI_PATCH_HAMMOND_ORGAN: u7 = u7::new(16);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PERCUSSIVE_ORGAN: u7 = u7::new(17);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ROCK_ORGAN: u7 = u7::new(18);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CHURCH_ORGAN: u7 = u7::new(19);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_REED_ORGAN: u7 = u7::new(20);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ACCORDION: u7 = u7::new(21);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_HARMONICA: u7 = u7::new(22);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TANGO_ACCORDION: u7 = u7::new(23);
pub(crate) const MIDI_PATCH_ACOUSTIC_GUITAR_NYLON: u7 = u7::new(24);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ACOUSTIC_GUITAR_STEEL: u7 = u7::new(25);
pub(crate) const MIDI_PATCH_ELECTRIC_GUITAR_JAZZ: u7 = u7::new(26);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_GUITAR_CLEAN: u7 = u7::new(27);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_GUITAR_MUTED: u7 = u7::new(28);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_OVERDRIVEN_GUITAR: u7 = u7::new(29);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_DISTORTION_GUITAR: u7 = u7::new(30);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_GUITAR_HARMONICS: u7 = u7::new(31);
pub(crate) const MIDI_PATCH_ACOUSTIC_BASS: u7 = u7::new(32);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_BASS_FINGER: u7 = u7::new(33);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ELECTRIC_BASS_PICK: u7 = u7::new(34);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FRETLESS_BASS: u7 = u7::new(35);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SLAP_BASS_1: u7 = u7::new(36);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SLAP_BASS_2: u7 = u7::new(37);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_BASS_1: u7 = u7::new(38);
pub(crate) const MIDI_PATCH_SYNTH_BASS_2: u7 = u7::new(39);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_VIOLIN: u7 = u7::new(40);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_VIOLA: u7 = u7::new(41);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CELLO: u7 = u7::new(42);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CONTRABASS: u7 = u7::new(43);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TREMOLO_STRINGS: u7 = u7::new(44);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PIZZICATO_STRINGS: u7 = u7::new(45);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ORCHESTRAL_HARP: u7 = u7::new(46);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TIMPANI: u7 = u7::new(47);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_STRING_ENSEMBLE_1: u7 = u7::new(48);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_STRING_ENSEMBLE_2: u7 = u7::new(49);
pub(crate) const MIDI_PATCH_SYNTH_STRINGS_1: u7 = u7::new(50);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_STRINGS_2: u7 = u7::new(51);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_CHOIR_AHHS: u7 = u7::new(52);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_VOICE_OOHS: u7 = u7::new(53);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_VOICE: u7 = u7::new(54);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ORCHESTRA_HIT: u7 = u7::new(55);
pub(crate) const MIDI_PATCH_TRUMPET: u7 = u7::new(56);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TROMBONE: u7 = u7::new(57);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TUBA: u7 = u7::new(58);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MUTED_TRUMPET: u7 = u7::new(59);
pub(crate) const MIDI_PATCH_FRENCH_HORN: u7 = u7::new(60);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BRASS_SECTION: u7 = u7::new(61);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_BRASS_1: u7 = u7::new(62);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_BRASS_2: u7 = u7::new(63);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SOPRANO_SAX: u7 = u7::new(64);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ALTO_SAX: u7 = u7::new(65);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TENOR_SAX: u7 = u7::new(66);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BARITONE_SAX: u7 = u7::new(67);
pub(crate) const MIDI_PATCH_OBOE: u7 = u7::new(68);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_ENGLISH_HORN: u7 = u7::new(69);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BASSOON: u7 = u7::new(70);
pub(crate) const MIDI_PATCH_CLARINET: u7 = u7::new(71);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PICCOLO: u7 = u7::new(72);
pub(crate) const MIDI_PATCH_FLUTE: u7 = u7::new(73);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_RECORDER: u7 = u7::new(74);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAN_FLUTE: u7 = u7::new(75);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BOTTLE_BLOW: u7 = u7::new(76);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SHAKUHACHI: u7 = u7::new(77);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_WHISTLE: u7 = u7::new(78);
pub(crate) const MIDI_PATCH_OCARINA: u7 = u7::new(79);
pub(crate) const MIDI_PATCH_LEAD_1_SQUARE: u7 = u7::new(80);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_2_SAWTOOTH: u7 = u7::new(81);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_3_CALLIOPE: u7 = u7::new(82);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_4_CHIFF: u7 = u7::new(83);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_5_CHANGARANG: u7 = u7::new(84);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_6_VOICE: u7 = u7::new(85);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_7_FIFTHS: u7 = u7::new(86);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_LEAD_8_BASS_LEAD: u7 = u7::new(87);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_1_NEW_AGE: u7 = u7::new(88);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_2_WARM: u7 = u7::new(89);
pub(crate) const MIDI_PATCH_PAD_3_POLYSYNTH: u7 = u7::new(90);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_4_CHOIR: u7 = u7::new(91);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_5_BOWED: u7 = u7::new(92);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_6_METALLIC: u7 = u7::new(93);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_7_HALO: u7 = u7::new(94);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_PAD_8_SWEEP: u7 = u7::new(95);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_1_RAIN: u7 = u7::new(96);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_2_SOUNDTRACK: u7 = u7::new(97);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_3_CRYSTAL: u7 = u7::new(98);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_4_ATMOSPHERE: u7 = u7::new(99);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_5_BRIGHTNESS: u7 = u7::new(100);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_6_GOBLINS: u7 = u7::new(101);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_7_ECHOES: u7 = u7::new(102);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FX_8_SCI_FI: u7 = u7::new(103);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SITAR: u7 = u7::new(104);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BANJO: u7 = u7::new(105);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SHAMISEN: u7 = u7::new(106);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_KOTO: u7 = u7::new(107);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_KALIMBA: u7 = u7::new(108);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BAGPIPE: u7 = u7::new(109);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_FIDDLE: u7 = u7::new(110);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SHANAI: u7 = u7::new(111);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TINKLE_BELL: u7 = u7::new(112);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_AGOGO: u7 = u7::new(113);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_STEEL_DRUMS: u7 = u7::new(114);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_WOODBLOCK: u7 = u7::new(115);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TAIKO_DRUM: u7 = u7::new(116);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_MELODIC_TOM: u7 = u7::new(117);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SYNTH_DRUM: u7 = u7::new(118);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_REVERSE_CYMBAL: u7 = u7::new(119);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_GUITAR_FRET_NOISE: u7 = u7::new(120);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BREATH_NOISE: u7 = u7::new(121);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_SEASHORE: u7 = u7::new(122);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_BIRD_TWEET: u7 = u7::new(123);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_TELEPHONE_RING: u7 = u7::new(124);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_HELICOPTER: u7 = u7::new(125);
#[allow(dead_code)]
pub(crate) const MIDI_PATCH_APPLAUSE: u7 = u7::new(126);
pub(crate) const MIDI_PATCH_GUNSHOT: u7 = u7::new(127);

pub(crate) struct MIDIShim<'smf, 'config> {
	delta_factor: f64,
	pub delta_time: u32,
	written_delay_position: u32, //FIXME: bad naming
	current_sample_position: u32,
	midi_file: Smf<'smf>,
	config: &'config Config,
}

impl<'smf, 'config> MIDIShim<'smf, 'config> {
	pub(crate) fn new<'c: 'config>(config: &'c Config) -> Result<MIDIShim> {
		let delta_fact =
			22050.0 / f64::from(config.cnv_accuracy) * config.get_tempo_modifier();

		let mut midi = Smf::new(midly::Header {
			format: midly::Format::SingleTrack,
			timing: midly::Timing::Metrical(config.cnv_accuracy.into()),
		});

		midi.tracks.push(Vec::new());

		Ok(MIDIShim {
			delta_factor: delta_fact,
			delta_time: 0,
			written_delay_position: 0,
			current_sample_position: 0,
			midi_file: midi,
			config,
		})
	}

	pub(crate) fn controller_write(&mut self, channel: u4, controller: u7, value: u7) {
		self.event_write(Midi {
			channel,
			message: Controller { controller, value },
		})
	}

	pub(crate) fn note_on_write(&mut self, channel: u4, note: u7, velocity: u7) {
		self.event_write(Midi {
			channel,
			message: NoteOn {
				key: note,
				vel: velocity,
			},
		});
	}

	pub(crate) fn note_off_write(&mut self, channel: u4, note: u7, velocity: u7) {
		self.event_write(Midi {
			channel,
			message: NoteOff {
				key: note,
				vel: velocity,
			},
		});
	}

	pub(crate) fn program_change_write(&mut self, channel: u4, program: u7) {
		self.event_write(Midi {
			channel,
			message: ProgramChange { program },
		});
	}

	pub(crate) fn pitch_bend_write(&mut self, channel: u4, pitchbend: u14) {
		self.event_write(Midi {
			channel,
			message: PitchBend {
				bend: midly::PitchBend(pitchbend),
			},
		});
	}

	pub(crate) fn event_write<'event: 'smf>(&mut self, event_kind: TrackEventKind<'event>) {
		self.current_sample_position += self.delta_time;
		self.delta_time = 0;

		let temp_long = (self.current_sample_position as f64 / self.delta_factor + 0.5)
			.round() as u32;
		let delta_delay = temp_long - self.written_delay_position;
		self.written_delay_position += delta_delay;

		let event = TrackEvent {
			delta: delta_delay.into(),
			kind: event_kind,
		};

		self.midi_file.tracks[0].push(event);
	}

	pub(crate) fn write_smf(&mut self, out_path: &Path) -> Result<()> {
		verbose!("Writing smf to {}", out_path.display());

		self.event_write(midly::TrackEventKind::Meta(midly::MetaMessage::EndOfTrack));

		self.midi_file.save(out_path).map_err(|err| anyhow!(err))
	}

	//TODO: Move initialization in to the respective modules to encapsulate their state.
	pub(crate) fn data_init(&mut self, psg_on: bool, t6w28_sn76489: bool) {
		let tempo_val = 500000.0 * self.config.get_tempo_modifier();

		self.event_write(Meta(Tempo((tempo_val.round() as u32).into())));

		// I think there's no need to init the instruments for the FM Channels.
		// Most things are done before playing the first note.

		// Pan (Stereo) Settings for FM CHs 1-9 (Ch 3 Special)
		for channel in 0..=8 {
			self.controller_write(channel.into(), MIDI_PAN, MIDI_PAN_CENTER);
		}

		// Program Changes for FM CHs 1-9 (Ch 3 Special)
		//for CH in 0..=8 {
		//	midi.event_write(MIDI_PROGRAM_CHANGE, CH, MIDI_PATCH_Lead_8_Bass_Lead);
		//Next

		// Initial Volume Levels for FM CHs 1-9 (Ch 3 Special)
		//for CH in 0..=8 {
		//	midi.event_write(Midi { channel: CH, message: Controller { controller: MIDI_VOLUME, value: 95 }});
		//Next

		// Change Pitch Wheel Sensitivity for All CHs  (Ch 3 Special)
		if self.config.pitchwheel_sensitivity != PITCHWHEEL_SENSITIVITY_DEFAULT {
			for channel in 0x0..=0xF {
				if channel == 9 {
					// Setting the PB Range for Ch 9 (Drum-Channel) is unnecessary
					continue;
				}
				self.controller_write(
					channel.into(),
					MIDI_RPN_MSB,
					RPN_PITCH_BEND_RANGE_M,
				);
				self.controller_write(
					channel.into(),
					MIDI_RPN_LSB,
					RPN_PITCH_BEND_RANGE_L,
				);
				self.controller_write(
					channel.into(),
					MIDI_RPN_MSB,
					self.config.pitchwheel_sensitivity.into(),
				);
			}
		}

		// Settings for Drum Channel
		self.controller_write(CHN_DAC, MIDI_PAN, MIDI_PAN_CENTER);
		self.program_change_write(CHN_DAC, 0x00.into());
		self.controller_write(CHN_DAC, MIDI_VOLUME, MIDI_VOLUME_MAX);

		if psg_on {
			// Pan (Stereo) Settings for SN76489 CHs 1-3 and Noise CH
			for channel in 10..=13 {
				if !t6w28_sn76489 {
					self.controller_write(
						channel.into(),
						MIDI_PAN,
						MIDI_PAN_CENTER,
					);
				} else {
					self.controller_write(
						(channel - 10).into(),
						MIDI_PAN,
						MIDI_PAN_RIGHT,
					);
					self.controller_write(
						channel.into(),
						MIDI_PAN,
						MIDI_PAN_LEFT,
					);
				}
			}

			// Program Changes for SN76489 CHs 1-3 and Noise CH
			for channel in 10..=12 {
				if t6w28_sn76489 {
					self.program_change_write(
						(channel - 10).into(),
						MIDI_PATCH_LEAD_1_SQUARE,
					);
				}
				self.program_change_write(channel.into(), MIDI_PATCH_LEAD_1_SQUARE);
			}
			if t6w28_sn76489 {
				self.program_change_write(0x03.into(), MIDI_PATCH_GUNSHOT);
			}
			self.program_change_write(0x0D.into(), MIDI_PATCH_GUNSHOT);

			// Initial Volume Levels for SN76489 CHs 1-3 and Noise CH
			for channel in 10..=13 {
				if t6w28_sn76489 {
					self.controller_write(
						channel.into(),
						MIDI_VOLUME,
						0x7F.into(),
					);
				}
				self.controller_write(channel.into(), MIDI_VOLUME, 0x7F.into());
			}
		}
	}

	pub(crate) fn do_note_on(
		&mut self,
		note_1: f64,
		note_2: f64,
		midi_channel: u4,
		midi_note: &mut u7,
		midi_wheel: &mut u14,
		opt_note_type: Option<u8>,
		opt_note_velocity: Option<u7>,
	) -> Result<bool> {
		if note_1 < 0.0 || note_2 < 0.0 {
			strict!("Notes below 0") //FIXME: Handle this as an error.
		}
		let note_velocity = opt_note_velocity.unwrap_or(u7::new(0x7F));
		let note_type = opt_note_type.unwrap_or(0);

		// the returned value ist used to detect written notes
		// (and avoid notes of Length 0)
		if note_type == 0xFF || note_1 == 0xFF as f64 || note_2 == 0xFF as f64 {
			return self.do_note_internal(
				note_2,
				midi_channel,
				midi_note,
				midi_wheel,
				note_velocity,
			);
		}

		let fuzzy_wheel = f64::from(midi_wheel.as_int())
			+ ((note_2 - note_1) * self.config.pitchwheel_steps as f64);

		//if Abs(Note_2 - Note_1) >= 0.5 {
		//	dblTestValue = -1
		//}
		if fuzzy_wheel >= MIDI_PITCHWHEEL_MIN.as_int() as f64
			&& fuzzy_wheel <= (MIDI_PITCHWHEEL_MAX.as_int() + 1) as f64
		{
			*midi_wheel = (fuzzy_wheel.round() as u16)
				.clamp(MIDI_PITCHWHEEL_MIN.as_int(), MIDI_PITCHWHEEL_MAX.as_int())
				.into(); //FIXME: This doesn't seem sane
			self.pitch_bend_write(midi_channel, *midi_wheel);
		} else {
			return self.do_note_internal(
				note_2,
				midi_channel,
				midi_note,
				midi_wheel,
				note_velocity,
			);
		}

		Ok(false) // only PitchBend written
	}

	fn do_note_internal(
		&mut self,
		mut note_2: f64,
		midi_channel: u4,
		midi_note: &mut u7,
		midi_wheel: &mut u14,
		note_velocity: u7,
	) -> Result<bool> {
		if note_2 >= f64::from(0xFF) {
			if *midi_note < 0xFF {
				self.note_off_write(midi_channel, *midi_note, 0x00.into());
				*midi_note = (note_2.clamp(0.0, 0xFF.into()).round() as u8).into();
			}
			return Ok(true);
		}

		let mut dbl_pitch_wheel: f64 = note_2 - note_2.trunc();
		note_2 = note_2.trunc().abs();
		if dbl_pitch_wheel >= 0.5 {
			note_2 += 1.0;
			dbl_pitch_wheel -= -1.0;
		}
		if *midi_note < 0xFF {
			self.note_off_write(midi_channel, *midi_note, 0x00.into());
		}
		let new_wheel = (MIDI_PITCHWHEEL_CENTER.as_int() as f64
			+ dbl_pitch_wheel * f64::from(self.config.pitchwheel_steps))
		.round() as u16;
		if new_wheel != *midi_wheel {
			*midi_wheel = new_wheel.into();
			if !(midi_channel == CHN_DAC && dbl_pitch_wheel.abs() < 0.1) {
				self.pitch_bend_write(midi_channel, *midi_wheel);
			}
		}
		*midi_note = (note_2.round() as u8).into();
		self.note_on_write(midi_channel, *midi_note, note_velocity);

		Ok(true) // wrote new Note
	}
}

//FIXME: automatic casting nonsense transliterated from VB
pub(crate) fn db_to_midi_vol(db: f64) -> u7 {
	((f64::powf(10.0, db / 40.0) * (0x7F as f64)) as u8).into()
}

pub(crate) fn lin_to_db(linear_val: f64) -> f64 {
	if linear_val > 0.0 {
		f64::log2(linear_val) * 6.0
	} else {
		-400.0 // results in volume 0
	}
}