From 4397ef3323420d392324530d4ebf098dde8632e2 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Fri, 24 Sep 2010 02:46:48 +0000 Subject: [PATCH] - Added HMP file support. SVN r2849 (trunk) --- src/sound/i_music.cpp | 7 + src/sound/i_musicinterns.h | 6 + src/sound/music_hmi_midiout.cpp | 259 ++++++++++++++++++++++++++------ 3 files changed, 230 insertions(+), 42 deletions(-) diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 172553df5d..76e33ff839 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -463,6 +463,13 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int { miditype = MIDI_HMI; } + // Check for HMP format + else + if (id[0] == MAKE_ID('H','M','I','M') && + id[1] == MAKE_ID('I','D','I','P')) + { + miditype = MIDI_HMI; + } // Check for MIDI format else if (id[0] == MAKE_ID('M','T','h','d')) { diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index ccf0145559..32acaec488 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -500,6 +500,8 @@ public: protected: HMISong(const HMISong *original, const char *filename, EMIDIDevice type); // file dump constructor + void SetupForHMI(int len); + void SetupForHMP(int len); void CheckCaps(int tech); void DoInitialSetup(); void DoRestart(); @@ -514,6 +516,9 @@ protected: TrackInfo *FindNextDue (); void SetTempo(int new_tempo); + static DWORD ReadVarLenHMI(TrackInfo *); + static DWORD ReadVarLenHMP(TrackInfo *); + struct AutoNoteOff { DWORD Delay; @@ -541,6 +546,7 @@ protected: TrackInfo *Tracks; TrackInfo *TrackDue; TrackInfo *FakeTrack; + DWORD (*ReadVarLen)(TrackInfo *); NoteOffQueue NoteOffs; }; diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index c847217eeb..e22149f8d3 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -41,7 +41,8 @@ // MACROS ------------------------------------------------------------------ -#define SONG_MAGIC "HMI-MIDISONG061595" +#define HMP_NEW_DATE "013195" +#define HMI_SONG_MAGIC "HMI-MIDISONG061595" #define TRACK_MAGIC "HMI-MIDITRACK" // Used by SendCommand to check for unexpected end-of-track conditions. @@ -53,15 +54,26 @@ } // In song header -#define DIVISION_OFFSET 0xD2 -#define TRACK_COUNT_OFFSET 0xE4 -#define TRACK_DIR_PTR_OFFSET 0xE8 +#define HMI_DIVISION_OFFSET 0xD2 +#define HMI_TRACK_COUNT_OFFSET 0xE4 +#define HMI_TRACK_DIR_PTR_OFFSET 0xE8 + +#define HMP_DIVISION_OFFSET 0x38 +#define HMP_TRACK_COUNT_OFFSET 0x30 +#define HMP_DESIGNATIONS_OFFSET 0x94 +#define HMP_TRACK_OFFSET_0 0x308 // original HMP +#define HMP_TRACK_OFFSET_1 0x388 // newer HMP // In track header -#define TRACK_DATA_PTR_OFFSET 0x57 -#define TRACK_DESIGNATION_OFFSET 0x99 +#define HMITRACK_DATA_PTR_OFFSET 0x57 +#define HMITRACK_DESIGNATION_OFFSET 0x99 -#define NUM_DESIGNATIONS 8 +#define HMPTRACK_LEN_OFFSET 4 +#define HMPTRACK_DESIGNATION_OFFSET 8 +#define HMPTRACK_MIDI_DATA_OFFSET 12 + +#define NUM_HMP_DESIGNATIONS 5 +#define NUM_HMI_DESIGNATIONS 8 // MIDI device types for designation #define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device) @@ -103,12 +115,13 @@ struct HMISong::TrackInfo size_t MaxTrackP; DWORD Delay; DWORD PlayedTime; - WORD Designation[NUM_DESIGNATIONS]; + WORD Designation[NUM_HMI_DESIGNATIONS]; bool Enabled; bool Finished; BYTE RunningStatus; - DWORD ReadVarLen (); + DWORD ReadVarLenHMI(); + DWORD ReadVarLenHMP(); }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -139,9 +152,6 @@ extern char MIDI_CommonLengths[15]; HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) : MIDIStreamer(type), MusHeader(0), Tracks(0) { - int p; - int i; - #ifdef _WIN32 if (ExitEvent == NULL) { @@ -154,6 +164,7 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) } MusHeader = new BYTE[len]; SongLen = len; + NumTracks = 0; if (file != NULL) { if (fread(MusHeader, 1, len, file) != (size_t)len) @@ -165,21 +176,59 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) } // Do some validation of the MIDI file - if (memcmp(MusHeader, SONG_MAGIC, sizeof(SONG_MAGIC)) != 0) - return; + if (memcmp(MusHeader, HMI_SONG_MAGIC, sizeof(HMI_SONG_MAGIC)) == 0) + { + SetupForHMI(len); + } + else if (((DWORD *)MusHeader)[0] == MAKE_ID('H','M','I','M') && + ((DWORD *)MusHeader)[1] == MAKE_ID('I','D','I','P')) + { + SetupForHMP(len); + } +} + +//========================================================================== +// +// HMISong Destructor +// +//========================================================================== + +HMISong::~HMISong() +{ + if (Tracks != NULL) + { + delete[] Tracks; + } + if (MusHeader != NULL) + { + delete[] MusHeader; + } +} + +//========================================================================== +// +// HMISong :: SetupForHMI +// +//========================================================================== + +void HMISong::SetupForHMI(int len) +{ + int i, p; + + ReadVarLen = ReadVarLenHMI; + NumTracks = GetShort(MusHeader + HMI_TRACK_COUNT_OFFSET); - NumTracks = GetShort(MusHeader + TRACK_COUNT_OFFSET); if (NumTracks <= 0) { return; } // The division is the number of pulses per quarter note (PPQN). - Division = GetShort(MusHeader + DIVISION_OFFSET); + Division = GetShort(MusHeader + HMI_DIVISION_OFFSET); InitialTempo = 4000000; Tracks = new TrackInfo[NumTracks + 1]; - int track_dir = GetInt(MusHeader + TRACK_DIR_PTR_OFFSET); + int track_dir = GetInt(MusHeader + HMI_TRACK_DIR_PTR_OFFSET); // Gather information about each track for (i = 0, p = 0; i < NumTracks; ++i) @@ -187,7 +236,7 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) int start = GetInt(MusHeader + track_dir + i*4); int tracklen, datastart; - if (start > len - TRACK_DESIGNATION_OFFSET - 4) + if (start > len - HMITRACK_DESIGNATION_OFFSET - 4) { // Track is incomplete. continue; } @@ -216,7 +265,7 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) } // Offset to actual MIDI events. - datastart = GetInt(MusHeader + start + TRACK_DATA_PTR_OFFSET); + datastart = GetInt(MusHeader + start + HMITRACK_DATA_PTR_OFFSET); tracklen -= datastart; if (tracklen <= 0) { @@ -230,9 +279,9 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) // Retrieve track designations. We can't check them yet, since we have not yet // connected to the MIDI device. - for (int ii = 0; ii < NUM_DESIGNATIONS; ++ii) + for (int ii = 0; ii < NUM_HMI_DESIGNATIONS; ++ii) { - Tracks[p].Designation[ii] = GetShort(MusHeader + start + TRACK_DESIGNATION_OFFSET + ii*2); + Tracks[p].Designation[ii] = GetShort(MusHeader + start + HMITRACK_DESIGNATION_OFFSET + ii*2); } p++; @@ -241,29 +290,108 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) // In case there were fewer actual chunks in the file than the // header specified, update NumTracks with the current value of p. NumTracks = p; - - if (NumTracks == 0) - { // No tracks, so nothing to play - return; - } } //========================================================================== // -// HMISong Destructor +// HMISong :: SetupForHMP // //========================================================================== -HMISong::~HMISong () +void HMISong::SetupForHMP(int len) { - if (Tracks != NULL) + int track_data; + int i, p; + + ReadVarLen = ReadVarLenHMP; + if (MusHeader[8] == 0) { - delete[] Tracks; + track_data = HMP_TRACK_OFFSET_0; } - if (MusHeader != NULL) + else if (memcmp(MusHeader + 8, HMP_NEW_DATE, sizeof(HMP_NEW_DATE)) == 0) { - delete[] MusHeader; + track_data = HMP_TRACK_OFFSET_1; } + else + { // unknown HMIMIDIP version + return; + } + + NumTracks = GetInt(MusHeader + HMP_TRACK_COUNT_OFFSET); + + if (NumTracks <= 0) + { + return; + } + + // The division is the number of pulses per quarter note (PPQN). + Division = GetInt(MusHeader + HMP_DIVISION_OFFSET); + InitialTempo = 1000000; + + Tracks = new TrackInfo[NumTracks + 1]; + + // Gather information about each track + for (i = 0, p = 0; i < NumTracks; ++i) + { + int start = track_data; + int tracklen; + + if (start > len - HMPTRACK_MIDI_DATA_OFFSET) + { // Track is incomplete. + break; + } + + tracklen = GetInt(MusHeader + start + HMPTRACK_LEN_OFFSET); + track_data += tracklen; + + // Clamp incomplete tracks to the end of the file. + tracklen = MIN(tracklen, len - start); + if (tracklen <= 0) + { + continue; + } + + // Subtract track header size. + tracklen -= HMPTRACK_MIDI_DATA_OFFSET; + if (tracklen <= 0) + { + continue; + } + + // Store track information + Tracks[p].TrackBegin = MusHeader + start + HMPTRACK_MIDI_DATA_OFFSET; + Tracks[p].TrackP = 0; + Tracks[p].MaxTrackP = tracklen; + + // Retrieve track designations. We can't check them yet, since we have not yet + // connected to the MIDI device. +#if 0 + // This is completely a guess based on knowledge of how designations work with + // HMI files. Some songs contain nothing but zeroes for this data, so I'd rather + // not go around using it without confirmation. + + Printf("Track %d: %d %08x %d: \034I", i, GetInt(MusHeader + start), + GetInt(MusHeader + start + 4), GetInt(MusHeader + start + 8)); + + int designations = HMP_DESIGNATIONS_OFFSET + + GetInt(MusHeader + start + HMPTRACK_DESIGNATION_OFFSET) * 4 * NUM_HMP_DESIGNATIONS; + for (int ii = 0; ii < NUM_HMP_DESIGNATIONS; ++ii) + { + Printf(" %04x", GetInt(MusHeader + designations + ii*4)); + } + Printf("\n"); +#endif + Tracks[p].Designation[0] = HMI_DEV_GM; + Tracks[p].Designation[1] = HMI_DEV_GUS; + Tracks[p].Designation[2] = HMI_DEV_OPL2; + Tracks[p].Designation[3] = 0; + + p++; + } + + // In case there were fewer actual chunks in the file than the + // header specified, update NumTracks with the current value of p. + NumTracks = p; } //========================================================================== @@ -295,7 +423,7 @@ void HMISong::CheckCaps(int tech) { Tracks[i].Enabled = false; // Track designations are stored in a 0-terminated array. - for (int j = 0; j < NUM_DESIGNATIONS && Tracks[i].Designation[j] != 0; ++j) + for (int j = 0; j < countof(Tracks[i].Designation) && Tracks[i].Designation[j] != 0; ++j) { if (Tracks[i].Designation[j] == tech) { @@ -367,7 +495,7 @@ void HMISong :: DoRestart() ProcessInitialMetaEvents (); for (i = 0; i < NumTracks; ++i) { - Tracks[i].Delay = Tracks[i].ReadVarLen(); + Tracks[i].Delay = ReadVarLen(&Tracks[i]); } Tracks[i].Delay = 0; // for the FakeTrack Tracks[i].Enabled = true; @@ -534,9 +662,9 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) } events += 3; - if ((event & 0x70) == (MIDI_NOTEON & 0x70)) + if (ReadVarLen == ReadVarLenHMI && (event & 0x70) == (MIDI_NOTEON & 0x70)) { // HMI note on events include the time until an implied note off event. - NoteOffs.AddNoteOff(track->ReadVarLen(), event & 0x0F, data1); + NoteOffs.AddNoteOff(track->ReadVarLenHMI(), event & 0x0F, data1); } } else @@ -546,7 +674,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) // anything that played before. if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { - len = track->ReadVarLen (); + len = ReadVarLen(track); track->TrackP += len; } else if (event == MIDI_META) @@ -554,7 +682,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) // It's a meta-event event = track->TrackBegin[track->TrackP++]; CHECK_FINISHED - len = track->ReadVarLen (); + len = ReadVarLen(track); CHECK_FINISHED if (track->TrackP + len <= track->MaxTrackP) @@ -614,7 +742,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) } if (!track->Finished) { - track->Delay = track->ReadVarLen(); + track->Delay = ReadVarLen(track); } return events; } @@ -644,7 +772,7 @@ void HMISong::ProcessInitialMetaEvents () { event = track->TrackBegin[track->TrackP+2]; track->TrackP += 3; - len = track->ReadVarLen (); + len = ReadVarLen(track); if (track->TrackP + len <= track->MaxTrackP) { switch (event) @@ -673,13 +801,35 @@ void HMISong::ProcessInitialMetaEvents () //========================================================================== // -// HMISong :: TrackInfo :: ReadVarLen +// HMISong :: ReadVarLenHMI static +// +//========================================================================== + +DWORD HMISong::ReadVarLenHMI(TrackInfo *track) +{ + return track->ReadVarLenHMI(); +} + +//========================================================================== +// +// HMISong :: ReadVarLenHMP static +// +//========================================================================== + +DWORD HMISong::ReadVarLenHMP(TrackInfo *track) +{ + return track->ReadVarLenHMP(); +} + +//========================================================================== +// +// HMISong :: TrackInfo :: ReadVarLenHMI // // Reads a variable-length SMF number. // //========================================================================== -DWORD HMISong::TrackInfo::ReadVarLen () +DWORD HMISong::TrackInfo::ReadVarLenHMI() { DWORD time = 0, t = 0x80; @@ -691,6 +841,31 @@ DWORD HMISong::TrackInfo::ReadVarLen () return time; } +//========================================================================== +// +// HMISong :: TrackInfo :: ReadVarLenHMP +// +// Reads a variable-length HMP number. This is similar to the standard SMF +// variable length number, except it's stored little-endian, and the high +// bit set means the number is done. +// +//========================================================================== + +DWORD HMISong::TrackInfo::ReadVarLenHMP() +{ + DWORD time = 0; + BYTE t = 0; + int off = 0; + + while (!(t & 0x80) && TrackP < MaxTrackP) + { + t = TrackBegin[TrackP++]; + time |= (t & 127) << off; + off += 7; + } + return time; +} + //========================================================================== // // HMISong :: NoteOffQueue :: AddNoteOff