mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-24 21:21:04 +00:00
- 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:
parent
64784b2dc8
commit
bc08502132
6 changed files with 101 additions and 77 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue