/*
 *  Copyright (C) 2002-2021  The DOSBox Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <algorithm>

#include "SDL.h"

#include "dosbox.h"
#include "midi.h"
#include "control.h"
#include "cross.h"
#include "support.h"
#include "setup.h"
#include "mapper.h"
#include "pic.h"
#include "hardware.h"
#include "logging.h"
#include "timer.h"

#define RAWBUF	1024

uint8_t MIDI_evt_len[256] = {
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x00
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x10
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x20
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x30
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x40
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x50
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x60
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x70

  3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0x80
  3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0x90
  3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xa0
  3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xb0

  2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2,  // 0xc0
  2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2,  // 0xd0

  3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xe0

  0,2,3,2, 0,0,1,0, 1,0,1,1, 1,0,1,0   // 0xf0
};

MidiHandler * handler_list = nullptr;

MidiHandler::MidiHandler(){
	next = handler_list;
	handler_list = this;
}

bool roland_gs_sysex = true;
MidiHandler Midi_none;
DB_Midi midi;
std::string sffile="Not available";

static struct {
	bool init;
	bool ignore;

	// NOTE: 16-bit ($ffff = not used, $00-ff = data value)
	uint16_t code_80[0x80];			// note off (w/ velocity)
	uint16_t code_90[0x80];			// note on (w/ velocity)
	uint16_t code_a0[0x80];			// aftertouch (polyphonic key pressure)
	uint16_t code_b0[0x80];			// Continuous controller (GM 1.0 + GS)
	uint16_t code_c0[1];				// patch change
	uint16_t code_d0[1];				// channel pressure (after-touch)
	uint16_t code_e0[2];				// pitch bend
	//uint16_t code_f0-ff				// system messages

	uint16_t code_rpn_coarse[3];		// registered parameter numbers (GM 1.0)
	uint16_t code_rpn_fine[3];			// registered parameter numbers (GM 1.0)
} midi_state[16];



/* Include different midi drivers, lowest ones get checked first for default */

#if C_MT32
#include "midi_mt32.h"
#endif

#if C_FLUIDSYNTH || defined(WIN32) && !defined(HX_DOS) && !defined(_WIN32_WINDOWS)
#include "midi_synth.h"
#endif

#if !defined(WIN32)
# include "midi_timidity.h"
#endif

#if defined(MACOSX)

#include "midi_coremidi.h"
#include "midi_coreaudio.h"

#elif defined (WIN32)

#include "midi_win32.h"

#else

#include "midi_oss.h"

#endif

#if defined (HAVE_ALSA)

#include "midi_alsa.h"

#endif


//#define WIN32_MIDI_STATE_DEBUG

void MIDI_State_Reset()
{
	memset( &midi_state, 0xff, sizeof(midi_state) );

	midi_state[0].init = true;
	midi_state[0].ignore = false;
}


void MIDI_State_SaveMessage()
{
	uint8_t channel, command, arg1, arg2;

	if( midi_state[0].init == false ) {
		MIDI_State_Reset();
	}

	if( midi_state[0].ignore == true ) return;

	// skip MUNT
	//if( strcmp( midi.handler->GetName(), "mt32" ) == 0 ) {
	//	return;
	//}

	channel = midi.cmd_buf[0] & 0xf;
	command = midi.cmd_buf[0] >> 4;
	arg1 = midi.cmd_buf[1];
	arg2 = midi.cmd_buf[2];

	switch( command ) {
		case 0x8:		// Note off
			// - arg1 = note, arg2 = velocity off
			midi_state[channel].code_80[arg1] = arg2;

			memset( &midi_state[channel].code_90[arg1], 0xff, sizeof(midi_state[channel].code_90[arg1]) );
			memset( &midi_state[channel].code_a0[arg1], 0xff, sizeof(midi_state[channel].code_a0[arg1]) );
			memset( midi_state[channel].code_d0, 0xff, sizeof(midi_state[channel].code_d0) );
			break;


		case 0x9:		// Note on
			if( arg2 > 0 ) {
				// - arg1 = note, arg2 = velocity on
				midi_state[channel].code_90[arg1] = arg2;

				memset( &midi_state[channel].code_80[arg1], 0xff, sizeof(midi_state[channel].code_80[arg1]) );
			}
			else {
				// velocity = 0 (note off)
				midi_state[channel].code_80[arg1] = arg2;

				memset( &midi_state[channel].code_90[arg1], 0xff, sizeof(midi_state[channel].code_90[arg1]) );
				memset( &midi_state[channel].code_a0[arg1], 0xff, sizeof(midi_state[channel].code_a0[arg1]) );
				memset( midi_state[channel].code_d0, 0xff, sizeof(midi_state[channel].code_d0) );
			}
			break;


		case 0xA:		// Aftertouch (polyphonic pressure)
			midi_state[channel].code_a0[arg1] = arg2;
			break;


		case 0xB:		// Controller #s
			midi_state[channel].code_b0[arg1] = arg2;

			switch( arg1 ) {
				// General Midi 1.0
				case 0x01: break;		// Modulation

				case 0x07: break;		// Volume
				case 0x0A: break;		// Pan
				case 0x0B: break;		// Expression

				case 0x40: break;		// Sustain pedal

				case 0x64: break;		// Registered Parameter Number (RPN) LSB
				case 0x65: break;		// Registered Parameter Number (RPN) MSB

				case 0x79:					// All controllers off
					/*
					(likely GM1+GM2)
					Set Expression (#11) to 127
					Set Modulation (#1) to 0
					Set Pedals (#64, #65, #66, #67) to 0
					Set Registered and Non-registered parameter number LSB and MSB (#98-#101) to null value (127)
					Set pitch bender to center (64/0)
					Reset channel pressure to 0 
					Reset polyphonic pressure for all notes to 0.
					*/
					memset( midi_state[channel].code_a0, 0xff, sizeof(midi_state[channel].code_a0) );
					memset( midi_state[channel].code_c0, 0xff, sizeof(midi_state[channel].code_c0) );
					memset( midi_state[channel].code_d0, 0xff, sizeof(midi_state[channel].code_d0) );
					memset( midi_state[channel].code_e0, 0xff, sizeof(midi_state[channel].code_e0) );

					memset( midi_state[channel].code_b0+0x01, 0xff, sizeof(midi_state[channel].code_b0[0x01]) );
					memset( midi_state[channel].code_b0+0x0b, 0xff, sizeof(midi_state[channel].code_b0[0x0b]) );
					memset( midi_state[channel].code_b0+0x40, 0xff, sizeof(midi_state[channel].code_b0[0x40]) );

					memset( midi_state[channel].code_rpn_coarse, 0xff, sizeof(midi_state[channel].code_rpn_coarse) );
					memset( midi_state[channel].code_rpn_fine, 0xff, sizeof(midi_state[channel].code_rpn_fine) );

					/*
					(likely GM1+GM2)
					Do NOT reset Bank Select (#0/#32)
					Do NOT reset Volume (#7)
					Do NOT reset Pan (#10)
					Do NOT reset Program Change
					Do NOT reset Effect Controllers (#91-#95) (5B-5F)
					Do NOT reset Sound Controllers (#70-#79) (46-4F)
					Do NOT reset other channel mode messages (#120-#127) (78-7F)
					Do NOT reset registered or non-registered parameters.
					*/
					//Intentional fall-through

				case 0x7b:					// All notes off
					memset( midi_state[channel].code_80, 0xff, sizeof(midi_state[channel].code_80) );
					memset( midi_state[channel].code_90, 0xff, sizeof(midi_state[channel].code_90) );
					break;

				//******************************************
				//******************************************
				//******************************************

				// Roland GS
				case 0x00: break;		// Bank select MSB
				case 0x05: break;		// Portamento time
				case 0x20: break;		// Bank select LSB
				case 0x41: break;		// Portamento
				case 0x42: break;		// Sostenuto
				case 0x43: break;		// Soft pedal
				case 0x54: break;		// Portamento control
				case 0x5B: break;		// Effect 1 (Reverb) send level
				case 0x5D: break;		// Effect 3 (Chorus) send level
				case 0x5E: break;		// Effect 4 (Variation) send level

				// TODO: Add more as needed
				case 0x62: break;		// NRPN LSB
				case 0x63: break;		// NRPN MSB
				case 0x78:					// All sounds off
					memset( midi_state[channel].code_80, 0xff, sizeof(midi_state[channel].code_80) );
					memset( midi_state[channel].code_90, 0xff, sizeof(midi_state[channel].code_90) );
					break;

				/*
				// Roland MT-32
				case 0x64:
					parts[part]->setRPNLSB(velocity);
					break;
				case 0x65:
					parts[part]->setRPNMSB(velocity);
					break;
				case 0x7B: // All notes off
					//printDebug("All notes off");
					parts[part]->allNotesOff();
					break;
				case 0x7C:
				case 0x7D:
				case 0x7E:
				case 0x7F:
					// CONFIRMED:Mok: A real LAPC-I responds to these controllers as follows:
					parts[part]->setHoldPedal(false);
					parts[part]->allNotesOff();
					break;
				*/

				//******************************************
				//******************************************
				//******************************************

				/*
				(LSB,MSB) RPN

				GM 1.0
				00,00 = Pitch bend range (fine + coarse)
				01,00 = Channel Fine tuning
				02,00 = Channel Coarse tuning
				7F,7F = None
				*/

				case 0x06:					// Data entry (coarse)
					{
						int rpn;

						rpn = midi_state[channel].code_b0[0x64];
						rpn |= (midi_state[channel].code_b0[0x65]) << 8;


						// GM 1.0 = 0-2
						if( rpn >= 3 ) break;
						//if( rpn == 0x7f7f ) break;

						midi_state[channel].code_rpn_coarse[rpn] = arg2;
					}
					break;


				case 0x26:					// Data entry (fine)
					{
						int rpn;

						rpn = midi_state[channel].code_b0[0x64];
						rpn |= (midi_state[channel].code_b0[0x65]) << 8;


						// GM 1.0 = 0-2
						if( rpn >= 3 ) break;
						//if( rpn == 0x7f7f ) break;

						midi_state[channel].code_rpn_fine[rpn] = arg2;
					}
					break;


				default:			// unsupported
					break;
			}
			break;


		case 0xC:		// Patch change
			midi_state[channel].code_c0[0] = arg1;
			break;

		case 0xD:		// Channel pressure (aftertouch)
			midi_state[channel].code_d0[0] = arg1;
			break;

		case 0xE:		// Pitch bend
			midi_state[channel].code_e0[0] = arg1;
			midi_state[channel].code_e0[1] = arg2;
			break;

		case 0xF:		// System messages
			// TODO: General Midi 1.0 says 'Master Volume' SysEx
			break;

		// unhandled case
		default:
			break;
	}
}


