From bc08502132d9cf64fb0f0f033c33ad7663daeb8f Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 29 Sep 2010 03:35:53 +0000 Subject: [PATCH] - 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) --- src/sound/i_music.cpp | 8 +++- src/sound/i_musicinterns.h | 7 ++-- src/sound/music_hmi_midiout.cpp | 18 +-------- src/sound/music_midistream.cpp | 70 ++++++++++++++++++++++++++++++--- src/sound/music_smf_midiout.cpp | 51 +++++++++++------------- src/sound/music_xmi_midiout.cpp | 24 ++--------- 6 files changed, 101 insertions(+), 77 deletions(-) diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 52a4698e4d..24310b9f0a 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -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); } diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 6912cdb66a..d518dbab90 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -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 diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index 465a20b1b6..fd65fde05c 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -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 diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index f302561552..5d271a11e4 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -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 &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 &file) file[20] = BYTE(len >> 8); file[21] = BYTE(len & 255); - IgnoreLoops = false; + LoopLimit = 0; } //========================================================================== @@ -1086,6 +1091,61 @@ static void WriteVarLen (TArray &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 diff --git a/src/sound/music_smf_midiout.cpp b/src/sound/music_smf_midiout.cpp index 64d3849b2d..6e59c60878 100644 --- a/src/sound/music_smf_midiout.cpp +++ b/src/sound/music_smf_midiout.cpp @@ -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 diff --git a/src/sound/music_xmi_midiout.cpp b/src/sound/music_xmi_midiout.cpp index 26c8a3557a..75dea937d1 100644 --- a/src/sound/music_xmi_midiout.cpp +++ b/src/sound/music_xmi_midiout.cpp @@ -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