mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-10 23:01:50 +00:00
- Added XMIDI support (including subsongs).
- Moved unaligned accessors into m_swap.h. SVN r2859 (trunk)
This commit is contained in:
parent
2d5755a80e
commit
5a3b3631c3
9 changed files with 1295 additions and 428 deletions
|
@ -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
|
||||
|
|
42
src/m_swap.h
42
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__
|
||||
|
|
|
@ -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'))
|
||||
{
|
||||
|
|
|
@ -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<AutoNoteOff>
|
||||
{
|
||||
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<AutoNoteOff>
|
||||
{
|
||||
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
|
||||
|
|
|
@ -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<AutoNoteOff>::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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
746
src/sound/music_xmi_midiout.cpp
Normal file
746
src/sound/music_xmi_midiout.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
754
zdoom.vcproj
754
zdoom.vcproj
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue