- Cleaned up the ugly MIDI song creating code a little.

- Added a generic Standard MIDI File creator that works with any of the sequencers. mus2midi.cpp
  is no longer used but is kept around as a reference.

SVN r2677 (trunk)
This commit is contained in:
Randy Heit 2010-09-03 05:08:05 +00:00
parent 092cbfd55b
commit 070ec75785
12 changed files with 344 additions and 202 deletions

View file

@ -661,7 +661,6 @@ add_executable( zdoom WIN32
m_png.cpp
m_random.cpp
md5.cpp
mus2midi.cpp
name.cpp
nodebuild.cpp
nodebuild_classify_nosse2.cpp

View file

@ -195,9 +195,9 @@ bool ProduceMIDI (const BYTE *musBuf, int len, TArray<BYTE> &outFile)
switch (event & 0x70)
{
case MUS_NOTEOFF:
midStatus |= MIDI_NOTEOFF;
midStatus |= MIDI_NOTEON;
mid1 = t & 127;
mid2 = 64;
mid2 = 0;
break;
case MUS_NOTEON:

View file

@ -75,7 +75,4 @@ typedef struct
// WORD UsedInstruments[NumInstruments];
} MUSHeader;
bool ProduceMIDI (const BYTE *musBuf, int len, TArray<BYTE> &outFile);
bool ProduceMIDI (const BYTE *musBuf, int len, FILE *outFile);
#endif //__MUS2MIDI_H__

View file

@ -3,7 +3,7 @@
** Plays music
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** Copyright 1998-2010 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -84,6 +84,14 @@ extern void ChildSigHandler (int signum);
#define GZIP_FNAME 8
#define GZIP_FCOMMENT 16
enum EMIDIType
{
MIDI_NOTMIDI,
MIDI_MIDI,
MIDI_HMI,
MIDI_MUS
};
extern int MUSHeaderSearch(const BYTE *head, int len);
EXTERN_CVAR (Int, snd_samplerate)
@ -305,6 +313,40 @@ MusInfo *I_RegisterURLSong (const char *url)
return NULL;
}
static MusInfo *CreateMIDISong(FILE *file, const char *filename, BYTE *musiccache, int offset, int len, EMIDIDevice devtype, EMIDIType miditype)
{
if (devtype == MIDI_Timidity)
{
assert(miditype == MIDI_MIDI);
return new TimiditySong(file, musiccache, len);
}
else if (devtype >= MIDI_Null)
{
assert(miditype == MIDI_MIDI);
if (musiccache != NULL)
{
return new StreamSong((char *)musiccache, -1, len);
}
else
{
return new StreamSong(filename, offset, len);
}
}
else if (miditype == MIDI_MUS)
{
return new MUSSong2(file, musiccache, len, devtype);
}
else if (miditype == MIDI_MIDI)
{
return new MIDISong2(file, musiccache, len, devtype);
}
else if (miditype == MIDI_HMI)
{
return new HMISong(file, musiccache, len, devtype);
}
return NULL;
}
MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int len, int device)
{
FILE *file;
@ -405,173 +447,130 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int
}
}
EMIDIType miditype = MIDI_NOTMIDI;
// Check for MUS format
// Tolerate sloppy wads by searching up to 32 bytes for the header
if (MUSHeaderSearch(idstr, sizeof(idstr)) >= 0)
{
/* MUS are played as:
- OPL:
- if explicitly selected by $mididevice
- when snd_mididevice is -3 and no midi device is set for the song
Timidity:
- if explicitly selected by $mididevice
- when snd_mididevice is -2 and no midi device is set for the song
FMod:
- if explicitly selected by $mididevice
- when snd_mididevice is -1 and no midi device is set for the song
- as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0
MMAPI (Win32 only):
- if explicitly selected by $mididevice (non-Win32 redirects this to FMOD)
- when snd_mididevice is >= 0 and no midi device is set for the song
- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
*/
if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL)
{
info = new MUSSong2 (file, musiccache, len, MIDI_OPL);
miditype = MIDI_MUS;
}
else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2))
{
info = new TimiditySong (file, musiccache, len);
}
else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
{
info = new MUSSong2(file, musiccache, len, MIDI_Timidity);
}
#ifdef HAVE_FLUIDSYNTH
else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
{
info = new MUSSong2(file, musiccache, len, MIDI_Fluid);
}
#endif
if (info != NULL && !info->IsValid())
{
delete info;
info = NULL;
device = MDEV_DEFAULT;
}
if (info == NULL && (snd_mididevice == -1 || device == MDEV_FMOD) && device != MDEV_MMAPI)
{
TArray<BYTE> midi;
bool midi_made = false;
if (file == NULL)
{
midi_made = ProduceMIDI((BYTE *)musiccache, len, midi);
}
else
{
BYTE *mus = new BYTE[len];
size_t did_read = fread(mus, 1, len, file);
if (did_read == (size_t)len)
{
midi_made = ProduceMIDI(mus, len, midi);
}
fseek(file, -(long)did_read, SEEK_CUR);
delete[] mus;
}
if (midi_made)
{
info = new StreamSong((char *)&midi[0], -1, midi.Size());
if (!info->IsValid())
{
delete info;
info = NULL;
}
}
}
#ifdef _WIN32
if (info == NULL)
{
info = new MUSSong2 (file, musiccache, len, MIDI_Win);
}
#endif // _WIN32
}
else
{
// Check for HMI format
else
if (id[0] == MAKE_ID('H','M','I','-') &&
id[1] == MAKE_ID('M','I','D','I') &&
id[2] == MAKE_ID('S','O','N','G'))
{
if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL)
miditype = MIDI_HMI;
}
// Check for MIDI format
else if (id[0] == MAKE_ID('M','T','h','d'))
{
info = new HMISong(file, musiccache, len, MIDI_OPL);
miditype = MIDI_MIDI;
}
if (miditype != MIDI_NOTMIDI)
{
TArray<BYTE> midi;
/* MIDI are played as:
- OPL:
- if explicitly selected by $mididevice
- when snd_mididevice is -3 and no midi device is set for the song
- Timidity:
- if explicitly selected by $mididevice
- when snd_mididevice is -2 and no midi device is set for the song
- FMod:
- if explicitly selected by $mididevice
- when snd_mididevice is -1 and no midi device is set for the song
- as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0
- MMAPI (Win32 only):
- if explicitly selected by $mididevice (non-Win32 redirects this to FMOD)
- when snd_mididevice is >= 0 and no midi device is set for the song
- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
*/
EMIDIDevice devtype = MIDI_Null;
// Choose the type of MIDI device we want.
if (device == MDEV_FMOD || (snd_mididevice == -1 && device == MDEV_DEFAULT))
{
devtype = MIDI_FMOD;
}
else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT))
{
devtype = MIDI_Timidity;
}
else if (device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT))
{
devtype = MIDI_OPL;
}
else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
{
info = new HMISong(file, musiccache, len, MIDI_Timidity);
devtype = MIDI_GUS;
}
#ifdef HAVE_FLUIDSYNTH
else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
{
info = new HMISong(file, musiccache, len, MIDI_Fluid);
devtype = MIDI_Fluid;
}
#endif
#ifdef _WIN32
else
{
info = new HMISong(file, musiccache, len, MIDI_Win);
devtype = MIDI_Win;
}
#endif
}
// Check for MIDI format
else if (id[0] == MAKE_ID('M','T','h','d'))
{
// This is a midi file
/* MIDI are played as:
OPL:
- if explicitly selected by $mididevice
- when snd_mididevice is -3 and no midi device is set for the song
retry_as_fmod:
if (miditype != MIDI_MIDI && devtype >= MIDI_Null)
{
// Convert to standard MIDI for external sequencers.
MIDIStreamer *streamer;
Timidity:
- if explicitly selected by $mididevice
- when snd_mididevice is -2 and no midi device is set for the song
FMOD:
- if explicitly selected by $mididevice
- when snd_mididevice is -1 and no midi device is set for the song
- as fallback when Timidity failed unless snd_mididevice is >= 0
MMAPI (Win32 only):
- if explicitly selected by $mididevice (non-Win32 redirects this to FMOD)
- when snd_mididevice is >= 0 and no midi device is set for the song
- as fallback when Timidity failed and snd_mididevice is >= 0
*/
if (device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT))
if (miditype == MIDI_MUS)
{
info = new MIDISong2 (file, musiccache, len, MIDI_OPL);
streamer = new MUSSong2(file, musiccache, len, MIDI_Null);
}
else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT))
else
{
info = new TimiditySong (file, musiccache, len);
assert(miditype == MIDI_HMI);
streamer = new HMISong(file, musiccache, len, MIDI_Null);
}
else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
if (streamer->IsValid())
{
info = new MIDISong2(file, musiccache, len, MIDI_Timidity);
}
#ifdef HAVE_FLUIDSYNTH
else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
streamer->CreateSMF(midi);
miditype = MIDI_MIDI;
musiccache = &midi[0];
len = midi.Size();
if (file != NULL)
{
info = new MIDISong2(file, musiccache, len, MIDI_Fluid);
fclose(file);
file = NULL;
}
#endif
}
delete streamer;
}
info = CreateMIDISong(file, filename, musiccache, offset, len, devtype, miditype);
if (info != NULL && !info->IsValid())
{
delete info;
info = NULL;
device = MDEV_DEFAULT;
}
if (info == NULL && devtype != MIDI_FMOD && snd_mididevice < 0)
{
devtype = MIDI_FMOD;
goto retry_as_fmod;
}
#ifdef _WIN32
if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI))
if (info == NULL && devtype != MIDI_Win && snd_mididevice >= 0)
{
info = new MIDISong2 (file, musiccache, len, MIDI_Win);
info = CreateMIDISong(file, filename, musiccache, offset, len, MIDI_Win, miditype);
}
#endif // _WIN32
#endif
}
// Check for various raw OPL formats
else if (
(id[0] == MAKE_ID('R','A','W','A') && id[1] == MAKE_ID('D','A','T','A')) || // Rdos Raw OPL
@ -590,7 +589,6 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int
{
info = MOD_OpenSong(file, musiccache, len);
}
}
if (info == NULL)
{

View file

@ -333,8 +333,13 @@ enum EMIDIDevice
{
MIDI_Win,
MIDI_OPL,
MIDI_Timidity,
MIDI_Fluid
MIDI_GUS,
MIDI_Fluid,
// only used by I_RegisterSong
MIDI_Null,
MIDI_FMOD,
MIDI_Timidity
};
class MIDIStreamer : public MusInfo
@ -357,6 +362,7 @@ public:
void FluidSettingInt(const char *setting, int value);
void FluidSettingNum(const char *setting, double value);
void FluidSettingStr(const char *setting, const char *value);
void CreateSMF(TArray<BYTE> &file);
protected:
MIDIStreamer(const char *dumpname, EMIDIDevice type);
@ -369,7 +375,7 @@ protected:
static void Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2);
// Virtuals for subclasses to override
virtual void CheckCaps();
virtual void CheckCaps(int tech);
virtual void DoInitialSetup() = 0;
virtual void DoRestart() = 0;
virtual bool CheckDone() = 0;
@ -457,7 +463,7 @@ public:
protected:
MIDISong2(const MIDISong2 *original, const char *filename, EMIDIDevice type); // file dump constructor
void CheckCaps();
void CheckCaps(int tech);
void DoInitialSetup();
void DoRestart();
bool CheckDone();
@ -494,7 +500,7 @@ public:
protected:
HMISong(const HMISong *original, const char *filename, EMIDIDevice type); // file dump constructor
void CheckCaps();
void CheckCaps(int tech);
void DoInitialSetup();
void DoRestart();
bool CheckDone();

View file

@ -273,10 +273,8 @@ HMISong::~HMISong ()
//
//==========================================================================
void HMISong::CheckCaps()
void HMISong::CheckCaps(int tech)
{
int tech = MIDI->GetTechnology();
// What's the equivalent HMI device for our technology?
if (tech == MOD_FMSYNTH)
{
@ -851,7 +849,7 @@ MusInfo *HMISong::GetOPLDumper(const char *filename)
MusInfo *HMISong::GetWaveDumper(const char *filename, int rate)
{
return new HMISong(this, filename, MIDI_Timidity);
return new HMISong(this, filename, MIDI_GUS);
}
//==========================================================================

View file

@ -207,7 +207,7 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len)
BYTE *buf;
if (file!=NULL)
if (file != NULL)
{
buf = new BYTE[len];
fread (buf, 1, len, file);
@ -217,18 +217,8 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len)
buf = musiccache;
}
// The file type has already been checked before this class instance was
// created, so we only need to check one character to determine if this
// is a MUS or MIDI file and write it to disk as appropriate.
if (buf[1] == 'T')
{
// Write to temporary file
success = (fwrite (buf, 1, len, f) == (size_t)len);
}
else
{
success = ProduceMIDI (buf, len, f);
}
fclose (f);
if (file != NULL)
{

View file

@ -49,6 +49,8 @@
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static void WriteVarLen (TArray<BYTE> &file, DWORD value);
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
EXTERN_CVAR(Float, snd_musicvolume)
@ -57,8 +59,21 @@ EXTERN_CVAR(Float, snd_musicvolume)
extern UINT mididevice;
#endif
extern char MIDI_EventLengths[7];
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static const BYTE StaticMIDIhead[] =
{
'M','T','h','d', 0, 0, 0, 6,
0, 0, // format 0: only one track
0, 1, // yes, there is really only one track
0, 0, // divisions (filled in)
'M','T','r','k', 0, 0, 0, 0,
// The first event sets the tempo (filled in)
0, 255, 81, 3, 0, 0, 0
};
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
@ -172,7 +187,7 @@ bool MIDIStreamer::IsValid() const
//
//==========================================================================
void MIDIStreamer::CheckCaps()
void MIDIStreamer::CheckCaps(int tech)
{
}
@ -200,7 +215,7 @@ void MIDIStreamer::Play(bool looping, int subsong)
{
MIDI = new OPLDumperMIDIDevice(DumpFilename);
}
else if (DeviceType == MIDI_Timidity)
else if (DeviceType == MIDI_GUS)
{
MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0);
}
@ -221,13 +236,17 @@ void MIDIStreamer::Play(bool looping, int subsong)
break;
#endif
case MIDI_Timidity:
case MIDI_GUS:
MIDI = new TimidityMIDIDevice;
break;
case MIDI_OPL:
MIDI = new OPLMIDIDevice;
break;
default:
MIDI = NULL;
break;
}
#ifndef _WIN32
@ -240,9 +259,9 @@ void MIDIStreamer::Play(bool looping, int subsong)
return;
}
CheckCaps();
CheckCaps(MIDI->GetTechnology());
Precache();
IgnoreLoops = true;
IgnoreLoops = false;
// Set time division and tempo.
if (0 != MIDI->SetTimeDiv(Division) ||
@ -515,7 +534,7 @@ void MIDIStreamer::OutputVolume (DWORD volume)
int MIDIStreamer::VolumeControllerChange(int channel, int volume)
{
ChannelVolumes[channel] = volume;
return ((volume + 1) * Volume) >> 16;
return IgnoreLoops ? volume : ((volume + 1) * Volume) >> 16;
}
//==========================================================================
@ -834,9 +853,9 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
//
// MIDIStreamer :: Precache
//
// Generates a list of instruments this song uses them and passes them to
// the MIDI device for precaching. The default implementation here pretends
// to play the song and watches for program change events on normal channels
// Generates a list of instruments this song uses and passes them to the
// MIDI device for precaching. The default implementation here pretends to
// play the song and watches for program change events on normal channels
// and note on events on channel 10.
//
//==========================================================================
@ -933,6 +952,138 @@ void MIDIStreamer::Precache()
MIDI->PrecacheInstruments(&packed[0], packed.Size());
}
//==========================================================================
//
// MIDIStreamer :: CreateSMF
//
// Simulates playback to create a Standard MIDI File.
//
//==========================================================================
void MIDIStreamer::CreateSMF(TArray<BYTE> &file)
{
DWORD delay = 0;
BYTE running_status = 0;
// Always create songs aimed at GM devices.
CheckCaps(MOD_MIDIPORT);
IgnoreLoops = true;
DoRestart();
file.Reserve(sizeof(StaticMIDIhead));
memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead));
file[12] = Division >> 8;
file[13] = Division & 0xFF;
file[26] = InitialTempo >> 16;
file[27] = InitialTempo >> 8;
file[28] = InitialTempo;
while (!CheckDone())
{
DWORD *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600);
for (DWORD *event = Events[0]; event < event_end; )
{
delay += event[0];
if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO)
{
WriteVarLen(file, delay);
delay = 0;
DWORD tempo = MEVT_EVENTPARM(event[2]);
file.Push(MIDI_META);
file.Push(MIDI_META_TEMPO);
file.Push(3);
file.Push(BYTE(tempo >> 16));
file.Push(BYTE(tempo >> 8));
file.Push(BYTE(tempo));
}
else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG)
{
WriteVarLen(file, delay);
delay = 0;
DWORD len = MEVT_EVENTPARM(event[2]);
BYTE *bytes = (BYTE *)&event[3];
if (bytes[0] == MIDI_SYSEX)
{
len--;
file.Push(MIDI_SYSEX);
WriteVarLen(file, len);
memcpy(&file[file.Reserve(len - 1)], bytes, len);
}
}
else if (MEVT_EVENTTYPE(event[2]) == 0)
{
WriteVarLen(file, delay);
delay = 0;
BYTE status = BYTE(event[2]);
if (status != running_status)
{
running_status = status;
file.Push(status);
}
file.Push(BYTE((event[2] >> 8) & 0x7F));
if (MIDI_EventLengths[(status >> 4) & 7] == 2)
{
file.Push(BYTE((event[2] >> 16) & 0x7F));
}
}
// Advance to next event
if (event[2] < 0x80000000)
{ // short message
event += 3;
}
else
{ // long message
event += 3 + ((MEVT_EVENTPARM(event[2]) + 3) >> 2);
}
}
}
// End track
WriteVarLen(file, delay);
file.Push(MIDI_META);
file.Push(MIDI_META_EOT);
file.Push(0);
// Fill in track length
DWORD len = file.Size() - 22;
file[18] = BYTE(len >> 24);
file[19] = BYTE(len >> 16);
file[20] = BYTE(len >> 8);
file[21] = BYTE(len & 255);
IgnoreLoops = false;
}
//==========================================================================
//
// WriteVarLen
//
//==========================================================================
static void WriteVarLen (TArray<BYTE> &file, DWORD value)
{
DWORD buffer = value & 0x7F;
while ( (value >>= 7) )
{
buffer <<= 8;
buffer |= (value & 0x7F) | 0x80;
}
for (;;)
{
file.Push(BYTE(buffer));
if (buffer & 0x80)
{
buffer >>= 8;
}
else
{
break;
}
}
}
//==========================================================================
//
// MIDIStreamer :: GetStats