void MIDI_RawOutByte(uint8_t);
void MIDI_State_LoadMessage()
{
	if( midi_state[0].init == false ) {
		MIDI_State_Reset();
	}


	midi_state[0].ignore = true;

	// flush data buffer - throw invalid midi message
	MIDI_RawOutByte(0xf7);

	// shutdown sound
	for( int lcv=0; lcv<16; lcv++ ) {
		MIDI_RawOutByte( 0xb0+lcv );
		MIDI_RawOutByte( 0x78 );
		MIDI_RawOutByte( 0x00 );
		MIDI_RawOutByte( 0x79 );
		MIDI_RawOutByte( 0x00 );

		MIDI_RawOutByte( 0x7b );
		MIDI_RawOutByte( 0x00 );
	}


	for( int lcv=0; lcv<16; lcv++ ) {
		// control states (bx - 00-5F)
		MIDI_RawOutByte( 0xb0+lcv );


		for( int lcv2=0; lcv2<0x80; lcv2++ ) {
			if( lcv2 >= 0x60 ) break;
			if( midi_state[lcv].code_b0[lcv2] == 0xffff ) continue;

			// RPN data - coarse / fine
			if( lcv2 == 0x06 ) continue;
			if( lcv2 == 0x26 ) continue;


			//MIDI_RawOutByte( 0xb0+lcv );
			MIDI_RawOutByte( lcv2 );
			MIDI_RawOutByte( midi_state[lcv].code_b0[lcv2] & 0xff );
		}


		// control states - RPN data (GM 1.0)
		for( int lcv2=0; lcv2<3; lcv2++ ) {
			if( midi_state[lcv].code_rpn_coarse[lcv2] == 0xffff &&
					midi_state[lcv].code_rpn_fine[lcv2] == 0xffff ) continue;


			//MIDI_RawOutByte( 0xb0+lcv );
			MIDI_RawOutByte( 0x64 );
			MIDI_RawOutByte( lcv2 );
			MIDI_RawOutByte( 0x65 );
			MIDI_RawOutByte( 0x00 );


			//MIDI_RawOutByte( 0xb0+lcv );
			if( midi_state[lcv].code_rpn_coarse[lcv2] != 0xffff ) {
				MIDI_RawOutByte( 0x06 );
				MIDI_RawOutByte( midi_state[lcv].code_rpn_coarse[lcv2] & 0xff );
			}

			if( midi_state[lcv].code_rpn_fine[lcv2] != 0xffff ) {
				MIDI_RawOutByte( 0x26 );
				MIDI_RawOutByte( midi_state[lcv].code_rpn_fine[lcv2] & 0xff );
			}


			//MIDI_RawOutByte( 0xb0+lcv );
			MIDI_RawOutByte( 0x64 );
			MIDI_RawOutByte( 0x7f );
			MIDI_RawOutByte( 0x65 );
			MIDI_RawOutByte( 0x7f );
		}


		// program change
		if( midi_state[lcv].code_c0[0] != 0xffff ) {
			MIDI_RawOutByte( 0xc0+lcv );
			MIDI_RawOutByte( midi_state[lcv].code_c0[0]&0xff );
		}


		// pitch wheel change
		if( midi_state[lcv].code_e0[0] != 0xffff ) {
			MIDI_RawOutByte( 0xe0+lcv );
			MIDI_RawOutByte( midi_state[lcv].code_e0[0]&0xff );
			MIDI_RawOutByte( midi_state[lcv].code_e0[1]&0xff );
		}

		//******************************************
		//******************************************
		//******************************************

		// note on
		MIDI_RawOutByte( 0x90+lcv );

		for( int lcv2=0; lcv2<0x80; lcv2++ ) {
			if( midi_state[lcv].code_90[lcv2] == 0xffff ) continue;


			//MIDI_RawOutByte( 0x90+lcv );
			MIDI_RawOutByte( lcv2 );
			MIDI_RawOutByte( midi_state[lcv].code_90[lcv2]&0xff );
		}


		// polyphonic aftertouch
		MIDI_RawOutByte( 0xa0+lcv );

		for( int lcv2=0; lcv2<0x80; lcv2++ ) {
			if( midi_state[lcv].code_a0[lcv2] == 0xffff ) continue;


			//MIDI_RawOutByte( 0xa0+lcv );
			MIDI_RawOutByte( lcv2 );
			MIDI_RawOutByte( midi_state[lcv].code_a0[lcv2]&0xff );
		}


		// channel aftertouch
		if( midi_state[lcv].code_d0[0] != 0xffff ) {
			MIDI_RawOutByte( 0xd0+lcv );
			MIDI_RawOutByte( midi_state[lcv].code_d0[0]&0xff );
		}
	}

	midi_state[0].ignore = false;
}


