diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 672267e54..64250ca64 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -828,6 +828,7 @@ add_executable( zdoom WIN32 sound/music_mus_midiout.cpp sound/music_smf_midiout.cpp sound/music_hmi_midiout.cpp + sound/music_xmi_midiout.cpp sound/music_midistream.cpp sound/music_midi_base.cpp sound/music_midi_timidity.cpp diff --git a/src/m_swap.h b/src/m_swap.h index 473e4b3c7..6e0bc88cc 100644 --- a/src/m_swap.h +++ b/src/m_swap.h @@ -174,4 +174,46 @@ inline int BigLong (int x) #endif // __BIG_ENDIAN__ #endif // __APPLE__ + + +// Data accessors, since some data is highly likely to be unaligned. +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) +inline int GetShort(const unsigned char *foo) +{ + return *(const short *)foo; +} +inline int GetInt(const unsigned char *foo) +{ + return *(const int *)foo; +} +inline int GetBigInt(const unsigned char *foo) +{ + return BigLong(GetInt(foo)); +} +#else +inline int GetShort(const unsigned char *foo) +{ + return short(foo[0] | (foo[1] << 8)); +} +inline int GetInt(const unsigned char *foo) +{ + return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24)); +} +inline int GetBigInt(const unsigned char *foo) +{ + return int((foo[0] << 24) | (foo[1] << 16) | (foo[2] << 8) | foo[3]); +} +#endif +#ifdef __BIG_ENDIAN__ +inline int GetNativeInt(const unsigned char *foo) +{ + return GetBigInt(foo); +} +#else +inline int GetNativeInt(const unsigned char *foo) +{ + return GetInt(foo); +} +#endif + #endif // __M_SWAP_H__ diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 76e33ff83..ab8cd8f66 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -89,6 +89,7 @@ enum EMIDIType MIDI_NOTMIDI, MIDI_MIDI, MIDI_HMI, + MIDI_XMI, MIDI_MUS }; @@ -344,6 +345,10 @@ static MusInfo *CreateMIDISong(FILE *file, const char *filename, BYTE *musiccach { return new HMISong(file, musiccache, len, devtype); } + else if (miditype == MIDI_XMI) + { + return new XMISong(file, musiccache, len, devtype); + } return NULL; } @@ -470,6 +475,15 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int { miditype = MIDI_HMI; } + // Check for XMI format + else + if ((id[0] == MAKE_ID('F','O','R','M') && + id[2] == MAKE_ID('X','D','I','R')) || + ((id[0] == MAKE_ID('C','A','T',' ') || id[0] == MAKE_ID('F','O','R','M')) && + id[2] == MAKE_ID('X','M','I','D'))) + { + miditype = MIDI_XMI; + } // Check for MIDI format else if (id[0] == MAKE_ID('M','T','h','d')) { diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 32acaec48..6912cdb66 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -357,6 +357,7 @@ public: bool IsPlaying(); bool IsMIDI() const; bool IsValid() const; + bool SetSubsong(int subsong); void Update(); FString GetStats(); void FluidSettingInt(const char *setting, int value); @@ -380,6 +381,7 @@ protected: virtual void DoRestart() = 0; virtual bool CheckDone() = 0; virtual void Precache(); + virtual bool SetMIDISubsong(int subsong); virtual DWORD *MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) = 0; enum @@ -488,6 +490,27 @@ protected: // HMI file played with a MIDI stream --------------------------------------- +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 +{ +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; } +}; + class HMISong : public MIDIStreamer { public: @@ -519,27 +542,6 @@ protected: static DWORD ReadVarLenHMI(TrackInfo *); static DWORD ReadVarLenHMP(TrackInfo *); - 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 - { - 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; @@ -550,6 +552,46 @@ protected: NoteOffQueue NoteOffs; }; +// XMI file played with a MIDI stream --------------------------------------- + +class XMISong : public MIDIStreamer +{ +public: + XMISong(FILE *file, BYTE *musiccache, int length, EMIDIDevice type); + ~XMISong(); + + MusInfo *GetOPLDumper(const char *filename); + MusInfo *GetWaveDumper(const char *filename, int rate); + +protected: + struct TrackInfo; + enum EventSource { EVENT_None, EVENT_Real, EVENT_Fake }; + + XMISong(const XMISong *original, const char *filename, EMIDIDevice type); // file dump constructor + + int FindXMIDforms(const BYTE *chunk, int len, TrackInfo *songs) const; + void FoundXMID(const BYTE *chunk, int len, TrackInfo *song) const; + bool SetMIDISubsong(int subsong); + void DoInitialSetup(); + void DoRestart(); + bool CheckDone(); + DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time); + void AdvanceSong(DWORD time); + + 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 + int NumSongs; + TrackInfo *Songs; + TrackInfo *CurrSong; + NoteOffQueue NoteOffs; + EventSource EventDue; +}; + // Anything supported by FMOD out of the box -------------------------------- class StreamSong : public MusInfo diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index 24bd9e041..465a20b1b 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -84,28 +84,6 @@ #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 @@ -567,7 +545,7 @@ DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) // // HMISong :: AdvanceTracks // -// Advaces time for all tracks by the specified amount. +// Advances time for all tracks by the specified amount. // //========================================================================== @@ -868,11 +846,11 @@ DWORD HMISong::TrackInfo::ReadVarLenHMP() //========================================================================== // -// HMISong :: NoteOffQueue :: AddNoteOff +// NoteOffQueue :: AddNoteOff // //========================================================================== -void HMISong::NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key) +void NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key) { unsigned int i = Reserve(1); while (i > 0 && (*this)[Parent(i)].Delay > delay) @@ -887,11 +865,11 @@ void HMISong::NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key) //========================================================================== // -// HMISong :: NoteOffQueue :: Pop +// NoteOffQueue :: Pop // //========================================================================== -bool HMISong::NoteOffQueue::Pop(AutoNoteOff &item) +bool NoteOffQueue::Pop(AutoNoteOff &item) { item = (*this)[0]; if (TArray::Pop((*this)[0])) @@ -904,11 +882,11 @@ bool HMISong::NoteOffQueue::Pop(AutoNoteOff &item) //========================================================================== // -// HMISong :: NoteOffQueue :: AdvanceTime +// NoteOffQueue :: AdvanceTime // //========================================================================== -void HMISong::NoteOffQueue::AdvanceTime(DWORD time) +void NoteOffQueue::AdvanceTime(DWORD time) { // Because the time is decreasing by the same amount for every entry, // the heap property is maintained. @@ -921,11 +899,11 @@ void HMISong::NoteOffQueue::AdvanceTime(DWORD time) //========================================================================== // -// HMISong :: NoteOffQueue :: Heapify +// NoteOffQueue :: Heapify // //========================================================================== -void HMISong::NoteOffQueue::Heapify() +void NoteOffQueue::Heapify() { unsigned int i = 0; for (;;) @@ -965,10 +943,16 @@ HMISong::TrackInfo *HMISong::FindNextDue () DWORD best; int i; + // Give precedence to whichever track last had events taken from it. if (TrackDue != FakeTrack && !TrackDue->Finished && TrackDue->Delay == 0) { return TrackDue; } + if (TrackDue == FakeTrack && NoteOffs.Size() != 0 && NoteOffs[0].Delay == 0) + { + FakeTrack->Delay = 0; + return FakeTrack; + } // Check regular tracks. track = NULL; diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index 454ff0d3f..f30256155 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -259,6 +259,7 @@ void MIDIStreamer::Play(bool looping, int subsong) return; } + SetMIDISubsong(subsong); CheckCaps(MIDI->GetTechnology()); Precache(); IgnoreLoops = false; @@ -1100,6 +1101,38 @@ FString MIDIStreamer::GetStats() return MIDI->GetStats(); } +//========================================================================== +// +// MIDIStreamer :: SetSubsong +// +// Selects which subsong to play in an already-playing file. This is public. +// +//========================================================================== + +bool MIDIStreamer::SetSubsong(int subsong) +{ + if (SetMIDISubsong(subsong)) + { + Stop(); + Play(m_Looping, subsong); + return true; + } + return false; +} + +//========================================================================== +// +// MIDIStreamer :: SetMIDISubsong +// +// Selects which subsong to play. This is private. +// +//========================================================================== + +bool MIDIStreamer::SetMIDISubsong(int subsong) +{ + return subsong == 0; +} + //========================================================================== // // MIDIDevice stubs. diff --git a/src/sound/music_smf_midiout.cpp b/src/sound/music_smf_midiout.cpp index 0f01be75b..64d3849b2 100644 --- a/src/sound/music_smf_midiout.cpp +++ b/src/sound/music_smf_midiout.cpp @@ -348,7 +348,7 @@ DWORD *MIDISong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) // // MIDISong2 :: AdvanceTracks // -// Advaces time for all tracks by the specified amount. +// Advances time for all tracks by the specified amount. // //========================================================================== @@ -726,6 +726,7 @@ MIDISong2::TrackInfo *MIDISong2::FindNextDue () DWORD best; int i; + // Give precedence to whichever track last had events taken from it. if (!TrackDue->Finished && TrackDue->Delay == 0) { return TrackDue; diff --git a/src/sound/music_xmi_midiout.cpp b/src/sound/music_xmi_midiout.cpp new file mode 100644 index 000000000..26c8a3557 --- /dev/null +++ b/src/sound/music_xmi_midiout.cpp @@ -0,0 +1,746 @@ +/* +** music_xmi_midiout.cpp +** Code to let ZDoom play XMIDI 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. +**--------------------------------------------------------------------------- +** +** This file also supports the Apogee Sound System's EMIDI files. That +** basically means you can play the Duke3D songs without any editing and +** have them sound right. +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" + +// MACROS ------------------------------------------------------------------ + +#define MAX_FOR_DEPTH 4 + +#define GET_DELAY (EventDue == EVENT_Real ? CurrSong->Delay : NoteOffs[0].Delay) + +// Used by SendCommand to check for unexpected end-of-track conditions. +#define CHECK_FINISHED \ + if (track->EventP >= track->EventLen) \ + { \ + track->Finished = true; \ + return events; \ + } + +// TYPES ------------------------------------------------------------------- + +struct LoopInfo +{ + size_t LoopBegin; + SBYTE LoopCount; + bool LoopFinished; +}; + +struct XMISong::TrackInfo +{ + const BYTE *EventChunk; + size_t EventLen; + size_t EventP; + + const BYTE *TimbreChunk; + size_t TimbreLen; + + DWORD Delay; + DWORD PlayedTime; + bool Finished; + + LoopInfo ForLoops[MAX_FOR_DEPTH]; + int ForDepth; + + DWORD ReadVarLen(); + DWORD ReadDelay(); +}; + +// 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 -------------------------------------------------------------------- + +//========================================================================== +// +// XMISong Constructor +// +// Buffers the file and does some validation of the SMF header. +// +//========================================================================== + +XMISong::XMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) +: MIDIStreamer(type), MusHeader(0), Songs(0) +{ +#ifdef _WIN32 + if (ExitEvent == NULL) + { + return; + } +#endif + MusHeader = new BYTE[len]; + SongLen = len; + if (file != NULL) + { + if (fread(MusHeader, 1, len, file) != (size_t)len) + return; + } + else + { + memcpy(MusHeader, musiccache, len); + } + + // Find all the songs in this file. + NumSongs = FindXMIDforms(MusHeader, len, NULL); + if (NumSongs == 0) + { + return; + } + Songs = new TrackInfo[NumSongs]; + memset(Songs, 0, sizeof(*Songs) * NumSongs); + FindXMIDforms(MusHeader, len, Songs); + CurrSong = Songs; + DPrintf("XMI song count: %d\n", NumSongs); + + // The division is the number of pulses per quarter note (PPQN). + Division = 60; +} + +//========================================================================== +// +// XMISong Destructor +// +//========================================================================== + +XMISong::~XMISong () +{ + if (Songs != NULL) + { + delete[] Songs; + } + if (MusHeader != NULL) + { + delete[] MusHeader; + } +} + +//========================================================================== +// +// XMISong :: FindXMIDforms +// +// Find all FORM XMID chunks in this chunk. +// +//========================================================================== + +int XMISong::FindXMIDforms(const BYTE *chunk, int len, TrackInfo *songs) const +{ + int count = 0; + + for (int p = 0; p <= len - 12; ) + { + int chunktype = GetNativeInt(chunk + p); + int chunklen = GetBigInt(chunk + p + 4); + + if (chunktype == MAKE_ID('F','O','R','M')) + { + if (GetNativeInt(chunk + p + 8) == MAKE_ID('X','M','I','D')) + { + if (songs != NULL) + { + FoundXMID(chunk + p + 12, chunklen - 4, songs + count); + } + count++; + } + } + else if (chunktype == MAKE_ID('C','A','T',' ')) + { + // Recurse to handle CAT chunks. + count += FindXMIDforms(chunk + p + 12, chunklen - 4, songs + count); + } + // IFF chunks are padded to even byte boundaries to avoid + // unaligned reads on 68k processors. + p += 8 + chunklen + (chunklen & 1); + } + return count; +} + +//========================================================================== +// +// XMISong :: FoundXMID +// +// Records information about this XMID song. +// +//========================================================================== + +void XMISong::FoundXMID(const BYTE *chunk, int len, TrackInfo *song) const +{ + for (int p = 0; p <= len - 8; ) + { + int chunktype = GetNativeInt(chunk + p); + int chunklen = GetBigInt(chunk + p + 4); + + if (chunktype == MAKE_ID('T','I','M','B')) + { + song->TimbreChunk = chunk + p + 8; + song->TimbreLen = chunklen; + } + else if (chunktype == MAKE_ID('E','V','N','T')) + { + song->EventChunk = chunk + p + 8; + song->EventLen = chunklen; + // EVNT must be the final chunk in the FORM. + break; + } + p += 8 + chunklen + (chunklen & 1); + } +} + +//========================================================================== +// +// XMISong :: SetMIDISubsong +// +// Selects which song in this file to play. +// +//========================================================================== + +bool XMISong::SetMIDISubsong(int subsong) +{ + if ((unsigned)subsong >= (unsigned)NumSongs) + { + return false; + } + CurrSong = &Songs[subsong]; + return true; +} + +//========================================================================== +// +// XMISong :: DoInitialSetup +// +// Sets the starting channel volumes. +// +//========================================================================== + +void XMISong::DoInitialSetup() +{ + for (int i = 0; i < 16; ++i) + { + ChannelVolumes[i] = 100; + } +} + +//========================================================================== +// +// XMISong :: DoRestart +// +// Rewinds the current song. +// +//========================================================================== + +void XMISong::DoRestart() +{ + CurrSong->EventP = 0; + CurrSong->Finished = false; + CurrSong->PlayedTime = 0; + CurrSong->ForDepth = 0; + NoteOffs.Clear(); + + ProcessInitialMetaEvents (); + + CurrSong->Delay = CurrSong->ReadDelay(); + EventDue = FindNextDue(); +} + +//========================================================================== +// +// XMISong :: CheckDone +// +//========================================================================== + +bool XMISong::CheckDone() +{ + return EventDue == EVENT_None; +} + +//========================================================================== +// +// XMISong :: MakeEvents +// +// Copies MIDI events from the SMF and puts them into a MIDI stream +// buffer. Returns the new position in the buffer. +// +//========================================================================== + +DWORD *XMISong::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 (EventDue != EVENT_None && 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 = GET_DELAY; + time += delay; + // Advance time for all tracks by the amount needed for the one up next. + tot_time += delay * Tempo / Division; + AdvanceSong(delay); + // Play all events for this tick. + do + { + DWORD *new_events = SendCommand(events, EventDue, time); + EventDue = FindNextDue(); + if (new_events != events) + { + time = 0; + } + events = new_events; + } + while (EventDue != EVENT_None && GET_DELAY == 0 && events < max_event_p); + } + while (start_events == events && EventDue != EVENT_None); + time = 0; + } + return events; +} + +//========================================================================== +// +// XMISong :: AdvanceSong +// +// Advances time for the current song by the specified amount. +// +//========================================================================== + +void XMISong::AdvanceSong(DWORD time) +{ + if (time != 0) + { + if (!CurrSong->Finished) + { + CurrSong->Delay -= time; + CurrSong->PlayedTime += time; + } + NoteOffs.AdvanceTime(time); + } +} + +//========================================================================== +// +// XMISong :: SendCommand +// +// Places a single MIDIEVENT in the event buffer. +// +//========================================================================== + +DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) +{ + DWORD len; + BYTE event, data1 = 0, data2 = 0; + + if (due == EVENT_Fake) + { + AutoNoteOff off; + NoteOffs.Pop(off); + events[0] = delay; + events[1] = 0; + events[2] = MIDI_NOTEON | off.Channel | (off.Key << 8); + return events + 3; + } + + TrackInfo *track = CurrSong; + + CHECK_FINISHED + event = track->EventChunk[track->EventP++]; + CHECK_FINISHED + + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND) + { + // Normal short message + if ((event & 0xF0) == 0xF0) + { + if (MIDI_CommonLengths[event & 15] > 0) + { + data1 = track->EventChunk[track->EventP++]; + if (MIDI_CommonLengths[event & 15] > 1) + { + data2 = track->EventChunk[track->EventP++]; + } + } + } + else + { + data1 = track->EventChunk[track->EventP++]; + } + + CHECK_FINISHED + + if (MIDI_EventLengths[(event&0x70)>>4] == 2) + { + data2 = track->EventChunk[track->EventP++]; + } + + if ((event & 0x70) == (MIDI_CTRLCHANGE & 0x70)) + { + switch (data1) + { + case 7: // Channel volume + data2 = VolumeControllerChange(event & 15, data2); + break; + + case 110: // XMI channel lock + case 111: // XMI channel lock protect + case 112: // XMI voice protect + case 113: // XMI timbre protect + case 115: // XMI indirect controller prefix + case 118: // XMI clear beat/bar count + case 119: // XMI callback trigger + case 120: + event = MIDI_META; // none of these are relevant to us. + break; + + case 114: // XMI patch bank select + data1 = 0; // Turn this into a standard MIDI bank select controller. + break; + + case 116: // XMI for loop controller + if (!IgnoreLoops && track->ForDepth < MAX_FOR_DEPTH) + { + track->ForLoops[track->ForDepth].LoopBegin = track->EventP; + track->ForLoops[track->ForDepth].LoopCount = data2; + track->ForLoops[track->ForDepth].LoopFinished = track->Finished; + } + track->ForDepth++; + event = MIDI_META; + break; + + case 117: // XMI next loop controller + if (track->ForDepth > 0) + { + int depth = track->ForDepth - 1; + if (depth < MAX_FOR_DEPTH) + { + if (data2 < 64 || (track->ForLoops[depth].LoopCount == 0 && !m_Looping)) + { // throw away this loop. + track->ForLoops[depth].LoopCount = 1; + } + // A loop count of 0 loops forever. + if (track->ForLoops[depth].LoopCount == 0 || --track->ForLoops[depth].LoopCount > 0) + { + track->EventP = track->ForLoops[depth].LoopBegin; + track->Finished = track->ForLoops[depth].LoopFinished; + } + else + { // done with this loop + track->ForDepth = depth; + } + } + else + { // ignore any loops deeper than the max depth + track->ForDepth = depth; + } + } + event = MIDI_META; + break; + } + } + 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)) + { // XMI 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->EventP += len; + } + else if (event == MIDI_META) + { + // It's a meta-event + event = track->EventChunk[track->EventP++]; + CHECK_FINISHED + len = track->ReadVarLen (); + CHECK_FINISHED + + if (track->EventP + len <= track->EventLen) + { + switch (event) + { + case MIDI_META_EOT: + track->Finished = true; + break; + + case MIDI_META_TEMPO: + Tempo = + (track->EventChunk[track->EventP+0]<<16) | + (track->EventChunk[track->EventP+1]<<8) | + (track->EventChunk[track->EventP+2]); + events[0] = delay; + events[1] = 0; + events[2] = (MEVT_TEMPO << 24) | Tempo; + events += 3; + break; + } + track->EventP += len; + if (track->EventP == track->EventLen) + { + track->Finished = true; + } + } + else + { + track->Finished = true; + } + } + } + if (!track->Finished) + { + track->Delay = track->ReadDelay(); + } + return events; +} + +//========================================================================== +// +// XMISong :: ProcessInitialMetaEvents +// +// Handle all the meta events at the start of the current song. +// +//========================================================================== + +void XMISong::ProcessInitialMetaEvents () +{ + TrackInfo *track = CurrSong; + BYTE event; + DWORD len; + + while (!track->Finished && + track->EventP < track->EventLen - 3 && + track->EventChunk[track->EventP] == 0xFF) + { + event = track->EventChunk[track->EventP+1]; + track->EventP += 2; + len = track->ReadVarLen(); + if (track->EventP + len <= track->EventLen) + { + switch (event) + { + case MIDI_META_EOT: + track->Finished = true; + break; + + case MIDI_META_TEMPO: + SetTempo( + (track->EventChunk[track->EventP+0]<<16) | + (track->EventChunk[track->EventP+1]<<8) | + (track->EventChunk[track->EventP+2]) + ); + break; + } + } + track->EventP += len; + } + if (track->EventP >= track->EventLen - 1) + { + track->Finished = true; + } +} + +//========================================================================== +// +// XMISong :: TrackInfo :: ReadVarLen +// +// Reads a variable length SMF number. +// +//========================================================================== + +DWORD XMISong::TrackInfo::ReadVarLen() +{ + DWORD time = 0, t = 0x80; + + while ((t & 0x80) && EventP < EventLen) + { + t = EventChunk[EventP++]; + time = (time << 7) | (t & 127); + } + return time; +} + +//========================================================================== +// +// XMISong :: TrackInfo :: ReadDelay +// +// XMI does not use variable length numbers for delays. Instead, it uses +// runs of bytes with the high bit clear. +// +//========================================================================== + +DWORD XMISong::TrackInfo::ReadDelay() +{ + DWORD time = 0, t; + + while (EventP < EventLen && !((t = EventChunk[EventP]) & 0x80)) + { + time += t; + EventP++; + } + return time; +} + +//========================================================================== +// +// XMISong :: FindNextDue +// +// Decides whether the next event should come from the actual stong or +// from the auto note offs. +// +//========================================================================== + +XMISong::EventSource XMISong::FindNextDue() +{ + // Are there still events available? + if (CurrSong->Finished && NoteOffs.Size() == 0) + { + return EVENT_None; + } + + // Which is due sooner? The current song or the note-offs? + DWORD real_delay = CurrSong->Finished ? 0xFFFFFFFF : CurrSong->Delay; + DWORD fake_delay = NoteOffs.Size() == 0 ? 0xFFFFFFFF : NoteOffs[0].Delay; + + return (fake_delay <= real_delay) ? EVENT_Fake : EVENT_Real; +} + + +//========================================================================== +// +// 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 +// +//========================================================================== + +MusInfo *XMISong::GetOPLDumper(const char *filename) +{ + return new XMISong(this, filename, MIDI_OPL); +} + +//========================================================================== +// +// XMISong :: GetWaveDumper +// +//========================================================================== + +MusInfo *XMISong::GetWaveDumper(const char *filename, int rate) +{ + return new XMISong(this, filename, MIDI_GUS); +} + +//========================================================================== +// +// XMISong File Dumping Constructor +// +//========================================================================== + +XMISong::XMISong(const XMISong *original, const char *filename, EMIDIDevice type) +: MIDIStreamer(filename, type) +{ + SongLen = original->SongLen; + MusHeader = new BYTE[original->SongLen]; + memcpy(MusHeader, original->MusHeader, original->SongLen); + NumSongs = original->NumSongs; + Tempo = InitialTempo = original->InitialTempo; + Songs = new TrackInfo[NumSongs]; + for (int i = 0; i < NumSongs; ++i) + { + TrackInfo *newtrack = &Songs[i]; + const TrackInfo *oldtrack = &original->Songs[i]; + + newtrack->EventChunk = MusHeader + (oldtrack->EventChunk - original->MusHeader); + newtrack->EventLen = oldtrack->EventLen; + newtrack->EventP = 0; + + newtrack->TimbreChunk = MusHeader + (oldtrack->TimbreChunk - original->MusHeader); + newtrack->TimbreLen = oldtrack->TimbreLen; + } +} diff --git a/zdoom.vcproj b/zdoom.vcproj index 11e243a83..8e6c9b9ca 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -1,7 +1,7 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - @@ -1892,6 +1884,14 @@ Outputs="$(IntDir)/$(InputName).obj" /> + + + @@ -2081,6 +2081,14 @@ Outputs="$(IntDir)\$(InputName).obj" /> + + + @@ -2091,14 +2099,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - + + + - - - + + + @@ -5391,14 +5399,6 @@ AdditionalIncludeDirectories="src\win32;$(NoInherit)" /> - - - @@ -5517,6 +5517,10 @@ RelativePath=".\src\sound\music_win_mididevice.cpp" > + + @@ -5689,7 +5693,7 @@ />