View file

@ -283,9 +283,9 @@ DWORD *MUSSong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
switch (event & 0x70)
{
case MUS_NOTEOFF:
status |= MIDI_NOTEOFF;
status |= MIDI_NOTEON;
mid1 = t;
mid2 = 64;
mid2 = 0;
break;
case MUS_NOTEON:
@ -382,7 +382,7 @@ MusInfo *MUSSong2::GetOPLDumper(const char *filename)
MusInfo *MUSSong2::GetWaveDumper(const char *filename, int rate)
{
return new MUSSong2(this, filename, MIDI_Timidity);
return new MUSSong2(this, filename, MIDI_GUS);
}
//==========================================================================

View file

@ -216,10 +216,8 @@ MIDISong2::~MIDISong2 ()
//
//==========================================================================
void MIDISong2::CheckCaps()
void MIDISong2::CheckCaps(int tech)
{
int tech = MIDI->GetTechnology();
DesignationMask = 0xFF0F;
if (tech == MOD_FMSYNTH)
{
@ -801,7 +799,7 @@ MusInfo *MIDISong2::GetOPLDumper(const char *filename)
MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate)
{
return new MIDISong2(this, filename, MIDI_Timidity);
return new MIDISong2(this, filename, MIDI_GUS);
}
//==========================================================================

View file

@ -141,6 +141,15 @@ public:
::new((void*)&Array[Count]) T(item);
return Count++;
}
bool Pop ()
{
if (Count > 0)
{
Array[--Count].~T();
return true;
}
return false;
}
bool Pop (T &item)
{
if (Count > 0)

View file

@ -712,10 +712,6 @@
RelativePath=".\src\md5.cpp"
>
</File>
<File
RelativePath=".\src\mus2midi.cpp"
>
</File>
<File
RelativePath=".\src\name.cpp"
>