mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-11 07:12:02 +00:00
- Added HMP file support.
SVN r2849 (trunk)
This commit is contained in:
parent
46eebe29a4
commit
4397ef3323
3 changed files with 230 additions and 42 deletions
|
@ -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'))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue