- Added HMP file support.

SVN r2849 (trunk)
This commit is contained in:
Randy Heit 2010-09-24 02:46:48 +00:00
parent 46eebe29a4
commit 4397ef3323
3 changed files with 230 additions and 42 deletions

View file

@ -463,6 +463,13 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int
{ {
miditype = MIDI_HMI; 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 // Check for MIDI format
else if (id[0] == MAKE_ID('M','T','h','d')) else if (id[0] == MAKE_ID('M','T','h','d'))
{ {

View file

@ -500,6 +500,8 @@ public:
protected: protected:
HMISong(const HMISong *original, const char *filename, EMIDIDevice type); // file dump constructor 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 CheckCaps(int tech);
void DoInitialSetup(); void DoInitialSetup();
void DoRestart(); void DoRestart();
@ -514,6 +516,9 @@ protected:
TrackInfo *FindNextDue (); TrackInfo *FindNextDue ();
void SetTempo(int new_tempo); void SetTempo(int new_tempo);
static DWORD ReadVarLenHMI(TrackInfo *);
static DWORD ReadVarLenHMP(TrackInfo *);
struct AutoNoteOff struct AutoNoteOff
{ {
DWORD Delay; DWORD Delay;
@ -541,6 +546,7 @@ protected:
TrackInfo *Tracks; TrackInfo *Tracks;
TrackInfo *TrackDue; TrackInfo *TrackDue;
TrackInfo *FakeTrack; TrackInfo *FakeTrack;
DWORD (*ReadVarLen)(TrackInfo *);
NoteOffQueue NoteOffs; NoteOffQueue NoteOffs;
}; };

View file

@ -41,7 +41,8 @@
// MACROS ------------------------------------------------------------------ // MACROS ------------------------------------------------------------------
#define SONG_MAGIC "HMI-MIDISONG061595" #define HMP_NEW_DATE "013195"
#define HMI_SONG_MAGIC "HMI-MIDISONG061595"
#define TRACK_MAGIC "HMI-MIDITRACK" #define TRACK_MAGIC "HMI-MIDITRACK"
// Used by SendCommand to check for unexpected end-of-track conditions. // Used by SendCommand to check for unexpected end-of-track conditions.
@ -53,15 +54,26 @@
} }
// In song header // In song header
#define DIVISION_OFFSET 0xD2 #define HMI_DIVISION_OFFSET 0xD2
#define TRACK_COUNT_OFFSET 0xE4 #define HMI_TRACK_COUNT_OFFSET 0xE4
#define TRACK_DIR_PTR_OFFSET 0xE8 #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 // In track header
#define TRACK_DATA_PTR_OFFSET 0x57 #define HMITRACK_DATA_PTR_OFFSET 0x57
#define TRACK_DESIGNATION_OFFSET 0x99 #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 // MIDI device types for designation
#define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device) #define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device)
@ -103,12 +115,13 @@ struct HMISong::TrackInfo
size_t MaxTrackP; size_t MaxTrackP;
DWORD Delay; DWORD Delay;
DWORD PlayedTime; DWORD PlayedTime;
WORD Designation[NUM_DESIGNATIONS]; WORD Designation[NUM_HMI_DESIGNATIONS];
bool Enabled; bool Enabled;
bool Finished; bool Finished;
BYTE RunningStatus; BYTE RunningStatus;
DWORD ReadVarLen (); DWORD ReadVarLenHMI();
DWORD ReadVarLenHMP();
}; };
// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@ -139,9 +152,6 @@ extern char MIDI_CommonLengths[15];
HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type)
: MIDIStreamer(type), MusHeader(0), Tracks(0) : MIDIStreamer(type), MusHeader(0), Tracks(0)
{ {
int p;
int i;
#ifdef _WIN32 #ifdef _WIN32
if (ExitEvent == NULL) if (ExitEvent == NULL)
{ {
@ -154,6 +164,7 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type)
} }
MusHeader = new BYTE[len]; MusHeader = new BYTE[len];
SongLen = len; SongLen = len;
NumTracks = 0;
if (file != NULL) if (file != NULL)
{ {
if (fread(MusHeader, 1, len, file) != (size_t)len) 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 // Do some validation of the MIDI file
if (memcmp(MusHeader, SONG_MAGIC, sizeof(SONG_MAGIC)) != 0) if (memcmp(MusHeader, HMI_SONG_MAGIC, sizeof(HMI_SONG_MAGIC)) == 0)
return; {
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) if (NumTracks <= 0)
{ {
return; return;
} }
// The division is the number of pulses per quarter note (PPQN). // The division is the number of pulses per quarter note (PPQN).
Division = GetShort(MusHeader + DIVISION_OFFSET); Division = GetShort(MusHeader + HMI_DIVISION_OFFSET);
InitialTempo = 4000000; InitialTempo = 4000000;
Tracks = new TrackInfo[NumTracks + 1]; 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 // Gather information about each track
for (i = 0, p = 0; i < NumTracks; ++i) 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 start = GetInt(MusHeader + track_dir + i*4);
int tracklen, datastart; int tracklen, datastart;
if (start > len - TRACK_DESIGNATION_OFFSET - 4) if (start > len - HMITRACK_DESIGNATION_OFFSET - 4)
{ // Track is incomplete. { // Track is incomplete.
continue; continue;
} }
@ -216,7 +265,7 @@ HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type)
} }
// Offset to actual MIDI events. // Offset to actual MIDI events.
datastart = GetInt(MusHeader + start + TRACK_DATA_PTR_OFFSET); datastart = GetInt(MusHeader + start + HMITRACK_DATA_PTR_OFFSET);
tracklen -= datastart; tracklen -= datastart;
if (tracklen <= 0) 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 // Retrieve track designations. We can't check them yet, since we have not yet
// connected to the MIDI device. // 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++; 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 // In case there were fewer actual chunks in the file than the
// header specified, update NumTracks with the current value of p. // header specified, update NumTracks with the current value of p.
NumTracks = 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; Tracks[i].Enabled = false;
// Track designations are stored in a 0-terminated array. // 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) if (Tracks[i].Designation[j] == tech)
{ {
@ -367,7 +495,7 @@ void HMISong :: DoRestart()
ProcessInitialMetaEvents (); ProcessInitialMetaEvents ();
for (i = 0; i < NumTracks; ++i) 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].Delay = 0; // for the FakeTrack
Tracks[i].Enabled = true; Tracks[i].Enabled = true;
@ -534,9 +662,9 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
} }
events += 3; 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. { // 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 else
@ -546,7 +674,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
// anything that played before. // anything that played before.
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{ {
len = track->ReadVarLen (); len = ReadVarLen(track);
track->TrackP += len; track->TrackP += len;
} }
else if (event == MIDI_META) else if (event == MIDI_META)
@ -554,7 +682,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
// It's a meta-event // It's a meta-event
event = track->TrackBegin[track->TrackP++]; event = track->TrackBegin[track->TrackP++];
CHECK_FINISHED CHECK_FINISHED
len = track->ReadVarLen (); len = ReadVarLen(track);
CHECK_FINISHED CHECK_FINISHED
if (track->TrackP + len <= track->MaxTrackP) if (track->TrackP + len <= track->MaxTrackP)
@ -614,7 +742,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
} }
if (!track->Finished) if (!track->Finished)
{ {
track->Delay = track->ReadVarLen(); track->Delay = ReadVarLen(track);
} }
return events; return events;
} }
@ -644,7 +772,7 @@ void HMISong::ProcessInitialMetaEvents ()
{ {
event = track->TrackBegin[track->TrackP+2]; event = track->TrackBegin[track->TrackP+2];
track->TrackP += 3; track->TrackP += 3;
len = track->ReadVarLen (); len = ReadVarLen(track);
if (track->TrackP + len <= track->MaxTrackP) if (track->TrackP + len <= track->MaxTrackP)
{ {
switch (event) 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. // Reads a variable-length SMF number.
// //
//========================================================================== //==========================================================================
DWORD HMISong::TrackInfo::ReadVarLen () DWORD HMISong::TrackInfo::ReadVarLenHMI()
{ {
DWORD time = 0, t = 0x80; DWORD time = 0, t = 0x80;
@ -691,6 +841,31 @@ DWORD HMISong::TrackInfo::ReadVarLen ()
return time; 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 // HMISong :: NoteOffQueue :: AddNoteOff