- Pass SMF files through the MIDI conversion process too, to give TiMidity++ and FMOD some

degree of support for songs that use loop controllers to loop the song back to a point after
  the very beginning of the song.
- Enable loops during SMF generation. Infinite loops will be clamped to some finite amount. (This is currently 30, so a 3 minute song will still restart from the very beginning after 90 minutes)
- Fixed: The SMF, HMI, and XMI readers all generated invalid MEVT_NOP events.
- Fixed: SMF generation died on songs that set their tempo during the initial beat.


SVN r2864 (trunk)
This commit is contained in:
Randy Heit 2010-09-29 03:35:53 +00:00
parent 64784b2dc8
commit bc08502132
6 changed files with 101 additions and 77 deletions

View File

@ -545,12 +545,16 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int
#endif
retry_as_fmod:
if (miditype != MIDI_MIDI && devtype >= MIDI_Null)
if (devtype >= MIDI_Null)
{
// Convert to standard MIDI for external sequencers.
MIDIStreamer *streamer;
if (miditype == MIDI_MUS)
if (miditype == MIDI_MIDI)
{
streamer = new MIDISong2(file, musiccache, len, MIDI_Null);
}
else if (miditype == MIDI_MUS)
{
streamer = new MUSSong2(file, musiccache, len, MIDI_Null);
}

View File

@ -372,6 +372,8 @@ protected:
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
int ServiceEvent();
int VolumeControllerChange(int channel, int volume);
int ClampLoopCount(int loopcount);
void SetTempo(int new_tempo);
static void Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2);
@ -421,7 +423,7 @@ protected:
DWORD Volume;
EMIDIDevice DeviceType;
bool CallbackIsThreaded;
bool IgnoreLoops;
int LoopLimit;
FString DumpFilename;
};
@ -477,7 +479,6 @@ protected:
void ProcessInitialMetaEvents ();
DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay);
TrackInfo *FindNextDue ();
void SetTempo(int new_tempo);
BYTE *MusHeader;
int SongLen;
@ -537,7 +538,6 @@ protected:
void ProcessInitialMetaEvents ();
DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay);
TrackInfo *FindNextDue ();
void SetTempo(int new_tempo);
static DWORD ReadVarLenHMI(TrackInfo *);
static DWORD ReadVarLenHMP(TrackInfo *);
@ -581,7 +581,6 @@ protected:
void ProcessInitialMetaEvents();
DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay);
EventSource FindNextDue();
void SetTempo(int new_tempo);
BYTE *MusHeader;
int SongLen; // length of the entire file

View File

@ -636,7 +636,7 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
}
else
{
events[2] = MEVT_NOP;
events[2] = MEVT_NOP << 24;
}
events += 3;
@ -975,22 +975,6 @@ HMISong::TrackInfo *HMISong::FindNextDue ()
}
//==========================================================================
//
// 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

View File

@ -43,6 +43,9 @@
#define MAX_TIME (1000000/10) // Send out 1/10 of a sec of events at a time.
#define EXPORT_LOOP_LIMIT 30 // Maximum number of times to loop when exporting a MIDI file.
// (for songs with loop controller events)
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
@ -262,7 +265,7 @@ void MIDIStreamer::Play(bool looping, int subsong)
SetMIDISubsong(subsong);
CheckCaps(MIDI->GetTechnology());
Precache();
IgnoreLoops = false;
LoopLimit = 0;
// Set time division and tempo.
if (0 != MIDI->SetTimeDiv(Division) ||
@ -535,7 +538,9 @@ void MIDIStreamer::OutputVolume (DWORD volume)
int MIDIStreamer::VolumeControllerChange(int channel, int volume)
{
ChannelVolumes[channel] = volume;
return IgnoreLoops ? volume : ((volume + 1) * Volume) >> 16;
// If loops are limited, we can assume we're exporting this MIDI file,
// so we should not adjust the volume level.
return LoopLimit != 0 ? volume : ((volume + 1) * Volume) >> 16;
}
//==========================================================================
@ -867,7 +872,7 @@ void MIDIStreamer::Precache()
BYTE found_banks[256] = { 0, };
bool multiple_banks = false;
IgnoreLoops = true;
LoopLimit = 1;
DoRestart();
found_banks[0] = true; // Bank 0 is always used.
found_banks[128] = true;
@ -968,7 +973,7 @@ void MIDIStreamer::CreateSMF(TArray<BYTE> &file)
// Always create songs aimed at GM devices.
CheckCaps(MOD_MIDIPORT);
IgnoreLoops = true;
LoopLimit = EXPORT_LOOP_LIMIT;
DoRestart();
Tempo = InitialTempo;
@ -1053,7 +1058,7 @@ void MIDIStreamer::CreateSMF(TArray<BYTE> &file)
file[20] = BYTE(len >> 8);
file[21] = BYTE(len & 255);
IgnoreLoops = false;
LoopLimit = 0;
}
//==========================================================================
@ -1086,6 +1091,61 @@ static void WriteVarLen (TArray<BYTE> &file, DWORD value)
}
}
//==========================================================================
//
// MIDIStreamer :: SetTempo
//
// Sets the tempo from a track's initial meta events. Later tempo changes
// create MEVT_TEMPO events instead.
//
//==========================================================================
void MIDIStreamer::SetTempo(int new_tempo)
{
if (NULL == MIDI)
{
InitialTempo = new_tempo;
}
else if (0 == MIDI->SetTempo(new_tempo))
{
Tempo = new_tempo;
}
}
//==========================================================================
//
// MIDIStreamer :: ClampLoopCount
//
// We use the XMIDI interpretation of loop count here, where 1 means it
// plays that section once (in other words, no loop) rather than the EMIDI
// interpretation where 1 means to loop it once.
//
// If LoopLimit is 1, we limit all loops, since this pass over the song is
// used to determine instruments for precaching.
//
// If LoopLimit is higher, we only limit infinite loops, since this song is
// being exported.
//
//==========================================================================
int MIDIStreamer::ClampLoopCount(int loopcount)
{
if (LoopLimit == 0)
{
return loopcount;
}
if (LoopLimit == 1)
{
return 1;
}
if (loopcount == 0)
{
return LoopLimit;
}
return loopcount;
}
//==========================================================================
//
// MIDIStreamer :: GetStats

View File

@ -63,7 +63,6 @@ struct MIDISong2::TrackInfo
DWORD PlayedTime;
bool Finished;
BYTE RunningStatus;
SBYTE LoopCount;
bool Designated;
bool EProgramChange;
bool EVolume;
@ -71,6 +70,7 @@ struct MIDISong2::TrackInfo
size_t LoopBegin;
DWORD LoopDelay;
int LoopCount;
bool LoopFinished;
DWORD ReadVarLen ();
@ -498,12 +498,18 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
break;
case 116: // EMIDI Loop Begin
if (!IgnoreLoops)
{
track->LoopBegin = track->TrackP;
track->LoopDelay = 0;
track->LoopCount = data2;
track->LoopFinished = track->Finished;
// We convert the loop count to XMIDI conventions before clamping.
// Then we convert it back to EMIDI conventions after clamping.
// (XMIDI can create "loops" that don't loop. EMIDI cannot.)
int loopcount = ClampLoopCount(data2 == 0 ? 0 : data2 + 1);
if (loopcount != 1)
{
track->LoopBegin = track->TrackP;
track->LoopDelay = 0;
track->LoopCount = loopcount == 0 ? 0 : loopcount - 1;
track->LoopFinished = track->Finished;
}
}
event = MIDI_META;
break;
@ -530,14 +536,17 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
break;
case 118: // EMIDI Global Loop Begin
if (!IgnoreLoops)
{
for (i = 0; i < NumTracks; ++i)
int loopcount = ClampLoopCount(data2 == 0 ? 0 : data2 + 1);
if (loopcount != 1)
{
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 = loopcount == 0 ? 0 : loopcount - 1;
Tracks[i].LoopFinished = Tracks[i].Finished;
}
}
}
event = MIDI_META;
@ -579,7 +588,7 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
}
else
{
events[2] = MEVT_NOP;
events[2] = MEVT_NOP << 24;
}
events += 3;
}
@ -765,22 +774,6 @@ MIDISong2::TrackInfo *MIDISong2::FindNextDue ()
}
//==========================================================================
//
// MIDISong2 :: SetTempo
//
// Sets the tempo from a track's initial meta events.
//
//==========================================================================
void MIDISong2::SetTempo(int new_tempo)
{
if (0 == MIDI->SetTempo(new_tempo))
{
Tempo = new_tempo;
}
}
//==========================================================================
//
// MIDISong2 :: GetOPLDumper

View File

@ -61,7 +61,7 @@
struct LoopInfo
{
size_t LoopBegin;
SBYTE LoopCount;
int LoopCount;
bool LoopFinished;
};
@ -452,10 +452,10 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
break;
case 116: // XMI for loop controller
if (!IgnoreLoops && track->ForDepth < MAX_FOR_DEPTH)
if (track->ForDepth < MAX_FOR_DEPTH)
{
track->ForLoops[track->ForDepth].LoopBegin = track->EventP;
track->ForLoops[track->ForDepth].LoopCount = data2;
track->ForLoops[track->ForDepth].LoopCount = ClampLoopCount(data2);
track->ForLoops[track->ForDepth].LoopFinished = track->Finished;
}
track->ForDepth++;
@ -500,7 +500,7 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
}
else
{
events[2] = MEVT_NOP;
events[2] = MEVT_NOP << 24;
}
events += 3;
@ -678,22 +678,6 @@ XMISong::EventSource XMISong::FindNextDue()
}
//==========================================================================
//
// XMISong :: SetTempo
//
// Sets the tempo from a track's initial meta events.
//
//==========================================================================
void XMISong::SetTempo(int new_tempo)
{
if (0 == MIDI->SetTempo(new_tempo))
{
Tempo = new_tempo;
}
}
//==========================================================================
//
// XMISong :: GetOPLDumper