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 #include +#include #include #include @@ -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 #include +#include #include #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 +#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 +#include +#include +#ifndef __APPLE__ +#include +#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;iTrack[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;iTrack[i].pos = 0; + pMF->Track[i].last_status = 0; + } + + for(i=0;iHeader.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;iTrack[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) && iTrack[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;iTrack[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;iTrack[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;iLastNote)/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