void MIDI_RawOutByte(uint8_t data) {
	if (midi.sysex.start) {
		uint32_t passed_ticks = GetTicks() - midi.sysex.start;
		if (passed_ticks < midi.sysex.delay) SDL_Delay((Uint32)(midi.sysex.delay - passed_ticks));
	}

	/* Test for a realtime MIDI message */
	if (data>=0xf8) {
		midi.rt_buf[0]=data;
		midi.handler->PlayMsg(midi.rt_buf);
		return;
	}	 
	/* Test for an active sysex transfer */
	if (midi.status==0xf0) {
		if (!(data&0x80)) { 
			if (midi.sysex.used<(SYSEX_SIZE-1)) midi.sysex.buf[midi.sysex.used++] = data;
			return;
		} else {
			midi.sysex.buf[midi.sysex.used++] = 0xf7;

			if ((midi.sysex.start) && (midi.sysex.used >= 4) && (midi.sysex.used <= 9) && (midi.sysex.buf[1] == 0x41) && (midi.sysex.buf[3] == 0x16)) {
				LOG(LOG_ALL,LOG_ERROR)("MIDI:Skipping invalid MT-32 SysEx midi message (too short to contain a checksum)");
			} else {
//				LOG(LOG_ALL,LOG_NORMAL)("Play sysex; address:%02X %02X %02X, length:%4d, delay:%3d", midi.sysex.buf[5], midi.sysex.buf[6], midi.sysex.buf[7], midi.sysex.used, midi.sysex.delay);
				midi.handler->PlaySysex(midi.sysex.buf, midi.sysex.used);

				if (roland_gs_sysex) {
					if (midi.sysex.buf[1] == 0x41/*Roland*/ && midi.sysex.buf[3] == 0x42/*GS*/ && midi.sysex.buf[4] == 0x12/*Send*/) {
						const uint32_t addr =
							((uint32_t)midi.sysex.buf[5] << 16) +
							((uint32_t)midi.sysex.buf[6] <<  8) +
							(uint32_t)midi.sysex.buf[7];

						switch (addr) {
							case 0x40007F: /* GS reset */
								{
									uint8_t msg[] = {0xFF};
									midi.handler->PlayMsg(msg); /* MIDI reset */
								}
								break;
							default:
								break;
						};
					}
				}

				if (midi.sysex.start) {
					if (midi.sysex.buf[5] == 0x7F) {
						midi.sysex.delay = 290; // All Parameters reset
					} else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x04) {
						midi.sysex.delay = 145; // Viking Child
					} else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x01) {
						midi.sysex.delay = 30; // Dark Sun 1
					} else {
						midi.sysex.delay = (Bitu)(((float)(midi.sysex.used) * 1.25f) * 1000.0f / 3125.0f) + 2;
						if (midi.sysex.extra_delay && midi.sysex.delay < 40) {
							midi.sysex.delay = 40;
						}
					}
					midi.sysex.start = GetTicks();
				}
			}

			LOG(LOG_ALL,LOG_NORMAL)("Sysex message size %d",(int)midi.sysex.used);
			if (CaptureState & CAPTURE_MIDI) {
				CAPTURE_AddMidi( true, midi.sysex.used-1, &midi.sysex.buf[1]);
			}
		}
	}
	if (data&0x80) {
		midi.status=data;
		midi.cmd_pos=0;
		midi.cmd_len=MIDI_evt_len[data];
		if (midi.status==0xf0) {
			midi.sysex.buf[0]=0xf0;
			midi.sysex.used=1;
		}
	}
	if (midi.cmd_len) {
		midi.cmd_buf[midi.cmd_pos++]=data;
		if (midi.cmd_pos >= midi.cmd_len) {
			if (CaptureState & CAPTURE_MIDI) {
				CAPTURE_AddMidi(false, midi.cmd_len, midi.cmd_buf);
			}

			midi.handler->PlayMsg(midi.cmd_buf);
			midi.cmd_pos=1;		//Use Running status

			MIDI_State_SaveMessage();
		}
	}
}

bool MIDI_Available(void)  {
	return midi.available;
}

class MIDI:public Module_base{
public:
	MIDI(Section* configuration):Module_base(configuration){
		Section_prop * section = static_cast<Section_prop *>(configuration);
		const char * dev=section->Get_string("mididevice");
		std::string fullconf = section->Get_string("midiconfig");
#if C_FLUIDSYNTH || defined(WIN32) && !defined(HX_DOS) && !defined(_WIN32_WINDOWS)
		synthsamplerate = section->Get_int("samplerate");
		if (synthsamplerate == 0) synthsamplerate = 44100;
#endif
		if (control->opt_silent) dev = "none";

		/* If device = "default" go for first handler that works */
		MidiHandler * handler;
		bool opened = false;

		roland_gs_sysex = section->Get_bool("roland gs sysex");

//		MAPPER_AddHandler(MIDI_SaveRawEvent,MK_f8,MMOD1|MMOD2,"caprawmidi","Cap MIDI");
		midi.sysex.delay = 0;
		midi.sysex.start = 0;
		if (fullconf.find("delaysysex") != std::string::npos) {
			midi.sysex.start = GetTicks();
			fullconf.erase(fullconf.find("delaysysex"));
			LOG(LOG_MISC,LOG_DEBUG)("MIDI:Using delayed SysEx processing");
			midi.sysex.extra_delay = true;
		}
		trim(fullconf);
		const char * conf = fullconf.c_str();
		midi.status=0x00;
		midi.cmd_pos=0;
		midi.cmd_len=0;

		if (strcasecmp(dev,"default")) {
			for (handler = handler_list; handler; handler = handler->next) {
				if (!strcasecmp(dev,handler->GetName())) {
					opened = handler->Open(conf);
					break;
				}
			}
			if (handler == NULL)
				LOG_MSG("MIDI:Cannot find device:%s. Finding default handler.",dev);
			else if (!opened)
				LOG_MSG("MIDI:Cannot open device:%s with config:%s. Finding default handler.",dev,conf);
		}

		if (!opened) {
			for (handler = handler_list; handler; handler = handler->next) {
				opened = handler->Open(conf);
				if (opened) break;
			}
		}

		if (!opened) {
			// This shouldn't be possible
			LOG_MSG("MIDI:Could not open a handler");
			return;
		}

		midi.available=true;
		midi.handler=handler;
		LOG_MSG("MIDI:Opened device:%s",handler->GetName());

		// force reset to prevent crashes (when not properly shutdown)
		// ex. Roland VSC = unexpected hard system crash
		midi_state[0].init = false;
		MIDI_State_LoadMessage();
	}
	~MIDI(){
		if( midi.status < 0xf0 ) {
			// throw invalid midi message - start new cmd
			MIDI_RawOutByte(0x80);
		}
		else if( midi.status == 0xf0 ) {
			// SysEx - throw invalid midi message
			MIDI_RawOutByte(0xf7);
		}
		if(midi.available) midi.handler->Close();
		midi.available = false;
		midi.handler = nullptr;
	}
};

void MIDI_ListHandler(Program *caller, const char *name)
{
    if (!*name) return;
    bool found=false;
    for (auto *handler = handler_list; handler; handler = handler->next)
        if (!strcasecmp(handler->GetName(), name)) {
            handler->ListAll(caller);
            found=true;
            break;
        }
    if (!found) caller->WriteOut("MIDI handler not available - %s\n", name);
}

static MIDI* test = NULL;
void MIDI_Destroy(Section* /*sec*/){
	if (test != NULL) {
		delete test;
		test = NULL;
	}
}

void MIDI_Init() {
	LOG(LOG_MISC,LOG_DEBUG)("Initializing MIDI emulation");

	test = new MIDI(control->GetSection("midi"));
	AddExitFunction(AddExitFunctionFuncPair(MIDI_Destroy),true);
}

void MIDI_GUI_OnSectionPropChange(Section *x) {
    (void)x;//UNUSED

	if (test != NULL) {
		delete test;
		test = NULL;
	}

	test = new MIDI(control->GetSection("midi"));
}

//save state support
namespace {
class SerializeMidi : public SerializeGlobalPOD {
public:
    SerializeMidi() : SerializeGlobalPOD("Midi")
    {}

private:
    void getBytes(std::ostream& stream) override
    {
				if( !test ) return;

				//******************************************
				//******************************************
				//******************************************

				SerializeGlobalPOD::getBytes(stream);


				// - pure data
				WRITE_POD( &midi.status, midi.status );
				WRITE_POD( &midi.cmd_len, midi.cmd_len );
				WRITE_POD( &midi.cmd_pos, midi.cmd_pos );
				WRITE_POD( &midi.cmd_buf, midi.cmd_buf );
				WRITE_POD( &midi.rt_buf, midi.rt_buf );
				WRITE_POD( &midi.sysex, midi.sysex );

				//*******************************************
				//*******************************************
				//*******************************************

				// Supports MT-32 MUNT only!!
				if(0) {
				}

				// external MIDI 
				else if( midi.available ) {
					const char pod_name[32] = "External";


					WRITE_POD( &pod_name, pod_name );

					//*******************************************
					//*******************************************
					//*******************************************

					// - pure data
					WRITE_POD( &midi_state, midi_state );
				}
    }

    void setBytes(std::istream& stream) override
    {
				if( !test ) return;

				//******************************************
				//******************************************
				//******************************************

        SerializeGlobalPOD::setBytes(stream);


				// - pure data
				READ_POD( &midi.status, midi.status );
				READ_POD( &midi.cmd_len, midi.cmd_len );
				READ_POD( &midi.cmd_pos, midi.cmd_pos );
				READ_POD( &midi.cmd_buf, midi.cmd_buf );
				READ_POD( &midi.rt_buf, midi.rt_buf );
				READ_POD( &midi.sysex, midi.sysex );

				//*******************************************
				//*******************************************
				//*******************************************

				// Supports MT-32 MUNT only!!
				if(0) {
				}

				// external MIDI
				else if( midi.available ) {
					char pod_name[32] = {0};


					// error checking
					READ_POD( &pod_name, pod_name );
					if( strcmp( pod_name, "External" ) ) {
						stream.clear( std::istream::failbit | std::istream::badbit );
						return;
					}

					//************************************************
					//************************************************
					//************************************************

				// - pure data
					READ_POD( &midi_state, midi_state );

					//************************************************
					//************************************************
					//************************************************

					MIDI_State_LoadMessage();
				}
    }
} dummy;
}
