mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-05-08 10:00:40 +00:00
316 lines
No EOL
7.1 KiB
C
316 lines
No EOL
7.1 KiB
C
/* mus2strm.c: This is based on the Quick MUS to MIDI converter by
|
|
** Sebasten Bacquet (bacquet@iie.cnam.fr) and also on the mid2strm.c
|
|
** file from the DirectX 5 SDK. It converts MUS files into MIDI
|
|
** streams suitable for passing to the MCI midiStreamOut() function.
|
|
** This is a much cleaner solution than the one I was using previously,
|
|
** which was to use the qmus2mid code to convert the MUS lump into a
|
|
** temporary MIDI file on disk and then use MCI calls to play that file.
|
|
** (The switch even shrank the executable by 3K. Woo!)
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <mmsystem.h>
|
|
#include <assert.h>
|
|
|
|
#include "doomtype.h"
|
|
#include "mus2strm.h"
|
|
#include "m_alloc.h"
|
|
#include "m_swap.h"
|
|
|
|
#define last(e) ((unsigned char)(e & 0x80))
|
|
#define event_type(e) ((unsigned char)((e & 0x7F) >> 4))
|
|
#define channel(e) ((unsigned char)(e & 0x0F))
|
|
|
|
|
|
#define NOTMUSFILE 1 /* Not a MUS file */
|
|
#define MUSFILECOR 2 /* MUS file corrupted */
|
|
#define TOOMCHAN 3 /* Too many channels */
|
|
#define MEMALLOC 4 /* Memory allocation error */
|
|
|
|
|
|
// Description of the input MUS file
|
|
//
|
|
typedef struct
|
|
{
|
|
DWORD cbFile; // Total bytes in file
|
|
LPBYTE pFile; // -> entire file in memory
|
|
DWORD cbLeft; // Bytes left unread
|
|
LPBYTE pFilePointer; // -> next byte to read
|
|
|
|
DWORD tkNextEventDue;
|
|
BOOL endEncountered;
|
|
} INFILESTATE;
|
|
|
|
|
|
static INFILESTATE ifs;
|
|
static byte MUS2MIDcontrol[15] = {
|
|
0, /* Program change - not a MIDI control change */
|
|
0x00, /* Bank select */
|
|
0x01, /* Modulation pot */
|
|
0x07, /* Volume */
|
|
0x0A, /* Pan pot */
|
|
0x0B, /* Expression pot */
|
|
0x5B, /* Reverb depth */
|
|
0x5D, /* Chorus depth */
|
|
0x40, /* Sustain pedal */
|
|
0x43, /* Soft pedal */
|
|
0x78, /* All sounds off */
|
|
0x7B, /* All notes off */
|
|
0x7E, /* Mono */
|
|
0x7F, /* Poly */
|
|
0x79 /* Reset all controllers */
|
|
};
|
|
static signed char MUS2MIDchannel[16];
|
|
static char MUSvelocity[16];
|
|
|
|
static DWORD ReadTime (void);
|
|
static char FirstChannelAvailable (signed char MUS2MIDchannel[]);
|
|
static BOOL ReadMUSheader (MUSheader *MUSh);
|
|
static BOOL GetTrackEvent (MUSheader *MUSh, MEVENT *me);
|
|
static int DoConversion (byte *in, DWORD insize, int division);
|
|
|
|
|
|
static BOOL ReadMUSheader (MUSheader *mush)
|
|
{
|
|
if (ifs.cbLeft < sizeof(*mush))
|
|
return FALSE;
|
|
|
|
memcpy (mush, ifs.pFilePointer, sizeof(*mush));
|
|
|
|
if (strncmp (mush->ID, MUSMAGIC, 4))
|
|
return FALSE;
|
|
|
|
mush->ScoreLength = SHORT(mush->ScoreLength);
|
|
mush->ScoreStart = SHORT(mush->ScoreStart);
|
|
mush->channels = SHORT(mush->channels);
|
|
mush->SecChannels = SHORT(mush->SecChannels);
|
|
mush->InstrCnt = SHORT(mush->InstrCnt);
|
|
|
|
if (ifs.cbLeft < mush->ScoreStart)
|
|
return FALSE;
|
|
|
|
ifs.cbLeft -= mush->ScoreStart;
|
|
ifs.pFilePointer += mush->ScoreStart;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static DWORD ReadTime (void)
|
|
{
|
|
DWORD time = 0;
|
|
byte byt = 0;
|
|
|
|
do {
|
|
if (ifs.cbLeft > 0) {
|
|
byt = *(ifs.pFilePointer++);
|
|
ifs.cbLeft--;
|
|
time = (time << 7) + (byt & 0x7F);
|
|
}
|
|
} while ((ifs.cbLeft > 0) && (byt & 0x80));
|
|
|
|
return time;
|
|
}
|
|
|
|
static char FirstChannelAvailable (signed char MUS2MIDchannel[])
|
|
{
|
|
int i;
|
|
signed char max = -1;
|
|
|
|
for (i = 0; i < 15; i++)
|
|
if (MUS2MIDchannel[i] > max)
|
|
max = MUS2MIDchannel[i];
|
|
|
|
return max == 8 ? 10 : max+1;
|
|
}
|
|
|
|
static BOOL GetTrackEvent (MUSheader *MUSh, MEVENT *me)
|
|
{
|
|
byte event, data;
|
|
int MUSchannel, MIDIchannel;
|
|
|
|
me->pEvent = NULL;
|
|
|
|
if (!ifs.cbLeft)
|
|
return FALSE;
|
|
event = *(ifs.pFilePointer++);
|
|
ifs.cbLeft--;
|
|
|
|
MUSchannel = channel(event);
|
|
if (MUS2MIDchannel[MUSchannel] == -1)
|
|
MIDIchannel = MUS2MIDchannel[MUSchannel] =
|
|
(MUSchannel == 15 ? 9 : FirstChannelAvailable (MUS2MIDchannel));
|
|
else
|
|
MIDIchannel = MUS2MIDchannel[MUSchannel];
|
|
|
|
if (event_type(event) <= MUS_CTRLCHANGE) {
|
|
if (ifs.cbLeft < 1)
|
|
return FALSE;
|
|
data = *(ifs.pFilePointer++);
|
|
ifs.cbLeft--;
|
|
}
|
|
|
|
switch (event_type(event))
|
|
{
|
|
case MUS_RELEASE:
|
|
me->cbEvent = 3;
|
|
me->abEvent[0] = MIDI_NOTEOFF | MIDIchannel;
|
|
me->abEvent[1] = data;
|
|
me->abEvent[2] = 64;
|
|
break;
|
|
|
|
case MUS_PLAY:
|
|
me->cbEvent = 3;
|
|
me->abEvent[0] = MIDI_NOTEON | MIDIchannel;
|
|
me->abEvent[1] = data & 0x7f;
|
|
if (data & 0x80) {
|
|
if (ifs.cbLeft < 1)
|
|
return FALSE;
|
|
else {
|
|
MUSvelocity[MUSchannel] = *(ifs.pFilePointer++);
|
|
ifs.cbLeft--;
|
|
}
|
|
}
|
|
me->abEvent[2] = MUSvelocity[MUSchannel];
|
|
break;
|
|
|
|
case MUS_PITCHBEND:
|
|
me->cbEvent = 3;
|
|
me->abEvent[0] = MIDI_PITCHBEND | MIDIchannel;
|
|
me->abEvent[1] = (data & 1) << 6;
|
|
me->abEvent[2] = data >> 1;
|
|
break ;
|
|
|
|
case MUS_SYSEVENT:
|
|
me->cbEvent = 3;
|
|
me->abEvent[0] = MIDI_CTRLCHANGE | MIDIchannel;
|
|
me->abEvent[1] = MUS2MIDcontrol[data];
|
|
me->abEvent[2] = data == 12 ? MUSh->channels+1 : 0;
|
|
break;
|
|
|
|
case MUS_CTRLCHANGE:
|
|
if (ifs.cbLeft < 1)
|
|
return FALSE;
|
|
ifs.cbLeft--;
|
|
if (data) {
|
|
me->cbEvent = 3;
|
|
me->abEvent[0] = MIDI_CTRLCHANGE | MIDIchannel;
|
|
me->abEvent[1] = MUS2MIDcontrol[data];
|
|
me->abEvent[2] = *(ifs.pFilePointer++);
|
|
} else {
|
|
me->cbEvent = 2;
|
|
me->abEvent[0] = MIDI_PRGMCHANGE | MIDIchannel;
|
|
me->abEvent[1] = *(ifs.pFilePointer++);
|
|
}
|
|
break ;
|
|
|
|
case MUS_UNKNOWN1:
|
|
case MUS_UNKNOWN2:
|
|
return FALSE;
|
|
|
|
case MUS_END:
|
|
ifs.endEncountered = TRUE;
|
|
break;
|
|
}
|
|
|
|
me->tkEvent = ifs.tkNextEventDue;
|
|
if (last(event))
|
|
ifs.tkNextEventDue += ReadTime ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int DoConversion (byte *in, DWORD insize, int division)
|
|
{
|
|
// The start of the first stream buffer contains an event to set
|
|
// the tempo to 500,000 microseconds per quarter note.
|
|
static BYTE tempoSet[3] = { 0x07, 0xa1, 0x20 };
|
|
static MEVENT tempoEvent = {
|
|
0,
|
|
MIDI_META, MIDI_META_TEMPO, 0, 0,
|
|
3,
|
|
tempoSet
|
|
};
|
|
|
|
MUSheader MUSh;
|
|
int i;
|
|
|
|
memset (UsedPatches, 0, sizeof(UsedPatches));
|
|
|
|
ifs.cbFile = insize;
|
|
ifs.pFile = in;
|
|
|
|
ifs.cbLeft = ifs.cbFile;
|
|
ifs.pFilePointer = ifs.pFile;
|
|
|
|
if (!ReadMUSheader (&MUSh))
|
|
return NOTMUSFILE;
|
|
|
|
if (MUSh.channels > 15) /* <=> MUSchannels+drums > 16 */
|
|
return TOOMCHAN;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
MUS2MIDchannel[i] = -1;
|
|
MUSvelocity[i] = 64;
|
|
}
|
|
ifs.tkNextEventDue = 0;
|
|
ifs.endEncountered = FALSE;
|
|
ots.tkTrack = 0;
|
|
ots.pFirst = NULL;
|
|
ots.pLast = NULL;
|
|
|
|
if (!AddEventToStream (&tempoEvent))
|
|
return MEMALLOC;
|
|
|
|
while (!ifs.endEncountered) {
|
|
MEVENT me;
|
|
|
|
if (!GetTrackEvent (&MUSh, &me))
|
|
return MUSFILECOR;
|
|
|
|
if (!ifs.endEncountered && !AddEventToStream (&me))
|
|
return MEMALLOC;
|
|
}
|
|
|
|
midTimeDiv = division ? division : 70;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mus2strmCleanup (void)
|
|
{
|
|
PSTREAMBUF pCurr = ots.pFirst;
|
|
while (pCurr) {
|
|
PSTREAMBUF pNext = pCurr->pNext;
|
|
free (pCurr);
|
|
pCurr = pNext;
|
|
}
|
|
}
|
|
|
|
PSTREAMBUF mus2strmConvert (BYTE *inFile, DWORD inSize)
|
|
{
|
|
int error = DoConversion (inFile, inSize, 0);
|
|
|
|
if (error) {
|
|
Printf (PRINT_HIGH, "MUS error: ");
|
|
switch (error) {
|
|
case NOTMUSFILE:
|
|
Printf (PRINT_HIGH, "Not a MUS file.\n"); break;
|
|
case MUSFILECOR:
|
|
Printf (PRINT_HIGH, "MUS file is corrupt.\n"); break;
|
|
case TOOMCHAN:
|
|
Printf (PRINT_HIGH, "MUS file has more than 16 channels.\n"); break;
|
|
case MEMALLOC:
|
|
Printf (PRINT_HIGH, "Not enough memory.\n"); break;
|
|
}
|
|
mus2strmCleanup ();
|
|
return NULL;
|
|
}
|
|
|
|
return ots.pFirst;
|
|
} |