// Midi2Composer.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <io.h>

int PrintMidi = 0;
int PrintSampleSizes = 0;
int ExportNoteOff = 0;

enum eNotes
{
	nC1 = 24, nCs1, nD1, nDs1, nE1, nF1, nFs1, nG1, nGs1, nA1, nAs1, nB1,
	nC2, nCs2, nD2, nDs2, nE2, nF2, nFs2, nG2, nGs2, nA2, nAs2, nB2,
	nC3, nCs3, nD3, nDs3, nE3, nF3, nFs3, nG3, nGs3, nA3, nAs3, nB3,
	nC4, nCs4, nD4, nDs4, nE4, nF4, nFs4, nG4, nGs4, nA4, nAs4, nB4,
	nC5, nCs5, nD5, nDs5, nE5, nF5, nFs5, nG5, nGs5, nA5, nAs5, nB5,
	nC6, nCs6, nD6, nDs6, nE6, nF6, nFs6, nG6, nGs6, nA6, nAs6, nB6,
	nC7, nCs7, nD7, nDs7, nE7, nF7, nFs7, nG7, nGs7, nA7, nAs7, nB7,
	nC8, nCs8, nD8, nDs8, nE8, nF8, nFs8, nG8, nGs8, nA8, nAs8, nB8,
	nBf1=nAs1, nBf2=nAs2, nBf3=nAs3, nBf4=nAs4, nBf5=nAs5, nBf6=nAs6, nBf7=nAs7, nBf8=nAs8,
	nEf1=nDs1, nEf2=nDs2, nEf3=nDs3, nEf4=nDs4, nEf5=nDs5, nEf6=nDs6, nEf7=nDs7, nEf8=nDs8,
	nLo = 0, nHi = 127
};

enum eDSCmds
{
	eDS_End		= 0xF0,		// End of track - stop playing
	eDS_Delay	= 0xE0,		// Delay for n loops [,n]
	eDS_Tempo	= 0xC0,		// Change tempo [hi,lo]
	eDS_On		= 0x80,		// Play this note [|channel,program,pitch]
	eDS_Off		= 0x90,		// Stop this note [|channel]
	eDS_Pan		= 0xA0,		// Set channel pan [|channel,pan]
	eDS_Vol		= 0xB0		// Set channel vol [|channel,vol]
};

#define NUM_MIDI_WAVS 97


//const char *cMidiPathIn = "Z:\\Rayman2\\DS\\Cpa\\Exe\\Main\\GameDn64\\World\\Sound\\";
const char *cMDXPathIn = "Z:\\Rayman2\\DS\\Cpa\\Exe\\Main\\GameDn64\\World\\Sound\\";
const char *cMidiPathIn = "C:\\Temp\\DSMusic\\Mid\\";
const char *cMidiPathOut = "C:\\Temp\\DSMusic\\Mid\\";

const char *cWavPath = "C:\\Temp\\DSMusic\\Wav\\";

const char *cBankFile = "C:\\Temp\\DSMidi0.tbl";

const char *cMidiBankFile = "C:\\Temp\\DSMidi0.sbk";

const char *cWavNames[NUM_MIDI_WAVS] =
{
		"M_GPiano_C2.adpcm.swav",
		"M_GPiano_Bb2.adpcm.swav",
		"M_GPiano_F3.adpcm.swav",
		"M_GPiano_C4.adpcm.swav",
		"M_GPiano_G4.adpcm.swav",
		"M_GPiano_C5.adpcm.swav",
		"M_GPiano_G5.adpcm.swav",
		"M_GPiano_C6.adpcm.swav",
		"M_MPC2.adpcm.swav",
		"M_MPG2.adpcm.swav",
		"M_MPB3.adpcm.swav",
		"M_CYC4.adpcm.swav",
		"M_CYG4.adpcm.swav",
		"M_HHCD5.adpcm.swav",
		"M_HHDD5.adpcm.swav",
		"M_Glock_C4.adpcm.swav",
		"M_Glock_Bb4.adpcm.swav",
		"M_Glock_F5.adpcm.swav",
		"M_Glock_C6.adpcm.swav",
		"M_Music_Box_C5.adpcm.swav",
		"M_CGAC1.adpcm.swav",
		"M_CGAD1.adpcm.swav",
		"M_CGAC2.adpcm.swav",
		"M_CGAA2.adpcm.swav",
		"M_CGAD3.adpcm.swav",
		"M_CGAF3.adpcm.swav",
		"M_CGAF4.adpcm.swav",
		"M_Marimba_C4.adpcm.swav",
		"M_Marimba_Bb4.adpcm.swav",
		"M_Marimba_F5.adpcm.swav",
		"M_Marimba_C6.adpcm.swav",
		"M_SHKDD4.adpcm.swav",
		"M_SHKG4.adpcm.swav",
		"M_SHKCD5.adpcm.swav",
		"M_TBD3.adpcm.swav",
		"M_PercOrgan_C6.adpcm.swav",
		"M_SND1.adpcm.swav",
		"M_BDC2.adpcm.swav",
		"M_INPE2.adpcm.swav",
		"M_INPF3.adpcm.swav",
		"M_INPG3.adpcm.swav",
		"M_INPA3.adpcm.swav",
		"M_INPAD3.adpcm.swav",
		"M_INPGD4.adpcm.swav",
		"M_BCC1.adpcm.swav",
		"M_BCD1.adpcm.swav",
		"M_Accordian_G4.adpcm.swav",
		"M_Accordian_A5.adpcm.swav",
		"M_ABA1.adpcm.swav",
		"M_ABD2.adpcm.swav",
		"M_VIG2.adpcm.swav",
		"M_VICD3.adpcm.swav",
		"M_VIG3.adpcm.swav",
		"M_VICD4.adpcm.swav",
		"M_StlStrGtr_G4.adpcm.swav",
		"M_StlStrGtr_E5.adpcm.swav",
		"M_StlStrGtr_Db6.adpcm.swav",
		"M_PIG3.adpcm.swav",
		"M_PICD4.adpcm.swav",
		"M_PIAD4.adpcm.swav",
		"M_MAC3.adpcm.swav",
		"M_ETC3.adpcm.swav",
		"M_SNF1.adpcm.swav",
		"M_BDA2.adpcm.swav",
		"M_FingerBass_G3.adpcm.swav",
		"M_PickBass_C3.adpcm.swav",
		"M_SlapBass2_C3.adpcm.swav",
		"M_Strings_Ab3.adpcm.swav",
		"M_Strings_E4.adpcm.swav",
		"M_Strings_Gb4.adpcm.swav",
		"M_Strings_D5.adpcm.swav",
		"M_Strings_C6.adpcm.swav",
		"M_Pizzicato_C5.adpcm.swav",
		"M_Harp_E5.adpcm.swav",
		"M_Timpani_C3.adpcm.swav",
		"M_ChoirOohs_G3.adpcm.swav",
		"M_ChoirOohs_C4.adpcm.swav",
		"M_ChoirOohs_G4.adpcm.swav",
		"M_ChoirOohs_C5.adpcm.swav",
		"M_ChoirOohs_G5.adpcm.swav",
		"M_OrchHit_C4.adpcm.swav",
		"M_Trombone_F3.adpcm.swav",
		"M_Trombone_C4.adpcm.swav",
		"M_FrHorn_F4.adpcm.swav",
		"M_BrassSect_E4.adpcm.swav",
		"M_BrassSect_C5.adpcm.swav",
		"M_EngHorn_C5.adpcm.swav",
		"M_Bassoon_G3.adpcm.swav",
		"M_Clarinet_C5.adpcm.swav",
		"M_Flute_C6.adpcm.swav",
		"M_PanFlute2_C5.adpcm.swav",
		"M_Shakuhachi_C5.adpcm.swav",
		"M_Soundtrack_C5.adpcm.swav",
		"M_Harmo.adpcm.swav",
		"M_Flute01.adpcm.swav",
		"M_Triangle.adpcm.swav",
		"M_Breath_G5.adpcm.swav"
};

const char *cWavNamesSorted[NUM_MIDI_WAVS] =
{
	"M_ABA1.adpcm.swav",
		"M_ABD2.adpcm.swav",
		"M_Accordian_A5.adpcm.swav",
		"M_Accordian_G4.adpcm.swav",
		"M_Bassoon_G3.adpcm.swav",
		"M_BCC1.adpcm.swav",
		"M_BCD1.adpcm.swav",
		"M_BDA2.adpcm.swav",
		"M_BDC2.adpcm.swav",
		"M_BrassSect_C5.adpcm.swav",
		"M_BrassSect_E4.adpcm.swav",
		"M_Breath_G5.adpcm.swav",
		"M_CGAA2.adpcm.swav",
		"M_CGAC1.adpcm.swav",
		"M_CGAC2.adpcm.swav",
		"M_CGAD1.adpcm.swav",
		"M_CGAD3.adpcm.swav",
		"M_CGAF3.adpcm.swav",
		"M_CGAF4.adpcm.swav",
		"M_ChoirOohs_C4.adpcm.swav",
		"M_ChoirOohs_C5.adpcm.swav",
		"M_ChoirOohs_G3.adpcm.swav",
		"M_ChoirOohs_G4.adpcm.swav",
		"M_ChoirOohs_G5.adpcm.swav",
		"M_Clarinet_C5.adpcm.swav",
		"M_CYC4.adpcm.swav",
		"M_CYG4.adpcm.swav",
		"M_EngHorn_C5.adpcm.swav",
		"M_ETC3.adpcm.swav",
		"M_FingerBass_G3.adpcm.swav",
		"M_Flute01.adpcm.swav",
		"M_Flute_C6.adpcm.swav",
		"M_FrHorn_F4.adpcm.swav",
		"M_Glock_Bb4.adpcm.swav",
		"M_Glock_C4.adpcm.swav",
		"M_Glock_C6.adpcm.swav",
		"M_Glock_F5.adpcm.swav",
		"M_GPiano_Bb2.adpcm.swav",
		"M_GPiano_C2.adpcm.swav",
		"M_GPiano_C4.adpcm.swav",
		"M_GPiano_C5.adpcm.swav",
		"M_GPiano_C6.adpcm.swav",
		"M_GPiano_F3.adpcm.swav",
		"M_GPiano_G4.adpcm.swav",
		"M_GPiano_G5.adpcm.swav",
		"M_Harmo.adpcm.swav",
		"M_Harp_E5.adpcm.swav",
		"M_HHCD5.adpcm.swav",
		"M_HHDD5.adpcm.swav",
		"M_INPA3.adpcm.swav",
		"M_INPAD3.adpcm.swav",
		"M_INPE2.adpcm.swav",
		"M_INPF3.adpcm.swav",
		"M_INPG3.adpcm.swav",
		"M_INPGD4.adpcm.swav",
		"M_MAC3.adpcm.swav",
		"M_Marimba_Bb4.adpcm.swav",
		"M_Marimba_C4.adpcm.swav",
		"M_Marimba_C6.adpcm.swav",
		"M_Marimba_F5.adpcm.swav",
		"M_MPB3.adpcm.swav",
		"M_MPC2.adpcm.swav",
		"M_MPG2.adpcm.swav",
		"M_Music_Box_C5.adpcm.swav",
		"M_OrchHit_C4.adpcm.swav",
		"M_PanFlute2_C5.adpcm.swav",
		"M_PercOrgan_C6.adpcm.swav",
		"M_PIAD4.adpcm.swav",
		"M_PICD4.adpcm.swav",
		"M_PickBass_C3.adpcm.swav",
		"M_PIG3.adpcm.swav",
		"M_Pizzicato_C5.adpcm.swav",
		"M_Shakuhachi_C5.adpcm.swav",
		"M_SHKCD5.adpcm.swav",
		"M_SHKDD4.adpcm.swav",
		"M_SHKG4.adpcm.swav",
		"M_SlapBass2_C3.adpcm.swav",
		"M_SND1.adpcm.swav",
		"M_SNF1.adpcm.swav",
		"M_Soundtrack_C5.adpcm.swav",
		"M_StlStrGtr_Db6.adpcm.swav",
		"M_StlStrGtr_E5.adpcm.swav",
		"M_StlStrGtr_G4.adpcm.swav",
		"M_Strings_Ab3.adpcm.swav",
		"M_Strings_C6.adpcm.swav",
		"M_Strings_D5.adpcm.swav",
		"M_Strings_E4.adpcm.swav",
		"M_Strings_Gb4.adpcm.swav",
		"M_TBD3.adpcm.swav",
		"M_Timpani_C3.adpcm.swav",
		"M_Triangle.adpcm.swav",
		"M_Trombone_C4.adpcm.swav",
		"M_Trombone_F3.adpcm.swav",
		"M_VICD3.adpcm.swav",
		"M_VICD4.adpcm.swav",
		"M_VIG2.adpcm.swav",
		"M_VIG3.adpcm.swav"
};

unsigned char uWav2SWav[NUM_MIDI_WAVS];

#define NUM_MIDI_FILES 292

const char *cMidiFiles[NUM_MIDI_FILES] =
{
	"reset.mdx",
		"Null1.mid",
		"wat1aa.mid",
		"wat1aa_1.mdx",
		"wat1aa_2.mdx",
		"mond.mid",
		"ray.mid",
		"end1.mid",
		"end2.mid",
		"rod1a.mid",
		"ro1a_1.mdx",
		"ro1a_2.mdx",
		"Ro1a_3.mdx",
		"rod1b.mid",
		"Ro1b_3.mdx",
		"ro1b_2.mdx",
		"ro1b_1.mdx",
		"astra.mid",
		"Astra_6.mdx",
		"Astra_5.mdx",
		"Astra_7.mdx",
		"hel3a.mid",
		"hel3b.mid",
		"astrb.mid",
		"Astra_1.mdx",
		"wha2ac.mid",
		"bast1a.mid",
		"bast1b.mid",
		"bast3b.mid",
		"nav4a.mid",
		"princa.mid",
		"wat1ab.mid",
		"wat2.mid",
		"wha2aa.mid",
		"wha2a.mid",
		"minea.mid",
		"Ba1a_3.mdx",
		"bast3a.mid",
		"Ba3a_1.mdx",
		"Ba3a_4.mdx",
		"Ba3a_2.mdx",
		"bast2a.mid",
		"Ba2a_1.mdx",
		"Ba1a_1.mdx",
		"Cask1aa.mid",
		"Cask1ab.mid",
		"ca1a_1.mdx",
		"ca1a_2.mdx",
		"cask1b.mid",
		"Ca1b_1.mdx",
		"cask2a.mid",
		"ca2a_1.mdx",
		"ca2a_2.mdx",
		"ca2a_3.mdx",
		"Ca2a_4.mdx",
		"Cask2b.mid",
		"Ca1a_3.mdx",
		"Ca2a_5.mdx",
		"ly.mid",
		"batan.mid",
		"boata.mid",
		"Boata_2.mdx",
		"Boata_6.mdx",
		"Boatb_1.mdx",
		"boatb.mid",
		"Boatb_2.mdx",
		"Boatb_3.mdx",
		"Boata_5.mdx",
		"Ly1b_1.mdx",
		"ly1b.mid",
		"tietr.mid",
		"CH1BA.mid",
		"Ch1ba_2.mdx",
		"ch1bb_1.mdx",
		"Ch1bb.mid",
		"ch1bb_3.mdx",
		"ch1bb_2.mdx",
		"ch2b.mid",
		"ch2b_1.mdx",
		"ch1bb_5.mdx",
		"ch1bb_4.mdx",
		"ch1a.mid",
		"ch1a_1.mdx",
		"ch1a_2.mdx",
		"ch1a_3.mdx",
		"Ch3a_4.mdx",
		"Ch3a.mid",
		"Ch3a_9.mdx",
		"Ch3a_5.mdx",
		"Ch3a_6.mdx",
		"Ch3a_7.mdx",
		"Ch3a_8.mdx",
		"Ch3b.mid",
		"Ch3a_1.mdx",
		"Ch3a_2.mdx",
		"Ch3a_3.mdx",
		"earth.mid",
		"ch2a.mid",
		"earth2b.mid",
		"Earth2_1.mdx",
		"earth2a.mid",
		"earth3b.mid",
		"Earth3_1.mdx",
		"earth3a.mid",
		"Earth3_2.mdx",
		"gld_1.mdx",
		"globd.mid",
		"gld_5.mdx",
		"gld_4.mdx",
		"globa.mid",
		"gla_1.mdx",
		"gla_2.mdx",
		"gla_5.mdx",
		"gla_6.mdx",
		"Gla_7.mdx",
		"globc.mid",
		"glc_1.mdx",
		"glc_2.mdx",
		"glc_3.mdx",
		"glb_5.mdx",
		"globb.mid",
		"glb_4.mdx",
		"glb_3.mdx",
		"morb1b.mid",
		"hel12a.mid",
		"hel12a_1.mdx",
		"hel12a_2.mdx",
		"hel12c.mid",
		"hel12a_3.mdx",
		"hel12b.mid",
		"hel3b_5.mdx",
		"wat3.mid",
		"hel3b_1.mdx",
		"hel3b_3.mdx",
		"hel3b_4.mdx",
		"hel3b_6.mdx",
		"esca.mid",
		"mask.mid",
		"seatb.mid",
		"Seatb_2.mdx",
		"fo1c_6.mdx",
		"for1c.mid",
		"fo1c_7.mdx",
		"injail.mid",
		"poura.mid",
		"jail20.mid",
		"for2ab.mid",
		"fo2ab_3.mdx",
		"for2aa.mid",
		"fo2ab_1.mdx",
		"fo2ab_2.mdx",
		"fo2ab_4.mdx",
		"fo2ab_5.mdx",
		"for2b.mid",
		"fo2b_1.mdx",
		"fo2b_2.mdx",
		"fo2b_3.mdx",
		"for1a.mid",
		"fo1a_1.mdx",
		"fo1a_2.mdx",
		"fo1a_3.mdx",
		"for1bb.mid",
		"fo1bb_1.mdx",
		"fo1bb_2.mdx",
		"fo1bb_3.mdx",
		"fo1bb_4.mdx",
		"fo1bb_5.mdx",
		"for1ba.mid",
		"fo1c_1.mdx",
		"fo1c_2.mdx",
		"fo1c_3.mdx",
		"fo1c_4.mdx",
		"fo1c_5.mdx",
		"nav3b.mid",
		"nav3a.mid",
		"Ba3a_3.mdx",
		"ch1c.mid",
		"ch1c_1.mdx",
		"ly1b_4.mdx",
		"ly1b_5.mdx",
		"ly1c.mid",
		"ly1b_2.mdx",
		"ly1b_3.mdx",
		"Ly1b_6.mdx",
		"ly1a.mid",
		"Ly1a_1.mdx",
		"ly2b.mid",
		"ly2a_2.mdx",
		"ly2a.mid",
		"ly2a_1.mdx",
		"Ly2a_3.mdx",
		"mineb.mid",
		"mine.mdx",
		"mine2.mdx",
		"mine3.mdx",
		"mine4.mdx",
		"Morba.mid",
		"morba_1.mdx",
		"morba_3.mdx",
		"morbb_4.mdx",
		"Morbb.mid",
		"morbb_3.mdx",
		"morbb_1.mdx",
		"morbb_2.mdx",
		"morb1c.mid",
		"poura_3.mdx",
		"na3b_1.mdx",
		"Na3b_5.mdx",
		"na3b_2.mdx",
		"Na3b_6.mdx",
		"na3b_4.mdx",
		"pourb.mid",
		"poura_1.mdx",
		"plum2a_2.mdx",
		"plum2a.mid",
		"plum2a_1.mdx",
		"plum2a_3.mdx",
		"vul1b_2.mdx",
		"vul1b.mid",
		"vul1b_1.mdx",
		"vul1ab_1.mdx",
		"Plum2a_4.mdx",
		"plum3.mid",
		"Plum3_3.mdx",
		"Plum3_2.mdx",
		"Plum5a_1.mdx",
		"plum5a.mid",
		"Plum5a_2.mdx",
		"Plum5a_3.mdx",
		"plum4a_3.mdx",
		"plum4a.mid",
		"Plum4a_4.mdx",
		"plum4a_2.mdx",
		"Plum4a_7.mdx",
		"Plum4a_6.mdx",
		"plum4a_1.mdx",
		"polo.mid",
		"Astrb_R.mdx",
		"rhopa.mid",
		"rhopb.mid",
		"rhopc.mid",
		"rhopd.mid",
		"fin.mdx",
		"rod2a.mid",
		"rod2b.mid",
		"seatad.mid",
		"seatab_3.mdx",
		"seatab.mid",
		"seatac.mid",
		"seatab_2.mdx",
		"seatab_1.mdx",
		"Seatb_1.mdx",
		"seataa.mid",
		"Seatad_3.mdx",
		"Seatad_2.mdx",
		"Seatad_1.mdx",
		"seat23b.mid",
		"Seat23_1.mdx",
		"seat23a.mid",
		"Seat23_2.mdx",
		"inski.mid",
		"sk_1.mdx",
		"Skiba.mid",
		"Skibb.mid",
		"sk_2.mdx",
		"skia.mid",
		"skic.mid",
		"princb.mid",
		"MAINA.mid",
		"MAINB.mid",
		"princc.mid",
		"vul2b.mid",
		"vul1ab.mid",
		"vul1a.mid",
		"vul1aa_2.mdx",
		"vul1aa.mid",
		"vul1aa_3.mdx",
		"vul1aa_1.mdx",
		"vul2a.mid",
		"vul2a_1.mdx",
		"vul2a_2.mdx",
		"vul2a_3.mdx",
		"vul2a_4.mdx",
		"wat1b.mid",
		"wat1b_1.mdx",
		"wat1b_2.mdx",
		"wat1b_3.mdx",
		"wat1b_4.mdx",
		"Astra_8.mdx",
		"wat_1.mdx",
		"wat.mid",
		"null.mid"
};

long lLargestOfAll = 0;

long lWavLengths[NUM_MIDI_WAVS];
unsigned char bUsedWav[NUM_MIDI_WAVS];

long lMaxWavTotal;


typedef struct _tdstMidiHeader
{
	unsigned uTotalSampleLen;			// Memory needed to load in all samples for this track
	unsigned uTimer;						// Initial tempo
	unsigned char uSamplesToLoad[(NUM_MIDI_WAVS + 3) & ~3];	// Samples to load (stop when sample num == 255, never loads ALL of them)
} tdstMidiHeader;


// Translation table - search by program & note no., resulting index is sample to play
typedef struct _midi_trans
{
	unsigned short uProgram;
	unsigned char uHiNote;
	unsigned char uKeyNote;
} MIDI_TRANS;


MIDI_TRANS trans_table[] =
{
	{	1,	nF1,	nDs2	},
	{	1,	nCs2,	nDs3	},
	{	1,	nA2,	nG3		},
	{	1,	nEf3,	nC4		},
	{	1,	nA3,	nG4		},
	{	1,	nEf4,	nC5		},
	{	1,	nA4,	nG5		},
	{	1,	nHi,	nDs6	},
	{	3,	nC3,	nC3		},
	{	3,	nE4,	nG3		},
	{	3,	nHi,	nB4		},
	{	5,	nC5,	nC5		},
	{	5,	nG5,	nG5		},
	{	5,	nD6,	nCs6	},
	{	5,	nHi,	nDs6	},
	{	9,	nF3,	nD4		},
	{	9,	nCs4,	nA4		},
	{	9,	nFs4,	nCs5	},
	{	9,	nHi,	nF5		},
	{	10,	nHi,	nCs5	},
	{	11,	nC2,	nC2		},
	{	11,	nG2,	nD2		},
	{	11,	nF3,	nC3		},
	{	11,	nB3,	nA3		},
	{	11,	nE4,	nD4		},
	{	11,	nC5,	nF4		},
	{	11,	nHi,	nF5		},
	{	12,	nF3,	nC4		},
	{	12,	nCs4,	nAs4	},
	{	12,	nGs4,	nF5		},
	{	12,	nHi,	nC6		},
	{	14,	nEf5,	nDs5	},
	{	14,	nG5,	nG5		},
	{	14,	nHi,	nCs6	},
	{	16,	nHi,	nD4		},
	{	17,	nHi,	nDs6	},
	{	18,	nF2,	nD2		},
	{	18,	nHi,	nC3		},
	{	19,	nG3,	nE3		},
	{	19,	nF4,	nF4		},
	{	19,	nG4,	nG4		},
	{	19,	nA4,	nA4		},
	{	19,	nG5,	nAs4	},
	{	19,	nHi,	nG5		},
	{	20,	nC2,	nC2		},
	{	20,	nHi,	nD2		},
	{	21,	nF4,	nG4		},
	{	21,	nHi,	nGs5	},
	{	22,	nGs2,	nA2		},
	{	22,	nHi,	nD3		},
	{	23,	nBf3,	nG3		},
	{	23,	nD4,	nCs4	},
	{	23,	nA4,	nG4		},
	{	23,	nHi,	nCs5	},
	{	25,	nB2,	nG3		},
	{	25,	nGs3,	nE4		},
	{	25,	nHi,	nCs5	},
	{	26,	nGs4,	nG4		},
	{	26,	nD5,	nCs5	},
	{	26,	nHi,	nAs5	},
	{	27,	nHi,	nC4		},
	{	29,	nHi,	nC4		},
	{	32,	nB2,	nC2		},
	{	32,	nHi,	nC3		},
	{	33,	nHi,	nG2		},
	{	34,	nHi,	nC2		},
	{	37,	nHi,	nC2		},
	{	44,	nB2,	nGs3	},
	{	44,	nE3,	nE4		},
	{	44,	nB3,	nFs4	},
	{	44,	nG4,	nD5		},
	{	44,	nHi,	nC6		},
	{	45,	nHi,	nE5		},
	{	46,	nHi,	nA5		},
	{	47,	nHi,	nFs3	},
	{	53,	nA2,	nG3		},
	{	53,	nEf3,	nC4		},
	{	53,	nA3,	nG4		},
	{	53,	nEf4,	nC5		},
	{	53,	nHi,	nG5		},
	{	55,	nHi,	nDs4	},
	{	57,	nG2,	nF3		},
	{	57,	nHi,	nC4		},
	{	60,	nHi,	nF4		},
	{	61,	nG3,	nE4		},
	{	61,	nHi,	nC5		},
	{	69,	nHi,	nC5		},
	{	70,	nHi,	nG3		},
	{	71,	nHi,	nC5		},
	{	73,	nHi,	nDs6	},
	{	75,	nHi,	nCs5	},
	{	77,	nHi,	nF5		},
	{	97,	nHi,	nC5		},
	{	100,	nHi,	nFs4	},
	{	103,	nHi,	nFs4	},
	{	112,	nHi,	nFs5	},
	{	121,	nHi,	nG5		},
	{	255,	255,	255		}
};



long GetFileLength(const char *pName)
{
	FILE *fp = fopen(pName, "rb");
	if (fp == NULL)
		return 0;
	fseek(fp, 0, SEEK_END);
	long lLen = ftell(fp);
	fclose(fp);
	return lLen;
}
long GetFileLength(FILE *fp)
{
	long lCurrent = ftell(fp);
	fseek(fp, 0, SEEK_END);
	long lLen = ftell(fp);
	fseek(fp, lCurrent, SEEK_SET);
	return lLen;
}
void AppendFile(FILE *fp, const char *pName)
{
	FILE *fAdd = fopen(pName, "rb");
	if (fAdd == NULL)
		return;
	fseek(fAdd, 0, SEEK_END);
	long lLen = ftell(fAdd);
	fseek(fAdd, 0, SEEK_SET);
	char *pTempBuffer = new char[lLen];
	fread(pTempBuffer, lLen, 1, fAdd);
	fclose(fAdd);
	fwrite(pTempBuffer, lLen, 1, fp);
	delete[] pTempBuffer;
}




typedef struct {
	char ID[4];
	unsigned int uLength;
} CHUNK_HEADER;

template <class ITEM> void Swap(ITEM *ptr)
{
	if (sizeof(ITEM)==2)
	{
		unsigned char *p = (unsigned char *)ptr, v;
		v = p[0]; p[0] = p[1]; p[1] = v;
	}
	if (sizeof(ITEM)==4)
	{
		unsigned char *p = (unsigned char *)ptr, v;
		v = p[0]; p[0] = p[3]; p[3] = v;
		v = p[1]; p[1] = p[2]; p[2] = v;
	}
}

void GetNext(unsigned char **pBuffer, long *plFileSize, void *pOut, int iSize)
{
	memcpy(pOut, *pBuffer, iSize);
	(*pBuffer) += iSize;
	(*plFileSize) -= iSize;
}

#define GETNEXT(x) GetNext(&pB, &lLen, &x, sizeof(x))


unsigned GetVarLen(unsigned char *&pB, long &lLen, unsigned char uStartWith = 0)
{
	unsigned uValue = uStartWith;

	for (;;)
	{
		unsigned char uDeltaByte;
		GETNEXT(uDeltaByte);
		uValue = (uValue << 7) | (uDeltaByte & 0x7F);
		if ((uDeltaByte & 0x80) == 0)
			break;
	}
	return uValue;
}

void Decode(unsigned char *pBuffer, long lFileSize, const char *pSeqName, const char *pOutputName)
{
	unsigned char *pB = pBuffer;
	long lLen = lFileSize;
	CHUNK_HEADER h;
	GETNEXT(h);
	if ((h.ID[0] != 'M') || (h.ID[1] != 'T') || (h.ID[2] != 'h') || (h.ID[3] != 'd'))
	{
		printf("No midi header\n");
		return;
	}
	Swap(&h.uLength);
	unsigned short uFormat;
	GETNEXT(uFormat);
	Swap(&uFormat);
	if (uFormat != 0)
	{
		printf("Not type 0\n");
		return;
	}
	unsigned short uNumTracks;
	GETNEXT(uNumTracks);
	Swap(&uNumTracks);
	if (uNumTracks != 1)
	{
		printf("Not just 1 track\n");
		return;
	}

	unsigned uPlayerTempo = 4167;	// Default to approx 120 bpm
	unsigned uInitialTempo = uPlayerTempo;
	unsigned uDivision;
	int iHaveCmd = 0;

	unsigned char uTimeLo, uTimeHi;
	GETNEXT(uTimeHi);
	GETNEXT(uTimeLo);
	if (uTimeHi & 0x80)
	{
		unsigned char uFramesPerSec = (uTimeHi ^ 0xFF) + 1;
		unsigned char uSubFrameTime = uTimeLo;
		printf("Code the frames / subframes bit!\n");
	}
	else
	{
		unsigned short uPPQN = (unsigned short)uTimeHi * 256 + uTimeLo;
		uDivision = uPPQN;
		uPlayerTempo = 500000 / uDivision;
	}

	// Done with the header

	for (;;)
	{
		GETNEXT(h);
		Swap(&h.uLength);
		if ((h.ID[0] == 'M') && (h.ID[1] == 'T') && (h.ID[2] == 'r') && (h.ID[3] == 'k'))
			break;
		pB += h.uLength;
	}

	// Now got the track

	unsigned uTime = 0;

	unsigned uDataLen = lLen - h.uLength;
	unsigned char uStatus = 0;
	bool bEndTrack = false;

	// Which programs are selected for the 16 channels (so can work out pitch change for note-on)
	unsigned char uPrograms[16];
	memset(uPrograms, 0, sizeof(uPrograms));

	unsigned char uProgramAdded[NUM_MIDI_WAVS];
	memset(uProgramAdded, 0, sizeof(uProgramAdded));
	unsigned char uProgramLoad[NUM_MIDI_WAVS];
	memset(uProgramLoad, 255, sizeof(uProgramLoad));
	unsigned uProgramIndex = 0;

	// Tracks which channels are on/off
	unsigned char uChannelOnOff[16][128];
	memset(uChannelOnOff, 0, sizeof(uChannelOnOff));

	unsigned char *pDSData = new unsigned char[256000];
	unsigned char *pDS = pDSData;

	unsigned uTotalWavSize = 0;

	while ((lLen > uDataLen) && (!bEndTrack))
	{
		unsigned uDelta = GetVarLen(pB, lLen);
		uTime += uDelta;

		if (uDelta != 0)
		{
			*pDS++ = eDS_Delay;
			*pDS++ = uDelta;
		}

		unsigned char uCmd;
		unsigned char uData1;

		GETNEXT(uData1);
		if (uData1 & 0x80)
		{
			uCmd = uData1;
			if (uCmd < 0xF0)
				uStatus = uCmd;
			else if (uCmd < 0xF8)
				uStatus = 0;
			GETNEXT(uData1);
		}
		else
			uCmd = uStatus;

		if (uCmd == 0xF0)
		{
			unsigned uEventLen = GetVarLen(pB, lLen, uData1);
			pB += uEventLen;
			lLen -= uEventLen;
		}
		else if (uCmd == 0xFF)
		{
			switch (uData1)
			{
			case 0x2F:
				{
					unsigned char uSkip;
					GETNEXT(uSkip);
					bEndTrack = true;
				}
				break;
			case 0x01:
			case 0x02:
			case 0x03:
			case 0x04:
			case 0x05:
			case 0x06:
			case 0x07:
			case 0x08:
			case 0x09:
				{
					// Track names, comments, other stuff
					unsigned char uType = uData1;
					unsigned char uLen;
					GETNEXT(uLen);
					pB += uLen;
					lLen -= uLen;
				}
				break;
			case 0x51:
				{
					// Tempo
					unsigned char uLen;
					GETNEXT(uLen);
					if (uLen == 3)
					{
						unsigned char uByte;
						GETNEXT(uByte);
						unsigned uTempo = uByte;
						uTempo <<= 8;
						GETNEXT(uByte);
						uTempo |= uByte;
						uTempo <<= 8;
						GETNEXT(uByte);
						uTempo |= uByte;

						uPlayerTempo = uTempo / uDivision;

						if (uPlayerTempo > 65535)
							printf("Tempo is too big!\n");

						if (iHaveCmd == 0)
							uInitialTempo = uPlayerTempo;

						// HI-LO ORDER!!!
						*pDS++ = eDS_Tempo;
						*pDS++ = (unsigned char)((uPlayerTempo >> 8) & 0xFF);
						*pDS++ = (unsigned char)(uPlayerTempo & 0xFF);
					}
				}
				break;
			case 0x54:
				{
					// SMPTE offset
					unsigned char uLen;
					GETNEXT(uLen);
					pB += uLen;
					lLen -= uLen;
				}
				break;
			case 0x58:
				{
					// Time signature
					unsigned char uLen;
					GETNEXT(uLen);
					pB += uLen;
					lLen -= uLen;
				}
				break;
			case 0x59:
				{
					// Key signature
					unsigned char uLen;
					GETNEXT(uLen);
					pB += uLen;
					lLen -= uLen;
				}
				break;
			case 0x7F:
				{
					// Any proprietary event
					unsigned ulLen = GetVarLen(pB, lLen);
					pB += ulLen;
					lLen -= ulLen;
				}
				break;
			default:
				printf("MISSED A CODE : %u, %s\n", uData1, pOutputName);
				bEndTrack = true;
				break;
			}
		}
		else if ((uCmd & 0x80) == 0)
		{
			printf("BAD MIDI FILE\n");
			return;
		}
		else
		{
			iHaveCmd = 1;

			unsigned char uType = uCmd >> 4;
			unsigned char uData = uCmd & 0x0F;
			switch (uType)
			{
			case 8:
				// Note off
				{
					unsigned char uChannel = uData;
					unsigned char uNote = uData1;
					unsigned char uVelocity;
					GETNEXT(uVelocity);

					if (uPrograms[uChannel] == 127)
						break;

					if (uChannelOnOff[uChannel][uNote] == 0)
					{
						//						printf("Channel %u.%u already off!\n", uChannel, uNote);
					}
					else
						uChannelOnOff[uChannel][uNote]--;

					if (ExportNoteOff)
						*pDS++ = eDS_Off | uChannel;

					if (PrintMidi)
						printf("%04X %02X\tOFF\t%02X %02X %02X\n", uTime, uDelta, uChannel, uNote, uVelocity);
				}
				break;
			case 9:
				// Note on
				{
					unsigned char uChannel = uData;
					unsigned char uNote = uData1;
					unsigned char uVelocity;
					GETNEXT(uVelocity);

					if (uVelocity == 0)
					{
						if (uPrograms[uChannel] == 127)
							break;

						if (ExportNoteOff)
							*pDS++ = eDS_Off | uChannel;

						if (uChannelOnOff[uChannel][uNote] == 0)
						{
							//							printf("Channel %u.%u already off!\n", uChannel, uNote);
						}
						else
							uChannelOnOff[uChannel][uNote]--;

						// Really a note off
						if (PrintMidi)
							printf("%04X %02X\tOFF\t%02X %02X %02X\n", uTime, uDelta, uChannel, uNote, uVelocity);
					}
					else
					{
						for (int i = 0 ; ; i++)
						{
							if (trans_table[i].uProgram == 255)
							{
								if (uPrograms[uChannel] != 127)
																printf("Didn't find the note-on %u\n", uPrograms[uChannel]);
								break;
								//								bEndTrack = true;
							}
							if ((uPrograms[uChannel] == trans_table[i].uProgram) &&
//								(uNote >= trans_table[i].uLoNote) &&
								(uNote <= trans_table[i].uHiNote))
							{
								*pDS++ = eDS_On | uChannel;
								*pDS++ = (unsigned char)(i & 0xFF);
								*pDS++ = (unsigned char)(uNote - trans_table[i].uKeyNote);
								*pDS++ = (unsigned char)uVelocity;

								if (uChannelOnOff[uChannel][uNote] == 127)
									printf("Channel %u.%u already on!\n", uChannel, uNote);
								else
									uChannelOnOff[uChannel][uNote]++;

								bUsedWav[i & 0xFF] = 1;

								if (uProgramAdded[i & 0xFF] == 0)
								{
									uProgramAdded[i & 0xFF] = 1;
									uProgramLoad[uProgramIndex++] = i & 0xFF;

									uTotalWavSize += lWavLengths[i & 0xFF];
									uTotalWavSize = (uTotalWavSize + 3) & ~3;
								}

								break;
							}
						}


						if (PrintMidi)
							printf("%04X %02X\tON\t%02X %02X %02X\n", uTime, uDelta, uChannel, uNote, uVelocity);
					}
				}
				break;
			case 10:
				// Aftertouch (key pressure)
				{
					unsigned char uChannel = uData;
					unsigned char uNote = uData1;
					unsigned char uPressure;
					GETNEXT(uPressure);
				}
				break;
			case 11:
				// Control change
				{
					unsigned char uChannel = uData;
					unsigned char uController = uData1;
					unsigned char uContValue;
					GETNEXT(uContValue);
					if (uController == 7)
					{
						// Change volume for uChannel to uContValue (0-127)
						*pDS++ = eDS_Vol | uChannel;
						*pDS++ = uContValue;
					}
					else if (uController == 10)
					{
						// Change pan for uChannel to uContValue (0-left, 64-mid, 127-right)
						*pDS++ = eDS_Pan | uChannel;
						*pDS++ = uContValue;
					}
				}
				break;
			case 12:
				// Program (patch) change
				{
					unsigned char uChannel = uData;
					unsigned char uProgram = uData1;

					uPrograms[uChannel] = uProgram;

					if (PrintMidi)
						printf("%04X %02X\tPATCH\tChannel %u uses %u\n", uTime, uDelta, uChannel, uProgram);
				}
				break;
			case 13:
				// Channel pressure
				{
					unsigned char uChannel = uData;
					unsigned char uPressure = uData1;
				}
				break;
			case 14:
				// Pitch wheel
				{
					unsigned char uChannel = uData;
					unsigned char uD1 = uData1, uD2;
					GETNEXT(uD2);
					unsigned short uWheel = ((unsigned short)uD1) | (((unsigned short)uD2)<<7);
				}
				break;
			case 15:
				// Status continuation stuff
				break;
			}
		}
	}
	*pDS++ = eDS_End;

	if (pOutputName != NULL)
	{
		FILE *ff = fopen(pOutputName, "wb");
		if (ff != NULL)
		{
			unsigned char uLoadFlags[32];
			memset(uLoadFlags, 0, sizeof(uLoadFlags));
			for (int i = 0 ; i < NUM_MIDI_WAVS ; i++)
			{
				if (bUsedWav[i])
				{
					unsigned char uIndex = uWav2SWav[i];
					uLoadFlags[uIndex >> 3] |= (1 << (uIndex & 7));
				}
			}
			fwrite(&uTotalWavSize, sizeof(uTotalWavSize), 1, ff);
			fwrite(uLoadFlags, sizeof(uLoadFlags), 1, ff);
			AppendFile(ff, pSeqName);
			long lLen = ftell(ff);
			while (lLen & 3)
			{
				fputc(0, ff);
				lLen++;
			}
			fclose(ff);
		}
	}




	delete[] pDSData;
}

void DecodeFile(const char *pFilename, const char *pSeqName, const char *pOutputName)
{
	int i;

	// Load in the midi file
	FILE *fp = fopen(pFilename, "rb");
	if (fp == NULL)
	{
		printf("Unable to open %s\n", pFilename);
		return;
	}
	fseek(fp, 0, SEEK_END);
	long lFileSize = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	unsigned char *pBuffer = new unsigned char[lFileSize];
	fread(pBuffer, 1, lFileSize, fp);
	fclose(fp);

	// Flag each wav when it's used
	memset(bUsedWav, 0, sizeof(bUsedWav));

	// Decode the midi file
	Decode(pBuffer, lFileSize, pSeqName, pOutputName);

	// Don't need the midi file any more
	delete[] pBuffer;

	long lTotal = 0;
	for (i = 0 ; i < NUM_MIDI_WAVS ; i++)
		if (bUsedWav[i])
			lTotal += lWavLengths[i];
	printf("Total wav space to load %s is %ld\n", pFilename, lTotal);

	if (lTotal > lLargestOfAll)
		lLargestOfAll = lTotal;
}

void BuildWavBank()
{
	FILE *fBank = fopen(cBankFile, "wb");
	if (fBank == NULL)
	{
		printf("Unable to create %s\n", cBankFile);
		return;
	}

	unsigned uNumWavs = NUM_MIDI_WAVS;
	fwrite(&uNumWavs, sizeof(uNumWavs), 1, fBank);

	unsigned uOffset = ftell(fBank) + uNumWavs * 2 * sizeof(unsigned);

	unsigned u;
	for (u = 0 ; u < uNumWavs ; u++)
	{
		char strWav[_MAX_PATH];
		strcpy(strWav, cWavPath);
		strcat(strWav, cWavNamesSorted[u]);

		unsigned uLen = (unsigned)GetFileLength(strWav);
		if (uLen == 0)
		{
			printf("Unable to open %s\n", strWav);
			unsigned uNull = 0;
			fwrite(&uNull, sizeof(uNull), 1, fBank);
			fwrite(&uNull, sizeof(uNull), 1, fBank);
		}
		else
		{
			fwrite(&uOffset, sizeof(uOffset), 1, fBank);
			fwrite(&uLen, sizeof(uLen), 1, fBank);
			uOffset += uLen;
		}
	}
	for (u = 0 ; u < uNumWavs ; u++)
	{
		char strWav[_MAX_PATH];
		strcpy(strWav, cWavPath);
		strcat(strWav, cWavNamesSorted[u]);

		AppendFile(fBank, strWav);
	}
	fclose(fBank);
}

void BuildMidiBank()
{
	// Create SBK file containing all of the .mdx / .dse files concatenated
	FILE *fBank = fopen(cMidiBankFile, "wb");
	if (fBank == NULL)
	{
		printf("Unable to create %s\n", cMidiBankFile);
		return;
	}

	// Number of entries in the offsets table
	unsigned uNumFiles = NUM_MIDI_FILES;
	fwrite(&uNumFiles, sizeof(uNumFiles), 1, fBank);

	unsigned uOffset = ftell(fBank) + uNumFiles* 2 * sizeof(unsigned);

	// First pass writes offset,length for each mdx/dse file
	unsigned u;
	for (u = 0 ; u < uNumFiles ; u++)
	{
		const char *pPtr = strrchr(cMidiFiles[u], '.');
		if (pPtr == NULL)
		{
			printf("BAD FILE NAME : %s\n", cMidiFiles[u]);
			unsigned uNull = 0;
			fwrite(&uNull, sizeof(uNull), 1, fBank);
			fwrite(&uNull, sizeof(uNull), 1, fBank);
		}
		else
		{
			if (stricmp(pPtr, ".mdx") == 0)
			{
				// Just copy the entire .mdx file contents
				char strMidi[_MAX_PATH];
				strcpy(strMidi, cMDXPathIn);
				strcat(strMidi, cMidiFiles[u]);

				unsigned uLen = (unsigned)GetFileLength(strMidi);
				fwrite(&uOffset, sizeof(uOffset), 1, fBank);
				fwrite(&uLen, sizeof(uLen), 1, fBank);
				uOffset += uLen;
			}
			else if (stricmp(pPtr, ".mid") == 0)
			{
				// Just copy the entire .dse file contents
				char strMidi[_MAX_PATH];
				strcpy(strMidi, cMidiPathOut);
				strcat(strMidi, cMidiFiles[u]);
				char *pPtr = strrchr(strMidi, '.');
				strcpy(pPtr, ".dse");

				unsigned uLen = (unsigned)GetFileLength(strMidi);
				fwrite(&uOffset, sizeof(uOffset), 1, fBank);
				fwrite(&uLen, sizeof(uLen), 1, fBank);
				uOffset += uLen;
			}
			else
			{
				printf("BAD FILE NAME : %s\n", cMidiFiles[u]);
				unsigned uNull = 0;
				fwrite(&uNull, sizeof(uNull), 1, fBank);
				fwrite(&uNull, sizeof(uNull), 1, fBank);
			}
		}
	}

	// Second pass appends all of the files
	for (u = 0 ; u < uNumFiles ; u++)
	{
		const char *pPtr = strrchr(cMidiFiles[u], '.');
		if (pPtr == NULL)
		{
			printf("BAD FILE NAME : %s\n", cMidiFiles[u]);
		}
		else
		{
			if (stricmp(pPtr, ".mdx") == 0)
			{
				char strMidi[_MAX_PATH];
				strcpy(strMidi, cMDXPathIn);
				strcat(strMidi, cMidiFiles[u]);

				AppendFile(fBank, strMidi);
			}
			else if (stricmp(pPtr, ".mid") == 0)
			{
				char strMidi[_MAX_PATH];
				strcpy(strMidi, cMidiPathOut);
				strcat(strMidi, cMidiFiles[u]);
				char *pPtr = strrchr(strMidi, '.');
				strcpy(pPtr, ".dse");

				AppendFile(fBank, strMidi);
			}
			else
			{
				printf("BAD FILE NAME : %s\n", cMidiFiles[u]);
			}
		}
	}
	fclose(fBank);
}

void DecodeOneFile(const char *pName)
{
	// Midi file is the input
	char cFile[_MAX_PATH];
	strcpy(cFile, cMidiPathIn);
	strcat(cFile, pName);

	// SSEQ file is another input
	char cSeqFile[_MAX_PATH];
	strcpy(cSeqFile, cMidiPathIn);
	strcat(cSeqFile, pName);
	char *pPtr = strrchr(cSeqFile, '.');
	strcpy(pPtr, ".sseq");

	// DSE file is the output
	char cDSFile[_MAX_PATH];
	strcpy(cDSFile, cMidiPathOut);
	strcat(cDSFile, pName);

	pPtr = strrchr(cDSFile, '.');
	strcpy(pPtr, ".dse");

	// Convert it
	DecodeFile(cFile, cSeqFile, cDSFile);
}

int _tmain(int argc, _TCHAR* argv[])
{
	int i;

	// Midi files index the wavs using the 'sound fonts' document ordering
	// Composer files index the wavs alphabetically so create a translation table for Midi->Composer
	for (i = 0 ; i < NUM_MIDI_WAVS ; i++)
	{
		int j;
		uWav2SWav[i] = 0xFF;
		for (j = 0 ; j < NUM_MIDI_WAVS ; j++)
		{
			if (stricmp(cWavNames[i], cWavNamesSorted[j]) == 0)
			{
				uWav2SWav[i] = j;
				break;
			}
		}
		if (uWav2SWav[i] == 0xFF)
		{
			printf("ERROR - No match for %s in cWavNamesSorted\n", cWavNames[i]);
			return 0;
		}
	}

	// Build up a list of lengths of each wav, so know how much memory wil be needed per song
	for (i = 0 ; i < NUM_MIDI_WAVS ; i++)
	{
		TCHAR wav_file[_MAX_PATH];
		strcpy(wav_file, cWavPath);
		strcat(wav_file, cWavNames[i]);
		FILE *fm = fopen(wav_file, "rb");
		if (fm != NULL)
		{
			fseek(fm, 0, SEEK_END);
			lWavLengths[i] = ftell(fm);
			fclose(fm);
		}
		else
			lWavLengths[i] = 0;
	}

	if (argc == 1)
	{
		// Go through list of all midi files
		for (i = 0 ; i < NUM_MIDI_FILES ; i++)
		{
			char *pPtr = strrchr(cMidiFiles[i], '.');
			if ((pPtr != NULL) && (stricmp(pPtr, ".mid") == 0))
			{
				DecodeOneFile(cMidiFiles[i]);
			}
		}
	}
	else if (argc == 2)
	{
		DecodeOneFile(argv[1]);
	}
	else
	{
		return 0;
	}

	// Print max mem required to play song with largest sample set
	printf("Total required = %ld\n", lLargestOfAll);

	BuildWavBank();
	BuildMidiBank();

	return 0;
}
