- 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;
}
// 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'))
{

View file

@ -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;
};

View file

@ -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