diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 14e7366de..dedf8b7f7 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,3 +1,7 @@ +April 8, 2008 +- Reimplemented snd_midiprecache, now for MIDI as well as MUS, and + defaulting to false. + April 8, 2008 (Changes by Graf Zahl) - Eliminated all use of global variables used as output for P_CheckPosition and P_TryMove. Moved BlockingLine and BlockingMobj into AActor because diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 7db13ed72..e8ad2b914 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -70,6 +70,8 @@ EXTERN_CVAR (Bool, snd_pitched) EXTERN_CVAR (Color, am_wallcolor) EXTERN_CVAR (Color, am_fdwallcolor) EXTERN_CVAR (Color, am_cdwallcolor) +EXTERN_CVAR (Float, spc_amp) +EXTERN_CVAR (Bool, snd_midiprecache) FString WeaponSection; @@ -297,14 +299,15 @@ void FGameConfigFile::DoGlobalSetup () } if (last < 206) { // spc_amp is now a float, not an int. - FBaseCVar *amp = FindCVar ("spc_amp", NULL); - if (amp != NULL) + if (spc_amp > 16) { - UCVarValue val = amp->GetGenericRep(CVAR_Float); - val.Float /= 16.f; - amp->SetGenericRep(val, CVAR_Float); + spc_amp = spc_amp / 16.f; } } + if (last < 207) + { // Now that snd_midiprecache works again, you probably don't want it on. + snd_midiprecache = false; + } } } } diff --git a/src/mus2midi.h b/src/mus2midi.h index e36da6782..79b410078 100644 --- a/src/mus2midi.h +++ b/src/mus2midi.h @@ -71,6 +71,7 @@ typedef struct WORD NumSecondaryChans; WORD NumInstruments; WORD Pad; + // WORD UsedInstruments[NumInstruments]; } MUSHeader; bool ProduceMIDI (const BYTE *musBuf, TArray &outFile); diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 58e31d6e8..e807839e1 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -112,6 +112,7 @@ public: virtual bool FakeVolume() = 0; virtual bool Pause(bool paused) = 0; virtual bool NeedThreadedCallback() = 0; + virtual void PrecacheInstruments(const BYTE *instruments, int count); }; // WinMM implementation of a MIDI output device ----------------------------- @@ -137,6 +138,7 @@ public: bool FakeVolume(); bool NeedThreadedCallback(); bool Pause(bool paused); + void PrecacheInstruments(const BYTE *instruments, int count); protected: static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD); @@ -236,6 +238,7 @@ protected: virtual void DoInitialSetup() = 0; virtual void DoRestart() = 0; virtual bool CheckDone() = 0; + virtual void Precache() = 0; virtual DWORD *MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) = 0; enum @@ -294,6 +297,7 @@ protected: void DoInitialSetup(); void DoRestart(); bool CheckDone(); + void Precache(); DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time); MUSHeader *MusHeader; @@ -319,6 +323,7 @@ protected: void DoInitialSetup(); void DoRestart(); bool CheckDone(); + void Precache(); DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time); void AdvanceTracks(DWORD time); diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index e85919a79..d7bc8f1fe 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -12,8 +12,6 @@ static bool nummididevicesset; #ifdef _WIN32 UINT mididevice; -CVAR (Bool, snd_midiprecache, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); - CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { UINT oldmididev = mididevice; @@ -155,8 +153,9 @@ CCMD (snd_listmididevices) MIDIOUTCAPS caps; MMRESULT res; - PrintMidiDevice (-2, "TiMidity++", 0, 0); - PrintMidiDevice (-1, "FMOD", 0, 0); + PrintMidiDevice (-3, "Emulated OPL FM Synth", MOD_FMSYNTH, 0); + PrintMidiDevice (-2, "TiMidity++", 0, MOD_WAVETABLE | MOD_SWSYNTH); + PrintMidiDevice (-1, "FMOD", 0, MOD_WAVETABLE | MOD_SWSYNTH); if (nummididevices != 0) { for (id = 0; id < nummididevices; ++id) @@ -206,6 +205,7 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) CCMD (snd_listmididevices) { + Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-1. FMOD\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : ""); } diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp index 99b918f5b..11c41f27f 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_midi_midiout.cpp @@ -754,6 +754,96 @@ 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() +{ + int i, j; + + // 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, }; + + DoRestart(); + for (i = 0; i < NumTracks; ++i) + { + TrackInfo *track = &Tracks[i]; + BYTE running_status = 0; + BYTE ev, data1, data2, command, channel; + int len; + + while (track->TrackP < track->MaxTrackP) + { + ev = track->TrackBegin[track->TrackP++]; + command = ev & 0xF0; + + if (command == MIDI_SYSEX || command == MIDI_SYSEXEND) + { + len = track->ReadVarLen(); + track->TrackP += len; + } + else if (command == MIDI_META) + { + track->TrackP++; + len = track->ReadVarLen(); + track->TrackP += len; + } + else if ((command & 0xF0) == 0xF0) + { + track->TrackP += CommonLengths[ev & 0xF]; + } + 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] = 1; + } + else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0) + { + found_instruments[data1 | 128] = 1; + } + track->ReadVarLen(); // Skip delay. + } + } + } + DoRestart(); + + // Now pack everything into a contiguous region for the PrecacheInstruments call(). + for (i = j = 0; i < 256; ++i) + { + if (found_instruments[i]) + { + found_instruments[j++] = i; + } + } + MIDI->PrecacheInstruments(found_instruments, j); +} + //========================================================================== // // MIDISong2 :: GetOPLDumper diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index db78a7ad6..c57ddfad6 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -221,6 +221,7 @@ void MIDIStreamer::Play(bool looping) } CheckCaps(); + Precache(); // Set time division and tempo. if (0 != MIDI->SetTimeDiv(Division) || @@ -301,7 +302,8 @@ void MIDIStreamer::Play(bool looping) // MIDIStreamer :: Pause // // "Pauses" the song by setting it to zero volume and filling subsequent -// buffers with NOPs until the song is unpaused. +// buffers with NOPs until the song is unpaused. A MIDI device that +// supports real pauses will return true from its Pause() method. // //========================================================================== @@ -711,3 +713,22 @@ MIDIDevice::MIDIDevice() MIDIDevice::~MIDIDevice() { } + +//========================================================================== +// +// MIDIDevice :: PrecacheInstruments +// +// The MIDIStreamer calls this method between device open and the first +// buffered stream with a list of instruments known to be used by the song. +// If the device can benefit from preloading the instruments, it can do so +// now. +// +// For each entry, bit 7 set indicates that the instrument is percussion and +// the lower 7 bits contain the note number to use on MIDI channel 10, +// otherwise it is melodic and the lower 7 bits are the program number. +// +//========================================================================== + +void MIDIDevice::PrecacheInstruments(const BYTE *instruments, int count) +{ +} diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index f7b875cfe..e048d2796 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -179,6 +179,34 @@ bool MUSSong2::CheckDone() return MusP >= MaxMusP; } +//========================================================================== +// +// MUSSong2 :: Precache +// +// MUS songs contain information in their header for exactly this purpose. +// +//========================================================================== + +void MUSSong2::Precache() +{ + BYTE *work = (BYTE *)alloca(MusHeader->NumInstruments); + const WORD *used = (WORD *)MusHeader + sizeof(MUSHeader) / 2; + int i, j; + + for (i = j = 0; i < MusHeader->NumInstruments; ++i) + { + if (used[i] < 128) + { + work[j++] = used[i]; + } + else if (used[i] >= 135 && used[i] <= 181) + { // Percussions are 100-based, not 128-based, eh? + work[j++] = (used[i] - 100) | 0x80; + } + } + MIDI->PrecacheInstruments(&work[0], j); +} + //========================================================================== // // MUSSong2 :: MakeEvents diff --git a/src/sound/music_win_mididevice.cpp b/src/sound/music_win_mididevice.cpp index 56080ec32..aa137a264 100644 --- a/src/sound/music_win_mididevice.cpp +++ b/src/sound/music_win_mididevice.cpp @@ -55,6 +55,8 @@ // PUBLIC DATA DEFINITIONS ------------------------------------------------- +CVAR (Bool, snd_midiprecache, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); + // CODE -------------------------------------------------------------------- //========================================================================== @@ -203,6 +205,61 @@ void WinMIDIDevice::Stop() } } +//========================================================================== +// +// WinMIDIDevice :: PrecacheInstruments +// +// For each entry, bit 7 set indicates that the instrument is percussion and +// the lower 7 bits contain the note number to use on MIDI channel 10, +// otherwise it is melodic and the lower 7 bits are the program number. +// +// My old GUS PnP needed the instruments to be preloaded, or it would miss +// some notes the first time through the song. I doubt any modern +// hardware has this problem, but since I'd already written the code for +// ZDoom 1.22 and below, I'm resurrecting it now for completeness, since I'm +// using preloading for the internal Timidity. +// +//========================================================================== + +void WinMIDIDevice::PrecacheInstruments(const BYTE *instruments, int count) +{ + // Setting snd_midiprecache to false disables this precaching, since it + // does involve sleeping for more than a miniscule amount of time. + if (!snd_midiprecache) + { + return; + } + for (int i = 0, chan = 0; i < count; ++i) + { + if (instruments[i] & 0x80) + { // Percussion + midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_NOTEON | 9 | ((instruments[i] & 0x7f) << 8) | (1 << 16)); + } + else + { // Melodic + midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_PRGMCHANGE | chan | (instruments[i] << 8)); + midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_NOTEON | chan | (60 << 8) | (1 << 16)); + if (++chan == 9) + { // Skip the percussion channel + chan = 10; + } + } + // Once we've got an instrument playing on each melodic channel, sleep to give + // the driver time to load the instruments. Also do this for the final batch + // of instruments. + if (chan == 16 || i == count - 1) + { + Sleep(250); + for (chan = 15; chan-- != 0; ) + { + // Turn all notes off + midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_CTRLCHANGE | chan | (123 << 8)); + } + // And now chan is back at 0, ready to start the cycle over. + } + } +} + //========================================================================== // // WinMIDIDevice :: Pause diff --git a/src/version.h b/src/version.h index 4a2716cb6..d157e3a44 100644 --- a/src/version.h +++ b/src/version.h @@ -59,7 +59,7 @@ // Version stored in the ini's [LastRun] section. // Bump it if you made some configuration change that you want to // be able to migrate in FGameConfigFile::DoGlobalSetup(). -#define LASTRUNVERSION "206" +#define LASTRUNVERSION "207" // Protocol version used in demos. // Bump it if you change existing DEM_ commands or add new ones. diff --git a/zdoom.vcproj b/zdoom.vcproj index 56998c3d7..63aa07d9b 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2920,6 +2920,90 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +