mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-28 06:53:58 +00:00
- Moved the identical code between the MUS and MIDI streamers into a new base
class so they all the low-level details of MIDI streaming are kept in one place. - Converted the SMF MIDI playback to use the same MIDI streams as MUS playback. - Moved MUS playback back into its own thread so that it can continue uninterrupted if the main thread is too busy to service it in a timely manner. - Fixed: The MEVT_* values are not defined shifted into their spot for a MIDIEVENT, so I need to do it myself. - Fixed: Pausing a MUS and the changing snd_midivolume caused the paused notes to become audible. SVN r784 (trunk)
This commit is contained in:
parent
69cebb7e57
commit
68a8ea2189
11 changed files with 1778 additions and 1343 deletions
|
@ -1,9 +1,23 @@
|
||||||
|
March 4, 2008
|
||||||
|
- Moved the identical code between the MUS and MIDI streamers into a new base
|
||||||
|
class so they all the low-level details of MIDI streaming are kept in
|
||||||
|
one place.
|
||||||
|
- Converted the SMF MIDI playback to use the same MIDI streams as MUS
|
||||||
|
playback.
|
||||||
|
- Moved MUS playback back into its own thread so that it can continue
|
||||||
|
uninterrupted if the main thread is too busy to service it in a timely
|
||||||
|
manner.
|
||||||
|
- Fixed: The MEVT_* values are not defined shifted into their spot for a
|
||||||
|
MIDIEVENT, so I need to do it myself.
|
||||||
|
- Fixed: Pausing a MUS and the changing snd_midivolume caused the paused
|
||||||
|
notes to become audible.
|
||||||
|
|
||||||
March 3, 2008
|
March 3, 2008
|
||||||
- Changed MUS playback to use MIDI streams, like it did during the early days
|
- Changed MUS playback to use MIDI streams, like it did during the early days
|
||||||
of ZDoom, except now the entire song isn't prebuffered in large chunks, so
|
of ZDoom, except now the entire song isn't prebuffered in large chunks, so
|
||||||
I can insert MIDI events into the playback with fairly low latency. This
|
I can insert MIDI events into the playback with fairly low latency. This
|
||||||
should offer more precise timing than the combination of low-level MIDI and
|
should offer more precise timing than the combination of low-level MIDI and
|
||||||
WaitForSingleObject timeouts.
|
WaitForMultipleObjects timeouts that it replaces.
|
||||||
- Fixed: PTR_BounceTraverse only checked for projectiles that were too
|
- Fixed: PTR_BounceTraverse only checked for projectiles that were too
|
||||||
high to pass through two-sided lines, but not ones that were too low.
|
high to pass through two-sided lines, but not ones that were too low.
|
||||||
- Fixed: SBARINFO couldn't detect the extreme death damage type for the
|
- Fixed: SBARINFO couldn't detect the extreme death damage type for the
|
||||||
|
|
|
@ -1312,6 +1312,8 @@ void S_UpdateSounds (void *listener_p)
|
||||||
angle_t angle;
|
angle_t angle;
|
||||||
int sep;
|
int sep;
|
||||||
|
|
||||||
|
I_UpdateMusic();
|
||||||
|
|
||||||
if (GSnd == NULL)
|
if (GSnd == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ bool MusInfo::SetPosition (int order)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MusInfo::ServiceEvent ()
|
void MusInfo::Update ()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,6 +459,14 @@ void *I_RegisterCDSong (int track, int id)
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void I_UpdateMusic()
|
||||||
|
{
|
||||||
|
if (currSong != NULL)
|
||||||
|
{
|
||||||
|
currSong->Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Is the song playing?
|
// Is the song playing?
|
||||||
bool I_QrySongPlaying (void *handle)
|
bool I_QrySongPlaying (void *handle)
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
void I_InitMusic ();
|
void I_InitMusic ();
|
||||||
void I_ShutdownMusic ();
|
void I_ShutdownMusic ();
|
||||||
void I_BuildMIDIMenuList (struct value_s **values, float *numValues);
|
void I_BuildMIDIMenuList (struct value_s **values, float *numValues);
|
||||||
|
void I_UpdateMusic ();
|
||||||
|
|
||||||
// Volume.
|
// Volume.
|
||||||
void I_SetMusicVolume (float volume);
|
void I_SetMusicVolume (float volume);
|
||||||
|
|
|
@ -42,7 +42,7 @@ public:
|
||||||
virtual bool IsMIDI () const = 0;
|
virtual bool IsMIDI () const = 0;
|
||||||
virtual bool IsValid () const = 0;
|
virtual bool IsValid () const = 0;
|
||||||
virtual bool SetPosition (int order);
|
virtual bool SetPosition (int order);
|
||||||
virtual void ServiceEvent ();
|
virtual void Update();
|
||||||
|
|
||||||
enum EState
|
enum EState
|
||||||
{
|
{
|
||||||
|
@ -53,26 +53,15 @@ public:
|
||||||
bool m_Looping;
|
bool m_Looping;
|
||||||
};
|
};
|
||||||
|
|
||||||
// MUS file played with MIDI output messages --------------------------------
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
struct SHORTMIDIEVENT
|
|
||||||
{
|
|
||||||
DWORD dwDeltaTime;
|
|
||||||
DWORD dwStreamID;
|
|
||||||
DWORD dwEvent;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VOLSYSEXEVENT : SHORTMIDIEVENT
|
// Base class for streaming MUS and MIDI files ------------------------------
|
||||||
{
|
|
||||||
BYTE SysEx[8];
|
|
||||||
};
|
|
||||||
|
|
||||||
class MUSSong2 : public MusInfo
|
class MIDIStreamer : public MusInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MUSSong2 (FILE *file, char * musiccache, int length);
|
MIDIStreamer();
|
||||||
~MUSSong2 ();
|
~MIDIStreamer();
|
||||||
|
|
||||||
void SetVolume(float volume);
|
void SetVolume(float volume);
|
||||||
void Play(bool looping);
|
void Play(bool looping);
|
||||||
|
@ -82,16 +71,23 @@ public:
|
||||||
bool IsPlaying();
|
bool IsPlaying();
|
||||||
bool IsMIDI() const;
|
bool IsMIDI() const;
|
||||||
bool IsValid() const;
|
bool IsValid() const;
|
||||||
|
void Update();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static DWORD WINAPI PlayerProc (LPVOID lpParameter);
|
static DWORD WINAPI PlayerProc (LPVOID lpParameter);
|
||||||
void OutputVolume (DWORD volume);
|
|
||||||
int SendCommand ();
|
|
||||||
bool TranslateSong(const BYTE *buffer, size_t len);
|
|
||||||
int CountEvents(const BYTE *buffer, size_t len);
|
|
||||||
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
|
|
||||||
void ServiceEvent();
|
|
||||||
static void CALLBACK Callback(HMIDIOUT handle, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2);
|
static void CALLBACK Callback(HMIDIOUT handle, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2);
|
||||||
|
DWORD PlayerLoop();
|
||||||
|
void OutputVolume (DWORD volume);
|
||||||
|
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
|
||||||
|
bool ServiceEvent();
|
||||||
|
int VolumeControllerChange(int channel, int volume);
|
||||||
|
|
||||||
|
// Virtuals for subclasses to override
|
||||||
|
virtual void CheckCaps(DWORD dev_id);
|
||||||
|
virtual void DoInitialSetup() = 0;
|
||||||
|
virtual void DoRestart() = 0;
|
||||||
|
virtual bool CheckDone() = 0;
|
||||||
|
virtual DWORD *MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) = 0;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -106,73 +102,76 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
HMIDISTRM MidiOut;
|
HMIDISTRM MidiOut;
|
||||||
|
HANDLE PlayerThread;
|
||||||
|
HANDLE ExitEvent;
|
||||||
|
HANDLE BufferDoneEvent;
|
||||||
DWORD SavedVolume;
|
DWORD SavedVolume;
|
||||||
bool VolumeWorks;
|
bool VolumeWorks;
|
||||||
|
DWORD Events[2][MAX_EVENTS*3];
|
||||||
MUSHeader *MusHeader;
|
|
||||||
BYTE *MusBuffer;
|
|
||||||
BYTE LastVelocity[16];
|
|
||||||
BYTE ChannelVolumes[16];
|
|
||||||
size_t MusP, MaxMusP;
|
|
||||||
VOLSYSEXEVENT FullVolEvent;
|
|
||||||
SHORTMIDIEVENT Events[2][MAX_EVENTS];
|
|
||||||
MIDIHDR Buffer[2];
|
MIDIHDR Buffer[2];
|
||||||
int BufferNum;
|
int BufferNum;
|
||||||
int EndQueued;
|
int EndQueued;
|
||||||
bool VolumeChanged;
|
bool VolumeChanged;
|
||||||
bool Restarting;
|
bool Restarting;
|
||||||
|
bool InitialPlayback;
|
||||||
DWORD NewVolume;
|
DWORD NewVolume;
|
||||||
|
int Division;
|
||||||
|
int Tempo;
|
||||||
|
int InitialTempo;
|
||||||
|
BYTE ChannelVolumes[16];
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
// MIDI file played with MIDI output messages -------------------------------
|
// MUS file played with a MIDI stream ---------------------------------------
|
||||||
|
|
||||||
#ifdef _WIN32
|
class MUSSong2 : public MIDIStreamer
|
||||||
class MIDISong2 : public MusInfo
|
{
|
||||||
|
public:
|
||||||
|
MUSSong2 (FILE *file, char *musiccache, int length);
|
||||||
|
~MUSSong2 ();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void DoInitialSetup();
|
||||||
|
void DoRestart();
|
||||||
|
bool CheckDone();
|
||||||
|
DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time);
|
||||||
|
|
||||||
|
MUSHeader *MusHeader;
|
||||||
|
BYTE *MusBuffer;
|
||||||
|
BYTE LastVelocity[16];
|
||||||
|
size_t MusP, MaxMusP;
|
||||||
|
};
|
||||||
|
|
||||||
|
// MIDI file played with a MIDI stream --------------------------------------
|
||||||
|
|
||||||
|
class MIDISong2 : public MIDIStreamer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MIDISong2 (FILE *file, char *musiccache, int length);
|
MIDISong2 (FILE *file, char *musiccache, int length);
|
||||||
~MIDISong2 ();
|
~MIDISong2 ();
|
||||||
|
|
||||||
void SetVolume (float volume);
|
|
||||||
void Play (bool looping);
|
|
||||||
void Pause ();
|
|
||||||
void Resume ();
|
|
||||||
void Stop ();
|
|
||||||
bool IsPlaying ();
|
|
||||||
bool IsMIDI () const;
|
|
||||||
bool IsValid () const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void CheckCaps(DWORD dev_id);
|
||||||
|
void DoInitialSetup();
|
||||||
|
void DoRestart();
|
||||||
|
bool CheckDone();
|
||||||
|
DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time);
|
||||||
|
|
||||||
struct TrackInfo;
|
struct TrackInfo;
|
||||||
|
|
||||||
static DWORD WINAPI PlayerProc (LPVOID lpParameter);
|
|
||||||
void OutputVolume (DWORD volume);
|
|
||||||
void ProcessInitialMetaEvents ();
|
void ProcessInitialMetaEvents ();
|
||||||
DWORD SendCommands ();
|
DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay);
|
||||||
void SendCommand (TrackInfo *track);
|
|
||||||
TrackInfo *FindNextDue ();
|
TrackInfo *FindNextDue ();
|
||||||
|
void SetTempo(int new_tempo);
|
||||||
HMIDIOUT MidiOut;
|
|
||||||
HANDLE PlayerThread;
|
|
||||||
HANDLE PauseEvent;
|
|
||||||
HANDLE ExitEvent;
|
|
||||||
HANDLE TicEvent;
|
|
||||||
HANDLE VolumeChangeEvent;
|
|
||||||
DWORD SavedVolume;
|
|
||||||
bool VolumeWorks;
|
|
||||||
|
|
||||||
BYTE *MusHeader;
|
BYTE *MusHeader;
|
||||||
BYTE ChannelVolumes[16];
|
|
||||||
TrackInfo *Tracks;
|
TrackInfo *Tracks;
|
||||||
TrackInfo *TrackDue;
|
TrackInfo *TrackDue;
|
||||||
int NumTracks;
|
int NumTracks;
|
||||||
int Format;
|
int Format;
|
||||||
int Division;
|
|
||||||
int Tempo;
|
|
||||||
WORD DesignationMask;
|
WORD DesignationMask;
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
// MOD file played with FMOD ------------------------------------------------
|
// MOD file played with FMOD ------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,62 @@
|
||||||
|
/*
|
||||||
|
** music_midi_midiout.cpp
|
||||||
|
** Code to let ZDoom play SMF MIDI music through the MIDI streaming API.
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2008 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.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
// HEADER FILES ------------------------------------------------------------
|
||||||
|
|
||||||
#include "i_musicinterns.h"
|
#include "i_musicinterns.h"
|
||||||
#include "templates.h"
|
#include "templates.h"
|
||||||
#include "doomdef.h"
|
#include "doomdef.h"
|
||||||
#include "m_swap.h"
|
#include "m_swap.h"
|
||||||
|
|
||||||
EXTERN_CVAR (Float, snd_midivolume)
|
// MACROS ------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define MAX_TIME (1000000/20) // Send out 1/20 of a sec of events at a time.
|
||||||
|
|
||||||
|
// Used by SendCommand to check for unexpected end-of-track conditions.
|
||||||
|
#define CHECK_FINISHED \
|
||||||
|
if (track->TrackP >= track->MaxTrackP) \
|
||||||
|
{ \
|
||||||
|
track->Finished = true; \
|
||||||
|
return events; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPES -------------------------------------------------------------------
|
||||||
|
|
||||||
struct MIDISong2::TrackInfo
|
struct MIDISong2::TrackInfo
|
||||||
{
|
{
|
||||||
|
@ -27,20 +79,46 @@ struct MIDISong2::TrackInfo
|
||||||
DWORD ReadVarLen ();
|
DWORD ReadVarLen ();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||||
|
|
||||||
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||||
|
|
||||||
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||||
|
|
||||||
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||||
|
|
||||||
|
EXTERN_CVAR (Float, snd_midivolume)
|
||||||
|
|
||||||
extern DWORD midivolume;
|
extern DWORD midivolume;
|
||||||
extern UINT mididevice;
|
extern UINT mididevice;
|
||||||
|
|
||||||
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||||
|
|
||||||
static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 };
|
static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 };
|
||||||
static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
|
|
||||||
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||||
|
|
||||||
|
// CODE --------------------------------------------------------------------
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 Constructor
|
||||||
|
//
|
||||||
|
// Buffers the file and does some validation of the SMF header.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len)
|
MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len)
|
||||||
: MidiOut (0), PlayerThread (0),
|
: MusHeader(0), Tracks(0)
|
||||||
PauseEvent (0), ExitEvent (0), VolumeChangeEvent (0),
|
|
||||||
MusHeader (0)
|
|
||||||
{
|
{
|
||||||
int p;
|
int p;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (ExitEvent == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
MusHeader = new BYTE[len];
|
MusHeader = new BYTE[len];
|
||||||
if (file != NULL)
|
if (file != NULL)
|
||||||
{
|
{
|
||||||
|
@ -70,8 +148,12 @@ MIDISong2::MIDISong2 (FILE *file, char * musiccache, int len)
|
||||||
NumTracks = MusHeader[10] * 256 + MusHeader[11];
|
NumTracks = MusHeader[10] * 256 + MusHeader[11];
|
||||||
}
|
}
|
||||||
|
|
||||||
// The timers only have millisecond accuracy, not microsecond.
|
// The division is the number of pulses per quarter note (PPQN).
|
||||||
Division = (MusHeader[12] * 256 + MusHeader[13]) * 1000;
|
Division = MusHeader[12] * 256 + MusHeader[13];
|
||||||
|
if (Division == 0)
|
||||||
|
{ // PPQN is zero? Then the song cannot play because it never pulses.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Tracks = new TrackInfo[NumTracks];
|
Tracks = new TrackInfo[NumTracks];
|
||||||
|
|
||||||
|
@ -110,42 +192,16 @@ MIDISong2::MIDISong2 (FILE *file, char * musiccache, int len)
|
||||||
{ // No tracks, so nothing to play
|
{ // No tracks, so nothing to play
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ExitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
//==========================================================================
|
||||||
if (ExitEvent == NULL)
|
//
|
||||||
{
|
// MIDISong2 Destructor
|
||||||
Printf (PRINT_BOLD, "Could not create exit event for MIDI playback\n");
|
//
|
||||||
return;
|
//==========================================================================
|
||||||
}
|
|
||||||
VolumeChangeEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
||||||
if (VolumeChangeEvent == NULL)
|
|
||||||
{
|
|
||||||
Printf (PRINT_BOLD, "Could not create volume event for MIDI playback\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PauseEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
||||||
if (PauseEvent == NULL)
|
|
||||||
{
|
|
||||||
Printf (PRINT_BOLD, "Could not create pause event for MIDI playback\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MIDISong2::~MIDISong2 ()
|
MIDISong2::~MIDISong2 ()
|
||||||
{
|
{
|
||||||
Stop ();
|
|
||||||
|
|
||||||
if (PauseEvent != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle (PauseEvent);
|
|
||||||
}
|
|
||||||
if (ExitEvent != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle (ExitEvent);
|
|
||||||
}
|
|
||||||
if (VolumeChangeEvent != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle (VolumeChangeEvent);
|
|
||||||
}
|
|
||||||
if (Tracks != NULL)
|
if (Tracks != NULL)
|
||||||
{
|
{
|
||||||
delete[] Tracks;
|
delete[] Tracks;
|
||||||
|
@ -156,27 +212,20 @@ MIDISong2::~MIDISong2 ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MIDISong2::IsMIDI () const
|
//==========================================================================
|
||||||
{
|
//
|
||||||
return true;
|
// MIDISong2 :: CheckCaps
|
||||||
}
|
//
|
||||||
|
// Find out if this is an FM synth or not for EMIDI's benefit.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
bool MIDISong2::IsValid () const
|
void MIDISong2::CheckCaps(DWORD dev_id)
|
||||||
{
|
|
||||||
return PauseEvent != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MIDISong2::Play (bool looping)
|
|
||||||
{
|
{
|
||||||
MIDIOUTCAPS caps;
|
MIDIOUTCAPS caps;
|
||||||
DWORD tid;
|
|
||||||
|
|
||||||
m_Status = STATE_Stopped;
|
|
||||||
m_Looping = looping;
|
|
||||||
|
|
||||||
// Find out if this an FM synth or not for EMIDI
|
|
||||||
DesignationMask = 0xFF0F;
|
DesignationMask = 0xFF0F;
|
||||||
if (MMSYSERR_NOERROR == midiOutGetDevCaps (mididevice<0? MIDI_MAPPER:mididevice, &caps, sizeof(caps)))
|
if (MMSYSERR_NOERROR == midiOutGetDevCaps (dev_id, &caps, sizeof(caps)))
|
||||||
{
|
{
|
||||||
if (caps.wTechnology == MOD_FMSYNTH)
|
if (caps.wTechnology == MOD_FMSYNTH)
|
||||||
{
|
{
|
||||||
|
@ -187,222 +236,123 @@ void MIDISong2::Play (bool looping)
|
||||||
DesignationMask = 0x0001;
|
DesignationMask = 0x0001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR != midiOutOpen (&MidiOut, mididevice<0? MIDI_MAPPER:mididevice, 0, 0, CALLBACK_NULL))
|
|
||||||
{
|
|
||||||
Printf (PRINT_BOLD, "Could not open MIDI out device\n");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try two different methods for setting the stream to full volume.
|
|
||||||
// Unfortunately, this isn't as reliable as it once was, which is a pity.
|
|
||||||
// The real volume selection is done by setting the volume controller for
|
|
||||||
// each channel. Because every General MIDI-compliant device must support
|
|
||||||
// this controller, it is the most reliable means of setting the volume.
|
|
||||||
|
|
||||||
VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume (MidiOut, &SavedVolume));
|
//==========================================================================
|
||||||
if (VolumeWorks)
|
//
|
||||||
{
|
// MIDISong2 :: DoInitialSetup
|
||||||
VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume (MidiOut, 0xffffffff));
|
//
|
||||||
}
|
// Sets the starting channel volumes.
|
||||||
else
|
//
|
||||||
{
|
//==========================================================================
|
||||||
// Send the standard SysEx message for full master volume
|
|
||||||
BYTE volmess[] = { 0xf0, 0x7f, 0x7f, 0x04, 0x01, 0x7f, 0x7f, 0xf7 };
|
|
||||||
MIDIHDR hdr = { (LPSTR)volmess, sizeof(volmess), };
|
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR == midiOutPrepareHeader (MidiOut, &hdr, sizeof(hdr)))
|
void MIDISong2 :: DoInitialSetup()
|
||||||
{
|
{
|
||||||
midiOutLongMsg (MidiOut, &hdr, sizeof(hdr));
|
for (int i = 0; i < 16; ++i)
|
||||||
while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader (MidiOut, &hdr, sizeof(hdr)))
|
|
||||||
{
|
|
||||||
Sleep (10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_midivolume.Callback(); // set volume to current music's properties
|
|
||||||
PlayerThread = CreateThread (NULL, 0, PlayerProc, this, 0, &tid);
|
|
||||||
if (PlayerThread == NULL)
|
|
||||||
{
|
|
||||||
if (VolumeWorks)
|
|
||||||
{
|
|
||||||
midiOutSetVolume (MidiOut, SavedVolume);
|
|
||||||
}
|
|
||||||
midiOutClose (MidiOut);
|
|
||||||
MidiOut = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Status = STATE_Playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MIDISong2::Pause ()
|
|
||||||
{
|
|
||||||
if (m_Status == STATE_Playing)
|
|
||||||
{
|
|
||||||
SetEvent (PauseEvent);
|
|
||||||
m_Status = STATE_Paused;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MIDISong2::Resume ()
|
|
||||||
{
|
|
||||||
if (m_Status == STATE_Paused)
|
|
||||||
{
|
|
||||||
SetEvent (PauseEvent);
|
|
||||||
m_Status = STATE_Playing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MIDISong2::Stop ()
|
|
||||||
{
|
|
||||||
if (PlayerThread)
|
|
||||||
{
|
|
||||||
SetEvent (ExitEvent);
|
|
||||||
WaitForSingleObject (PlayerThread, INFINITE);
|
|
||||||
CloseHandle (PlayerThread);
|
|
||||||
PlayerThread = NULL;
|
|
||||||
}
|
|
||||||
if (MidiOut)
|
|
||||||
{
|
|
||||||
midiOutReset (MidiOut);
|
|
||||||
if (VolumeWorks)
|
|
||||||
{
|
|
||||||
midiOutSetVolume (MidiOut, SavedVolume);
|
|
||||||
}
|
|
||||||
midiOutClose (MidiOut);
|
|
||||||
MidiOut = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MIDISong2::IsPlaying ()
|
|
||||||
{
|
|
||||||
return m_Status != STATE_Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MIDISong2::SetVolume (float volume)
|
|
||||||
{
|
|
||||||
SetEvent (VolumeChangeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD WINAPI MIDISong2::PlayerProc (LPVOID lpParameter)
|
|
||||||
{
|
|
||||||
MIDISong2 *song = (MIDISong2 *)lpParameter;
|
|
||||||
HANDLE events[2] = { song->ExitEvent, song->PauseEvent };
|
|
||||||
bool waited = false;
|
|
||||||
int i;
|
|
||||||
DWORD wait;
|
|
||||||
|
|
||||||
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);
|
|
||||||
|
|
||||||
for (i = 0; i < 16; ++i)
|
|
||||||
{
|
{
|
||||||
// The ASS uses a default volume of 90, but all the other
|
// The ASS uses a default volume of 90, but all the other
|
||||||
// sources I can find say it's 100. Ideally, any song that
|
// sources I can find say it's 100. Ideally, any song that
|
||||||
// cares about its volume is going to initialize it to
|
// cares about its volume is going to initialize it to
|
||||||
// whatever it wants and override this default.
|
// whatever it wants and override this default.
|
||||||
song->ChannelVolumes[i] = 100;
|
ChannelVolumes[i] = 100;
|
||||||
}
|
|
||||||
|
|
||||||
song->OutputVolume (midivolume & 0xffff);
|
|
||||||
song->Tempo = 500000;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
for (i = 0; i < song->NumTracks; ++i)
|
|
||||||
{
|
|
||||||
song->Tracks[i].TrackP = 0;
|
|
||||||
song->Tracks[i].Finished = false;
|
|
||||||
song->Tracks[i].RunningStatus = 0;
|
|
||||||
song->Tracks[i].Designated = false;
|
|
||||||
song->Tracks[i].Designation = 0;
|
|
||||||
song->Tracks[i].LoopCount = -1;
|
|
||||||
song->Tracks[i].EProgramChange = false;
|
|
||||||
song->Tracks[i].EVolume = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
song->ProcessInitialMetaEvents ();
|
|
||||||
|
|
||||||
for (i = 0; i < song->NumTracks; ++i)
|
|
||||||
{
|
|
||||||
song->Tracks[i].Delay = song->Tracks[i].ReadVarLen ();
|
|
||||||
}
|
|
||||||
|
|
||||||
song->TrackDue = song->Tracks;
|
|
||||||
song->TrackDue = song->FindNextDue ();
|
|
||||||
|
|
||||||
while (0 != (wait = song->SendCommands ()))
|
|
||||||
{
|
|
||||||
waited = true;
|
|
||||||
|
|
||||||
// Wait for the exit or pause event or the next note
|
|
||||||
switch (WaitForMultipleObjects (2, events, FALSE, wait * song->Tempo / song->Division))
|
|
||||||
{
|
|
||||||
case WAIT_OBJECT_0:
|
|
||||||
song->m_Status = STATE_Stopped;
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case WAIT_OBJECT_0+1:
|
|
||||||
// Go paused
|
|
||||||
song->OutputVolume (0);
|
|
||||||
// Wait for the exit or pause event
|
|
||||||
if (WAIT_OBJECT_0 == WaitForMultipleObjects (2, events, FALSE, INFINITE))
|
|
||||||
{
|
|
||||||
song->m_Status = STATE_Stopped;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
song->OutputVolume (midivolume & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < song->NumTracks; ++i)
|
|
||||||
{
|
|
||||||
if (!song->Tracks[i].Finished)
|
|
||||||
{
|
|
||||||
song->Tracks[i].Delay -= wait;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
song->TrackDue = song->FindNextDue ();
|
|
||||||
|
|
||||||
// Check if the volume needs changing
|
|
||||||
if (WAIT_OBJECT_0 == WaitForSingleObject (song->VolumeChangeEvent, 0))
|
|
||||||
{
|
|
||||||
song->OutputVolume (midivolume & 0xffff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (waited && song->m_Looping);
|
|
||||||
|
|
||||||
song->m_Status = STATE_Stopped;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MIDISong2::OutputVolume (DWORD volume)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 16; ++i)
|
|
||||||
{
|
|
||||||
BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * volume) >> 16);
|
|
||||||
midiOutShortMsg (MidiOut, i | MIDI_CTRLCHANGE | (7<<8) | (courseVol<<16));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD MIDISong2::SendCommands ()
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: DoRestart
|
||||||
|
//
|
||||||
|
// Rewinds every track.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDISong2 :: DoRestart()
|
||||||
{
|
{
|
||||||
while (TrackDue && TrackDue->Delay == 0)
|
int i;
|
||||||
|
|
||||||
|
// Set initial state.
|
||||||
|
for (i = 0; i < NumTracks; ++i)
|
||||||
{
|
{
|
||||||
SendCommand (TrackDue);
|
Tracks[i].TrackP = 0;
|
||||||
|
Tracks[i].Finished = false;
|
||||||
|
Tracks[i].RunningStatus = 0;
|
||||||
|
Tracks[i].Designated = false;
|
||||||
|
Tracks[i].Designation = 0;
|
||||||
|
Tracks[i].LoopCount = -1;
|
||||||
|
Tracks[i].EProgramChange = false;
|
||||||
|
Tracks[i].EVolume = false;
|
||||||
|
}
|
||||||
|
ProcessInitialMetaEvents ();
|
||||||
|
for (i = 0; i < NumTracks; ++i)
|
||||||
|
{
|
||||||
|
Tracks[i].Delay = Tracks[i].ReadVarLen();
|
||||||
|
}
|
||||||
|
TrackDue = Tracks;
|
||||||
TrackDue = FindNextDue();
|
TrackDue = FindNextDue();
|
||||||
}
|
}
|
||||||
return TrackDue ? TrackDue->Delay : 0;
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: CheckDone
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool MIDISong2::CheckDone()
|
||||||
|
{
|
||||||
|
return TrackDue == NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_FINISHED \
|
//==========================================================================
|
||||||
if (track->TrackP >= track->MaxTrackP) \
|
//
|
||||||
{ \
|
// MIDISong2 :: MakeEvents
|
||||||
track->Finished = true; \
|
//
|
||||||
return; \
|
// Copies MIDI events from the SMF and puts them into a MIDI stream
|
||||||
|
// buffer. Returns the new position in the buffer.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
DWORD *MIDISong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
|
||||||
|
{
|
||||||
|
DWORD tot_time = 0;
|
||||||
|
DWORD time = 0;
|
||||||
|
|
||||||
|
while (TrackDue && events < max_event_p && tot_time <= max_time)
|
||||||
|
{
|
||||||
|
time = TrackDue->Delay;
|
||||||
|
// Advance time for all tracks by the amount needed for the one up next.
|
||||||
|
if (time != 0)
|
||||||
|
{
|
||||||
|
tot_time += time * Tempo / Division;
|
||||||
|
for (int i = 0; i < NumTracks; ++i)
|
||||||
|
{
|
||||||
|
if (!Tracks[i].Finished)
|
||||||
|
{
|
||||||
|
Tracks[i].Delay -= time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Play all events for this tic.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
events = SendCommand(events, TrackDue, time);
|
||||||
|
TrackDue = FindNextDue();
|
||||||
|
time = 0;
|
||||||
|
}
|
||||||
|
while (TrackDue && TrackDue->Delay == 0 && events < max_event_p);
|
||||||
|
}
|
||||||
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MIDISong2::SendCommand (TrackInfo *track)
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: SendCommand
|
||||||
|
//
|
||||||
|
// Places a single MIDIEVENT in the event buffer.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
|
||||||
{
|
{
|
||||||
DWORD len;
|
DWORD len;
|
||||||
BYTE event, data1 = 0, data2 = 0;
|
BYTE event, data1 = 0, data2 = 0;
|
||||||
|
@ -412,7 +362,7 @@ void MIDISong2::SendCommand (TrackInfo *track)
|
||||||
event = track->TrackBegin[track->TrackP++];
|
event = track->TrackBegin[track->TrackP++];
|
||||||
CHECK_FINISHED
|
CHECK_FINISHED
|
||||||
|
|
||||||
if (event != 0xF0 && event != 0xFF && event != 0xF7)
|
if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND)
|
||||||
{
|
{
|
||||||
// Normal short message
|
// Normal short message
|
||||||
if ((event & 0xF0) == 0xF0)
|
if ((event & 0xF0) == 0xF0)
|
||||||
|
@ -446,39 +396,35 @@ void MIDISong2::SendCommand (TrackInfo *track)
|
||||||
|
|
||||||
switch (event & 0x70)
|
switch (event & 0x70)
|
||||||
{
|
{
|
||||||
case 0x40:
|
case MIDI_PRGMCHANGE & 0x70:
|
||||||
if (track->EProgramChange)
|
if (track->EProgramChange)
|
||||||
{
|
{
|
||||||
event = 0xFF;
|
event = MIDI_META;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x30:
|
case MIDI_CTRLCHANGE & 0x70:
|
||||||
switch (data1)
|
switch (data1)
|
||||||
{
|
{
|
||||||
case 7:
|
case 7: // Channel volume
|
||||||
if (track->EVolume)
|
if (track->EVolume)
|
||||||
{
|
{ // Tracks that use EMIDI volume ignore normal volume changes.
|
||||||
event = 0xFF;
|
event = MIDI_META;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Some devices don't support master volume
|
data2 = VolumeControllerChange(event & 15, data2);
|
||||||
// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
|
|
||||||
// so assume none of them do and scale channel volumes manually.
|
|
||||||
ChannelVolumes[event & 15] = data2;
|
|
||||||
data2 = (BYTE)(((data2 + 1) * (midivolume & 0xffff)) >> 16);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 39:
|
case 39: // Fine channel volume
|
||||||
// Skip fine volume adjustment because I am lazy.
|
// Skip fine volume adjustment because I am lazy.
|
||||||
// (And it doesn't seem to be used much anyway.)
|
// (And it doesn't seem to be used much anyway.)
|
||||||
event = 0xFF;
|
event = MIDI_META;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 110: // EMIDI Track Designation
|
case 110: // EMIDI Track Designation
|
||||||
// Instruments 4, 5, 6, and 7 are all FM syth.
|
// Instruments 4, 5, 6, and 7 are all FM synth.
|
||||||
// The rest are all wavetable.
|
// The rest are all wavetable.
|
||||||
if (data2 == 127)
|
if (data2 == 127)
|
||||||
{
|
{
|
||||||
|
@ -513,8 +459,7 @@ void MIDISong2::SendCommand (TrackInfo *track)
|
||||||
case 113: // EMIDI Volume
|
case 113: // EMIDI Volume
|
||||||
track->EVolume = true;
|
track->EVolume = true;
|
||||||
data1 = 7;
|
data1 = 7;
|
||||||
ChannelVolumes[event & 15] = data2;
|
data2 = VolumeControllerChange(event & 15, data2);
|
||||||
data2 = (BYTE)(((data2 + 1) * (midivolume & 0xffff)) >> 16);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 116: // EMIDI Loop Begin
|
case 116: // EMIDI Loop Begin
|
||||||
|
@ -581,31 +526,29 @@ void MIDISong2::SendCommand (TrackInfo *track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event = 0xFF;
|
event = MIDI_META;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event != 0xFF && (!track->Designated || (track->Designation & DesignationMask)))
|
if (event != MIDI_META && (!track->Designated || (track->Designation & DesignationMask)))
|
||||||
{
|
{
|
||||||
if (MMSYSERR_NOERROR != midiOutShortMsg (MidiOut, event | (data1<<8) | (data2<<16)))
|
events[0] = delay;
|
||||||
{
|
events[1] = 0;
|
||||||
track->Finished = true;
|
events[2] = event | (data1<<8) | (data2<<16);
|
||||||
return;
|
events += 3;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Skip SysEx events just because I don't want to bother with
|
// Skip SysEx events just because I don't want to bother with them.
|
||||||
// preparing headers and sending them out. The old MIDI player
|
// The old MIDI player ignored them too, so this won't break
|
||||||
// ignores them too, so this won't break anything that played
|
// anything that played before.
|
||||||
// before.
|
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
|
||||||
if (event == 0xF0 || event == 0xF7)
|
|
||||||
{
|
{
|
||||||
len = track->ReadVarLen ();
|
len = track->ReadVarLen ();
|
||||||
track->TrackP += len;
|
track->TrackP += len;
|
||||||
}
|
}
|
||||||
else if (event == 0xFF)
|
else if (event == MIDI_META)
|
||||||
{
|
{
|
||||||
// It's a meta-event
|
// It's a meta-event
|
||||||
event = track->TrackBegin[track->TrackP++];
|
event = track->TrackBegin[track->TrackP++];
|
||||||
|
@ -617,15 +560,19 @@ void MIDISong2::SendCommand (TrackInfo *track)
|
||||||
{
|
{
|
||||||
switch (event)
|
switch (event)
|
||||||
{
|
{
|
||||||
case 0x2F:
|
case MIDI_META_EOT:
|
||||||
track->Finished = true;
|
track->Finished = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x51:
|
case MIDI_META_TEMPO:
|
||||||
Tempo =
|
Tempo =
|
||||||
(track->TrackBegin[track->TrackP+0]<<16) |
|
(track->TrackBegin[track->TrackP+0]<<16) |
|
||||||
(track->TrackBegin[track->TrackP+1]<<8) |
|
(track->TrackBegin[track->TrackP+1]<<8) |
|
||||||
(track->TrackBegin[track->TrackP+2]);
|
(track->TrackBegin[track->TrackP+2]);
|
||||||
|
events[0] = delay;
|
||||||
|
events[1] = 0;
|
||||||
|
events[2] = (MEVT_TEMPO << 24) | Tempo;
|
||||||
|
events += 3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
track->TrackP += len;
|
track->TrackP += len;
|
||||||
|
@ -644,9 +591,16 @@ void MIDISong2::SendCommand (TrackInfo *track)
|
||||||
{
|
{
|
||||||
track->Delay = track->ReadVarLen();
|
track->Delay = track->ReadVarLen();
|
||||||
}
|
}
|
||||||
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef CHECK_FINISHED
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: ProcessInitialMetaEvents
|
||||||
|
//
|
||||||
|
// Handle all the meta events at the start of each track.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
void MIDISong2::ProcessInitialMetaEvents ()
|
void MIDISong2::ProcessInitialMetaEvents ()
|
||||||
{
|
{
|
||||||
|
@ -670,15 +624,16 @@ void MIDISong2::ProcessInitialMetaEvents ()
|
||||||
{
|
{
|
||||||
switch (event)
|
switch (event)
|
||||||
{
|
{
|
||||||
case 0x2F:
|
case MIDI_META_EOT:
|
||||||
track->Finished = true;
|
track->Finished = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x51:
|
case MIDI_META_TEMPO:
|
||||||
Tempo =
|
SetTempo(
|
||||||
(track->TrackBegin[track->TrackP+0]<<16) |
|
(track->TrackBegin[track->TrackP+0]<<16) |
|
||||||
(track->TrackBegin[track->TrackP+1]<<8) |
|
(track->TrackBegin[track->TrackP+1]<<8) |
|
||||||
(track->TrackBegin[track->TrackP+2]);
|
(track->TrackBegin[track->TrackP+2])
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -691,6 +646,14 @@ void MIDISong2::ProcessInitialMetaEvents ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: TrackInfo :: ReadVarLen
|
||||||
|
//
|
||||||
|
// Reads a variable-length SMF number.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
DWORD MIDISong2::TrackInfo::ReadVarLen ()
|
DWORD MIDISong2::TrackInfo::ReadVarLen ()
|
||||||
{
|
{
|
||||||
DWORD time = 0, t = 0x80;
|
DWORD time = 0, t = 0x80;
|
||||||
|
@ -703,6 +666,15 @@ DWORD MIDISong2::TrackInfo::ReadVarLen ()
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: TrackInfo :: FindNextDue
|
||||||
|
//
|
||||||
|
// Scans every track for the next event to play. Returns NULL if all events
|
||||||
|
// have been consumed.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
MIDISong2::TrackInfo *MIDISong2::FindNextDue ()
|
MIDISong2::TrackInfo *MIDISong2::FindNextDue ()
|
||||||
{
|
{
|
||||||
TrackInfo *track;
|
TrackInfo *track;
|
||||||
|
@ -745,4 +717,23 @@ MIDISong2::TrackInfo *MIDISong2::FindNextDue ()
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 :: SetTempo
|
||||||
|
//
|
||||||
|
// Sets the tempo from a track's initial meta events.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDISong2::SetTempo(int new_tempo)
|
||||||
|
{
|
||||||
|
MIDIPROPTEMPO tempo = { sizeof(MIDIPROPTEMPO), new_tempo };
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR == midiStreamProperty(MidiOut, (LPBYTE)&tempo, MIDIPROP_SET | MIDIPROP_TEMPO))
|
||||||
|
{
|
||||||
|
Tempo = new_tempo;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
636
src/sound/music_midistream.cpp
Normal file
636
src/sound/music_midistream.cpp
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
/*
|
||||||
|
** music_midistream.cpp
|
||||||
|
** Implements base class for MIDI and MUS streaming.
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2008 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.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
// HEADER FILES ------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "i_musicinterns.h"
|
||||||
|
#include "templates.h"
|
||||||
|
#include "doomdef.h"
|
||||||
|
#include "m_swap.h"
|
||||||
|
|
||||||
|
// MACROS ------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define MAX_TIME (1000000/20) // Send out 1/20 of a sec of events at a time.
|
||||||
|
|
||||||
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||||
|
|
||||||
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||||
|
|
||||||
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||||
|
|
||||||
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||||
|
|
||||||
|
EXTERN_CVAR (Float, snd_midivolume)
|
||||||
|
|
||||||
|
extern DWORD midivolume;
|
||||||
|
extern UINT mididevice;
|
||||||
|
|
||||||
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||||
|
|
||||||
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||||
|
|
||||||
|
// CODE --------------------------------------------------------------------
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer Constructor
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
MIDIStreamer::MIDIStreamer()
|
||||||
|
: MidiOut(0), PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
|
||||||
|
Division(0), InitialTempo(500000)
|
||||||
|
{
|
||||||
|
BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
if (BufferDoneEvent == NULL)
|
||||||
|
{
|
||||||
|
Printf(PRINT_BOLD, "Could not create buffer done event for MIDI playback\n");
|
||||||
|
}
|
||||||
|
ExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
if (ExitEvent == NULL)
|
||||||
|
{
|
||||||
|
Printf(PRINT_BOLD, "Could not create exit event for MIDI playback\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer Destructor
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
MIDIStreamer::~MIDIStreamer()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
if (ExitEvent != NULL)
|
||||||
|
{
|
||||||
|
CloseHandle(ExitEvent);
|
||||||
|
}
|
||||||
|
if (BufferDoneEvent != NULL)
|
||||||
|
{
|
||||||
|
CloseHandle(BufferDoneEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: IsMIDI
|
||||||
|
//
|
||||||
|
// You bet it is!
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool MIDIStreamer::IsMIDI() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: IsValid
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool MIDIStreamer::IsValid() const
|
||||||
|
{
|
||||||
|
return ExitEvent != NULL && Division != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: CheckCaps
|
||||||
|
//
|
||||||
|
// Called immediately after the device is opened in case a subclass should
|
||||||
|
// want to alter its behavior depending on which device it got.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::CheckCaps(DWORD dev_id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: Play
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::Play (bool looping)
|
||||||
|
{
|
||||||
|
DWORD tid;
|
||||||
|
UINT dev_id;
|
||||||
|
|
||||||
|
m_Status = STATE_Stopped;
|
||||||
|
m_Looping = looping;
|
||||||
|
EndQueued = 0;
|
||||||
|
VolumeChanged = false;
|
||||||
|
Restarting = true;
|
||||||
|
InitialPlayback = true;
|
||||||
|
dev_id = MAX(mididevice, 0u);
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOpen(&MidiOut, &dev_id, 1, (DWORD_PTR)Callback, (DWORD_PTR)this, CALLBACK_FUNCTION))
|
||||||
|
{
|
||||||
|
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckCaps(dev_id);
|
||||||
|
|
||||||
|
// Set time division and tempo.
|
||||||
|
MIDIPROPTIMEDIV timediv = { sizeof(MIDIPROPTIMEDIV), Division };
|
||||||
|
MIDIPROPTEMPO tempo = { sizeof(MIDIPROPTEMPO), Tempo = InitialTempo };
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&timediv, MIDIPROP_SET | MIDIPROP_TIMEDIV) ||
|
||||||
|
MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&tempo, MIDIPROP_SET | MIDIPROP_TEMPO))
|
||||||
|
{
|
||||||
|
Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
|
||||||
|
midiStreamClose(MidiOut);
|
||||||
|
MidiOut = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try two different methods for setting the stream to full volume.
|
||||||
|
// Unfortunately, this isn't as reliable as it once was, which is a pity.
|
||||||
|
// The real volume selection is done by setting the volume controller for
|
||||||
|
// each channel. Because every General MIDI-compliant device must support
|
||||||
|
// this controller, it is the most reliable means of setting the volume.
|
||||||
|
|
||||||
|
VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume((HMIDIOUT)MidiOut, &SavedVolume));
|
||||||
|
if (VolumeWorks)
|
||||||
|
{
|
||||||
|
VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume((HMIDIOUT)MidiOut, 0xffffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
snd_midivolume.Callback(); // set volume to current music's properties
|
||||||
|
OutputVolume (midivolume & 0xffff);
|
||||||
|
|
||||||
|
ResetEvent(ExitEvent);
|
||||||
|
ResetEvent(BufferDoneEvent);
|
||||||
|
|
||||||
|
// Fill the initial buffers for the song.
|
||||||
|
BufferNum = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
|
||||||
|
if (res == SONG_MORE)
|
||||||
|
{
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
Printf ("Initial midiStreamOut failed\n");
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BufferNum ^= 1;
|
||||||
|
}
|
||||||
|
else if (res == SONG_DONE)
|
||||||
|
{
|
||||||
|
if (looping)
|
||||||
|
{
|
||||||
|
Restarting = true;
|
||||||
|
if (SONG_MORE == FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
|
||||||
|
{
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
Printf ("Initial midiStreamOut failed\n");
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BufferNum ^= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EndQueued = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (BufferNum != 0);
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamRestart(MidiOut))
|
||||||
|
{
|
||||||
|
Printf ("midiStreamRestart failed\n");
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayerThread = CreateThread(NULL, 0, PlayerProc, this, 0, &tid);
|
||||||
|
if (PlayerThread == NULL)
|
||||||
|
{
|
||||||
|
Printf ("MUS CreateThread failed\n");
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Status = STATE_Playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: Pause
|
||||||
|
//
|
||||||
|
// "Pauses" the song by setting it to zero volume and filling subsequent
|
||||||
|
// buffers with NOPs until the song is unpaused.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::Pause ()
|
||||||
|
{
|
||||||
|
if (m_Status == STATE_Playing)
|
||||||
|
{
|
||||||
|
m_Status = STATE_Paused;
|
||||||
|
OutputVolume(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: Resume
|
||||||
|
//
|
||||||
|
// "Unpauses" a song by restoring the volume and letting subsequent
|
||||||
|
// buffers store real MIDI events again.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::Resume ()
|
||||||
|
{
|
||||||
|
if (m_Status == STATE_Paused)
|
||||||
|
{
|
||||||
|
OutputVolume(midivolume & 0xffff);
|
||||||
|
m_Status = STATE_Playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: Stop
|
||||||
|
//
|
||||||
|
// Stops playback and closes the player thread and MIDI device.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::Stop ()
|
||||||
|
{
|
||||||
|
EndQueued = 2;
|
||||||
|
if (PlayerThread)
|
||||||
|
{
|
||||||
|
SetEvent(ExitEvent);
|
||||||
|
WaitForSingleObject(PlayerThread, INFINITE);
|
||||||
|
CloseHandle(PlayerThread);
|
||||||
|
PlayerThread = NULL;
|
||||||
|
}
|
||||||
|
if (MidiOut)
|
||||||
|
{
|
||||||
|
midiStreamStop(MidiOut);
|
||||||
|
midiOutReset((HMIDIOUT)MidiOut);
|
||||||
|
if (VolumeWorks)
|
||||||
|
{
|
||||||
|
midiOutSetVolume((HMIDIOUT)MidiOut, SavedVolume);
|
||||||
|
}
|
||||||
|
midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[0], sizeof(MIDIHDR));
|
||||||
|
midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[1], sizeof(MIDIHDR));
|
||||||
|
midiStreamClose(MidiOut);
|
||||||
|
MidiOut = NULL;
|
||||||
|
}
|
||||||
|
m_Status = STATE_Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: IsPlaying
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool MIDIStreamer::IsPlaying ()
|
||||||
|
{
|
||||||
|
return m_Status != STATE_Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: SetVolume
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::SetVolume (float volume)
|
||||||
|
{
|
||||||
|
OutputVolume(midivolume & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: OutputVolume
|
||||||
|
//
|
||||||
|
// Signals the buffer filler to send volume change events on all channels.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::OutputVolume (DWORD volume)
|
||||||
|
{
|
||||||
|
NewVolume = volume;
|
||||||
|
VolumeChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: VolumeControllerChange
|
||||||
|
//
|
||||||
|
// Some devices don't support master volume
|
||||||
|
// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
|
||||||
|
// so assume none of them do and scale channel volumes manually.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int MIDIStreamer::VolumeControllerChange(int channel, int volume)
|
||||||
|
{
|
||||||
|
ChannelVolumes[channel] = volume;
|
||||||
|
return ((volume + 1) * (midivolume & 0xffff)) >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: Callback Static
|
||||||
|
//
|
||||||
|
// Signals the BufferDoneEvent to prepare the next buffer. The buffer is not
|
||||||
|
// prepared in the callback directly, because it's generally still in use by
|
||||||
|
// the MIDI streamer when this callback is executed.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void CALLBACK MIDIStreamer::Callback(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
|
||||||
|
{
|
||||||
|
MIDIStreamer *self = (MIDIStreamer *)dwInstance;
|
||||||
|
|
||||||
|
if (self->EndQueued > 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (uMsg == MOM_DONE)
|
||||||
|
{
|
||||||
|
SetEvent(self->BufferDoneEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: Update
|
||||||
|
//
|
||||||
|
// Called periodically to see if the player thread is still alive. If it
|
||||||
|
// isn't, stop playback now.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MIDIStreamer::Update()
|
||||||
|
{
|
||||||
|
// If the PlayerThread is signalled, then it's dead.
|
||||||
|
if (PlayerThread != NULL &&
|
||||||
|
WaitForSingleObject(PlayerThread, 0) == WAIT_OBJECT_0)
|
||||||
|
{
|
||||||
|
CloseHandle(PlayerThread);
|
||||||
|
PlayerThread = NULL;
|
||||||
|
Printf ("MIDI playback failure\n");
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: PlayerProc Static
|
||||||
|
//
|
||||||
|
// Entry point for the player thread.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
DWORD WINAPI MIDIStreamer::PlayerProc (LPVOID lpParameter)
|
||||||
|
{
|
||||||
|
return ((MIDIStreamer *)lpParameter)->PlayerLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: PlayerLoop
|
||||||
|
//
|
||||||
|
// Services MIDI playback events.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
DWORD MIDIStreamer::PlayerLoop()
|
||||||
|
{
|
||||||
|
HANDLE events[2] = { BufferDoneEvent, ExitEvent };
|
||||||
|
|
||||||
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
|
||||||
|
{
|
||||||
|
case WAIT_OBJECT_0:
|
||||||
|
if (ServiceEvent())
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WAIT_OBJECT_0 + 1:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Should not happen.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: ServiceEvent
|
||||||
|
//
|
||||||
|
// Fills the buffer that just finished playing with new events and appends
|
||||||
|
// it to the MIDI stream queue. Stops the song if playback is over. Returns
|
||||||
|
// true if a problem occured and playback should stop.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool MIDIStreamer::ServiceEvent()
|
||||||
|
{
|
||||||
|
if (EndQueued == 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (MMSYSERR_NOERROR != midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fill:
|
||||||
|
switch (FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
|
||||||
|
{
|
||||||
|
case SONG_MORE:
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BufferNum ^= 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SONG_DONE:
|
||||||
|
if (m_Looping)
|
||||||
|
{
|
||||||
|
Restarting = true;
|
||||||
|
goto fill;
|
||||||
|
}
|
||||||
|
EndQueued = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDIStreamer :: FillBuffer
|
||||||
|
//
|
||||||
|
// Copies MIDI events from the SMF and puts them into a MIDI stream
|
||||||
|
// buffer. Filling the buffer stops when the song end is encountered, the
|
||||||
|
// buffer space is used up, or the maximum time for a buffer is hit.
|
||||||
|
//
|
||||||
|
// Can return:
|
||||||
|
// - SONG_MORE if the buffer was prepared with data.
|
||||||
|
// - SONG_DONE if the song's end was reached.
|
||||||
|
// The buffer will never have data in this case.
|
||||||
|
// - SONG_ERROR if there was a problem preparing the buffer.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
|
||||||
|
{
|
||||||
|
if (!Restarting && CheckDone())
|
||||||
|
{
|
||||||
|
return SONG_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i;
|
||||||
|
DWORD *events = Events[buffer_num], *max_event_p;
|
||||||
|
DWORD tot_time = 0;
|
||||||
|
DWORD time = 0;
|
||||||
|
|
||||||
|
// The final event is for a NOP to hold the delay from the last event.
|
||||||
|
max_event_p = events + (max_events - 1) * 3;
|
||||||
|
|
||||||
|
if (InitialPlayback)
|
||||||
|
{
|
||||||
|
InitialPlayback = false;
|
||||||
|
if (!VolumeWorks)
|
||||||
|
{
|
||||||
|
// Send the full master volume SysEx message.
|
||||||
|
events[0] = 0; // dwDeltaTime
|
||||||
|
events[1] = 0; // dwStreamID
|
||||||
|
events[2] = (MEVT_LONGMSG << 24) | 8; // dwEvent
|
||||||
|
events[3] = 0x047f7ff0; // dwParms[0]
|
||||||
|
events[4] = 0xf77f7f01; // dwParms[1]
|
||||||
|
events += 5;
|
||||||
|
}
|
||||||
|
DoInitialSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the volume has changed, stick those events at the start of this buffer.
|
||||||
|
if (VolumeChanged && (m_Status != STATE_Paused || NewVolume == 0))
|
||||||
|
{
|
||||||
|
VolumeChanged = false;
|
||||||
|
for (i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * NewVolume) >> 16);
|
||||||
|
events[0] = 0; // dwDeltaTime
|
||||||
|
events[1] = 0; // dwStreamID
|
||||||
|
events[2] = MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16);
|
||||||
|
events += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play nothing while paused.
|
||||||
|
if (m_Status == STATE_Paused)
|
||||||
|
{
|
||||||
|
// Be more responsive when unpausing by only playing each buffer
|
||||||
|
// for a third of the maximum time.
|
||||||
|
events[0] = MAX<DWORD>(1, (max_time / 3) * Division / Tempo);
|
||||||
|
events[1] = 0;
|
||||||
|
events[2] = MEVT_NOP << 24;
|
||||||
|
events += 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Restarting)
|
||||||
|
{
|
||||||
|
Restarting = false;
|
||||||
|
// Stop all notes in case any were left hanging.
|
||||||
|
for (i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
events[0] = 0; // dwDeltaTime
|
||||||
|
events[1] = 0; // dwStreamID
|
||||||
|
events[2] = MIDI_NOTEOFF | i | (60 << 8) | (64<<16);
|
||||||
|
events += 3;
|
||||||
|
}
|
||||||
|
DoRestart();
|
||||||
|
}
|
||||||
|
events = MakeEvents(events, max_event_p, max_time);
|
||||||
|
}
|
||||||
|
memset(&Buffer[buffer_num], 0, sizeof(MIDIHDR));
|
||||||
|
Buffer[buffer_num].lpData = (LPSTR)Events[buffer_num];
|
||||||
|
Buffer[buffer_num].dwBufferLength = DWORD((LPSTR)events - Buffer[buffer_num].lpData);
|
||||||
|
Buffer[buffer_num].dwBytesRecorded = Buffer[buffer_num].dwBufferLength;
|
||||||
|
if (MMSYSERR_NOERROR != midiOutPrepareHeader((HMIDIOUT)MidiOut, &Buffer[buffer_num], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
return SONG_ERROR;
|
||||||
|
}
|
||||||
|
return SONG_MORE;
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -1,17 +1,66 @@
|
||||||
|
/*
|
||||||
|
** music_mus_midiout.cpp
|
||||||
|
** Code to let ZDoom play MUS music through the MIDI streaming API.
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2008 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.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
// HEADER FILES ------------------------------------------------------------
|
||||||
|
|
||||||
#include "i_musicinterns.h"
|
#include "i_musicinterns.h"
|
||||||
#include "templates.h"
|
#include "templates.h"
|
||||||
#include "doomdef.h"
|
#include "doomdef.h"
|
||||||
#include "m_swap.h"
|
#include "m_swap.h"
|
||||||
|
|
||||||
|
// MACROS ------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define MAX_TIME (140/20) // Each stream buffer lasts only 1/20 of a second
|
||||||
|
|
||||||
|
// TYPES -------------------------------------------------------------------
|
||||||
|
|
||||||
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||||
|
|
||||||
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||||
|
|
||||||
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||||
|
|
||||||
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||||
|
|
||||||
extern DWORD midivolume;
|
extern DWORD midivolume;
|
||||||
extern UINT mididevice;
|
extern UINT mididevice;
|
||||||
extern HANDLE MusicEvent;
|
|
||||||
|
|
||||||
#define MAX_TIME (10)
|
|
||||||
|
|
||||||
EXTERN_CVAR (Float, snd_midivolume)
|
EXTERN_CVAR (Float, snd_midivolume)
|
||||||
|
|
||||||
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||||
|
|
||||||
static const BYTE CtrlTranslate[15] =
|
static const BYTE CtrlTranslate[15] =
|
||||||
{
|
{
|
||||||
0, // program change
|
0, // program change
|
||||||
|
@ -31,11 +80,28 @@ static const BYTE CtrlTranslate[15] =
|
||||||
121, // reset all controllers
|
121, // reset all controllers
|
||||||
};
|
};
|
||||||
|
|
||||||
MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len)
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||||
: MidiOut(0), MusHeader(0), MusBuffer(0)
|
|
||||||
{
|
|
||||||
MusHeader = (MUSHeader *)new BYTE[len];
|
|
||||||
|
|
||||||
|
// CODE --------------------------------------------------------------------
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MUSSong2 Constructor
|
||||||
|
//
|
||||||
|
// Performs some validity checks on the MUS file, buffers it, and creates
|
||||||
|
// the playback thread control events.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len)
|
||||||
|
: MusHeader(0), MusBuffer(0)
|
||||||
|
{
|
||||||
|
if (ExitEvent == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MusHeader = (MUSHeader *)new BYTE[len];
|
||||||
if (file != NULL)
|
if (file != NULL)
|
||||||
{
|
{
|
||||||
if (fread(MusHeader, 1, len, file) != (size_t)len)
|
if (fread(MusHeader, 1, len, file) != (size_t)len)
|
||||||
|
@ -59,322 +125,84 @@ MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FullVolEvent.dwDeltaTime = 0;
|
|
||||||
FullVolEvent.dwStreamID = 0;
|
|
||||||
FullVolEvent.dwEvent = MEVT_LONGMSG | 8;
|
|
||||||
FullVolEvent.SysEx[0] = 0xf0;
|
|
||||||
FullVolEvent.SysEx[1] = 0x7f;
|
|
||||||
FullVolEvent.SysEx[2] = 0x7f;
|
|
||||||
FullVolEvent.SysEx[3] = 0x04;
|
|
||||||
FullVolEvent.SysEx[4] = 0x01;
|
|
||||||
FullVolEvent.SysEx[5] = 0x7f;
|
|
||||||
FullVolEvent.SysEx[6] = 0x7f;
|
|
||||||
FullVolEvent.SysEx[7] = 0xf7;
|
|
||||||
|
|
||||||
MusBuffer = (BYTE *)MusHeader + LittleShort(MusHeader->SongStart);
|
MusBuffer = (BYTE *)MusHeader + LittleShort(MusHeader->SongStart);
|
||||||
MaxMusP = MIN<int> (LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart));
|
MaxMusP = MIN<int> (LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart));
|
||||||
MusP = 0;
|
Division = 140;
|
||||||
|
InitialTempo = 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MUSSong2 Destructor
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
MUSSong2::~MUSSong2 ()
|
MUSSong2::~MUSSong2 ()
|
||||||
{
|
{
|
||||||
Stop ();
|
if (MusHeader != NULL)
|
||||||
}
|
|
||||||
|
|
||||||
bool MUSSong2::IsMIDI () const
|
|
||||||
{
|
{
|
||||||
return true;
|
delete[] (BYTE *)MusHeader;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MUSSong2::IsValid () const
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MUSSong2 :: DoInitialSetup
|
||||||
|
//
|
||||||
|
// Sets up initial velocities and channel volumes.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void MUSSong2::DoInitialSetup()
|
||||||
{
|
{
|
||||||
return MusBuffer != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MUSSong2::Play (bool looping)
|
|
||||||
{
|
|
||||||
UINT dev_id;
|
|
||||||
|
|
||||||
m_Status = STATE_Stopped;
|
|
||||||
m_Looping = looping;
|
|
||||||
EndQueued = false;
|
|
||||||
VolumeChanged = false;
|
|
||||||
Restarting = false;
|
|
||||||
|
|
||||||
dev_id = MAX(mididevice, 0u);
|
|
||||||
if (MMSYSERR_NOERROR != midiStreamOpen(&MidiOut, &dev_id, 1, (DWORD_PTR)Callback, (DWORD_PTR)this, CALLBACK_FUNCTION))
|
|
||||||
{
|
|
||||||
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set time division and tempo.
|
|
||||||
MIDIPROPTIMEDIV timediv = { sizeof(MIDIPROPTIMEDIV), 140 };
|
|
||||||
MIDIPROPTEMPO tempo = { sizeof(MIDIPROPTEMPO), 1000000 };
|
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&timediv, MIDIPROP_SET | MIDIPROP_TIMEDIV) ||
|
|
||||||
MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&tempo, MIDIPROP_SET | MIDIPROP_TEMPO))
|
|
||||||
{
|
|
||||||
Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
|
|
||||||
midiStreamClose(MidiOut);
|
|
||||||
MidiOut = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try two different methods for setting the stream to full volume.
|
|
||||||
// Unfortunately, this isn't as reliable as it once was, which is a pity.
|
|
||||||
// The real volume selection is done by setting the volume controller for
|
|
||||||
// each channel. Because every General MIDI-compliant device must support
|
|
||||||
// this controller, it is the most reliable means of setting the volume.
|
|
||||||
|
|
||||||
VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume((HMIDIOUT)MidiOut, &SavedVolume));
|
|
||||||
if (VolumeWorks)
|
|
||||||
{
|
|
||||||
VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume((HMIDIOUT)MidiOut, 0xffffffff));
|
|
||||||
}
|
|
||||||
if (!VolumeWorks)
|
|
||||||
{ // Send the standard SysEx message for full master volume
|
|
||||||
memset(&Buffer[0], 0, sizeof(Buffer[0]));
|
|
||||||
Buffer[0].lpData = (LPSTR)&FullVolEvent;
|
|
||||||
Buffer[0].dwBufferLength = sizeof(FullVolEvent);
|
|
||||||
Buffer[0].dwBytesRecorded = sizeof(FullVolEvent);
|
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR == midiOutPrepareHeader((HMIDIOUT)MidiOut, &Buffer[0], sizeof(Buffer[0])))
|
|
||||||
{
|
|
||||||
midiStreamOut(MidiOut, &Buffer[0], sizeof(Buffer[0]));
|
|
||||||
}
|
|
||||||
BufferNum = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BufferNum = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_midivolume.Callback(); // set volume to current music's properties
|
|
||||||
for (int i = 0; i < 16; ++i)
|
for (int i = 0; i < 16; ++i)
|
||||||
{
|
{
|
||||||
LastVelocity[i] = 64;
|
LastVelocity[i] = 64;
|
||||||
ChannelVolumes[i] = 127;
|
ChannelVolumes[i] = 127;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fill the initial buffers for the song.
|
//==========================================================================
|
||||||
do
|
//
|
||||||
{
|
// MUSSong2 :: DoRestart
|
||||||
int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
|
//
|
||||||
if (res == SONG_MORE)
|
// Rewinds the song.
|
||||||
{
|
//
|
||||||
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(Buffer[0])))
|
//==========================================================================
|
||||||
{
|
|
||||||
Stop();
|
void MUSSong2::DoRestart()
|
||||||
return;
|
|
||||||
}
|
|
||||||
BufferNum ^= 1;
|
|
||||||
}
|
|
||||||
else if (res == SONG_DONE)
|
|
||||||
{
|
|
||||||
if (looping)
|
|
||||||
{
|
{
|
||||||
MusP = 0;
|
MusP = 0;
|
||||||
if (SONG_MORE == FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
|
|
||||||
{
|
|
||||||
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BufferNum ^= 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EndQueued = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (BufferNum != 0);
|
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR != midiStreamRestart(MidiOut))
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_Status = STATE_Playing;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MUSSong2::Pause ()
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MUSSong2 :: CheckDone
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool MUSSong2::CheckDone()
|
||||||
{
|
{
|
||||||
if (m_Status == STATE_Playing)
|
return MusP >= MaxMusP;
|
||||||
{
|
|
||||||
m_Status = STATE_Paused;
|
|
||||||
OutputVolume(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MUSSong2::Resume ()
|
//==========================================================================
|
||||||
{
|
//
|
||||||
if (m_Status == STATE_Paused)
|
// MUSSong2 :: MakeEvents
|
||||||
{
|
//
|
||||||
OutputVolume(midivolume & 0xffff);
|
// Translates MUS events into MIDI events and puts them into a MIDI stream
|
||||||
m_Status = STATE_Playing;
|
// buffer. Returns the new position in the buffer.
|
||||||
}
|
//
|
||||||
}
|
//==========================================================================
|
||||||
|
|
||||||
void MUSSong2::Stop ()
|
DWORD *MUSSong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
|
||||||
{
|
{
|
||||||
EndQueued = 2;
|
|
||||||
if (MidiOut)
|
|
||||||
{
|
|
||||||
midiStreamStop(MidiOut);
|
|
||||||
midiOutReset((HMIDIOUT)MidiOut);
|
|
||||||
if (VolumeWorks)
|
|
||||||
{
|
|
||||||
midiOutSetVolume((HMIDIOUT)MidiOut, SavedVolume);
|
|
||||||
}
|
|
||||||
midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[0], sizeof(MIDIHDR));
|
|
||||||
midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[1], sizeof(MIDIHDR));
|
|
||||||
midiStreamClose(MidiOut);
|
|
||||||
MidiOut = NULL;
|
|
||||||
}
|
|
||||||
m_Status = STATE_Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MUSSong2::IsPlaying ()
|
|
||||||
{
|
|
||||||
return m_Status != STATE_Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MUSSong2::SetVolume (float volume)
|
|
||||||
{
|
|
||||||
OutputVolume(midivolume & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MUSSong2::OutputVolume (DWORD volume)
|
|
||||||
{
|
|
||||||
NewVolume = volume;
|
|
||||||
VolumeChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CALLBACK MUSSong2::Callback(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
|
|
||||||
{
|
|
||||||
MUSSong2 *self = (MUSSong2 *)dwInstance;
|
|
||||||
|
|
||||||
if (self->EndQueued > 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (uMsg == MOM_DONE)
|
|
||||||
{
|
|
||||||
SetEvent(MusicEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MUSSong2::ServiceEvent()
|
|
||||||
{
|
|
||||||
if (EndQueued == 1)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (MMSYSERR_NOERROR != midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
|
||||||
{
|
|
||||||
Printf ("Failed unpreparing MIDI header.\n");
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fill:
|
|
||||||
switch (FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
|
|
||||||
{
|
|
||||||
case SONG_MORE:
|
|
||||||
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
|
||||||
{
|
|
||||||
Printf ("Failed streaming MIDI buffer.\n");
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BufferNum ^= 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SONG_DONE:
|
|
||||||
if (m_Looping)
|
|
||||||
{
|
|
||||||
MusP = 0;
|
|
||||||
Restarting = true;
|
|
||||||
goto fill;
|
|
||||||
}
|
|
||||||
EndQueued = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Stop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns SONG_MORE if the buffer was prepared with data.
|
|
||||||
// Returns SONG_DONE if the song's end was reached. The buffer will never have data in this case.
|
|
||||||
// Returns SONG_ERROR if there was a problem preparing the buffer.
|
|
||||||
int MUSSong2::FillBuffer(int buffer_num, int max_events, DWORD max_time)
|
|
||||||
{
|
|
||||||
if (MusP >= MaxMusP)
|
|
||||||
{
|
|
||||||
return SONG_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
SHORTMIDIEVENT *events = Events[buffer_num];
|
|
||||||
DWORD tot_time = 0;
|
DWORD tot_time = 0;
|
||||||
DWORD time = 0;
|
DWORD time = 0;
|
||||||
|
|
||||||
// If the volume has changed, stick those events at the start of this buffer.
|
max_time = max_time * Division / Tempo;
|
||||||
if (VolumeChanged)
|
|
||||||
{
|
|
||||||
VolumeChanged = false;
|
|
||||||
for (; i < 16; ++i)
|
|
||||||
{
|
|
||||||
BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * NewVolume) >> 16);
|
|
||||||
events[i].dwDeltaTime = 0;
|
|
||||||
events[i].dwStreamID = 0;
|
|
||||||
events[i].dwEvent = MEVT_SHORTMSG | MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the song is starting over, stop all notes in case any were left hanging.
|
while (events < max_event_p && tot_time <= max_time)
|
||||||
if (Restarting)
|
|
||||||
{
|
|
||||||
Restarting = false;
|
|
||||||
for (int j = 0; j < 16; ++i, ++j)
|
|
||||||
{
|
|
||||||
events[i].dwDeltaTime = 0;
|
|
||||||
events[i].dwStreamID = 0;
|
|
||||||
events[i].dwEvent = MEVT_SHORTMSG | MIDI_NOTEOFF | i | (60 << 8) | (64<<16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play nothing while paused.
|
|
||||||
if (m_Status == STATE_Paused)
|
|
||||||
{
|
|
||||||
time = max_time;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The final event is for a NOP to hold the delay from the last event.
|
|
||||||
max_events--;
|
|
||||||
|
|
||||||
for (; i < max_events && tot_time <= max_time; ++i)
|
|
||||||
{
|
{
|
||||||
BYTE mid1, mid2;
|
BYTE mid1, mid2;
|
||||||
BYTE channel;
|
BYTE channel;
|
||||||
|
@ -441,14 +269,9 @@ int MUSSong2::FillBuffer(int buffer_num, int max_events, DWORD max_time)
|
||||||
status |= MIDI_CTRLCHANGE;
|
status |= MIDI_CTRLCHANGE;
|
||||||
mid1 = CtrlTranslate[t];
|
mid1 = CtrlTranslate[t];
|
||||||
mid2 = MusBuffer[MusP++];
|
mid2 = MusBuffer[MusP++];
|
||||||
|
|
||||||
// Some devices don't support master volume
|
|
||||||
// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
|
|
||||||
// so assume none of them do and scale channel volumes manually.
|
|
||||||
if (mid1 == 7)
|
if (mid1 == 7)
|
||||||
{
|
{
|
||||||
ChannelVolumes[channel] = mid2;
|
mid2 = VolumeControllerChange(channel, mid2);
|
||||||
mid2 = (BYTE)(((mid2 + 1) * (midivolume & 0xffff)) >> 16);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -459,9 +282,10 @@ int MUSSong2::FillBuffer(int buffer_num, int max_events, DWORD max_time)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
events[i].dwDeltaTime = time;
|
events[0] = time; // dwDeltaTime
|
||||||
events[i].dwStreamID = 0;
|
events[1] = 0; // dwStreamID
|
||||||
events[i].dwEvent = MEVT_SHORTMSG | status | (mid1 << 8) | (mid2 << 16);
|
events[2] = status | (mid1 << 8) | (mid2 << 16);
|
||||||
|
events += 3;
|
||||||
|
|
||||||
time = 0;
|
time = 0;
|
||||||
if (event & 128)
|
if (event & 128)
|
||||||
|
@ -478,20 +302,11 @@ int MUSSong2::FillBuffer(int buffer_num, int max_events, DWORD max_time)
|
||||||
end:
|
end:
|
||||||
if (time != 0)
|
if (time != 0)
|
||||||
{
|
{
|
||||||
events[i].dwDeltaTime = time;
|
events[0] = time; // dwDeltaTime
|
||||||
events[i].dwStreamID = 0;
|
events[1] = 0; // dwStreamID
|
||||||
events[i].dwEvent = MEVT_NOP;
|
events[2] = MEVT_NOP << 24; // dwEvent
|
||||||
i++;
|
events += 3;
|
||||||
}
|
}
|
||||||
memset(&Buffer[buffer_num], 0, sizeof(MIDIHDR));
|
return events;
|
||||||
Buffer[buffer_num].lpData = (LPSTR)events;
|
|
||||||
Buffer[buffer_num].dwBufferLength = sizeof(events[0]) * i;
|
|
||||||
Buffer[buffer_num].dwBytesRecorded = sizeof(events[0]) * i;
|
|
||||||
if (MMSYSERR_NOERROR != midiOutPrepareHeader((HMIDIOUT)MidiOut, &Buffer[buffer_num], sizeof(MIDIHDR)))
|
|
||||||
{
|
|
||||||
Printf ("Preparing MIDI header failed.\n");
|
|
||||||
return SONG_ERROR;
|
|
||||||
}
|
|
||||||
return SONG_MORE;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -95,7 +95,6 @@
|
||||||
#include "gameconfigfile.h"
|
#include "gameconfigfile.h"
|
||||||
#include "win32iface.h"
|
#include "win32iface.h"
|
||||||
#include "templates.h"
|
#include "templates.h"
|
||||||
#include "i_musicinterns.h"
|
|
||||||
|
|
||||||
#define DINPUT_BUFFERSIZE 32
|
#define DINPUT_BUFFERSIZE 32
|
||||||
|
|
||||||
|
@ -110,7 +109,6 @@ BOOL DI_InitJoy (void);
|
||||||
|
|
||||||
extern HINSTANCE g_hInst;
|
extern HINSTANCE g_hInst;
|
||||||
extern DWORD SessionID;
|
extern DWORD SessionID;
|
||||||
extern HANDLE MusicEvent;
|
|
||||||
|
|
||||||
extern void ShowEAXEditor ();
|
extern void ShowEAXEditor ();
|
||||||
extern bool SpawnEAXWindow;
|
extern bool SpawnEAXWindow;
|
||||||
|
@ -1931,23 +1929,7 @@ void I_GetEvent ()
|
||||||
|
|
||||||
// Briefly enter an alertable state so that if a secondary thread
|
// Briefly enter an alertable state so that if a secondary thread
|
||||||
// crashed, we will execute the APC it sent now.
|
// crashed, we will execute the APC it sent now.
|
||||||
if (MusicEvent != NULL)
|
|
||||||
{
|
|
||||||
DWORD res;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
res = WaitForSingleObjectEx(MusicEvent, 0, TRUE);
|
|
||||||
}
|
|
||||||
while (res == WAIT_IO_COMPLETION);
|
|
||||||
if (res == WAIT_OBJECT_0 && currSong != NULL)
|
|
||||||
{
|
|
||||||
currSong->ServiceEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SleepEx (0, TRUE);
|
SleepEx (0, TRUE);
|
||||||
}
|
|
||||||
|
|
||||||
while (PeekMessage (&mess, NULL, 0, 0, PM_REMOVE))
|
while (PeekMessage (&mess, NULL, 0, 0, PM_REMOVE))
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,7 +62,6 @@
|
||||||
#include "templates.h"
|
#include "templates.h"
|
||||||
#include "gameconfigfile.h"
|
#include "gameconfigfile.h"
|
||||||
#include "v_font.h"
|
#include "v_font.h"
|
||||||
#include "i_musicinterns.h"
|
|
||||||
|
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
|
|
||||||
|
@ -86,7 +85,6 @@ UINT TimerPeriod;
|
||||||
UINT TimerEventID;
|
UINT TimerEventID;
|
||||||
UINT MillisecondsPerTic;
|
UINT MillisecondsPerTic;
|
||||||
HANDLE NewTicArrived;
|
HANDLE NewTicArrived;
|
||||||
HANDLE MusicEvent;
|
|
||||||
uint32 LanguageIDs[4];
|
uint32 LanguageIDs[4];
|
||||||
void CalculateCPUSpeed ();
|
void CalculateCPUSpeed ();
|
||||||
|
|
||||||
|
@ -175,15 +173,7 @@ int I_WaitForTicEvent (int prevtic)
|
||||||
{
|
{
|
||||||
while (prevtic >= tics)
|
while (prevtic >= tics)
|
||||||
{
|
{
|
||||||
HANDLE handles[2] = { NewTicArrived, MusicEvent };
|
WaitForSingleObject(NewTicArrived, 1000/TICRATE);
|
||||||
switch(WaitForMultipleObjects(1 + (MusicEvent != NULL), handles, FALSE, 1000/TICRATE))
|
|
||||||
{
|
|
||||||
case WAIT_OBJECT_0 + 1:
|
|
||||||
if (currSong != NULL)
|
|
||||||
{
|
|
||||||
currSong->ServiceEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tics;
|
return tics;
|
||||||
|
@ -445,11 +435,6 @@ void I_Init (void)
|
||||||
I_WaitForTic = I_WaitForTicPolled;
|
I_WaitForTic = I_WaitForTicPolled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((MusicEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
|
|
||||||
{
|
|
||||||
Printf ("Creation of music event failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
atterm (I_ShutdownSound);
|
atterm (I_ShutdownSound);
|
||||||
I_InitSound ();
|
I_InitSound ();
|
||||||
}
|
}
|
||||||
|
@ -515,8 +500,6 @@ void I_Quit (void)
|
||||||
timeKillEvent (TimerEventID);
|
timeKillEvent (TimerEventID);
|
||||||
if (NewTicArrived)
|
if (NewTicArrived)
|
||||||
CloseHandle (NewTicArrived);
|
CloseHandle (NewTicArrived);
|
||||||
if (MusicEvent)
|
|
||||||
CloseHandle (MusicEvent);
|
|
||||||
|
|
||||||
timeEndPeriod (TimerPeriod);
|
timeEndPeriod (TimerPeriod);
|
||||||
|
|
||||||
|
|
1258
zdoom.vcproj
1258
zdoom.vcproj
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue