From b3bdd04dd9f1c1a5647ae18a4f33eafa04da7dfc Mon Sep 17 00:00:00 2001
From: Quantum <quantum2048@gmail.com>
Date: Thu, 12 Sep 2013 19:10:54 -0400
Subject: [PATCH] Added saving abilities.

Included parts of Steven Goodwin's "Steevs MIDI Library" to dump a MIDI file.

Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
---
 Makefile               |   13 +-
 include/MainWindow.hpp |    6 +
 include/midifile.h     |  213 +++++++
 include/midiinfo.h     |  632 +++++++++++++++++++++
 src/MainWindow.cpp     |  140 ++++-
 src/midifile.c         | 1191 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 2174 insertions(+), 21 deletions(-)
 create mode 100644 include/midifile.h
 create mode 100644 include/midiinfo.h
 create mode 100644 src/midifile.c

diff --git a/Makefile b/Makefile
index 5d1031d..4d78468 100644
--- a/Makefile
+++ b/Makefile
@@ -24,22 +24,25 @@ FILES=$(OUTDIR)\Keyboard.obj \
       $(OUTDIR)\MainWindow.obj \
       $(OUTDIR)\PianoControl.obj \
       $(OUTDIR)\Window.obj \
+      $(OUTDIR)\midifile.obj \
       $(OUTDIR)\keyboard.res
 
 all: initdir $(DISTDIR)\Keyboard.exe
 
 initdir:
-	if not exist build md build
-	if not exist $(OUTDIR) md $(OUTDIR)
-	if not exist build md dist
-	if not exist $(DISTDIR) md $(DISTDIR)
+	@if not exist build md build
+	@if not exist $(OUTDIR) md $(OUTDIR)
+	@if not exist build md dist
+	@if not exist $(DISTDIR) md $(DISTDIR)
 
-$(INCDIR)\MainWindow.hpp: $(INCDIR)\Window.hpp $(INCDIR)\PianoControl.hpp
+$(INCDIR)\MainWindow.hpp: $(INCDIR)\Window.hpp $(INCDIR)\PianoControl.hpp $(INCDIR)\midifile.h
+$(INCDIR)\midifile.h: $(INCDIR)\midiinfo.h
 
 $(SRCDIR)\MainWindow.cpp: $(INCDIR)\MainWindow.hpp
 $(SRCDIR)\PianoControl.cpp: $(INCDIR)\PianoControl.hpp
 $(SRCDIR)\Keyboard.cpp: $(INCDIR)\MainWindow.hpp
 $(SRCDIR)\Window.cpp: $(INCDIR)\Window.hpp
+$(SRCDIR)\midifile.c: $(INCDIR)\midifile.h
 keyboard.rc: keyboard.ico
 
 $(OUTDIR)\keyboard.res: keyboard.rc
diff --git a/include/MainWindow.hpp b/include/MainWindow.hpp
index 6f04ebc..a5bdef0 100644
--- a/include/MainWindow.hpp
+++ b/include/MainWindow.hpp
@@ -4,6 +4,7 @@
 
 #include <Window.hpp>
 #include <PianoControl.hpp>
+#include <midifile.h>
 
 #include <mmsystem.h>
 #include <commctrl.h>
@@ -22,18 +23,23 @@ protected:
     BOOL WinRegisterClass(WNDCLASS *pwc);
     WORD GetQWERTYKeyCode(WORD wKeyCode);
     WORD GetRealKeyCode(WORD wQWERTYCode);
+    virtual void PaintContent(PAINTSTRUCT *pps);
 
     virtual HICON GetIcon();
     
     HWND m_volumeLabel, m_volumeBar;
     HWND m_forceLabel, m_forceBar;
     HWND m_instruLabel, m_instruSelect;
+    HWND m_saveCheck, m_saveLabel, m_saveFile, m_saveBrowse;
     int m_instrument, m_volume, m_force;
     HMIDIOUT m_midi;
     bool isQWERTY;
     HKL hklQWERTY;
     PianoControl *piano;
     LPWSTR m_keychars;
+    MIDI_FILE *m_midifile;
+    DWORD deltaTime;
+    bool saving;
 private:
     HFONT hFont;
     HBRUSH hBrush;
diff --git a/include/midifile.h b/include/midifile.h
new file mode 100644
index 0000000..1209c1e
--- /dev/null
+++ b/include/midifile.h
@@ -0,0 +1,213 @@
+#pragma once
+#ifndef _MIDIFILE_H
+#define _MIDIFILE_H
+
+#include "midiinfo.h"		/* enumerations and constants for GM */
+
+/*
+ * midiFile.c -  Header file for Steevs MIDI Library
+ * Version 1.4
+ *
+ *  AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
+ *			Copyright 1998-2010, Steven Goodwin.
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* 
+** All functions start with one of the following prefixes:
+**		midiFile*		For non-GM features that relate to the file, and have
+**						no use once the file has been created, i.e. CreateFile
+**						or SetTrack (those data is embedded into the file, but
+**						not explicitly stored)
+**		midiSong*		For operations that work across the song, i.e. SetTempo
+**		midiTrack*		For operations on a specific track, i.e. AddNoteOn
+*/
+
+/*
+** Types because we're dealing with files, and need to be careful
+*/
+typedef	unsigned char		BYTE;
+typedef	unsigned short		WORD;
+typedef	unsigned long		DWORD;
+typedef int					BOOL;
+#ifndef TRUE
+#define TRUE	1
+#endif
+#ifndef FALSE
+#define FALSE	0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** MIDI Constants
+*/
+#define MIDI_PPQN_DEFAULT		384
+#define MIDI_VERSION_DEFAULT	1
+
+/*
+** MIDI Limits
+*/
+#define MAX_MIDI_TRACKS			256
+#define MAX_TRACK_POLYPHONY		64
+
+/*
+** MIDI structures, accessibly externably
+*/
+typedef	void 	MIDI_FILE;
+typedef struct {
+					tMIDI_MSG	iType;
+
+					DWORD		dt;		/* delta time */
+					DWORD		dwAbsPos;
+					DWORD		iMsgSize;
+
+					BOOL		bImpliedMsg;
+					tMIDI_MSG	iImpliedMsg;
+
+					/* Raw data chunk */
+					BYTE *data;		/* dynamic data block */
+					DWORD data_sz;
+					
+					union {
+						struct {
+								int			iNote;
+								int			iChannel;
+								int			iVolume;
+								} NoteOn;
+						struct {
+								int			iNote;
+								int			iChannel;
+								} NoteOff;
+						struct {
+								int			iNote;
+								int			iChannel;
+								int			iPressure;
+								} NoteKeyPressure;
+						struct {
+								int			iChannel;
+								tMIDI_CC	iControl;
+								int			iParam;
+								} NoteParameter;
+						struct {
+								int			iChannel;
+								int			iProgram;
+								} ChangeProgram;
+						struct {
+								int			iChannel;
+								int			iPressure;
+								} ChangePressure;
+						struct {
+								int			iChannel;
+								int			iPitch;
+								} PitchWheel;
+						struct {
+								tMIDI_META	iType;
+								union {
+									int					iMIDIPort;
+									int					iSequenceNumber;
+									struct {
+										BYTE			*pData;
+										} Text;
+									struct {
+										int				iBPM;
+										} Tempo;
+									struct {
+										int				iHours, iMins;
+										int				iSecs, iFrames,iFF;
+										} SMPTE;
+									struct {
+										tMIDI_KEYSIG	iKey;
+										} KeySig;
+									struct {
+										int				iNom, iDenom;
+										} TimeSig;
+									struct {
+										BYTE			*pData;
+										int				iSize;
+										} Sequencer;
+									} Data;
+								} MetaEvent;
+						struct {
+								BYTE		*pData;
+								int			iSize;
+								} SysEx;
+						} MsgData;
+
+				/* State information - Please treat these as private*/
+				tMIDI_MSG	iLastMsgType;
+				BYTE		iLastMsgChnl;
+	
+				} MIDI_MSG;
+
+/*
+** midiFile* Prototypes
+*/
+MIDI_FILE  *midiFileCreate(const char *pFilename, BOOL bOverwriteIfExists);
+int			midiFileSetTracksDefaultChannel(MIDI_FILE *pMF, int iTrack, int iChannel);
+int			midiFileGetTracksDefaultChannel(const MIDI_FILE *pMF, int iTrack);
+BOOL		midiFileFlushTrack(MIDI_FILE *pMF, int iTrack, BOOL bFlushToEnd, DWORD dwEndTimePos);
+BOOL		midiFileSyncTracks(MIDI_FILE *pMF, int iTrack1, int iTrack2);
+int			midiFileSetPPQN(MIDI_FILE *pMF, int PPQN);
+int			midiFileGetPPQN(const MIDI_FILE *pMF);
+int			midiFileSetVersion(MIDI_FILE *pMF, int iVersion);
+int			midiFileGetVersion(const MIDI_FILE *pMF);
+MIDI_FILE  *midiFileOpen(const char *pFilename);
+BOOL		midiFileClose(MIDI_FILE *pMF);
+
+/*
+** midiSong* Prototypes
+*/
+BOOL		midiSongAddSMPTEOffset(MIDI_FILE *pMF, int iTrack, int iHours, int iMins, int iSecs, int iFrames, int iFFrames);
+BOOL		midiSongAddSimpleTimeSig(MIDI_FILE *pMF, int iTrack, int iNom, int iDenom);
+BOOL		midiSongAddTimeSig(MIDI_FILE *pMF, int iTrack, int iNom, int iDenom, int iClockInMetroTick, int iNotated32nds);
+BOOL		midiSongAddKeySig(MIDI_FILE *pMF, int iTrack, tMIDI_KEYSIG iKey);
+BOOL		midiSongAddTempo(MIDI_FILE *pMF, int iTrack, int iTempo);
+BOOL		midiSongAddMIDIPort(MIDI_FILE *pMF, int iTrack, int iPort);
+BOOL		midiSongAddEndSequence(MIDI_FILE *pMF, int iTrack);
+
+/*
+** midiTrack* Prototypes
+*/
+BOOL		midiTrackAddRaw(MIDI_FILE *pMF, int iTrack, int iDataSize, const BYTE *pData, BOOL bMovePtr, int iDeltaTime);
+BOOL		midiTrackIncTime(MIDI_FILE *pMF, int iTrack, int iDeltaTime, BOOL bOverridePPQN);
+BOOL		midiTrackAddText(MIDI_FILE *pMF, int iTrack, tMIDI_TEXT iType, const char *pTxt);
+BOOL		midiTrackAddMsg(MIDI_FILE *pMF, int iTrack, tMIDI_MSG iMsg, int iParam1, int iParam2);
+BOOL		midiTrackSetKeyPressure(MIDI_FILE *pMF, int iTrack, int iNote, int iAftertouch);
+BOOL		midiTrackAddControlChange(MIDI_FILE *pMF, int iTrack, tMIDI_CC iCCType, int iParam);
+BOOL		midiTrackAddProgramChange(MIDI_FILE *pMF, int iTrack, int iInstrPatch);
+BOOL		midiTrackChangeKeyPressure(MIDI_FILE *pMF, int iTrack, int iDeltaPressure);
+BOOL		midiTrackSetPitchWheel(MIDI_FILE *pMF, int iTrack, int iWheelPos);
+BOOL		midiTrackAddNote(MIDI_FILE *pMF, int iTrack, int iNote, int iLength, int iVol, BOOL bAutoInc, BOOL bOverrideLength);
+BOOL		midiTrackAddRest(MIDI_FILE *pMF, int iTrack, int iLength, BOOL bOverridePPQN);
+BOOL		midiTrackGetEndPos(MIDI_FILE *pMF, int iTrack);
+
+/*
+** midiRead* Prototypes
+*/
+int			midiReadGetNumTracks(const MIDI_FILE *pMF);
+BOOL		midiReadGetNextMessage(const MIDI_FILE *pMF, int iTrack, MIDI_MSG *pMsg);
+void		midiReadInitMessage(MIDI_MSG *pMsg);
+void		midiReadFreeMessage(MIDI_MSG *pMsg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MIDIFILE_H */
+
diff --git a/include/midiinfo.h b/include/midiinfo.h
new file mode 100644
index 0000000..2eba3f6
--- /dev/null
+++ b/include/midiinfo.h
@@ -0,0 +1,632 @@
+/*
+ * midiinfo.h -  A collection of macros and constants for MIDI programming.
+ *	These should work independant of 'Steevs MIDI Library'
+ * Version 1.4
+ *
+ *  AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
+ *			Copyright 1998-2010, Steven Goodwin.
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _MIDIINFO_H
+#define _MIDIINFO_H
+
+/*
+** MIDI Messages [ consist of message, and optional bytes ]
+**				a 'msg' has two nibbles: message type & channel
+*/
+typedef enum {
+		msgNoteOff				= 0x80,		/* [ pitch, volume ] */
+		msgNoteOn				= 0x90,		/* [ pitch, volume ] */
+		msgNoteKeyPressure		= 0xa0,		/* [ pitch, pressure (after touch) ] */
+		msgSetParameter			= 0xb0,		/* [ param number (CC), setting ] */
+		msgSetProgram			= 0xc0,		/* [ program ] */
+		msgChangePressure		= 0xd0,		/* [ pressure (after touch) ] */
+		msgSetPitchWheel		= 0xe0,		/* [ LSB,  MSG (two 7 bit values) ] */
+		
+		msgMetaEvent			= 0xff,
+		msgSysEx1				= 0xf0,
+		msgSysEx2				= 0xf7,
+
+		/* Alternative names */
+		msgPatchChange			= msgSetProgram,
+		msgControlChange		= msgSetParameter,
+
+		msgSysMask				= 0xf0,
+		} tMIDI_MSG;
+
+/*
+** Control Changes
+*/
+typedef enum {
+		/* 0-31, where defined, all indicate the MSB */
+		ccBankSelect			= 0,
+		ccModulation			= 1,
+		ccBreathControl			= 2,
+		ccUndefined3			= 3,
+		ccFootControl				= 4,
+		ccPortamentoTime		= 5,
+		ccDateEntry				= 6,
+		ccVolume				= 7,
+		ccBalance				= 8,
+		ccUndefined9			= 9,
+		ccPan					= 10,
+		ccExpression			= 11,
+		ccEffectControl1		= 12,
+		ccEffectControl2		= 13,
+		ccUndefined14			= 14,
+		ccUndefined15			= 15,
+		ccGeneralPurpose1		= 16,
+		ccGeneralPurpose2		= 17,
+		ccGeneralPurpose3		= 18,
+		ccGeneralPurpose4		= 19,
+		/* 20-31 are undefined */
+		ccUndefined20			= 20,
+		ccUndefined21			= 21,
+		ccUndefined22			= 22,
+		ccUndefined23			= 23,
+		ccUndefined24			= 24,
+		ccUndefined25			= 25,
+		ccUndefined26			= 26,
+		ccUndefined27			= 27,
+		ccUndefined28			= 28,
+		ccUndefined29			= 29,
+		ccUndefined30			= 30,
+		ccUndefined31			= 31,
+		/* LSB for control changes 0-31		32-63 */
+		ccBankSelectLSB			= 32,
+		ccModulationLSB			= 33,
+		ccBreathControlLSB		= 34,
+		ccUndefined35			= 35,
+		ccFootControlLSB		= 36,
+		ccPortamentoTimeLSB		= 37,
+		ccDateEntryLSB			= 38,
+		ccVolumeLSB			= 39,
+		ccBalanceLSB			= 40,
+		ccUndefined41			= 41,
+		ccPanLSB			= 42,
+		ccExpressionLSB			= 43,
+		ccEffectControl1LSB		= 44,
+		ccEffectControl2LSB		= 45,
+		ccUndefined46			= 46,
+		ccUndefined47			= 47,
+		ccGeneralPurpose1LSB		= 48,
+		ccGeneralPurpose2LSB		= 49,
+		ccGeneralPurpose3LSB		= 50,
+		ccGeneralPurpose4LSB		= 51,
+		/* 52-63 are undefined */
+		ccUndefined52			= 52,
+		ccUndefined53			= 53,
+		ccUndefined54			= 54,
+		ccUndefined55			= 55,
+		ccUndefined56			= 56,
+		ccUndefined57			= 57,
+		ccUndefined58			= 58,
+		ccUndefined59			= 59,
+		ccUndefined60			= 60,
+		ccUndefined61			= 61,
+		ccUndefined62			= 62,
+		ccUndefined63			= 63,
+
+		ccSustainPedal			= 64,
+		ccPortamento			= 65,
+		ccPedalSustenuto		= 66,
+		ccPedalSoft				= 67,
+		ccLegatoFootSwitch		= 68,
+		ccHold2					= 69,
+		ccSoundVariation		= 70,
+		ccTimbre			= 71,
+		ccReleaseTime			= 72,
+		ccAttackTime			= 73,
+		ccBrightness			= 74,
+		ccReverb				= 75,
+		ccDelay					= 76,
+		ccPitchTranspose		= 77,
+		ccFlange				= 78,
+		ccSpecialFX				= 79,
+		ccGeneralPurpose5		= 80,
+		ccGeneralPurpose6		= 81,
+		ccGeneralPurpose7		= 82,
+		ccGeneralPurpose8		= 83,
+		ccPortamentoControl		= 84,
+		/* 85-90 are undefined */
+		ccUndefined85			= 85,
+		ccUndefined86			= 86,
+		ccUndefined87			= 87,
+		ccUndefined88			= 88,
+		ccUndefined89			= 89,
+		ccUndefined90			= 90,
+		/* Effects depth */
+		ccFXDepth				= 91,
+		ccTremeloDepth			= 92,
+		ccChorusDepth			= 93,
+		ccCelestaDepth			= 94,
+		ccPhaserDepth			= 95,
+		ccDataInc				= 96,
+		ccDataDec				= 97,
+		ccNonRegParamLSB		= 98,
+		ccNonRefParamMSB		= 99,
+		ccRegParamLSB			= 100,
+		ccRegParamMSB			= 101,
+		/* 102-119 are undefined */
+		ccUndefined102			= 102,
+		ccUndefined103			= 103,
+		ccUndefined104			= 104,
+		ccUndefined105			= 105,
+		ccUndefined106			= 106,
+		ccUndefined107			= 107,
+		ccUndefined108			= 108,
+		ccUndefined109			= 109,
+		ccUndefined110			= 110,
+		ccUndefined111			= 111,
+		ccUndefined112			= 112,
+		ccUndefined113			= 113,
+		ccUndefined114			= 114,
+		ccUndefined115			= 115,
+		ccUndefined116			= 116,
+		ccUndefined117			= 117,
+		ccUndefined118			= 118,
+		ccUndefined119			= 119,
+		ccAllSoundOff			= 120,
+		ccResetAllControllers	= 121,
+		ccLocalControl			= 122,
+		ccAllNotesOff			= 123,
+		ccOmniModeOff			= 124,
+		ccOmniModeOn			= 125,
+		ccMonoModeOn			= 126,
+		ccPolyModeOn			= 127,
+		/* Alternative names */
+		ccModWheel				= 1,
+		/* All sound controllers have only LSB */
+		ccHarmContent			= 71,
+		ccSoundController1			= 70,
+		ccSoundController2			= 71,
+		ccSoundController3			= 72,
+		ccSoundController4			= 73,
+		ccSoundController5			= 74,
+		ccSoundController6			= 75,
+		ccSoundController7			= 76,
+		ccSoundController8			= 77,
+		ccSoundController9			= 78,
+		ccSoundController10			= 79,
+		ccEffect1Depth				= 91,
+		ccEffect2Depth				= 92,
+		ccEffect3Depth				= 93,
+		ccEffect4Depth				= 94,
+		ccEffect5Depth				= 95,
+		ccDetuneDepth			= 94,
+
+		} tMIDI_CC;
+
+/*
+** System Common (Status byte: 1111 0ttt)
+*/
+typedef enum {
+		sysUndefinedF1			= 0xf1,
+		sysSongPosition			= 0xf2, /* [LSB, MSB] */
+		sysSongSelect			= 0xf3,
+		sysUndefinedF4			= 0xf4,
+		sysUndefinedF5			= 0xf5,
+		sysTuneRequest			= 0xf6,
+		sysEOX				= 0xf7, /* End of Exclusive */
+		} tMIDI_SYSCOMMON;
+
+/*
+** System Real Time (Status byte: 1111 1ttt)
+*/
+typedef enum {
+		srtTimingClock			= 0xf8,
+		srtUndefinedF9			= 0xf9,
+		srtStart			= 0xfa,
+		srtContinue			= 0xfb,
+		srtStop				= 0xfc,
+		srtUndefinedFD			= 0xfd,
+		srtActiveSensing		= 0xfe,
+		srtSystemReset			= 0xff,
+		} tMIDI_REALTIME;
+
+/*
+** System Exclusive (Status byte: 1111 0000)
+**
+** The first byte of a sysex must be the identification number
+** (7 bits, MSB=0). This is followed by an arbitary number of
+** data bytes (all MSB=0), and ending in the sexEOX msg.
+** Note: Any other status byte (where MSB=1) will also terminate
+** a sysex message, with the exception of the System Real Time
+** events above.
+*/
+typedef enum {
+		sexEOX				= 0xf7,
+		} tMIDI_SYSEX;
+
+/*
+** Key signatures
+*/
+typedef enum {
+		keyCFlatMaj				= 0x87,
+		keyGFlatMaj				= 0x86,
+		keyDFlatMaj				= 0x85,
+		keyAFlatMaj				= 0x84,
+		keyEFlatMaj				= 0x83,
+		keyBFlatMaj				= 0x82,
+		keyFMaj					= 0x81,
+		keyCMaj					= 0x00,
+		keyGMaj					= 0x01,
+		keyDMaj					= 0x02,
+		keyAMaj					= 0x03,
+		keyEMaj					= 0x04,
+		keyBMaj					= 0x05,
+		keyFSharpMaj			= 0x06,
+		keyCSharpMaj			= 0x07,
+		keyCFlatMin				= 0xc7,
+		keyGFlatMin				= 0xc6,
+		keyDFlatMin				= 0xc5,
+		keyAFlatMin				= 0xc4,
+		keyEFlatMin				= 0xc3,
+		keyBFlatMin				= 0xc2,
+		keyFMin					= 0xc1,
+		keyCMin					= 0x40,
+		keyGMin					= 0x41,
+		keyDMin					= 0x42,
+		keyAMin					= 0x43,
+		keyEMin					= 0x44,
+		keyBMin					= 0x45,
+		keyFSharpMin			= 0x46,
+		keyCSharpMin			= 0x47,
+		/* Format: Bit 7=represent as negative, Bit 6=Minor key, bits 0-3=key id*/
+		/* By no coincidence, masking out the 'minor' flag,we have a signed char value */
+		keyMaskNeg				= 0x80,
+		keyMaskFlatKeys			= 0x80,
+		keyMaskMin				= 0x40,
+		keyMaskKey				= 0x07,
+		} tMIDI_KEYSIG;
+
+
+typedef enum {
+		metaSequenceNumber		= 0,	/* followed by 2 and then the sequence number */
+		/* Text Information */
+		metaTextEvent			= 1,
+		metaCopyright			= 2,
+		metaTrackName			= 3,
+		metaInstrument			= 4,
+		metaLyric				= 5,
+		metaMarker				= 6,
+		metaCuePoint			= 7,
+		/* Data */
+		metaMIDIPort			= 0x21, 	/* followed by 1, then the port number */
+		metaEndSequence			= 0x2f,		/* followed by zero */
+		metaSetTempo			= 0x51,		/* followed by 3 (size), and time between beats in us: us = 60000000/tempo. Write as three bytes, MSG first */
+		metaSMPTEOffset			= 0x54,		/* followed by 5 (size), and 5 bytes detailing frame info:	hr.mins.sec:frame.ff */
+		metaTimeSig				= 0x58,		/* followed by 4 (size), and 4 bytes detailing  nominator and denominator of sig,clock_in_metro_tick and notated_32nds_in_quarter */
+		metaKeySig				= 0x59,		/* followed by 2 (size), and the key (-7=7 flats, 0=key of C,7=7 sharps)), followed by a 'major?' flag (0=major, 1=minor) */
+		/* Custom */
+		metaSequencerSpecific	= 0x7f,		/* followed by the number of bytes, then the data */
+		} tMIDI_META;	
+
+typedef enum {
+		textTextEvent			= 1,
+		textCopyright			= 2,
+		textTrackName			= 3,
+		textInstrument			= 4,
+		textLyric				= 5,
+		textMarker				= 6,
+		textCuePoint			= 7,
+		} tMIDI_TEXT;	
+
+
+/*
+** MIDI Constants
+*/
+#define MIDI_WHEEL_CENTRE			8192
+
+/*
+** MIDI Channels
+*/
+#define MIDI_CHANNEL_1			1
+#define MIDI_CHANNEL_2			2
+#define MIDI_CHANNEL_3			3
+#define MIDI_CHANNEL_4			4
+#define MIDI_CHANNEL_5			5
+#define MIDI_CHANNEL_6			6
+#define MIDI_CHANNEL_7			7
+#define MIDI_CHANNEL_8			8
+#define MIDI_CHANNEL_9			9
+#define MIDI_CHANNEL_10			10
+#define MIDI_CHANNEL_11			11
+#define MIDI_CHANNEL_12			12
+#define MIDI_CHANNEL_13			13
+#define MIDI_CHANNEL_14			14
+#define MIDI_CHANNEL_15			15
+#define MIDI_CHANNEL_16			16
+
+#define MIDI_CHANNEL_DRUMS		10
+
+/*
+** Notes
+*/
+#define MIDI_OCTAVE			12
+
+/*
+** The MIDI spec only indicates middle C to be
+** 60. It doesn't indicate which octave this is.
+** Some may consider 4, if they label octaves 
+** from -1, instead of 0. I have adopted an octave
+** number here for tighter intergration.
+*/
+#define MIDI_NOTE_MIDDLE_C	MIDI_NOTE_C5
+
+#define MIDI_NOTE_C			0
+#define MIDI_NOTE_C_SHARP	1
+#define MIDI_NOTE_C_FLAT	-11
+#define MIDI_NOTE_D			2
+#define MIDI_NOTE_D_SHARP	3
+#define MIDI_NOTE_D_FLAT	1
+#define MIDI_NOTE_E			4
+#define MIDI_NOTE_E_SHARP	5
+#define MIDI_NOTE_E_FLAT	3
+#define MIDI_NOTE_F			5
+#define MIDI_NOTE_F_SHARP	6
+#define MIDI_NOTE_F_FLAT	5
+#define MIDI_NOTE_G			7
+#define MIDI_NOTE_G_SHARP	8
+#define MIDI_NOTE_G_FLAT	6
+#define MIDI_NOTE_A			9
+#define MIDI_NOTE_A_SHARP	10
+#define MIDI_NOTE_A_FLAT	8
+#define MIDI_NOTE_B			11
+#define MIDI_NOTE_B_SHARP	12
+#define MIDI_NOTE_B_FLAT	10
+
+#define MIDI_NOTE_C0		0 
+#define MIDI_NOTE_C1		12
+#define MIDI_NOTE_C2		24
+#define MIDI_NOTE_C3		36
+#define MIDI_NOTE_C4		48
+#define MIDI_NOTE_C5		60
+#define MIDI_NOTE_C6		72
+#define MIDI_NOTE_C7		84
+#define MIDI_NOTE_C8		96
+#define MIDI_NOTE_C9		108
+#define MIDI_NOTE_C10		120
+
+#define MIDI_OCTAVE_0		MIDI_NOTE_C0
+#define MIDI_OCTAVE_1		MIDI_NOTE_C1
+#define MIDI_OCTAVE_2		MIDI_NOTE_C2
+#define MIDI_OCTAVE_3		MIDI_NOTE_C3
+#define MIDI_OCTAVE_4		MIDI_NOTE_C4
+#define MIDI_OCTAVE_5		MIDI_NOTE_C5
+#define MIDI_OCTAVE_6		MIDI_NOTE_C6
+#define MIDI_OCTAVE_7		MIDI_NOTE_C7
+#define MIDI_OCTAVE_8		MIDI_NOTE_C8
+#define MIDI_OCTAVE_9		MIDI_NOTE_C9
+#define MIDI_OCTAVE_10		MIDI_NOTE_C10
+
+
+/*
+** Note Duration (on PPQN=384)
+*/
+#define MIDI_NOTE_BREVE					1536
+#define MIDI_NOTE_MINIM					 768
+#define MIDI_NOTE_CROCHET				 384
+#define MIDI_NOTE_QUAVER				 192
+#define MIDI_NOTE_SEMIQUAVER			  96
+#define MIDI_NOTE_SEMIDEMIQUAVER		  48
+
+#define MIDI_NOTE_DOTTED_MINIM			 (768+384)
+#define MIDI_NOTE_DOTTED_CROCHET		 (384+192)
+#define MIDI_NOTE_DOTTED_QUAVER			 (192+96)
+#define MIDI_NOTE_DOTTED_SEMIQUAVER		  (96+48)
+#define MIDI_NOTE_DOTTED_SEMIDEMIQUAVER	  (48+24)
+
+#define MIDI_NOTE_TRIPLE_CROCHET			256			/* 3 notes in 2 crochet */
+
+
+/*
+** Notes - Volume
+*/
+#define MIDI_VOL_FULL		127
+#define MIDI_VOL_HALF		64
+#define MIDI_VOL_OFF		0
+
+
+/*
+** Notes - Instrument Names
+*/
+#define MIDI_PATCH_ACOUSTIC_GRAND_PIANO         0 
+#define MIDI_PATCH_BRIGHT_ACOUSTIC_PIANO        1
+#define MIDI_PATCH_ELECTRIC_GRAND_PIANO         2
+#define MIDI_PATCH_HONKY_TONK_PIANO             3
+#define MIDI_PATCH_ELECTRIC_PIANO_1				4
+#define MIDI_PATCH_ELECTRIC_PIANO_2             5
+#define MIDI_PATCH_HARPSICHORD                  6
+#define MIDI_PATCH_CLAVI						7
+#define MIDI_PATCH_CELESTA   					8
+#define MIDI_PATCH_GLOCKENSPIEL					9
+#define MIDI_PATCH_MUSIC_BOX					10
+#define MIDI_PATCH_VIBRAPHONE					11
+#define MIDI_PATCH_MARIMBA						12
+#define MIDI_PATCH_XYLOPHONE       				13
+#define MIDI_PATCH_TUBULAR_BELLS                14
+#define MIDI_PATCH_DULCIMER						15
+#define MIDI_PATCH_DRAWBAR_ORGAN                16
+#define MIDI_PATCH_PERCUSSIVE_ORGAN             17
+#define MIDI_PATCH_ROCK_ORGAN					18
+#define MIDI_PATCH_CHURCH_ORGAN					19
+#define MIDI_PATCH_REED_ORGAN					20
+#define MIDI_PATCH_ACCORDION					21
+#define MIDI_PATCH_HARMONICA					22
+#define MIDI_PATCH_TANGO_ACCORDION              23
+#define MIDI_PATCH_ACOUSTIC_GUITAR_NYLON        24
+#define MIDI_PATCH_ACOUSTIC_GUITAR_STEEL        25
+#define MIDI_PATCH_ELECTRIC_GUITAR_JAZZ         26
+#define MIDI_PATCH_ELECTRIC_GUITAR_CLEAN        27
+#define MIDI_PATCH_ELECTRIC_GUITAR_MUTED        28
+#define MIDI_PATCH_OVERDRIVEN_GUITAR			29
+#define MIDI_PATCH_DISTORTION_GUITAR			30
+#define MIDI_PATCH_GUITAR_HARMONICS				31
+#define MIDI_PATCH_ACOUSTIC_BASS				32
+#define MIDI_PATCH_ELECTRIC_BASS_FINGER			33
+#define MIDI_PATCH_ELECTRIC_BASS_PICK			34
+#define MIDI_PATCH_FRETLESS_BASS                35
+#define MIDI_PATCH_SLAP_BASS_1                  36
+#define MIDI_PATCH_SLAP_BASS_2                  37
+#define MIDI_PATCH_SYNTH_BASS_1                 38
+#define MIDI_PATCH_SYNTH_BASS_2					39
+#define MIDI_PATCH_VIOLIN						40
+#define MIDI_PATCH_VIOLA						41
+#define MIDI_PATCH_CELLO 						42
+#define MIDI_PATCH_CONTRABASS            		43
+#define MIDI_PATCH_TREMOLO_STRINGS         		44
+#define MIDI_PATCH_PIZZICATO_STRINGS			45
+#define MIDI_PATCH_ORCHESTRAL_HARP				46
+#define MIDI_PATCH_TIMPANI        				47
+#define MIDI_PATCH_STRING_ENSEMBLE_1			48
+#define MIDI_PATCH_STRING_ENSEMBLE_2            49
+#define MIDI_PATCH_SYNTHSTRINGS_1               50
+#define MIDI_PATCH_SYNTHSTRINGS_2               51
+#define MIDI_PATCH_CHOIR_AAHS					52
+#define MIDI_PATCH_VOICE_OOHS					53
+#define MIDI_PATCH_SYNTH_VOICE					54
+#define MIDI_PATCH_ORCHESTRA_HIT				55
+#define MIDI_PATCH_TRUMPET						56
+#define MIDI_PATCH_TROMBONE						57
+#define MIDI_PATCH_TUBA							58
+#define MIDI_PATCH_MUTED_TRUMPET				59
+#define MIDI_PATCH_FRENCH_HORN					60
+#define MIDI_PATCH_BRASS_SECTION				61
+#define MIDI_PATCH_SYNTHBRASS_1					62
+#define MIDI_PATCH_SYNTHBRASS_2					63
+#define MIDI_PATCH_SOPRANO_SAX					64
+#define MIDI_PATCH_ALTO_SAX						65
+#define MIDI_PATCH_TENOR_SAX           			66
+#define MIDI_PATCH_BARITONE_SAX        			67
+#define MIDI_PATCH_OBOE            				68
+#define MIDI_PATCH_ENGLISH_HORN    		  		69
+#define MIDI_PATCH_BASSOON            			70
+#define MIDI_PATCH_CLARINET            			71
+#define MIDI_PATCH_PICCOLO              		72
+#define MIDI_PATCH_FLUTE              			73
+#define MIDI_PATCH_RECORDER           		    74
+#define MIDI_PATCH_PAN_FLUTE					75
+#define MIDI_PATCH_BLOWN_BOTTLE				    76
+#define MIDI_PATCH_SHAKUHACHI					77
+#define MIDI_PATCH_WHISTLE						78
+#define MIDI_PATCH_OCARINA					    79
+#define MIDI_PATCH_LEAD_1_SQUARE	            80
+#define MIDI_PATCH_LEAD_2_SAWTOOTH				81
+#define MIDI_PATCH_LEAD_3_CALLIOPE				82
+#define MIDI_PATCH_LEAD_4_CHIFF					83
+#define MIDI_PATCH_LEAD_5_CHARANG				84
+#define MIDI_PATCH_LEAD_6_VOICE 				85
+#define MIDI_PATCH_LEAD_7_FIFTHS				86
+#define MIDI_PATCH_LEAD_8_BASS_AND_LEAD			87
+#define MIDI_PATCH_PAD_1_NEW_AGE				88
+#define MIDI_PATCH_PAD_2_WARM					89
+#define MIDI_PATCH_PAD_3_POLYSYNTH				90
+#define MIDI_PATCH_PAD_4_CHOIR					91
+#define MIDI_PATCH_PAD_5_BOWED					92
+#define MIDI_PATCH_PAD_6_METALLIC				93
+#define MIDI_PATCH_PAD_7_HALO					94
+#define MIDI_PATCH_PAD_8_SWEEP					95
+#define MIDI_PATCH_FX_1_RAIN					96
+#define MIDI_PATCH_FX_2_SOUNDTRACK				97
+#define MIDI_PATCH_FX_3_CRYSTAL					98
+#define MIDI_PATCH_FX_4_ATMOSPHERE				99
+#define MIDI_PATCH_FX_5_BRIGHTNESS				100
+#define MIDI_PATCH_FX_6_GOBLINS					101
+#define MIDI_PATCH_FX_7_ECHOES					102
+#define MIDI_PATCH_FX_8_SCIFI					103
+#define MIDI_PATCH_SITAR						104
+#define MIDI_PATCH_BANJO						105
+#define MIDI_PATCH_SHAMISEN						106
+#define MIDI_PATCH_KOTO							107
+#define MIDI_PATCH_KALIMBA						108
+#define MIDI_PATCH_BAG_PIPE						109
+#define MIDI_PATCH_FIDDLE						110
+#define MIDI_PATCH_SHANAI						111
+#define MIDI_PATCH_TINKLE_BELL					112
+#define MIDI_PATCH_AGOGO						113
+#define MIDI_PATCH_STEEL_DRUMS					114
+#define MIDI_PATCH_WOODBLOCK					115
+#define MIDI_PATCH_TAIKO_DRUM					116
+#define MIDI_PATCH_MELODIC_TOM					117
+#define MIDI_PATCH_SYNTH_DRUM					118
+#define MIDI_PATCH_REVERSE_CYMBAL				119
+#define MIDI_PATCH_GUITAR_FRET NOISE			120	
+#define MIDI_PATCH_BREATH_NOISE					121
+#define MIDI_PATCH_SEASHORE						122
+#define MIDI_PATCH_BIRD_TWEET					123
+#define MIDI_PATCH_TELEPHONE_RING				124
+#define MIDI_PATCH_HELICOPTER					125
+#define MIDI_PATCH_APPLAUSE						126
+#define MIDI_PATCH_GUNSHOT						127
+	
+
+/*
+** Notes - Keyed Percussion Names
+*/
+#define MIDI_DRUM_ACOUSTIC_BASS_DRUM		35
+#define MIDI_DRUM_BASS_DRUM					36
+#define MIDI_DRUM_SIDE_STICK				37
+#define MIDI_DRUM_ACOUSTIC_SNARE			38
+#define MIDI_DRUM_HAND_CLAP					39
+#define MIDI_DRUM_ELECTRIC_SNARE			40
+#define MIDI_DRUM_LOW_FLOOR_TOM				41
+#define MIDI_DRUM_CLOSED_HI_HAT				42
+#define MIDI_DRUM_HIGH_FLOOR_TOM			43
+#define MIDI_DRUM_PEDAL_HI_HAT				44
+#define MIDI_DRUM_LOW_TOM					45
+#define MIDI_DRUM_OPEN_HI_HAT				46
+#define MIDI_DRUM_LOW_MID_TOM				47
+#define MIDI_DRUM_HI_MID_TOM				48
+#define MIDI_DRUM_CRASH_CYMBAL_1			49
+#define MIDI_DRUM_HIGH_TOM					50
+#define MIDI_DRUM_RIDE_CYMBAL_1				51
+#define MIDI_DRUM_CHINESE_CYMBAL			52
+#define MIDI_DRUM_RIDE_BELL					53
+#define MIDI_DRUM_TAMBOURINE				54
+#define MIDI_DRUM_SPLASH_CYMBAL				55
+#define MIDI_DRUM_COWBELL					56
+#define MIDI_DRUM_CRASH_CYMBAL_2			57
+#define MIDI_DRUM_VIBRA_SLAP				58
+#define MIDI_DRUM_RIDE_CYMBAL_2				59
+#define MIDI_DRUM_HI_BONGO					60
+#define MIDI_DRUM_LOW_BONGO					61
+#define MIDI_DRUM_MUTE_HI_CONGA				62
+#define MIDI_DRUM_OPEN_HI_CONGA				63
+#define MIDI_DRUM_LOW_CONGA					64
+#define MIDI_DRUM_HIGH_TIMBALE				65
+#define MIDI_DRUM_LOW_TIMBALE				66
+#define MIDI_DRUM_HIGH_AGOGO				67
+#define MIDI_DRUM_LOW_AGOGO					68
+#define MIDI_DRUM_CABASA					69
+#define MIDI_DRUM_MARACAS					70
+#define MIDI_DRUM_SHORT_WHISTLE				71
+#define MIDI_DRUM_LONG_WHISTLE				72
+#define MIDI_DRUM_SHORT_GUIRO				73
+#define MIDI_DRUM_LONG_GUIRO				74
+#define MIDI_DRUM_CLAVES					75
+#define MIDI_DRUM_HI_WOOD_BLOCK				76
+#define MIDI_DRUM_LOW_WOOD_BLOCK			77
+#define MIDI_DRUM_MUTE_CUICA				78
+#define MIDI_DRUM_OPEN_CUICA				79
+#define MIDI_DRUM_MUTE_TRIANGLE				80
+#define MIDI_DRUM_OPEN_TRIANGLE				81
+
+
+#endif	/* _MIDIINFO_H */
+
+
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index a9d27cb..f47ba4e 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -2,6 +2,7 @@
 
 #include <resource.h>
 #include <commctrl.h>
+#include <commdlg.h>
 #include <windowsx.h>
 
 #define LEFT(x, y, cx, cy) x, y, cx, cy
@@ -12,11 +13,24 @@
 #define KEYBOARD_VOLUME     0xAA01
 #define KEYBOARD_FORCE      0xAA02
 #define KEYBOARD_INSTRUMENT 0xAA03
+#define KEYBOARD_SAVE       0xAB00
+#define KEYBOARD_SAVE_FILE  0xAB01
+#define KEYBOARD_BROWSE     0xAB02
 #define MIDI_MESSAGE(handle, code, arg1, arg2) \
     midiOutShortMsg(handle, ((arg2 & 0x7F) << 16) |\
                     ((arg1 & 0x7F) << 8) | (code & 0xFF))
 
 #pragma comment(lib, "winmm.lib")
+#pragma comment(lib, "comdlg32.lib")
+
+#include <stdio.h>
+#define MessageIntBox(hwnd, i, title, opt) \
+    do { \
+        char buf[16]; \
+        sprintf(buf, "%d", i); \
+        MessageBoxA(hwnd, buf, title, opt); \
+    } while (0)
+
 
 DWORD rgbWindowBackground;
 
@@ -95,6 +109,20 @@ LRESULT MainWindow::OnCreate()
                     CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL, 0, 0, 0, 0,
             m_hwnd, (HMENU) KEYBOARD_INSTRUMENT, GetInstance(), NULL);
 
+    m_saveCheck = CreateWindow(WC_BUTTON, L"Save?", WS_CHILD | WS_VISIBLE | BS_CHECKBOX,
+            0, 0, 0, 0, m_hwnd, (HMENU) KEYBOARD_SAVE, GetInstance(), NULL);
+    m_saveLabel = CreateWindow(WC_STATIC, L"File:",
+            WS_CHILD | WS_VISIBLE | WS_DISABLED | SS_CENTERIMAGE, 0, 0, 0, 0,
+            m_hwnd, NULL, GetInstance(), NULL);
+    m_saveFile = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, NULL,
+            WS_CHILD | WS_VISIBLE | WS_DISABLED | ES_READONLY, 0, 0, 0, 0,
+            m_hwnd, (HMENU) KEYBOARD_SAVE_FILE, GetInstance(), NULL);
+    m_saveBrowse = CreateWindow(WC_BUTTON, L"Browse...",
+            WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
+            0, 0, 0, 0, m_hwnd, (HMENU) KEYBOARD_BROWSE, GetInstance(), NULL);
+    if (!m_saveLabel)
+        MessageBox(m_hwnd, NULL, NULL, NULL);
+
     SendMessage(m_volumeBar, TBM_SETRANGEMIN, FALSE, 0x0000);
     SendMessage(m_volumeBar, TBM_SETRANGEMAX, FALSE, 0xFFFF);
     SendMessage(m_forceBar, TBM_SETRANGE, FALSE, 127 << 16);
@@ -102,6 +130,10 @@ LRESULT MainWindow::OnCreate()
     SendMessage(m_forceBar, TBM_SETPOS, FALSE, 64);
     m_force = 64;
     m_volume = 0xFFFF;
+    m_midifile = NULL;
+    m_instrument = 0;
+    saving = false;
+    deltaTime = (DWORD) -1;
 
     WCHAR buf[MAX_PATH];
     int piano;
@@ -122,6 +154,10 @@ LRESULT MainWindow::OnCreate()
     SETFONT(m_forceBar);
     SETFONT(m_instruLabel);
     SETFONT(m_instruSelect);
+    SETFONT(m_saveCheck);
+    SETFONT(m_saveLabel);
+    SETFONT(m_saveFile);
+    SETFONT(m_saveBrowse);
 #undef SETFONT
 
     if (midiOutOpen(&m_midi, 0, NULL, NULL, CALLBACK_NULL) != MMSYSERR_NOERROR)
@@ -193,7 +229,13 @@ LRESULT MainWindow::OnDestroy()
     DestroyWindow(m_forceBar);
     DestroyWindow(m_instruLabel);
     DestroyWindow(m_instruSelect);
+    DestroyWindow(m_saveCheck);
+    DestroyWindow(m_saveLabel);
+    DestroyWindow(m_saveFile);
+    DestroyWindow(m_saveBrowse);
     midiOutClose(m_midi);
+    if (m_midifile)
+        midiFileClose(m_midifile);
     return 0;
 }
 
@@ -279,10 +321,32 @@ void MainWindow::PlayNote(int note, bool down)
             num += 24;
         }
     }
-    if (down)
-        MIDI_MESSAGE(m_midi, 0x90, note, m_force);
-    else
-        MIDI_MESSAGE(m_midi, 0x90, note, 0);
+    MIDI_MESSAGE(m_midi, down ? 0x90 : 0x80, note, m_force);
+    
+    if (m_midifile && saving) {
+        if (deltaTime == (DWORD) -1)
+            deltaTime = GetTickCount();
+        midiTrackAddRest(m_midifile, 1, GetTickCount() - deltaTime, TRUE);
+        midiTrackAddMsg(m_midifile, 1, down ? msgNoteOn : msgNoteOff, note, m_force);
+        deltaTime = GetTickCount();
+    }
+}
+
+
+void MainWindow::PaintContent(PAINTSTRUCT *pps)
+{
+    HPEN hOldPen = SelectPen(pps->hdc, GetStockPen(DC_PEN));
+    HBRUSH hOldBrush = SelectBrush(pps->hdc, GetSysColorBrush(COLOR_3DFACE));
+    RECT client;
+    
+    GetClientRect(m_hwnd, &client);
+    SetBkColor(pps->hdc, GetSysColor(COLOR_3DFACE));
+    SetDCPenColor(pps->hdc, GetSysColor(COLOR_3DHILIGHT));
+    
+    RoundRect(pps->hdc, 12, client.bottom - 52, client.right - 12, client.bottom - 12, 5, 5);
+    
+    SelectPen(pps->hdc, hOldPen);
+    SelectBrush(pps->hdc, hOldBrush);
 }
 
 LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
@@ -301,19 +365,21 @@ LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
         GetClientRect(m_hwnd, &client);
 #define REPOS(hwnd, k) hdwp = DeferWindowPos(hdwp, hwnd, 0, k, SWP_NOACTIVATE|SWP_NOZORDER)
         hdwp = BeginDeferWindowPos(14);
-        REPOS(m_volumeLabel,    BOTTOM(12, client.bottom - 12, 70, 25));
-        REPOS(m_volumeBar,      BOTTOM(82, client.bottom - 12, client.right - 94, 25));
-        REPOS(m_forceLabel,     BOTTOM(12, client.bottom - 42, 70, 25));
-        REPOS(m_forceBar,       BOTTOM(82, client.bottom - 42, client.right - 94, 25));
-        REPOS(m_instruLabel,    BOTTOM(12, client.bottom - 72, 70, 25));
-        REPOS(m_instruSelect,   BOTTOM(82, client.bottom - 72, client.right - 94, 25));
+        REPOS(piano->GetHWND(), LEFT(12, 12, client.right - 24, client.bottom - 172));
+        REPOS(m_instruLabel,    BOTTOM(12, client.bottom - 127, 70, 25));
+        REPOS(m_instruSelect,   BOTTOM(82, client.bottom - 127, client.right - 94, 25));
+        REPOS(m_forceLabel,     BOTTOM(12, client.bottom - 97, 70, 25));
+        REPOS(m_forceBar,       BOTTOM(82, client.bottom - 97, client.right - 94, 25));
+        REPOS(m_volumeLabel,    BOTTOM(12, client.bottom - 67, 70, 25));
+        REPOS(m_volumeBar,      BOTTOM(82, client.bottom - 67, client.right - 94, 25));
+        REPOS(m_saveCheck,      BOTTOM(22, client.bottom - 42, 50, 20));
+        REPOS(m_saveLabel,      BOTTOM(27, client.bottom - 19, 30, 20));
+        REPOS(m_saveFile,       BOTTOM(62, client.bottom - 17, client.right - 164, 25));
+        REPOS(m_saveBrowse,     BOTTOMRIGHT(client.right - 17, client.bottom - 17, 80, 25));
         EndDeferWindowPos(hdwp);
 #undef REPOS
-        //REPOS(piano->GetHWND(), LEFT(12, 12, client.right - 24, client.bottom - 117));
-        if (!MoveWindow(piano->GetHWND(), 12, 12, client.right - 24, client.bottom - 117, TRUE))
-            MessageBox(m_hwnd, 0, 0, 0);
         return 0;
-      }
+    }
     case WM_COMMAND:
         switch (HIWORD(wParam)) {
         case CBN_SELCHANGE:
@@ -322,6 +388,48 @@ LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
                 m_instrument = SendMessage((HWND) lParam, CB_GETITEMDATA,
                         SendMessage((HWND) lParam, CB_GETCURSEL, 0, 0), 0);
                 MIDI_MESSAGE(m_midi, 0xC0, m_instrument, 0);
+                if (m_midifile && saving)
+                    midiTrackAddProgramChange(m_midifile, 1, m_instrument);
+            }
+        case BN_CLICKED:
+            switch (LOWORD(wParam)) {
+                case KEYBOARD_SAVE: {
+                    BOOL checked = !IsDlgButtonChecked(m_hwnd, KEYBOARD_SAVE);
+                    Button_SetCheck(m_saveCheck, checked);
+                    EnableWindow(m_saveLabel, checked);
+                    EnableWindow(m_saveFile, checked);
+                    EnableWindow(m_saveBrowse, checked);
+                    saving = checked == TRUE;
+                    break;
+                }
+                case KEYBOARD_BROWSE: {
+                    OPENFILENAME ofn = { sizeof(OPENFILENAME), 0 };
+                    WCHAR path[MAX_PATH] = { 0 };
+                    char cpath[MAX_PATH * 2] = { 0 };
+                    
+                    ofn.hwndOwner = m_hwnd;
+                    ofn.lpstrFilter = L"Standard MIDI Files (*.mid)\0*.mid\0All Files (*.*)\0*.*\0";
+                    ofn.lpstrFile = path;
+                    ofn.nMaxFile = MAX_PATH;
+                    ofn.lpstrTitle = L"Save MIDI Output...";
+                    ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT;
+                    ofn.lpstrDefExt = L"txt";
+
+                    if (!GetSaveFileName(&ofn))
+                        break;
+                    
+                    WideCharToMultiByte(CP_ACP, 0, path, -1, cpath, MAX_PATH * 2, NULL, NULL);
+                    SetDlgItemText(m_hwnd, KEYBOARD_SAVE_FILE, path);
+                    
+                    if (m_midifile)
+                        midiFileClose(m_midifile);
+                    m_midifile = midiFileCreate(cpath, TRUE);
+                    midiSongAddTempo(m_midifile, 1, 150);
+                    midiFileSetTracksDefaultChannel(m_midifile, 1, MIDI_CHANNEL_1);
+                    midiTrackAddProgramChange(m_midifile, 1, m_instrument);
+                    midiSongAddSimpleTimeSig(m_midifile, 1, 4, MIDI_NOTE_CROCHET);
+                    break;
+                }
             }
         }
         break;
@@ -385,7 +493,7 @@ LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
         
         for (s = m_keychars, i = 0; i < 24 && lstrlen(s); s += lstrlen(s) + 1, ++i)
             piano->SetKeyText(i, s);
-      }
+    }
     case WM_CHAR:
     case WM_DEADCHAR:
     case WM_SYSCHAR:
@@ -430,7 +538,7 @@ LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
 MainWindow *MainWindow::Create(LPCTSTR szTitle)
 {
     MainWindow *self = new MainWindow();
-    RECT client = {0, 0, 622, 286};
+    RECT client = {0, 0, 622, 341};
     AdjustWindowRect(&client, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, FALSE);
     if (self &&
         self->WinCreateWindow(0,
diff --git a/src/midifile.c b/src/midifile.c
new file mode 100644
index 0000000..a40a098
--- /dev/null
+++ b/src/midifile.c
@@ -0,0 +1,1191 @@
+/*
+ * midiFile.c - A general purpose midi file handling library. This code
+ *				can read and write MIDI files in formats 0 and 1.
+ * Version 1.4
+ *
+ *  AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
+ *          Copyright 1998-2010, Steven Goodwin
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _CRT_SECURE_NO_WARNINGS
+#	define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef  __APPLE__
+#include <malloc.h>
+#endif
+#include "midifile.h"
+
+#ifdef _MSC_VER
+#	pragma warning(push)
+#	pragma warning(disable: 4706)
+#endif
+
+/*
+** Internal Data Structures
+*/
+typedef struct 	{
+				BYTE note, chn;
+				BYTE valid, p2;
+				DWORD end_pos;
+				} MIDI_LAST_NOTE;
+
+typedef struct 	{
+				BYTE *ptr;
+				BYTE *pBase;
+				BYTE *pEnd;
+				
+				DWORD pos;
+				DWORD dt;
+				/* For Reading MIDI Files */
+				DWORD sz;						/* size of whole iTrack */
+				/* For Writing MIDI Files */
+				DWORD iBlockSize;				/* max size of track */
+				BYTE iDefaultChannel;			/* use for write only */
+				BYTE last_status;				/* used for running status */
+				
+				MIDI_LAST_NOTE LastNote[MAX_TRACK_POLYPHONY];
+				} MIDI_FILE_TRACK;
+
+typedef struct 	{
+				DWORD	iHeaderSize;
+				/**/
+				WORD	iVersion;		/* 0, 1 or 2 */
+				WORD	iNumTracks;		/* number of tracks... (will be 1 for MIDI type 0) */
+				WORD	PPQN;			/* pulses per quarter note */
+				} MIDI_HEADER;
+
+typedef struct {
+				FILE				*pFile;
+				BOOL				bOpenForWriting;
+				
+				MIDI_HEADER			Header;
+				BYTE *ptr;			/* to whole data block */
+				DWORD file_sz;
+				
+				MIDI_FILE_TRACK		Track[MAX_MIDI_TRACKS];
+				} _MIDI_FILE;
+
+
+/*
+** Internal Functions
+*/
+#define DT_DEF				32			/* assume maximum delta-time + msg is no more than 32 bytes */
+#define SWAP_WORD(w)		(WORD)(((w)>>8)|((w)<<8))
+#define SWAP_DWORD(d)		(DWORD)((d)>>24)|(((d)>>8)&0xff00)|(((d)<<8)&0xff0000)|(((d)<<24))
+
+#define _VAR_CAST				_MIDI_FILE *pMF = (_MIDI_FILE *)_pMF
+#define IsFilePtrValid(pMF)		(pMF)
+#define IsTrackValid(_x)		(_midiValidateTrack(pMF, _x))
+#define IsChannelValid(_x)		((_x)>=1 && (_x)<=16)
+#define IsNoteValid(_x)			((_x)>=0 && (_x)<128)
+#define IsMessageValid(_x)		((_x)>=msgNoteOff && (_x)<=msgMetaEvent)
+
+
+static BOOL _midiValidateTrack(const _MIDI_FILE *pMF, int iTrack)
+{
+	if (!IsFilePtrValid(pMF))	return FALSE;
+	
+	if (pMF->bOpenForWriting)
+		{
+		if (iTrack < 0 || iTrack >= MAX_MIDI_TRACKS)
+			return FALSE;
+		}
+	else	/* open for reading */
+		{
+		if (!pMF->ptr)
+			return FALSE;
+		
+		if (iTrack < 0 || iTrack>=pMF->Header.iNumTracks)
+			return FALSE;
+		}
+	
+	return TRUE;
+}
+
+static BYTE *_midiWriteVarLen(BYTE *ptr, int n)
+{
+register long buffer;
+register long value=n;
+
+	buffer = value & 0x7f;
+	while ((value >>= 7) > 0)
+		{
+		buffer <<= 8;
+		buffer |= 0x80;
+		buffer += (value & 0x7f);
+		}
+
+	while (TRUE)
+		{
+		*ptr++ = (BYTE)buffer;
+		if (buffer & 0x80)
+			buffer >>= 8;
+		else
+			break;
+		}
+	
+	return(ptr);
+}
+
+/* Return a ptr to valid block of memory to store a message
+** of up to sz_reqd bytes 
+*/
+static BYTE *_midiGetPtr(_MIDI_FILE *pMF, int iTrack, int sz_reqd)
+{
+const DWORD mem_sz_inc = 8092;	/* arbitary */
+BYTE *ptr;
+int curr_offset;
+MIDI_FILE_TRACK *pTrack = &pMF->Track[iTrack];
+
+	ptr = pTrack->ptr;
+	if (ptr == NULL || ptr+sz_reqd > pTrack->pEnd)		/* need more RAM! */
+		{
+		curr_offset = ptr-pTrack->pBase;
+		if ((ptr = (BYTE *)realloc(pTrack->pBase, mem_sz_inc+pTrack->iBlockSize)))
+			{
+			pTrack->pBase = ptr;
+			pTrack->iBlockSize += mem_sz_inc;
+			pTrack->pEnd = ptr+pTrack->iBlockSize;
+			/* Move new ptr to continue data entry: */
+			pTrack->ptr = ptr+curr_offset;
+			ptr += curr_offset;
+			}
+		else
+			{
+			/* NO MEMORY LEFT */
+			return NULL;
+			}
+		}
+
+	return ptr;
+}
+
+
+static int _midiGetLength(int ppqn, int iNoteLen, BOOL bOverride)
+{
+int length = ppqn;
+	
+	if (bOverride)
+		{
+		length = iNoteLen;
+		}
+	else
+		{
+		switch(iNoteLen)
+			{
+			case	MIDI_NOTE_DOTTED_MINIM:
+						length *= 3;
+						break;
+
+			case	MIDI_NOTE_DOTTED_CROCHET:
+						length *= 3;
+						length /= 2;
+						break;
+
+			case	MIDI_NOTE_DOTTED_QUAVER:
+						length *= 3;
+						length /= 4;
+						break;
+
+			case	MIDI_NOTE_DOTTED_SEMIQUAVER:
+						length *= 3;
+						length /= 8;
+						break;
+
+			case	MIDI_NOTE_DOTTED_SEMIDEMIQUAVER:
+						length *= 3;
+						length /= 16;
+						break;
+
+			case	MIDI_NOTE_BREVE:
+						length *= 4;
+						break;
+
+			case	MIDI_NOTE_MINIM:
+						length *= 2;
+						break;
+
+			case	MIDI_NOTE_QUAVER:
+						length /= 2;
+						break;
+
+			case	MIDI_NOTE_SEMIQUAVER:
+						length /= 4;
+						break;
+
+			case	MIDI_NOTE_SEMIDEMIQUAVER:
+						length /= 8;
+						break;
+			
+			case	MIDI_NOTE_TRIPLE_CROCHET:
+						length *= 2;
+						length /= 3;
+						break;			
+			}
+		}
+	
+	return length;
+}
+
+/*
+** midiFile* Functions
+*/
+MIDI_FILE  *midiFileCreate(const char *pFilename, BOOL bOverwriteIfExists)
+{
+_MIDI_FILE *pMF = (_MIDI_FILE *)malloc(sizeof(_MIDI_FILE));
+int i;
+
+	if (!pMF)							return NULL;
+	
+	if (!bOverwriteIfExists)
+		{
+		if ((pMF->pFile = fopen(pFilename, "r")))
+			{
+			fclose(pMF->pFile);
+			free(pMF);
+			return NULL;
+			}
+		}
+	
+	if ((pMF->pFile = fopen(pFilename, "wb+")))
+		{/*empty*/}
+	else
+		{
+		free((void *)pMF);
+		return NULL;
+		}
+	
+	pMF->bOpenForWriting = TRUE;
+	pMF->Header.PPQN = MIDI_PPQN_DEFAULT;
+	pMF->Header.iVersion = MIDI_VERSION_DEFAULT;
+	
+	for(i=0;i<MAX_MIDI_TRACKS;++i)
+		{
+		pMF->Track[i].pos = 0;
+		pMF->Track[i].ptr = NULL;
+		pMF->Track[i].pBase = NULL;
+		pMF->Track[i].pEnd = NULL;
+		pMF->Track[i].iBlockSize = 0;
+		pMF->Track[i].dt = 0;
+		pMF->Track[i].iDefaultChannel = (BYTE)(i & 0xf);
+		
+		memset(pMF->Track[i].LastNote, '\0', sizeof(pMF->Track[i].LastNote));
+		}
+	
+	return (MIDI_FILE *)pMF;
+}
+
+int		midiFileSetTracksDefaultChannel(MIDI_FILE *_pMF, int iTrack, int iChannel)
+{
+int prev;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return 0;
+	if (!IsTrackValid(iTrack))				return 0;
+	if (!IsChannelValid(iChannel))			return 0;
+
+	/* For programmer each, iChannel is between 1 & 16 - but MIDI uses
+	** 0-15. Thus, the fudge factor of 1 :)
+	*/
+	prev = pMF->Track[iTrack].iDefaultChannel+1;
+	pMF->Track[iTrack].iDefaultChannel = (BYTE)(iChannel-1);
+	return prev;
+}
+
+int		midiFileGetTracksDefaultChannel(const MIDI_FILE *_pMF, int iTrack)
+{
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return 0;
+	if (!IsTrackValid(iTrack))				return 0;
+
+	return pMF->Track[iTrack].iDefaultChannel+1;
+}
+
+int		midiFileSetPPQN(MIDI_FILE *_pMF, int PPQN)
+{
+int prev;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return MIDI_PPQN_DEFAULT;
+	prev = pMF->Header.PPQN;
+	pMF->Header.PPQN = (WORD)PPQN;
+	return prev;
+}
+
+int		midiFileGetPPQN(const MIDI_FILE *_pMF)
+{
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return MIDI_PPQN_DEFAULT;
+	return (int)pMF->Header.PPQN;
+}
+
+int		midiFileSetVersion(MIDI_FILE *_pMF, int iVersion)
+{
+int prev;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return MIDI_VERSION_DEFAULT;
+	if (iVersion<0 || iVersion>2)			return MIDI_VERSION_DEFAULT;
+	prev = pMF->Header.iVersion;
+	pMF->Header.iVersion = (WORD)iVersion;
+	return prev;
+}
+
+int			midiFileGetVersion(const MIDI_FILE *_pMF)
+{
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return MIDI_VERSION_DEFAULT;
+	return pMF->Header.iVersion;
+}
+
+MIDI_FILE  *midiFileOpen(const char *pFilename)
+{
+FILE *fp = fopen(pFilename, "rb");
+_MIDI_FILE *pMF = NULL;
+BYTE *ptr;
+BOOL bValidFile=FALSE;
+long size;
+
+	if (fp)
+		{
+		if ((pMF = (_MIDI_FILE *)malloc(sizeof(_MIDI_FILE))))
+			{
+			fseek(fp, 0L, SEEK_END);
+			size = ftell(fp);
+			if ((pMF->ptr = (BYTE *)malloc(size)))
+				{
+				fseek(fp, 0L, SEEK_SET);
+				fread(pMF->ptr, sizeof(BYTE), size, fp);
+				/* Is this a valid MIDI file ? */
+				ptr = pMF->ptr;
+				if (*(ptr+0) == 'M' && *(ptr+1) == 'T' && 
+					*(ptr+2) == 'h' && *(ptr+3) == 'd')
+					{
+					DWORD dwData;
+					WORD wData;
+					int i;
+
+					dwData = *((DWORD *)(ptr+4));
+					pMF->Header.iHeaderSize = SWAP_DWORD(dwData);
+					
+					wData = *((WORD *)(ptr+8));
+					pMF->Header.iVersion = (WORD)SWAP_WORD(wData);
+					
+					wData = *((WORD *)(ptr+10));
+					pMF->Header.iNumTracks = (WORD)SWAP_WORD(wData);
+					
+					wData = *((WORD *)(ptr+12));
+					pMF->Header.PPQN = (WORD)SWAP_WORD(wData);
+					
+					ptr += pMF->Header.iHeaderSize+8;
+					/*
+					**	 Get all tracks
+					*/
+					for(i=0;i<MAX_MIDI_TRACKS;++i)
+						{
+						pMF->Track[i].pos = 0;
+						pMF->Track[i].last_status = 0;
+						}
+					
+					for(i=0;i<pMF->Header.iNumTracks;++i)
+						{
+						pMF->Track[i].pBase = ptr;
+						pMF->Track[i].ptr = ptr+8;
+						dwData = *((DWORD *)(ptr+4));
+						pMF->Track[i].sz = SWAP_DWORD(dwData);
+						pMF->Track[i].pEnd = ptr+pMF->Track[i].sz+8;
+						ptr += pMF->Track[i].sz+8;
+						}
+						   
+					pMF->bOpenForWriting = FALSE;
+					pMF->pFile = NULL;
+					bValidFile = TRUE;
+					}
+				}
+			}
+
+		fclose(fp);
+		}
+	
+	if (!bValidFile)
+		{
+		if (pMF)		free((void *)pMF);
+		return NULL;
+		}
+	
+	return (MIDI_FILE *)pMF;
+}
+
+typedef struct {
+		int	iIdx;
+		int	iEndPos;
+		} MIDI_END_POINT;
+
+static int qs_cmp_pEndPoints(const void *e1, const void *e2)
+{
+MIDI_END_POINT *p1 = (MIDI_END_POINT *)e1;
+MIDI_END_POINT *p2 = (MIDI_END_POINT *)e2;
+
+	return p1->iEndPos-p2->iEndPos;
+}
+
+BOOL	midiFileFlushTrack(MIDI_FILE *_pMF, int iTrack, BOOL bFlushToEnd, DWORD dwEndTimePos)
+{
+int sz;
+BYTE *ptr;
+MIDI_END_POINT *pEndPoints;
+int num, i, mx_pts;
+BOOL bNoChanges = TRUE;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!_midiValidateTrack(pMF, iTrack))	return FALSE;
+	sz = sizeof(pMF->Track[0].LastNote)/sizeof(pMF->Track[0].LastNote[0]);
+
+	/*
+	** Flush all 
+	*/
+	pEndPoints = (MIDI_END_POINT *)malloc(sz * sizeof(MIDI_END_POINT));
+	mx_pts = 0;
+	for(i=0;i<sz;++i)
+		if (pMF->Track[iTrack].LastNote[i].valid)
+			{
+			pEndPoints[mx_pts].iIdx = i;
+			pEndPoints[mx_pts].iEndPos = pMF->Track[iTrack].LastNote[i].end_pos;
+			mx_pts++;
+			}
+	
+	if (bFlushToEnd)
+		{
+		if (mx_pts)
+			dwEndTimePos = pEndPoints[mx_pts-1].iEndPos;
+		else
+			dwEndTimePos = pMF->Track[iTrack].pos;
+		}
+	
+	if (mx_pts)
+		{
+		/* Sort, smallest first, and add the note off msgs */
+		qsort(pEndPoints, mx_pts, sizeof(MIDI_END_POINT), qs_cmp_pEndPoints);
+		
+		i = 0;
+		while ((dwEndTimePos >= (DWORD)pEndPoints[i].iEndPos || bFlushToEnd) && i<mx_pts)
+			{
+			ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
+			if (!ptr)
+				return FALSE;
+			
+			num = pEndPoints[i].iIdx;		/* get 'LastNote' index */
+			
+			ptr = _midiWriteVarLen(ptr, pMF->Track[iTrack].LastNote[num].end_pos - pMF->Track[iTrack].pos);
+			/* msgNoteOn  msgNoteOff */
+			*ptr++ = (BYTE)(msgNoteOff | pMF->Track[iTrack].LastNote[num].chn);
+			*ptr++ = pMF->Track[iTrack].LastNote[num].note;
+			*ptr++ = 0;
+			
+			pMF->Track[iTrack].LastNote[num].valid = FALSE;
+			pMF->Track[iTrack].pos = pMF->Track[iTrack].LastNote[num].end_pos;
+			
+			pMF->Track[iTrack].ptr = ptr;
+			
+			++i;
+			bNoChanges = FALSE;
+			}
+		}
+	
+	free((void *)pEndPoints);
+	/*
+	** Re-calc current position
+	*/
+	pMF->Track[iTrack].dt = dwEndTimePos - pMF->Track[iTrack].pos;
+	
+	return TRUE;
+}
+
+BOOL	midiFileSyncTracks(MIDI_FILE *_pMF, int iTrack1, int iTrack2)
+{
+int p1, p2;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))			return FALSE;
+	if (!IsTrackValid(iTrack1))			return FALSE;
+	if (!IsTrackValid(iTrack2))			return FALSE;
+
+	p1 = pMF->Track[iTrack1].pos + pMF->Track[iTrack1].dt;
+	p2 = pMF->Track[iTrack2].pos + pMF->Track[iTrack2].dt;
+	
+	if (p1 < p2)		midiTrackIncTime(pMF, iTrack1, p2-p1, TRUE);
+	else if (p2 < p1)	midiTrackIncTime(pMF, iTrack2, p1-p2, TRUE);
+	
+	return TRUE;
+}
+
+
+BOOL	midiFileClose(MIDI_FILE *_pMF)
+{
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))			return FALSE;
+	
+	if (pMF->bOpenForWriting)	
+		{
+		WORD iNumTracks = 0;
+		WORD wTest = 256;
+		BOOL bSwap = FALSE;
+		int i;
+
+		/* Intel processor style-endians need byte swap :( */
+		if (*((BYTE *)&wTest) == 0)
+			bSwap = TRUE;
+
+		/* Flush our buffers  */
+		for(i=0;i<MAX_MIDI_TRACKS;++i)
+			{
+			if (pMF->Track[i].ptr)
+				{
+				midiSongAddEndSequence(pMF, i);
+				midiFileFlushTrack(pMF, i, TRUE, 0);
+				iNumTracks++;
+				}
+			}
+		/* 
+		** Header 
+		*/
+		{
+		const BYTE mthd[4] = {'M', 'T', 'h', 'd'};
+		DWORD dwData;
+		WORD wData;
+		WORD version, PPQN;
+
+			fwrite(mthd, sizeof(BYTE), 4, pMF->pFile);
+			dwData = 6;
+			if (bSwap)	dwData = SWAP_DWORD(dwData);
+			fwrite(&dwData, sizeof(DWORD), 1, pMF->pFile);
+
+			wData = (WORD)(iNumTracks==1?pMF->Header.iVersion:1);
+			if (bSwap)	version = SWAP_WORD(wData); else version = (WORD)wData;
+			if (bSwap)	iNumTracks = SWAP_WORD(iNumTracks);
+			wData = pMF->Header.PPQN;
+			if (bSwap)	PPQN = SWAP_WORD(wData); else PPQN = wData;
+			fwrite(&version, sizeof(WORD), 1, pMF->pFile);
+			fwrite(&iNumTracks, sizeof(WORD), 1, pMF->pFile);
+			fwrite(&PPQN, sizeof(WORD), 1, pMF->pFile);
+		}
+		/*
+		** Track data
+		*/
+		for(i=0;i<MAX_MIDI_TRACKS;++i)
+			if (pMF->Track[i].ptr)
+				{
+				const BYTE mtrk[4] = {'M', 'T', 'r', 'k'};
+				DWORD sz, dwData;
+
+				/* Write track header */
+				fwrite(&mtrk, sizeof(BYTE), 4, pMF->pFile);
+
+				/* Write data size */
+				sz = dwData = (int)(pMF->Track[i].ptr - pMF->Track[i].pBase);
+				if (bSwap)	sz = SWAP_DWORD(sz);
+				fwrite(&sz, sizeof(DWORD), 1, pMF->pFile);
+
+				/* Write data */
+				fwrite(pMF->Track[i].pBase, sizeof(BYTE), dwData, pMF->pFile);
+				
+				/* Free memory */
+				free((void *)pMF->Track[i].pBase);
+				}
+
+		}
+
+	if (pMF->pFile)
+		return fclose(pMF->pFile)?FALSE:TRUE;
+	free((void *)pMF);
+	return TRUE;
+}
+
+
+/*
+** midiSong* Functions
+*/
+BOOL	midiSongAddSMPTEOffset(MIDI_FILE *_pMF, int iTrack, int iHours, int iMins, int iSecs, int iFrames, int iFFrames)
+{
+static BYTE tmp[] = {msgMetaEvent, metaSMPTEOffset, 0x05, 0,0,0,0,0};
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	if (iMins<0 || iMins>59)		iMins=0;
+	if (iSecs<0 || iSecs>59)		iSecs=0;
+	if (iFrames<0 || iFrames>24)	iFrames=0;
+
+	tmp[3] = (BYTE)iHours;
+	tmp[4] = (BYTE)iMins;
+	tmp[5] = (BYTE)iSecs;
+	tmp[6] = (BYTE)iFrames;
+	tmp[7] = (BYTE)iFFrames;
+	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
+}
+
+
+BOOL	midiSongAddSimpleTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom)
+{
+	return midiSongAddTimeSig(_pMF, iTrack, iNom, iDenom, 24, 8);
+}
+
+BOOL	midiSongAddTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom, int iClockInMetroTick, int iNotated32nds)
+{
+static BYTE tmp[] = {msgMetaEvent, metaTimeSig, 0x04, 0,0,0,0};
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	tmp[3] = (BYTE)iNom;
+	tmp[4] = (BYTE)(MIDI_NOTE_MINIM/iDenom);
+	tmp[5] = (BYTE)iClockInMetroTick;
+	tmp[6] = (BYTE)iNotated32nds;
+	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
+}
+
+BOOL	midiSongAddKeySig(MIDI_FILE *_pMF, int iTrack, tMIDI_KEYSIG iKey)
+{
+static BYTE tmp[] = {msgMetaEvent, metaKeySig, 0x02, 0, 0};
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	tmp[3] = (BYTE)((iKey&keyMaskKey)*((iKey&keyMaskNeg)?-1:1));
+	tmp[4] = (BYTE)((iKey&keyMaskMin)?1:0);
+	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
+}
+
+BOOL	midiSongAddTempo(MIDI_FILE *_pMF, int iTrack, int iTempo)
+{
+static BYTE tmp[] = {msgMetaEvent, metaSetTempo, 0x03, 0,0,0};
+int us;	/* micro-seconds per qn */
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	us = 60000000L/iTempo;
+	tmp[3] = (BYTE)((us>>16)&0xff);
+	tmp[4] = (BYTE)((us>>8)&0xff);
+	tmp[5] = (BYTE)((us>>0)&0xff);
+	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
+}
+
+BOOL	midiSongAddMIDIPort(MIDI_FILE *_pMF, int iTrack, int iPort)
+{
+static BYTE tmp[] = {msgMetaEvent, metaMIDIPort, 1, 0};
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+	tmp[3] = (BYTE)iPort;
+	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
+}
+
+BOOL	midiSongAddEndSequence(MIDI_FILE *_pMF, int iTrack)
+{
+static BYTE tmp[] = {msgMetaEvent, metaEndSequence, 0};
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
+}
+
+
+/*
+** midiTrack* Functions
+*/
+BOOL	midiTrackAddRaw(MIDI_FILE *_pMF, int iTrack, int data_sz, const BYTE *pData, BOOL bMovePtr, int dt)
+{
+MIDI_FILE_TRACK *pTrk;
+BYTE *ptr;
+int dtime;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))			return FALSE;
+	if (!IsTrackValid(iTrack))			return FALSE;
+	
+	pTrk = &pMF->Track[iTrack];
+	ptr = _midiGetPtr(pMF, iTrack, data_sz+DT_DEF);
+	if (!ptr)
+		return FALSE;
+	
+	dtime = pTrk->dt;
+	if (bMovePtr)
+		dtime += dt;
+	
+	ptr = _midiWriteVarLen(ptr, dtime);
+	memcpy(ptr, pData, data_sz);
+	
+	pTrk->pos += dtime;
+	pTrk->dt = 0;
+	pTrk->ptr = ptr+data_sz;
+	
+	return TRUE;
+}
+
+
+BOOL	midiTrackIncTime(MIDI_FILE *_pMF, int iTrack, int iDeltaTime, BOOL bOverridePPQN)
+{
+DWORD will_end_at;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+	
+	will_end_at = _midiGetLength(pMF->Header.PPQN, iDeltaTime, bOverridePPQN);
+	will_end_at += pMF->Track[iTrack].pos + pMF->Track[iTrack].dt;
+	
+	midiFileFlushTrack(pMF, iTrack, FALSE, will_end_at);
+	
+	return TRUE;
+}
+
+BOOL	midiTrackAddText(MIDI_FILE *_pMF, int iTrack, tMIDI_TEXT iType, const char *pTxt)
+{
+BYTE *ptr;
+int sz;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	sz = strlen(pTxt);
+	if ((ptr = _midiGetPtr(pMF, iTrack, sz+DT_DEF)))
+		{
+		*ptr++ = 0;		/* delta-time=0 */
+		*ptr++ = msgMetaEvent;
+		*ptr++ = (BYTE)iType;
+		ptr = _midiWriteVarLen((BYTE *)ptr, sz);
+		strcpy((char *)ptr, pTxt);
+		pMF->Track[iTrack].ptr = ptr+sz;
+		return TRUE;
+		}
+	else		 
+		{
+		return FALSE;
+		}
+}
+
+BOOL	midiTrackSetKeyPressure(MIDI_FILE *pMF, int iTrack, int iNote, int iAftertouch)
+{
+	return midiTrackAddMsg(pMF, iTrack, msgNoteKeyPressure, iNote, iAftertouch);
+}
+
+BOOL	midiTrackAddControlChange(MIDI_FILE *pMF, int iTrack, tMIDI_CC iCCType, int iParam)
+{
+	return midiTrackAddMsg(pMF, iTrack, msgControlChange, iCCType, iParam);
+}
+
+BOOL	midiTrackAddProgramChange(MIDI_FILE *pMF, int iTrack, int iInstrPatch)
+{
+	return midiTrackAddMsg(pMF, iTrack, msgSetProgram, iInstrPatch, 0);
+}
+
+BOOL	midiTrackChangeKeyPressure(MIDI_FILE *pMF, int iTrack, int iDeltaPressure)
+{
+	return midiTrackAddMsg(pMF, iTrack, msgChangePressure, iDeltaPressure&0x7f, 0);
+}
+
+BOOL	midiTrackSetPitchWheel(MIDI_FILE *pMF, int iTrack, int iWheelPos)
+{
+WORD wheel = (WORD)iWheelPos;
+
+	/* bitshift 7 instead of eight because we're dealing with 7 bit numbers */
+	wheel += MIDI_WHEEL_CENTRE;
+	return midiTrackAddMsg(pMF, iTrack, msgSetPitchWheel, wheel&0x7f, (wheel>>7)&0x7f);
+}
+
+BOOL	midiTrackAddMsg(MIDI_FILE *_pMF, int iTrack, tMIDI_MSG iMsg, int iParam1, int iParam2)
+{
+BYTE *ptr;
+BYTE data[3];
+int sz;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+	if (!IsMessageValid(iMsg))				return FALSE;
+
+	ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
+	if (!ptr)
+		return FALSE;
+	
+	data[0] = (BYTE)(iMsg | pMF->Track[iTrack].iDefaultChannel);
+	data[1] = (BYTE)(iParam1 & 0x7f); 
+	data[2] = (BYTE)(iParam2 & 0x7f); 
+	/*
+	** Is this msg a single, or double BYTE, prm?
+	*/
+ 	switch(iMsg)
+		{
+		case	msgSetProgram:			/* only one byte required for these msgs */
+		case	msgChangePressure:
+									sz = 2;
+									break;
+
+		default:						/* double byte messages */
+									sz = 3;
+									break;
+		}
+	
+	return midiTrackAddRaw(pMF, iTrack, sz, data, FALSE, 0);
+
+}
+
+BOOL	midiTrackAddNote(MIDI_FILE *_pMF, int iTrack, int iNote, int iLength, int iVol, BOOL bAutoInc, BOOL bOverrideLength)
+{
+MIDI_FILE_TRACK *pTrk;
+BYTE *ptr;
+BOOL bSuccess = FALSE;
+int i, chn;
+
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+	if (!IsNoteValid(iNote))				return FALSE;
+	
+	pTrk = &pMF->Track[iTrack];
+	ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
+	if (!ptr)
+		return FALSE;
+	
+	chn = pTrk->iDefaultChannel;
+	iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverrideLength);
+	
+	for(i=0;i<sizeof(pTrk->LastNote)/sizeof(pTrk->LastNote[0]);++i)
+		if (pTrk->LastNote[i].valid == FALSE)
+			{
+			pTrk->LastNote[i].note = (BYTE)iNote;
+			pTrk->LastNote[i].chn = (BYTE)chn;
+			pTrk->LastNote[i].end_pos = pTrk->pos+pTrk->dt+iLength;
+			pTrk->LastNote[i].valid = TRUE;
+			bSuccess = TRUE;
+			
+			ptr = _midiWriteVarLen(ptr, pTrk->dt);		/* delta-time */
+			*ptr++ = (BYTE)(msgNoteOn | chn);
+			*ptr++ = (BYTE)iNote;
+			*ptr++ = (BYTE)iVol;
+			break;
+			}
+	
+	if (!bSuccess)
+		return FALSE;
+	
+	pTrk->ptr = ptr;
+	
+	pTrk->pos += pTrk->dt;
+	pTrk->dt = 0;
+	
+	if (bAutoInc)
+		return midiTrackIncTime(pMF, iTrack, iLength, bOverrideLength);
+	
+	return TRUE;
+}
+
+BOOL	midiTrackAddRest(MIDI_FILE *_pMF, int iTrack, int iLength, BOOL bOverridePPQN)
+{
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverridePPQN);
+	return midiTrackIncTime(pMF, iTrack, iLength, bOverridePPQN);
+}
+
+int		midiTrackGetEndPos(MIDI_FILE *_pMF, int iTrack)
+{
+	_VAR_CAST;
+	if (!IsFilePtrValid(pMF))				return FALSE;
+	if (!IsTrackValid(iTrack))				return FALSE;
+
+	return pMF->Track[iTrack].pos;
+}
+
+/*
+** midiRead* Functions
+*/
+static BYTE *_midiReadVarLen(BYTE *ptr, DWORD *num)
+{
+register DWORD value;
+register BYTE c;
+
+    if ((value = *ptr++) & 0x80)
+		{
+		value &= 0x7f;
+		do
+			{
+			value = (value << 7) + ((c = *ptr++) & 0x7f);
+			} while (c & 0x80);
+		}
+	*num = value;
+	return(ptr);
+}
+
+
+static BOOL _midiReadTrackCopyData(MIDI_MSG *pMsg, BYTE *ptr, DWORD sz, BOOL bCopyPtrData)
+{
+	if (sz > pMsg->data_sz)
+		{
+		pMsg->data = (BYTE *)realloc(pMsg->data, sz);
+		pMsg->data_sz = sz;
+		}
+	
+	if (!pMsg->data)
+		return FALSE;
+	
+	if (bCopyPtrData && ptr)
+		memcpy(pMsg->data, ptr, sz);
+	
+	return TRUE;
+}
+
+int midiReadGetNumTracks(const MIDI_FILE *_pMF)
+{
+	_VAR_CAST;
+	return pMF->Header.iNumTracks;
+}
+
+BOOL midiReadGetNextMessage(const MIDI_FILE *_pMF, int iTrack, MIDI_MSG *pMsg)
+{
+MIDI_FILE_TRACK *pTrack;
+BYTE *bptr, *pMsgDataPtr;
+int sz;
+
+	_VAR_CAST;
+	if (!IsTrackValid(iTrack))			return FALSE;
+	
+	pTrack = &pMF->Track[iTrack];
+	/* FIXME: Check if there is data on this track first!!!	*/
+	if (pTrack->ptr >= pTrack->pEnd)
+		return FALSE;
+	
+	pTrack->ptr = _midiReadVarLen(pTrack->ptr, &pMsg->dt);
+	pTrack->pos += pMsg->dt;
+
+	pMsg->dwAbsPos = pTrack->pos;
+
+	if (*pTrack->ptr & 0x80)	/* Is this is sys message */
+		{
+		pMsg->iType = (tMIDI_MSG)((*pTrack->ptr) & 0xf0);
+		pMsgDataPtr = pTrack->ptr+1;
+
+		/* SysEx & Meta events don't carry channel info, but something
+		** important in their lower bits that we must keep */
+		if (pMsg->iType == 0xf0)
+			pMsg->iType = (tMIDI_MSG)(*pTrack->ptr);
+		}
+	else						/* just data - so use the last msg type */
+		{
+		pMsg->iType = pMsg->iLastMsgType;
+		pMsgDataPtr = pTrack->ptr;
+		}
+	
+	pMsg->iLastMsgType = (tMIDI_MSG)pMsg->iType;
+	pMsg->iLastMsgChnl = (BYTE)((*pTrack->ptr) & 0x0f)+1;
+
+	switch(pMsg->iType)
+		{
+		case	msgNoteOn:
+							pMsg->MsgData.NoteOn.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.NoteOn.iNote = *(pMsgDataPtr);
+							pMsg->MsgData.NoteOn.iVolume = *(pMsgDataPtr+1);
+							pMsg->iMsgSize = 3;
+							break;
+
+		case	msgNoteOff:
+							pMsg->MsgData.NoteOff.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.NoteOff.iNote = *(pMsgDataPtr);
+							pMsg->iMsgSize = 3;
+							break;
+
+		case	msgNoteKeyPressure:
+							pMsg->MsgData.NoteKeyPressure.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.NoteKeyPressure.iNote = *(pMsgDataPtr);
+							pMsg->MsgData.NoteKeyPressure.iPressure = *(pMsgDataPtr+1);
+							pMsg->iMsgSize = 3;
+							break;
+
+		case	msgSetParameter:
+							pMsg->MsgData.NoteParameter.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.NoteParameter.iControl = (tMIDI_CC)*(pMsgDataPtr);
+							pMsg->MsgData.NoteParameter.iParam = *(pMsgDataPtr+1);
+							pMsg->iMsgSize = 3;
+							break;
+
+		case	msgSetProgram:
+							pMsg->MsgData.ChangeProgram.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.ChangeProgram.iProgram = *(pMsgDataPtr);
+							pMsg->iMsgSize = 2;
+							break;
+
+		case	msgChangePressure:
+							pMsg->MsgData.ChangePressure.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.ChangePressure.iPressure = *(pMsgDataPtr);
+							pMsg->iMsgSize = 2;
+							break;
+
+		case	msgSetPitchWheel:
+							pMsg->MsgData.PitchWheel.iChannel = pMsg->iLastMsgChnl;
+							pMsg->MsgData.PitchWheel.iPitch = *(pMsgDataPtr) | (*(pMsgDataPtr+1) << 7);
+							pMsg->MsgData.PitchWheel.iPitch -= MIDI_WHEEL_CENTRE;
+							pMsg->iMsgSize = 3;
+							break;
+
+		case	msgMetaEvent:
+							/* We can use 'pTrack->ptr' from now on, since meta events
+							** always have bit 7 set */
+							bptr = pTrack->ptr;
+							pMsg->MsgData.MetaEvent.iType = (tMIDI_META)*(pTrack->ptr+1);
+							pTrack->ptr = _midiReadVarLen(pTrack->ptr+2, &pMsg->iMsgSize);
+							sz = (pTrack->ptr-bptr)+pMsg->iMsgSize;
+							
+							if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
+								return FALSE;
+
+							/* Now copy the data...*/
+							memcpy(pMsg->data, bptr, sz);
+
+							/* Now place it in a neat structure */
+							switch(pMsg->MsgData.MetaEvent.iType)
+								{
+								case	metaMIDIPort:
+										pMsg->MsgData.MetaEvent.Data.iMIDIPort = *(pTrack->ptr+0);
+										break;
+								case	metaSequenceNumber:
+										pMsg->MsgData.MetaEvent.Data.iSequenceNumber = *(pTrack->ptr+0);
+										break;
+								case	metaTextEvent:
+								case	metaCopyright:
+								case	metaTrackName:
+								case	metaInstrument:
+								case	metaLyric:
+								case	metaMarker:
+								case	metaCuePoint:
+										/* TODO - Add NULL terminator ??? */
+										pMsg->MsgData.MetaEvent.Data.Text.pData = pTrack->ptr;
+										break;
+								case	metaEndSequence:
+										/* NO DATA */
+										break;
+								case	metaSetTempo:
+										{
+										DWORD us = ((*(pTrack->ptr+0))<<16)|((*(pTrack->ptr+1))<<8)|(*(pTrack->ptr+2));
+										pMsg->MsgData.MetaEvent.Data.Tempo.iBPM = 60000000L/us;
+										}
+										break;
+								case	metaSMPTEOffset:
+										pMsg->MsgData.MetaEvent.Data.SMPTE.iHours = *(pTrack->ptr+0);
+										pMsg->MsgData.MetaEvent.Data.SMPTE.iMins= *(pTrack->ptr+1);
+										pMsg->MsgData.MetaEvent.Data.SMPTE.iSecs = *(pTrack->ptr+2);
+										pMsg->MsgData.MetaEvent.Data.SMPTE.iFrames = *(pTrack->ptr+3);
+										pMsg->MsgData.MetaEvent.Data.SMPTE.iFF = *(pTrack->ptr+4);
+										break;
+								case	metaTimeSig:
+										pMsg->MsgData.MetaEvent.Data.TimeSig.iNom = *(pTrack->ptr+0);
+										pMsg->MsgData.MetaEvent.Data.TimeSig.iDenom = *(pTrack->ptr+1) * MIDI_NOTE_MINIM;
+										/* TODO: Variations without 24 & 8 */
+										break;
+								case	metaKeySig:
+										if (*pTrack->ptr & 0x80)
+											{
+											/* Do some trendy sign extending in reverse :) */
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey = ((256-*pTrack->ptr)&keyMaskKey);
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskNeg;
+											}
+										else
+											{
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey = (tMIDI_KEYSIG)(*pTrack->ptr&keyMaskKey);
+											}
+										if (*(pTrack->ptr+1)) 
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskMin;
+										break;
+								case	metaSequencerSpecific:
+										pMsg->MsgData.MetaEvent.Data.Sequencer.iSize = pMsg->iMsgSize;
+										pMsg->MsgData.MetaEvent.Data.Sequencer.pData = pTrack->ptr;
+										break;
+								}
+
+							pTrack->ptr += pMsg->iMsgSize;
+							pMsg->iMsgSize = sz;
+							break;
+
+		case	msgSysEx1:
+		case	msgSysEx2:
+							bptr = pTrack->ptr;
+							pTrack->ptr = _midiReadVarLen(pTrack->ptr+1, &pMsg->iMsgSize);
+							sz = (pTrack->ptr-bptr)+pMsg->iMsgSize;
+							
+							if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
+								return FALSE;
+
+							/* Now copy the data... */
+							memcpy(pMsg->data, bptr, sz);
+							pTrack->ptr += pMsg->iMsgSize;
+							pMsg->iMsgSize = sz;
+							pMsg->MsgData.SysEx.pData = pMsg->data;
+							pMsg->MsgData.SysEx.iSize = sz;
+							break;
+		}
+	/*
+	** Standard MIDI messages use a common copy routine
+	*/
+	pMsg->bImpliedMsg = FALSE;
+	if ((pMsg->iType&0xf0) != 0xf0)
+		{
+		if (*pTrack->ptr & 0x80) 
+			{
+			}
+		else 
+			{
+			pMsg->bImpliedMsg = TRUE;
+			pMsg->iImpliedMsg = pMsg->iLastMsgType;
+			pMsg->iMsgSize--;
+			}
+		_midiReadTrackCopyData(pMsg, pTrack->ptr, pMsg->iMsgSize, TRUE);
+		pTrack->ptr+=pMsg->iMsgSize;
+		}
+	return TRUE;
+}
+
+void midiReadInitMessage(MIDI_MSG *pMsg)
+{
+	pMsg->data = NULL;
+	pMsg->data_sz = 0;
+	pMsg->bImpliedMsg = FALSE;
+}
+
+void midiReadFreeMessage(MIDI_MSG *pMsg)
+{
+	if (pMsg->data)	free((void *)pMsg->data);
+	pMsg->data = NULL;
+}
+
+#ifdef _MSC_VER
+#	pragma warning(pop)
+#endif