mirror of
https://github.com/ZDoom/gzdoom-last-svn.git
synced 2024-11-12 23:44:14 +00:00
* 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:
parent
b2aec4871a
commit
adb28b67b1
18 changed files with 1509 additions and 349 deletions
28
gzdoom.sln
28
gzdoom.sln
|
@ -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}"
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
880
src/sound/music_hmi_midiout.cpp
Normal file
880
src/sound/music_hmi_midiout.cpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
//==========================================================================
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -21,6 +21,7 @@ A80E7EE40E0D0C76A6FBD242BE29FE27 // map15
|
|||
2F1F8E27FBB5EF21AFBE1F3B13C03037 // map16
|
||||
1CE294781A2455DE72C197E0B3DF6212 // map31
|
||||
{
|
||||
setslopeoverflow
|
||||
resetplayerspeed
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue