* Band-aid fixes:

- Fixed crash on startup in music_midi_out.cpp by preventing division by Tempo if Tempo is zero.
- Fixed crash on startup in gl_vertexbuffer.cpp when trying to access the sectors array though it was NULL. (It shouldn't even happen given numsectors is zero so the for iteration shouldn't even start, but it happened anyway for some mysterious reason.)
* Updated to ZDoom r2677:
- 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.
- Fixed: When the game nodes were the same as the render nodes, their pointers would not be NULLed.
- Renamed music_midi_midiout.cpp to music_smf_midiout.cpp.
- Moved MIDI precaching logic into MIDIStreamer so that SMF and HMI files can both use the same implementation.
- Added a player for HMI midi files.
- Set 'setslopeoverflow' compatibility flag for all maps in Massmouth2.

git-svn-id: http://mancubus.net/svn/hosted/gzdoom/trunk@932 b0f79afe-0144-0410-b225-9a4edf0717df
This commit is contained in:
gez 2010-09-03 08:11:47 +00:00
parent b2aec4871a
commit adb28b67b1
18 changed files with 1509 additions and 349 deletions

View file

@ -1,21 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005
# Visual C++ Express 2005
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zdoom", "gzdoom.vcproj", "{8049475B-5C87-46F9-9358-635218A4EF18}"
ProjectSection(ProjectDependencies) = postProject
{AC3F5340-40CB-4C3A-8AA7-CB7158DB4466} = {AC3F5340-40CB-4C3A-8AA7-CB7158DB4466}
{1D179D4B-F008-431B-8C72-111F8372584F} = {1D179D4B-F008-431B-8C72-111F8372584F}
{087B206F-F49E-4EFB-92CB-E1F6E32D1278} = {087B206F-F49E-4EFB-92CB-E1F6E32D1278}
{DA47396F-60C1-4BDE-A977-7F7DE461CF77} = {DA47396F-60C1-4BDE-A977-7F7DE461CF77}
{31090871-A623-4BBC-A167-DE821CD1240C} = {31090871-A623-4BBC-A167-DE821CD1240C}
{A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} = {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}
{6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F}
{9B465A9E-E5C7-4577-B559-3CA2F7AE7D96} = {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}
{8997289F-10BF-4678-8BAA-3BB509C84953} = {8997289F-10BF-4678-8BAA-3BB509C84953}
{B68E0ABF-B627-48A3-A92F-D8F827A75054} = {B68E0ABF-B627-48A3-A92F-D8F827A75054}
{0F80ACBF-460E-44F0-B28E-B3272D1774A7} = {0F80ACBF-460E-44F0-B28E-B3272D1774A7}
{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} = {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}
{6077B7D6-349F-4077-B552-3BC302EF5859} = {6077B7D6-349F-4077-B552-3BC302EF5859}
{667D2EE7-C357-49E2-9BAB-0A4A45F0F76E} = {667D2EE7-C357-49E2-9BAB-0A4A45F0F76E}
{0F80ACBF-460E-44F0-B28E-B3272D1774A7} = {0F80ACBF-460E-44F0-B28E-B3272D1774A7}
{DA47396F-60C1-4BDE-A977-7F7DE461CF77} = {DA47396F-60C1-4BDE-A977-7F7DE461CF77}
{31090871-A623-4BBC-A167-DE821CD1240C} = {31090871-A623-4BBC-A167-DE821CD1240C}
{087B206F-F49E-4EFB-92CB-E1F6E32D1278} = {087B206F-F49E-4EFB-92CB-E1F6E32D1278}
{1D179D4B-F008-431B-8C72-111F8372584F} = {1D179D4B-F008-431B-8C72-111F8372584F}
{B68E0ABF-B627-48A3-A92F-D8F827A75054} = {B68E0ABF-B627-48A3-A92F-D8F827A75054}
{8997289F-10BF-4678-8BAA-3BB509C84953} = {8997289F-10BF-4678-8BAA-3BB509C84953}
{AC3F5340-40CB-4C3A-8AA7-CB7158DB4466} = {AC3F5340-40CB-4C3A-8AA7-CB7158DB4466}
{A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} = {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}
{6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib\zlib.vcproj", "{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}"
@ -35,12 +35,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpeg-6b", "jpeg-6b\jpeg-6b.
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fixrtext", "tools\fixrtext\fixrtext.vcproj", "{DA47396F-60C1-4BDE-A977-7F7DE461CF77}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wadsrc", "wadsrc_bm\brightmaps.vcproj", "{087B206F-F49E-4EFB-92CB-E1F6E32D1278}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wadsrc-brightmaps", "wadsrc_bm\brightmaps.vcproj", "{087B206F-F49E-4EFB-92CB-E1F6E32D1278}"
ProjectSection(ProjectDependencies) = postProject
{24A19C02-F041-4AB0-A1A1-02E1E88EDBD3} = {24A19C02-F041-4AB0-A1A1-02E1E88EDBD3}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wadsrc", "wadsrc_lights\lights.vcproj", "{31090871-A623-4BBC-A167-DE821CD1240C}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wadsrc-lights", "wadsrc_lights\lights.vcproj", "{31090871-A623-4BBC-A167-DE821CD1240C}"
ProjectSection(ProjectDependencies) = postProject
{24A19C02-F041-4AB0-A1A1-02E1E88EDBD3} = {24A19C02-F041-4AB0-A1A1-02E1E88EDBD3}
EndProjectSection
@ -52,8 +52,8 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zipdir", "tools\zipdir\zipdir.vcproj", "{24A19C02-F041-4AB0-A1A1-02E1E88EDBD3}"
ProjectSection(ProjectDependencies) = postProject
{A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} = {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}
{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} = {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}
{6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F}
{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} = {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lzmalib", "lzma\lzmalib.vcproj", "{6EB27E78-7C7A-4F08-8E19-957E8EB3A20F}"

View file

@ -712,10 +712,6 @@
RelativePath=".\src\md5.cpp"
>
</File>
<File
RelativePath=".\src\mus2midi.cpp"
>
</File>
<File
RelativePath=".\src\name.cpp"
>
@ -5474,11 +5470,11 @@
>
</File>
<File
RelativePath=".\src\sound\music_midi_base.cpp"
RelativePath=".\src\sound\music_hmi_midiout.cpp"
>
</File>
<File
RelativePath="src\sound\music_midi_midiout.cpp"
RelativePath=".\src\sound\music_midi_base.cpp"
>
</File>
<File
@ -5505,6 +5501,10 @@
RelativePath=".\src\oplsynth\music_opldumper_mididevice.cpp"
>
</File>
<File
RelativePath=".\src\sound\music_smf_midiout.cpp"
>
</File>
<File
RelativePath=".\src\sound\music_softsynth_mididevice.cpp"
>

View file

@ -679,7 +679,6 @@ add_executable( zdoom WIN32
m_png.cpp
m_random.cpp
md5.cpp
mus2midi.cpp
name.cpp
nodebuild.cpp
nodebuild_classify_nosse2.cpp
@ -881,11 +880,12 @@ add_executable( zdoom WIN32
sound/music_cd.cpp
sound/music_dumb.cpp
sound/music_gme.cpp
sound/music_mus_midiout.cpp
sound/music_smf_midiout.cpp
sound/music_hmi_midiout.cpp
sound/music_midistream.cpp
sound/music_midi_base.cpp
sound/music_midi_midiout.cpp
sound/music_midi_timidity.cpp
sound/music_mus_midiout.cpp
sound/music_mus_opl.cpp
sound/music_stream.cpp
sound/music_fluidsynth_mididevice.cpp

View file

@ -324,7 +324,7 @@ void FVertexBuffer::CreateVBO()
gl.BindBuffer(GL_ARRAY_BUFFER, vbo_id);
gl.BufferData(GL_ARRAY_BUFFER, vbo_shadowdata.Size() * sizeof(FVBOVertex), &vbo_shadowdata[0], GL_DYNAMIC_DRAW);
}
else
else if (sectors)
{
// set all VBO info to invalid values so that we can save some checks in the rendering code
for(int i=0;i<numsectors;i++)

View file

@ -69,6 +69,7 @@ void gl_ParseBrightmap(FScanner &sc, int);
void gl_ParseHardwareShader(FScanner &sc, int deflump);
void gl_ParseSkybox(FScanner &sc);
void gl_ParseDetailTexture(FScanner &sc);
void gl_ParseBillboards(FScanner &sc);
void gl_ParseVavoomSkybox();
//==========================================================================
@ -856,6 +857,7 @@ static const char *CoreKeywords[]=
"disable_fullbright",
"hardwareshader",
"detail",
"billboards",
"#include",
NULL
};
@ -878,6 +880,7 @@ enum
TAG_DISABLE_FB,
TAG_HARDWARESHADER,
TAG_DETAIL,
TAG_BILLBOARDS,
TAG_INCLUDE,
};
@ -1269,6 +1272,9 @@ void gl_DoParseDefs(FScanner &sc, int workingLump)
case TAG_DETAIL:
gl_ParseDetailTexture(sc);
break;
case TAG_BILLBOARDS:
gl_ParseBillboards(sc);
break;
case TAG_DISABLE_FB:
{
/* not implemented.
@ -1358,3 +1364,64 @@ void AddStateLight(FState *State, const char *lname)
ParsedStateLights.Push(FName(lname));
}
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void gl_ParseBillboards(FScanner &sc)
{
// check for opening brace
sc.GetString();
if (sc.Compare("{"))
{
sc.GetString();
// Until end of block
while (!sc.Compare("}"))
{
// Identifier
bool billboardXY;
if (sc.Compare("x"))
billboardXY = false;
else if (sc.Compare("xy"))
billboardXY = true;
else sc.ScriptError("Expected 'x' or 'xy', got '%s'.\n", sc.String);
sc.GetString();
if (sc.Compare("{"))
{
sc.GetString();
// Until end of block
while (!sc.Compare("}"))
{
const PClass * infoc = PClass::FindClass(sc.String);
if (infoc != NULL)
{
AActor * info = (AActor*)infoc->Defaults;
if (billboardXY)
{
info->renderflags |= RF_FORCEXYBILLBOARD;
info->renderflags &= ~RF_FORCEYBILLBOARD;
}
else
{
info->renderflags |= RF_FORCEYBILLBOARD;
info->renderflags &= ~RF_FORCEXYBILLBOARD;
}
}
else sc.ScriptMessage("Non-existent actor class '%s'.\n", sc.String);
sc.GetString();
}
}
sc.GetString();
}
}
else
{
sc.ScriptError("Expected '{'.\n");
}
}

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

@ -3412,17 +3412,13 @@ void P_FreeLevelData ()
sectors = NULL;
numsectors = 0; // needed for the pointer cleanup code
}
if (gamenodes && gamenodes!=nodes)
if (gamenodes != NULL && gamenodes != nodes)
{
delete [] gamenodes;
gamenodes = NULL;
numgamenodes = 0;
delete[] gamenodes;
}
if (gamesubsectors && gamesubsectors!=subsectors)
if (gamesubsectors != NULL && gamesubsectors != subsectors)
{
delete [] gamesubsectors;
gamesubsectors = NULL;
numgamesubsectors = 0;
delete[] gamesubsectors;
}
if (subsectors != NULL)
{
@ -3434,13 +3430,15 @@ void P_FreeLevelData ()
}
}
delete[] subsectors;
subsectors = NULL;
}
if (nodes != NULL)
{
delete[] nodes;
nodes = NULL;
}
subsectors = gamesubsectors = NULL;
numsubsectors = numgamesubsectors = 0;
nodes = gamenodes = NULL;
numnodes = numgamenodes = 0;
if (lines != NULL)
{
delete[] lines;

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,165 +447,147 @@ 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
miditype = MIDI_MUS;
}
// 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'))
{
miditype = MIDI_HMI;
}
// Check for MIDI format
else if (id[0] == MAKE_ID('M','T','h','d'))
{
miditype = MIDI_MIDI;
}
Timidity:
- if explicitly selected by $mididevice
- when snd_mididevice is -2 and no midi device is set for the song
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
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
- Timidity:
- if explicitly selected by $mididevice
- when snd_mididevice is -2 and no midi device is set for the song
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
- 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)
EMIDIDevice devtype = MIDI_Null;
// Choose the type of MIDI device we want.
if (device == MDEV_FMOD || (snd_mididevice == -1 && device == MDEV_DEFAULT))
{
info = new MUSSong2 (file, musiccache, len, MIDI_OPL);
devtype = MIDI_FMOD;
}
else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2))
else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT))
{
info = new TimiditySong (file, musiccache, len);
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 MUSSong2(file, musiccache, len, MIDI_Timidity);
devtype = MIDI_GUS;
}
#ifdef HAVE_FLUIDSYNTH
else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
{
info = new MUSSong2(file, musiccache, len, MIDI_Fluid);
devtype = MIDI_Fluid;
}
#endif
#ifdef _WIN32
else
{
devtype = MIDI_Win;
}
#endif
retry_as_fmod:
if (miditype != MIDI_MIDI && devtype >= MIDI_Null)
{
// Convert to standard MIDI for external sequencers.
MIDIStreamer *streamer;
if (miditype == MIDI_MUS)
{
streamer = new MUSSong2(file, musiccache, len, MIDI_Null);
}
else
{
assert(miditype == MIDI_HMI);
streamer = new HMISong(file, musiccache, len, MIDI_Null);
}
if (streamer->IsValid())
{
streamer->CreateSMF(midi);
miditype = MIDI_MIDI;
musiccache = &midi[0];
len = midi.Size();
if (file != NULL)
{
fclose(file);
file = NULL;
}
}
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 && (snd_mididevice == -1 || device == MDEV_FMOD) && device != MDEV_MMAPI)
if (info == NULL && devtype != MIDI_FMOD && snd_mididevice < 0)
{
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;
}
}
devtype = MIDI_FMOD;
goto retry_as_fmod;
}
#ifdef _WIN32
if (info == NULL)
if (info == NULL && devtype != MIDI_Win && snd_mididevice >= 0)
{
info = new MUSSong2 (file, musiccache, len, MIDI_Win);
info = CreateMIDISong(file, filename, musiccache, offset, len, MIDI_Win, miditype);
}
#endif // _WIN32
}
// 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
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))
{
info = new MIDISong2 (file, musiccache, len, MIDI_OPL);
}
else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT))
{
info = new TimiditySong (file, musiccache, len);
}
else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
{
info = new MIDISong2(file, musiccache, len, MIDI_Timidity);
}
#ifdef HAVE_FLUIDSYNTH
else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
{
info = new MIDISong2(file, musiccache, len, MIDI_Fluid);
}
#endif
if (info != NULL && !info->IsValid())
{
delete info;
info = NULL;
device = MDEV_DEFAULT;
}
#ifdef _WIN32
if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI))
{
info = new MIDISong2 (file, musiccache, len, MIDI_Win);
}
#endif // _WIN32
}
// 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
(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) || // DosBox Raw OPL
(id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B')) // Martin Fernandez's modified IMF
{
info = new OPLMUSSong (file, musiccache, len);
}
// Check for game music
else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0')
{
info = GME_OpenSong(file, musiccache, len, fmt);
}
// Check for module formats
else
{
info = MOD_OpenSong(file, musiccache, len);
}
}
// 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
(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) || // DosBox Raw OPL
(id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B')) // Martin Fernandez's modified IMF
{
info = new OPLMUSSong (file, musiccache, len);
}
// Check for game music
else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0')
{
info = GME_OpenSong(file, musiccache, len, fmt);
}
// Check for module formats
else
{
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,11 +375,11 @@ 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;
virtual void Precache() = 0;
virtual void Precache();
virtual DWORD *MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) = 0;
enum
@ -413,6 +419,7 @@ protected:
DWORD Volume;
EMIDIDevice DeviceType;
bool CallbackIsThreaded;
bool IgnoreLoops;
FString DumpFilename;
};
@ -456,11 +463,10 @@ 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();
void Precache();
DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time);
void AdvanceTracks(DWORD time);
@ -480,6 +486,64 @@ protected:
WORD DesignationMask;
};
// HMI file played with a MIDI stream ---------------------------------------
class HMISong : public MIDIStreamer
{
public:
HMISong(FILE *file, BYTE *musiccache, int length, EMIDIDevice type);
~HMISong();
MusInfo *GetOPLDumper(const char *filename);
MusInfo *GetWaveDumper(const char *filename, int rate);
protected:
HMISong(const HMISong *original, const char *filename, EMIDIDevice type); // file dump constructor
void CheckCaps(int tech);
void DoInitialSetup();
void DoRestart();
bool CheckDone();
DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time);
void AdvanceTracks(DWORD time);
struct TrackInfo;
void ProcessInitialMetaEvents ();
DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay);
TrackInfo *FindNextDue ();
void SetTempo(int new_tempo);
struct AutoNoteOff
{
DWORD Delay;
BYTE Channel, Key;
};
// Sorry, std::priority_queue, but I want to be able to modify the contents of the heap.
class NoteOffQueue : public TArray<AutoNoteOff>
{
public:
void AddNoteOff(DWORD delay, BYTE channel, BYTE key);
void AdvanceTime(DWORD time);
bool Pop(AutoNoteOff &item);
protected:
void Heapify();
unsigned int Parent(unsigned int i) { return (i + 1u) / 2u - 1u; }
unsigned int Left(unsigned int i) { return (i + 1u) * 2u - 1u; }
unsigned int Right(unsigned int i) { return (i + 1u) * 2u; }
};
BYTE *MusHeader;
int SongLen;
int NumTracks;
TrackInfo *Tracks;
TrackInfo *TrackDue;
TrackInfo *FakeTrack;
NoteOffQueue NoteOffs;
};
// Anything supported by FMOD out of the box --------------------------------
class StreamSong : public MusInfo

View file

@ -0,0 +1,880 @@
/*
** music_midi_midiout.cpp
** Code to let ZDoom play HMI MIDI music through the MIDI streaming API.
**
**---------------------------------------------------------------------------
** Copyright 2010 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include "i_musicinterns.h"
#include "templates.h"
#include "doomdef.h"
#include "m_swap.h"
// MACROS ------------------------------------------------------------------
#define SONG_MAGIC "HMI-MIDISONG"
#define TRACK_MAGIC "HMI-MIDITRACK"
// Used by SendCommand to check for unexpected end-of-track conditions.
#define CHECK_FINISHED \
if (track->TrackP >= track->MaxTrackP) \
{ \
track->Finished = true; \
return events; \
}
// In song header
#define TRACK_COUNT_OFFSET 0xE4
#define TRACK_DIR_PTR_OFFSET 0xE8
// In track header
#define TRACK_DATA_PTR_OFFSET 0x57
#define TRACK_DESIGNATION_OFFSET 0x99
#define NUM_DESIGNATIONS 8
// MIDI device types for designation
#define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device)
#define HMI_DEV_MPU401 0xA001 // MPU-401, Roland Sound Canvas, Ensoniq SoundScape, Rolad RAP-10
#define HMI_DEV_OPL2 0xA002 // SoundBlaster (Pro), ESS AudioDrive
#define HMI_DEV_MT32 0xA004 // MT-32
#define HMI_DEV_SBAWE32 0xA008 // SoundBlaster AWE32
#define HMI_DEV_OPL3 0xA009 // SoundBlaster 16, Microsoft Sound System, Pro Audio Spectrum 16
#define HMI_DEV_GUS 0xA00A // Gravis UltraSound, Gravis UltraSound Max/Ace
// Data accessors, since this data is highly likely to be unaligned.
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__)
inline int GetShort(const BYTE *foo)
{
return *(const short *)foo;
}
inline int GetInt(const BYTE *foo)
{
return *(const int *)foo;
}
#else
inline int GetShort(const BYTE *foo)
{
return short(foo[0] | (foo[1] << 8));
}
inline int GetInt(const BYTE *foo)
{
return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24));
}
#endif
// TYPES -------------------------------------------------------------------
struct HMISong::TrackInfo
{
const BYTE *TrackBegin;
size_t TrackP;
size_t MaxTrackP;
DWORD Delay;
DWORD PlayedTime;
WORD Designation[NUM_DESIGNATIONS];
bool Enabled;
bool Finished;
BYTE RunningStatus;
DWORD ReadVarLen ();
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern char MIDI_EventLengths[7];
extern char MIDI_CommonLengths[15];
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// HMISong Constructor
//
// Buffers the file and does some validation of the HMI header.
//
//==========================================================================
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)
{
return;
}
#endif
if (len < 0x100)
{ // Way too small to be HMI.
return;
}
MusHeader = new BYTE[len];
SongLen = len;
if (file != NULL)
{
if (fread(MusHeader, 1, len, file) != (size_t)len)
return;
}
else
{
memcpy(MusHeader, musiccache, len);
}
// Do some validation of the MIDI file
if (memcmp(MusHeader, SONG_MAGIC, 12) != 0)
return;
NumTracks = GetShort(MusHeader + TRACK_COUNT_OFFSET);
if (NumTracks <= 0)
{
return;
}
// The division is the number of pulses per quarter note (PPQN).
Division = 60;
Tracks = new TrackInfo[NumTracks + 1];
int track_dir = GetInt(MusHeader + TRACK_DIR_PTR_OFFSET);
// Gather information about each track
for (i = 0, p = 0; i < NumTracks; ++i)
{
int start = GetInt(MusHeader + track_dir + i*4);
int tracklen, datastart;
if (start > len - TRACK_DESIGNATION_OFFSET - 4)
{ // Track is incomplete.
continue;
}
// BTW, HMI does not actually check the track header.
if (memcmp(MusHeader + start, TRACK_MAGIC, 13) != 0)
{
continue;
}
// The track ends where the next one begins. If this is the
// last track, then it ends at the end of the file.
if (i == NumTracks - 1)
{
tracklen = len - start;
}
else
{
tracklen = GetInt(MusHeader + track_dir + i*4 + 4) - start;
}
// Clamp incomplete tracks to the end of the file.
tracklen = MIN(tracklen, len - start);
if (tracklen <= 0)
{
continue;
}
// Offset to actual MIDI events.
datastart = GetInt(MusHeader + start + TRACK_DATA_PTR_OFFSET);
tracklen -= datastart;
if (tracklen <= 0)
{
continue;
}
// Store track information
Tracks[p].TrackBegin = MusHeader + start + datastart;
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.
for (int ii = 0; ii < NUM_DESIGNATIONS; ++ii)
{
Tracks[p].Designation[ii] = GetShort(MusHeader + start + TRACK_DESIGNATION_OFFSET + ii*2);
}
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;
if (NumTracks == 0)
{ // No tracks, so nothing to play
return;
}
}
//==========================================================================
//
// HMISong Destructor
//
//==========================================================================
HMISong::~HMISong ()
{
if (Tracks != NULL)
{
delete[] Tracks;
}
if (MusHeader != NULL)
{
delete[] MusHeader;
}
}
//==========================================================================
//
// HMISong :: CheckCaps
//
// Check track designations and disable tracks that have not been
// designated for the device we will be playing on.
//
//==========================================================================
void HMISong::CheckCaps(int tech)
{
// What's the equivalent HMI device for our technology?
if (tech == MOD_FMSYNTH)
{
tech = HMI_DEV_OPL3;
}
else if (tech == MOD_MIDIPORT)
{
tech = HMI_DEV_MPU401;
}
else
{ // Good enough? Or should we just say we're GM.
tech = HMI_DEV_SBAWE32;
}
for (int i = 0; i < NumTracks; ++i)
{
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)
{
if (Tracks[i].Designation[j] == tech)
{
Tracks[i].Enabled = true;
}
// If a track is designated for device 0xA000, it will be played by a MIDI
// driver for device types 0xA000, 0xA001, and 0xA008. Why this does not
// include the GUS, I do not know.
else if (Tracks[i].Designation[j] == HMI_DEV_GM)
{
Tracks[i].Enabled = (tech == HMI_DEV_MPU401 || tech == HMI_DEV_SBAWE32);
}
// If a track is designated for device 0xA002, it will be played by a MIDI
// driver for device types 0xA002 or 0xA009.
else if (Tracks[i].Designation[j] == HMI_DEV_OPL2)
{
Tracks[i].Enabled = (tech == HMI_DEV_OPL3);
}
// Any other designation must match the specific MIDI driver device number.
// (Which we handled first above.)
if (Tracks[i].Enabled)
{ // This track's been enabled, so we can stop checking other designations.
break;
}
}
}
}
//==========================================================================
//
// HMISong :: DoInitialSetup
//
// Sets the starting channel volumes.
//
//==========================================================================
void HMISong :: DoInitialSetup()
{
for (int i = 0; i < 16; ++i)
{
ChannelVolumes[i] = 100;
}
}
//==========================================================================
//
// HMISong :: DoRestart
//
// Rewinds every track.
//
//==========================================================================
void HMISong :: DoRestart()
{
int i;
// Set initial state.
FakeTrack = &Tracks[NumTracks];
NoteOffs.Clear();
for (i = 0; i <= NumTracks; ++i)
{
Tracks[i].TrackP = 0;
Tracks[i].Finished = false;
Tracks[i].RunningStatus = 0;
Tracks[i].PlayedTime = 0;
}
ProcessInitialMetaEvents ();
for (i = 0; i < NumTracks; ++i)
{
Tracks[i].Delay = Tracks[i].ReadVarLen();
}
Tracks[i].Delay = 0; // for the FakeTrack
Tracks[i].Enabled = true;
TrackDue = Tracks;
TrackDue = FindNextDue();
}
//==========================================================================
//
// HMISong :: CheckDone
//
//==========================================================================
bool HMISong::CheckDone()
{
return TrackDue == NULL;
}
//==========================================================================
//
// HMISong :: MakeEvents
//
// Copies MIDI events from the file and puts them into a MIDI stream
// buffer. Returns the new position in the buffer.
//
//==========================================================================
DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
{
DWORD *start_events;
DWORD tot_time = 0;
DWORD time = 0;
DWORD delay;
start_events = events;
while (TrackDue && events < max_event_p && tot_time <= max_time)
{
// It's possible that this tick may be nothing but meta-events and
// not generate any real events. Repeat this until we actually
// get some output so we don't send an empty buffer to the MIDI
// device.
do
{
delay = TrackDue->Delay;
time += delay;
// Advance time for all tracks by the amount needed for the one up next.
tot_time += delay * Tempo / Division;
AdvanceTracks(delay);
// Play all events for this tick.
do
{
DWORD *new_events = SendCommand(events, TrackDue, time);
TrackDue = FindNextDue();
if (new_events != events)
{
time = 0;
}
events = new_events;
}
while (TrackDue && TrackDue->Delay == 0 && events < max_event_p);
}
while (start_events == events && TrackDue);
time = 0;
}
return events;
}
//==========================================================================
//
// HMISong :: AdvanceTracks
//
// Advaces time for all tracks by the specified amount.
//
//==========================================================================
void HMISong::AdvanceTracks(DWORD time)
{
for (int i = 0; i <= NumTracks; ++i)
{
if (Tracks[i].Enabled && !Tracks[i].Finished)
{
Tracks[i].Delay -= time;
Tracks[i].PlayedTime += time;
}
}
NoteOffs.AdvanceTime(time);
}
//==========================================================================
//
// HMISong :: SendCommand
//
// Places a single MIDIEVENT in the event buffer.
//
//==========================================================================
DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
{
DWORD len;
BYTE event, data1 = 0, data2 = 0;
// If the next event comes from the fake track, pop an entry off the note-off queue.
if (track == FakeTrack)
{
AutoNoteOff off;
NoteOffs.Pop(off);
events[0] = delay;
events[1] = 0;
events[2] = MIDI_NOTEON | off.Channel | (off.Key << 8);
return events + 3;
}
CHECK_FINISHED
event = track->TrackBegin[track->TrackP++];
CHECK_FINISHED
if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe)
{
// Normal short message
if ((event & 0xF0) == 0xF0)
{
if (MIDI_CommonLengths[event & 15] > 0)
{
data1 = track->TrackBegin[track->TrackP++];
if (MIDI_CommonLengths[event & 15] > 1)
{
data2 = track->TrackBegin[track->TrackP++];
}
}
}
else if ((event & 0x80) == 0)
{
data1 = event;
event = track->RunningStatus;
}
else
{
track->RunningStatus = event;
data1 = track->TrackBegin[track->TrackP++];
}
CHECK_FINISHED
if (MIDI_EventLengths[(event&0x70)>>4] == 2)
{
data2 = track->TrackBegin[track->TrackP++];
}
// Monitor channel volume controller changes.
if ((event & 0x70) == (MIDI_CTRLCHANGE & 0x70) && data1 == 7)
{
data2 = VolumeControllerChange(event & 15, data2);
}
events[0] = delay;
events[1] = 0;
if (event != MIDI_META)
{
events[2] = event | (data1<<8) | (data2<<16);
}
else
{
events[2] = MEVT_NOP;
}
events += 3;
if ((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);
}
}
else
{
// Skip SysEx events just because I don't want to bother with them.
// The old MIDI player ignored them too, so this won't break
// anything that played before.
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{
len = track->ReadVarLen ();
track->TrackP += len;
}
else if (event == MIDI_META)
{
// It's a meta-event
event = track->TrackBegin[track->TrackP++];
CHECK_FINISHED
len = track->ReadVarLen ();
CHECK_FINISHED
if (track->TrackP + len <= track->MaxTrackP)
{
switch (event)
{
case MIDI_META_EOT:
track->Finished = true;
break;
case MIDI_META_TEMPO:
Tempo =
(track->TrackBegin[track->TrackP+0]<<16) |
(track->TrackBegin[track->TrackP+1]<<8) |
(track->TrackBegin[track->TrackP+2]);
events[0] = delay;
events[1] = 0;
events[2] = (MEVT_TEMPO << 24) | Tempo;
events += 3;
break;
}
track->TrackP += len;
if (track->TrackP == track->MaxTrackP)
{
track->Finished = true;
}
}
else
{
track->Finished = true;
}
}
else if (event == 0xFE)
{ // Skip unknown HMI events.
event = track->TrackBegin[track->TrackP++];
CHECK_FINISHED
if (event == 0x13 || event == 0x15)
{
track->TrackP += 6;
}
else if (event == 0x12 || event == 0x14)
{
track->TrackP += 2;
}
else if (event == 0x10)
{
track->TrackP += 2;
CHECK_FINISHED
track->TrackP += track->TrackBegin[track->TrackP] + 5;
CHECK_FINISHED
}
else
{ // No idea.
track->Finished = true;
}
}
}
if (!track->Finished)
{
track->Delay = track->ReadVarLen();
}
return events;
}
//==========================================================================
//
// HMISong :: ProcessInitialMetaEvents
//
// Handle all the meta events at the start of each track.
//
//==========================================================================
void HMISong::ProcessInitialMetaEvents ()
{
TrackInfo *track;
int i;
BYTE event;
DWORD len;
for (i = 0; i < NumTracks; ++i)
{
track = &Tracks[i];
while (!track->Finished &&
track->TrackP < track->MaxTrackP - 4 &&
track->TrackBegin[track->TrackP] == 0 &&
track->TrackBegin[track->TrackP+1] == 0xFF)
{
event = track->TrackBegin[track->TrackP+2];
track->TrackP += 3;
len = track->ReadVarLen ();
if (track->TrackP + len <= track->MaxTrackP)
{
switch (event)
{
case MIDI_META_EOT:
track->Finished = true;
break;
case MIDI_META_TEMPO:
SetTempo(
(track->TrackBegin[track->TrackP+0]<<16) |
(track->TrackBegin[track->TrackP+1]<<8) |
(track->TrackBegin[track->TrackP+2])
);
break;
}
}
track->TrackP += len;
}
if (track->TrackP >= track->MaxTrackP - 4)
{
track->Finished = true;
}
}
}
//==========================================================================
//
// HMISong :: TrackInfo :: ReadVarLen
//
// Reads a variable-length SMF number.
//
//==========================================================================
DWORD HMISong::TrackInfo::ReadVarLen ()
{
DWORD time = 0, t = 0x80;
while ((t & 0x80) && TrackP < MaxTrackP)
{
t = TrackBegin[TrackP++];
time = (time << 7) | (t & 127);
}
return time;
}
//==========================================================================
//
// HMISong :: NoteOffQueue :: AddNoteOff
//
//==========================================================================
void HMISong::NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key)
{
unsigned int i = Reserve(1);
while (i > 0 && (*this)[Parent(i)].Delay > delay)
{
(*this)[i] = (*this)[Parent(i)];
i = Parent(i);
}
(*this)[i].Delay = delay;
(*this)[i].Channel = channel;
(*this)[i].Key = key;
}
//==========================================================================
//
// HMISong :: NoteOffQueue :: Pop
//
//==========================================================================
bool HMISong::NoteOffQueue::Pop(AutoNoteOff &item)
{
item = (*this)[0];
if (TArray::Pop((*this)[0]))
{
Heapify();
return true;
}
return false;
}
//==========================================================================
//
// HMISong :: NoteOffQueue :: AdvanceTime
//
//==========================================================================
void HMISong::NoteOffQueue::AdvanceTime(DWORD time)
{
// Because the time is decreasing by the same amount for every entry,
// the heap property is maintained.
for (unsigned int i = 0; i < Size(); ++i)
{
assert((*this)[i].Delay >= time);
(*this)[i].Delay -= time;
}
}
//==========================================================================
//
// HMISong :: NoteOffQueue :: Heapify
//
//==========================================================================
void HMISong::NoteOffQueue::Heapify()
{
unsigned int i = 0;
for (;;)
{
unsigned int l = Left(i);
unsigned int r = Right(i);
unsigned int smallest = i;
if (l < Size() && (*this)[l].Delay < (*this)[i].Delay)
{
smallest = l;
}
if (r < Size() && (*this)[r].Delay < (*this)[smallest].Delay)
{
smallest = r;
}
if (smallest == i)
{
break;
}
swapvalues((*this)[i], (*this)[smallest]);
i = smallest;
}
}
//==========================================================================
//
// HMISong :: FindNextDue
//
// Scans every track for the next event to play. Returns NULL if all events
// have been consumed.
//
//==========================================================================
HMISong::TrackInfo *HMISong::FindNextDue ()
{
TrackInfo *track;
DWORD best;
int i;
if (TrackDue != FakeTrack && !TrackDue->Finished && TrackDue->Delay == 0)
{
return TrackDue;
}
// Check regular tracks.
track = NULL;
best = 0xFFFFFFFF;
for (i = 0; i < NumTracks; ++i)
{
if (Tracks[i].Enabled && !Tracks[i].Finished && Tracks[i].Delay < best)
{
best = Tracks[i].Delay;
track = &Tracks[i];
}
}
// Check automatic note-offs.
if (NoteOffs.Size() != 0 && NoteOffs[0].Delay <= best)
{
FakeTrack->Delay = NoteOffs[0].Delay;
return FakeTrack;
}
return track;
}
//==========================================================================
//
// HMISong :: SetTempo
//
// Sets the tempo from a track's initial meta events.
//
//==========================================================================
void HMISong::SetTempo(int new_tempo)
{
if (0 == MIDI->SetTempo(new_tempo))
{
Tempo = new_tempo;
}
}
//==========================================================================
//
// HMISong :: GetOPLDumper
//
//==========================================================================
MusInfo *HMISong::GetOPLDumper(const char *filename)
{
return new HMISong(this, filename, MIDI_OPL);
}
//==========================================================================
//
// HMISong :: GetWaveDumper
//
//==========================================================================
MusInfo *HMISong::GetWaveDumper(const char *filename, int rate)
{
return new HMISong(this, filename, MIDI_GUS);
}
//==========================================================================
//
// HMISong File Dumping Constructor
//
//==========================================================================
HMISong::HMISong(const HMISong *original, const char *filename, EMIDIDevice type)
: MIDIStreamer(filename, type)
{
SongLen = original->SongLen;
MusHeader = new BYTE[original->SongLen];
memcpy(MusHeader, original->MusHeader, original->SongLen);
NumTracks = original->NumTracks;
Division = original->Division;
Tempo = InitialTempo = original->InitialTempo;
Tracks = new TrackInfo[NumTracks];
for (int i = 0; i < NumTracks; ++i)
{
TrackInfo *newtrack = &Tracks[i];
const TrackInfo *oldtrack = &original->Tracks[i];
newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader);
newtrack->TrackP = 0;
newtrack->MaxTrackP = oldtrack->MaxTrackP;
}
}

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')
{
success = (fwrite (buf, 1, len, f) == (size_t)len);
}
else
{
success = ProduceMIDI (buf, len, f);
}
// Write to temporary file
success = (fwrite (buf, 1, len, f) == (size_t)len);
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,8 +259,9 @@ void MIDIStreamer::Play(bool looping, int subsong)
return;
}
CheckCaps();
CheckCaps(MIDI->GetTechnology());
Precache();
IgnoreLoops = false;
// Set time division and tempo.
if (0 != MIDI->SetTimeDiv(Division) ||
@ -514,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;
}
//==========================================================================
@ -734,7 +754,7 @@ fill:
//
// MIDIStreamer :: FillBuffer
//
// Copies MIDI events from the SMF and puts them into a MIDI stream
// Copies MIDI events from the MIDI file and puts them into a MIDI stream
// buffer. Filling the buffer stops when the song end is encountered, the
// buffer space is used up, or the maximum time for a buffer is hit.
//
@ -789,7 +809,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
}
// Play nothing while paused.
if (m_Status == STATE_Paused)
if (m_Status == STATE_Paused && Tempo)
{
// Be more responsive when unpausing by only playing each buffer
// for a third of the maximum time.
@ -829,6 +849,241 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
return SONG_MORE;
}
//==========================================================================
//
// MIDIStreamer :: Precache
//
// 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.
//
//==========================================================================
void MIDIStreamer::Precache()
{
BYTE found_instruments[256] = { 0, };
BYTE found_banks[256] = { 0, };
bool multiple_banks = false;
IgnoreLoops = true;
DoRestart();
found_banks[0] = true; // Bank 0 is always used.
found_banks[128] = true;
// Simulate playback to pick out used instruments.
while (!CheckDone())
{
DWORD *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600);
for (DWORD *event = Events[0]; event < event_end; )
{
if (MEVT_EVENTTYPE(event[2]) == 0)
{
int command = (event[2] & 0x70);
int channel = (event[2] & 0x0f);
int data1 = (event[2] >> 8) & 0x7f;
int data2 = (event[2] >> 16) & 0x7f;
if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70))
{
found_instruments[data1] = true;
}
else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0)
{ // On a percussion channel, program change also serves as bank select.
multiple_banks = true;
found_banks[data1 | 128] = true;
}
else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0)
{
found_instruments[data1 | 128] = true;
}
else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0)
{
multiple_banks = true;
if (channel == 9)
{
found_banks[data2 | 128] = true;
}
else
{
found_banks[data2] = true;
}
}
}
// Advance to next event
if (event[2] < 0x80000000)
{ // short message
event += 3;
}
else
{ // long message
event += 3 + ((MEVT_EVENTPARM(event[2]) + 3) >> 2);
}
}
}
DoRestart();
// Now pack everything into a contiguous region for the PrecacheInstruments call().
TArray<WORD> packed;
for (int i = 0; i < 256; ++i)
{
if (found_instruments[i])
{
WORD packnum = (i & 127) | ((i & 128) << 7);
if (!multiple_banks)
{
packed.Push(packnum);
}
else
{ // In order to avoid having to multiplex tracks in a type 1 file,
// precache every used instrument in every used bank, even if not
// all combinations are actually used.
for (int j = 0; j < 128; ++j)
{
if (found_banks[j + (i & 128)])
{
packed.Push(packnum | (j << 7));
}
}
}
}
}
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

@ -253,7 +253,7 @@ DWORD *MUSSong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
DWORD tot_time = 0;
DWORD time = 0;
max_time = max_time * Division / Tempo;
if (Tempo) max_time = max_time * Division / Tempo;
while (events < max_event_p && tot_time <= max_time)
{
@ -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

@ -86,11 +86,11 @@ struct MIDISong2::TrackInfo
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 };
static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// PUBLIC DATA DEFINITIONS -------------------------------------------------
char MIDI_EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 };
char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// CODE --------------------------------------------------------------------
//==========================================================================
@ -216,10 +216,8 @@ MIDISong2::~MIDISong2 ()
//
//==========================================================================
void MIDISong2::CheckCaps()
void MIDISong2::CheckCaps(int tech)
{
int tech = MIDI->GetTechnology();
DesignationMask = 0xFF0F;
if (tech == MOD_FMSYNTH)
{
@ -389,10 +387,10 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
// Normal short message
if ((event & 0xF0) == 0xF0)
{
if (CommonLengths[event & 15] > 0)
if (MIDI_CommonLengths[event & 15] > 0)
{
data1 = track->TrackBegin[track->TrackP++];
if (CommonLengths[event & 15] > 1)
if (MIDI_CommonLengths[event & 15] > 1)
{
data2 = track->TrackBegin[track->TrackP++];
}
@ -411,7 +409,7 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
CHECK_FINISHED
if (EventLengths[(event&0x70)>>4] == 2)
if (MIDI_EventLengths[(event&0x70)>>4] == 2)
{
data2 = track->TrackBegin[track->TrackP++];
}
@ -500,10 +498,13 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
break;
case 116: // EMIDI Loop Begin
track->LoopBegin = track->TrackP;
track->LoopDelay = 0;
track->LoopCount = data2;
track->LoopFinished = track->Finished;
if (!IgnoreLoops)
{
track->LoopBegin = track->TrackP;
track->LoopDelay = 0;
track->LoopCount = data2;
track->LoopFinished = track->Finished;
}
event = MIDI_META;
break;
@ -529,12 +530,15 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
break;
case 118: // EMIDI Global Loop Begin
for (i = 0; i < NumTracks; ++i)
if (!IgnoreLoops)
{
Tracks[i].LoopBegin = Tracks[i].TrackP;
Tracks[i].LoopDelay = Tracks[i].Delay;
Tracks[i].LoopCount = data2;
Tracks[i].LoopFinished = Tracks[i].Finished;
for (i = 0; i < NumTracks; ++i)
{
Tracks[i].LoopBegin = Tracks[i].TrackP;
Tracks[i].LoopDelay = Tracks[i].Delay;
Tracks[i].LoopCount = data2;
Tracks[i].LoopFinished = Tracks[i].Finished;
}
}
event = MIDI_META;
break;
@ -709,7 +713,7 @@ DWORD MIDISong2::TrackInfo::ReadVarLen ()
//==========================================================================
//
// MIDISong2 :: TrackInfo :: FindNextDue
// MIDISong2 :: FindNextDue
//
// Scans every track for the next event to play. Returns NULL if all events
// have been consumed.
@ -776,135 +780,6 @@ void MIDISong2::SetTempo(int new_tempo)
}
}
//==========================================================================
//
// MIDISong2 :: Precache
//
// Scans each track for program change events on normal channels and note on
// events on channel 10. Does not care about bank selects, since they're
// unlikely to appear in a song aimed at Doom.
//
//==========================================================================
void MIDISong2::Precache()
{
// This array keeps track of instruments that are used. The first 128
// entries are for melodic instruments. The second 128 are for
// percussion.
BYTE found_instruments[256] = { 0, };
BYTE found_banks[256] = { 0, };
bool multiple_banks = false;
int i, j;
DoRestart();
found_banks[0] = true; // Bank 0 is always used.
found_banks[128] = true;
for (i = 0; i < NumTracks; ++i)
{
TrackInfo *track = &Tracks[i];
BYTE running_status = 0;
BYTE ev, data1, data2, command, channel;
int len;
data2 = 0; // Silence, GCC
while (track->TrackP < track->MaxTrackP)
{
ev = track->TrackBegin[track->TrackP++];
command = ev & 0xF0;
if (ev == MIDI_META)
{
track->TrackP++;
len = track->ReadVarLen();
track->TrackP += len;
}
else if (ev == MIDI_SYSEX || ev == MIDI_SYSEXEND)
{
len = track->ReadVarLen();
track->TrackP += len;
}
else if (command == 0xF0)
{
track->TrackP += CommonLengths[ev & 0x0F];
}
else
{
if ((ev & 0x80) == 0)
{ // Use running status.
data1 = ev;
ev = running_status;
}
else
{ // Store new running status.
running_status = ev;
data1 = track->TrackBegin[track->TrackP++];
}
command = ev & 0x70;
channel = ev & 0x0F;
if (EventLengths[command >> 4] == 2)
{
data2 = track->TrackBegin[track->TrackP++];
}
if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70))
{
found_instruments[data1 & 127] = true;
}
else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0)
{ // On a percussion channel, program change also serves as bank select.
multiple_banks = true;
found_banks[data1 | 128] = true;
}
else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0)
{
found_instruments[data1 | 128] = true;
}
else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0)
{
multiple_banks = true;
if (channel == 9)
{
found_banks[data2 | 128] = true;
}
else
{
found_banks[data2 & 127] = true;
}
}
}
track->ReadVarLen(); // Skip delay.
}
}
DoRestart();
// Now pack everything into a contiguous region for the PrecacheInstruments call().
TArray<WORD> packed;
for (i = 0; i < 256; ++i)
{
if (found_instruments[i])
{
WORD packnum = (i & 127) | ((i & 128) << 7);
if (!multiple_banks)
{
packed.Push(packnum);
}
else
{ // In order to avoid having to multiplex tracks in a type 1 file,
// precache every used instrument in every used bank, even if not
// all combinations are actually used.
for (j = 0; j < 128; ++j)
{
if (found_banks[j + (i & 128)])
{
packed.Push(packnum | (j << 7));
}
}
}
}
}
MIDI->PrecacheInstruments(&packed[0], packed.Size());
}
//==========================================================================
//
// MIDISong2 :: GetOPLDumper
@ -924,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

@ -3,5 +3,5 @@
// This file was automatically generated by the
// updaterevision tool. Do not edit by hand.
#define ZD_SVN_REVISION_STRING "2666"
#define ZD_SVN_REVISION_NUMBER 2666
#define ZD_SVN_REVISION_STRING "2677"
#define ZD_SVN_REVISION_NUMBER 2677

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

@ -21,6 +21,7 @@ A80E7EE40E0D0C76A6FBD242BE29FE27 // map15
2F1F8E27FBB5EF21AFBE1F3B13C03037 // map16
1CE294781A2455DE72C197E0B3DF6212 // map31
{
setslopeoverflow
resetplayerspeed
}