2016-03-01 15:47:10 +00:00
/*
* * 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 .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
// HEADER FILES ------------------------------------------------------------
2017-03-10 15:12:52 +00:00
2016-03-01 15:47:10 +00:00
# include "i_musicinterns.h"
# include "templates.h"
# include "doomerrors.h"
2018-03-03 07:52:25 +00:00
# include "v_text.h"
2016-03-01 15:47:10 +00:00
// MACROS ------------------------------------------------------------------
# define MAX_TIME (1000000 / 10) // Send out 1/10 of a sec of events at a time.
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
2019-06-27 07:56:08 +00:00
2016-03-01 15:47:10 +00:00
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
2017-03-10 18:46:22 +00:00
static void WriteVarLen ( TArray < uint8_t > & file , uint32_t value ) ;
2016-03-01 15:47:10 +00:00
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
2019-09-28 14:50:00 +00:00
EXTERN_CVAR ( Bool , snd_midiprecache ) ;
2016-03-01 15:47:10 +00:00
EXTERN_CVAR ( Float , snd_musicvolume )
EXTERN_CVAR ( Int , snd_mididevice )
# ifdef _WIN32
2017-03-10 15:12:52 +00:00
extern unsigned mididevice ;
2016-03-01 15:47:10 +00:00
# endif
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// MIDIStreamer Constructor
//
//==========================================================================
MIDIStreamer : : MIDIStreamer ( EMidiDevice type , const char * args )
:
2019-09-28 08:00:22 +00:00
DeviceType ( type ) , Args ( args )
2016-03-01 15:47:10 +00:00
{
2017-03-10 11:01:29 +00:00
memset ( Buffer , 0 , sizeof ( Buffer ) ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// MIDIStreamer Destructor
//
//==========================================================================
MIDIStreamer : : ~ MIDIStreamer ( )
{
Stop ( ) ;
}
//==========================================================================
//
// MIDIStreamer :: IsMIDI
//
// You bet it is!
//
//==========================================================================
bool MIDIStreamer : : IsMIDI ( ) const
{
return true ;
}
//==========================================================================
//
// MIDIStreamer :: IsValid
//
//==========================================================================
bool MIDIStreamer : : IsValid ( ) const
{
2018-02-23 12:20:07 +00:00
return source ! = nullptr & & source - > isValid ( ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// MIDIStreamer :: SelectMIDIDevice static
//
// Select the MIDI device to play on
//
//==========================================================================
EMidiDevice MIDIStreamer : : SelectMIDIDevice ( EMidiDevice device )
{
/* MIDI are played as:
- OPL :
- if explicitly selected by $ mididevice
- when snd_mididevice is - 3 and no midi device is set for the song
- Timidity :
- if explicitly selected by $ mididevice
- when snd_mididevice is - 2 and no midi device is set for the song
- MMAPI ( Win32 only ) :
- if explicitly selected by $ mididevice ( non - Win32 redirects this to Sound System )
- when snd_mididevice is > = 0 and no midi device is set for the song
- as fallback when both OPL and Timidity failed and snd_mididevice is > = 0
*/
// Choose the type of MIDI device we want.
if ( device ! = MDEV_DEFAULT )
{
return device ;
}
switch ( snd_mididevice )
{
case - 1 : return MDEV_SNDSYS ;
case - 2 : return MDEV_TIMIDITY ;
case - 3 : return MDEV_OPL ;
case - 4 : return MDEV_GUS ;
case - 5 : return MDEV_FLUIDSYNTH ;
case - 6 : return MDEV_WILDMIDI ;
2018-03-07 20:20:25 +00:00
case - 7 : return MDEV_ADL ;
2018-03-24 14:58:47 +00:00
case - 8 : return MDEV_OPN ;
2016-03-01 15:47:10 +00:00
default :
# ifdef _WIN32
return MDEV_MMAPI ;
# else
return MDEV_SNDSYS ;
# endif
}
}
//==========================================================================
//
// MIDIStreamer :: CreateMIDIDevice
//
//==========================================================================
2018-03-03 07:52:25 +00:00
static EMidiDevice lastRequestedDevice , lastSelectedDevice ;
2018-03-06 21:41:27 +00:00
MIDIDevice * MIDIStreamer : : CreateMIDIDevice ( EMidiDevice devtype , int samplerate )
2016-03-01 15:47:10 +00:00
{
2018-02-26 15:34:58 +00:00
bool checked [ MDEV_COUNT ] = { false } ;
2018-02-23 13:36:26 +00:00
2018-03-03 07:52:25 +00:00
MIDIDevice * dev = nullptr ;
2018-02-26 15:34:58 +00:00
if ( devtype = = MDEV_SNDSYS ) devtype = MDEV_FLUIDSYNTH ;
2018-03-03 07:52:25 +00:00
EMidiDevice requestedDevice = devtype , selectedDevice ;
while ( dev = = nullptr )
2016-03-01 15:47:10 +00:00
{
2018-03-03 07:52:25 +00:00
selectedDevice = devtype ;
2018-02-26 15:34:58 +00:00
try
{
2018-03-07 20:20:25 +00:00
switch ( devtype )
{
case MDEV_GUS :
2019-09-27 00:31:27 +00:00
GUS_SetupConfig ( & gusConfig , Args ) ;
dev = CreateTimidityMIDIDevice ( & gusConfig , samplerate ) ;
2018-03-03 07:52:25 +00:00
break ;
2018-02-26 15:34:58 +00:00
2018-03-07 20:20:25 +00:00
case MDEV_ADL :
2019-09-27 00:31:27 +00:00
ADL_SetupConfig ( & adlConfig , Args ) ;
2019-09-26 23:51:05 +00:00
dev = CreateADLMIDIDevice ( & adlConfig ) ;
2018-03-07 20:20:25 +00:00
break ;
2018-03-24 14:58:47 +00:00
case MDEV_OPN :
2019-09-27 00:31:27 +00:00
OPN_SetupConfig ( & opnConfig , Args ) ;
dev = CreateOPNMIDIDevice ( & opnConfig ) ;
2018-03-24 14:58:47 +00:00
break ;
2018-03-07 20:20:25 +00:00
case MDEV_MMAPI :
# ifdef _WIN32
2019-09-28 14:50:00 +00:00
dev = CreateWinMIDIDevice ( mididevice , snd_midiprecache ) ;
2018-03-03 07:52:25 +00:00
break ;
2016-03-01 15:47:10 +00:00
# endif
2018-03-07 20:20:25 +00:00
// Intentional fall-through for non-Windows systems.
2017-05-13 10:33:14 +00:00
2018-03-07 20:20:25 +00:00
case MDEV_FLUIDSYNTH :
2019-09-27 00:31:27 +00:00
Fluid_SetupConfig ( & fluidConfig , Args , true ) ;
2019-09-26 22:16:32 +00:00
dev = CreateFluidSynthMIDIDevice ( samplerate , & fluidConfig , Printf ) ;
2018-03-03 07:52:25 +00:00
break ;
2016-03-01 15:47:10 +00:00
2018-03-07 20:20:25 +00:00
case MDEV_OPL :
2019-09-27 00:31:27 +00:00
OPL_SetupConfig ( & oplMidiConfig , Args ) ;
2019-09-26 23:01:52 +00:00
dev = CreateOplMIDIDevice ( & oplMidiConfig ) ;
2018-03-03 07:52:25 +00:00
break ;
2018-02-26 15:34:58 +00:00
2018-03-07 20:20:25 +00:00
case MDEV_TIMIDITY :
2019-09-27 20:19:00 +00:00
Timidity_SetupConfig ( & timidityConfig , Args ) ;
dev = CreateTimidityPPMIDIDevice ( & timidityConfig , samplerate ) ;
2018-03-03 07:52:25 +00:00
break ;
2018-02-26 15:34:58 +00:00
2018-03-07 20:20:25 +00:00
case MDEV_WILDMIDI :
2019-09-27 22:10:39 +00:00
WildMidi_SetupConfig ( & wildMidiConfig , Args ) ;
dev = CreateWildMIDIDevice ( & wildMidiConfig , samplerate ) ;
2018-03-03 07:52:25 +00:00
break ;
2018-02-26 15:34:58 +00:00
2018-03-07 20:20:25 +00:00
default :
2018-02-26 15:34:58 +00:00
break ;
2018-03-07 20:20:25 +00:00
}
2016-03-01 15:47:10 +00:00
}
2019-09-26 20:30:07 +00:00
catch ( std : : runtime_error & err )
2016-03-01 15:47:10 +00:00
{
2019-09-26 20:30:07 +00:00
DPrintf ( DMSG_WARNING , " %s \n " , err . what ( ) ) ;
2018-02-26 15:34:58 +00:00
checked [ devtype ] = true ;
devtype = MDEV_DEFAULT ;
// Opening the requested device did not work out so choose another one.
if ( ! checked [ MDEV_FLUIDSYNTH ] ) devtype = MDEV_FLUIDSYNTH ;
else if ( ! checked [ MDEV_TIMIDITY ] ) devtype = MDEV_TIMIDITY ;
else if ( ! checked [ MDEV_WILDMIDI ] ) devtype = MDEV_WILDMIDI ;
else if ( ! checked [ MDEV_GUS ] ) devtype = MDEV_GUS ;
# ifdef _WIN32
else if ( ! checked [ MDEV_MMAPI ] ) devtype = MDEV_MMAPI ;
# endif
else if ( ! checked [ MDEV_OPL ] ) devtype = MDEV_OPL ;
2016-03-01 15:47:10 +00:00
2018-02-26 15:34:58 +00:00
if ( devtype = = MDEV_DEFAULT )
{
2018-05-11 15:03:57 +00:00
Printf ( " Failed to play music: Unable to open any MIDI Device. \n " ) ;
2018-02-26 15:34:58 +00:00
return nullptr ;
}
}
2016-03-01 15:47:10 +00:00
}
2018-03-03 07:52:25 +00:00
if ( selectedDevice ! = requestedDevice & & ( selectedDevice ! = lastSelectedDevice | | requestedDevice ! = lastRequestedDevice ) )
{
static const char * devnames [ ] = {
" Windows Default " ,
" OPL " ,
" " ,
" Timidity++ " ,
" FluidSynth " ,
" GUS " ,
" WildMidi "
} ;
lastRequestedDevice = requestedDevice ;
lastSelectedDevice = selectedDevice ;
Printf ( TEXTCOLOR_RED " Unable to create " TEXTCOLOR_ORANGE " %s " TEXTCOLOR_RED " MIDI device. Falling back to " TEXTCOLOR_ORANGE " %s \n " , devnames [ requestedDevice ] , devnames [ selectedDevice ] ) ;
}
return dev ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// MIDIStreamer :: Play
//
//==========================================================================
void MIDIStreamer : : Play ( bool looping , int subsong )
{
EMidiDevice devtype ;
2018-02-23 11:40:43 +00:00
if ( source = = nullptr ) return ; // We have nothing to play so abort.
2016-03-01 15:47:10 +00:00
assert ( MIDI = = NULL ) ;
2018-02-24 17:55:42 +00:00
m_Looping = looping ;
source - > SetMIDISubsong ( subsong ) ;
2016-03-01 15:47:10 +00:00
devtype = SelectMIDIDevice ( DeviceType ) ;
2019-09-28 11:59:46 +00:00
MIDI . reset ( CreateMIDIDevice ( devtype , ( int ) GSnd - > GetOutputRate ( ) ) ) ;
2018-02-24 17:55:42 +00:00
InitPlayback ( ) ;
}
//==========================================================================
//
// MIDIStreamer :: DumpWave
//
//==========================================================================
bool MIDIStreamer : : DumpWave ( const char * filename , int subsong , int samplerate )
{
m_Looping = false ;
if ( source = = nullptr ) return false ; // We have nothing to play so abort.
source - > SetMIDISubsong ( subsong ) ;
assert ( MIDI = = NULL ) ;
auto devtype = SelectMIDIDevice ( DeviceType ) ;
2018-03-08 09:38:02 +00:00
if ( devtype = = MDEV_MMAPI )
{
2019-09-28 11:59:46 +00:00
throw std : : runtime_error ( " MMAPI device is not supported " ) ;
2018-03-08 09:38:02 +00:00
}
2019-09-28 11:59:46 +00:00
auto iMIDI = CreateMIDIDevice ( devtype , samplerate ) ;
2019-09-28 14:50:00 +00:00
auto writer = new MIDIWaveWriter ( filename , static_cast < SoftSynthMIDIDevice * > ( iMIDI ) ) ;
MIDI . reset ( writer ) ;
bool res = InitPlayback ( ) ;
if ( ! writer - > CloseFile ( ) )
{
char buffer [ 80 ] ;
snprintf ( buffer , 80 , " Could not finish writing wave file: %s \n " , strerror ( errno ) ) ;
throw std : : runtime_error ( buffer ) ;
}
return res ;
2018-02-24 17:55:42 +00:00
}
//==========================================================================
//
// MIDIStreamer :: InitPlayback
//
//==========================================================================
bool MIDIStreamer : : InitPlayback ( )
{
m_Status = STATE_Stopped ;
EndQueued = 0 ;
VolumeChanged = false ;
Restarting = true ;
InitialPlayback = true ;
2019-09-28 08:00:22 +00:00
if ( MIDI ) MIDI - > SetCallback ( Callback , this ) ;
2018-02-24 17:19:27 +00:00
2019-09-28 08:00:22 +00:00
if ( MIDI = = NULL | | 0 ! = MIDI - > Open ( ) )
2016-03-01 15:47:10 +00:00
{
2019-09-28 11:59:46 +00:00
throw std : : runtime_error ( " Could not open MIDI out device " ) ;
2016-03-01 15:47:10 +00:00
}
2018-02-23 11:40:43 +00:00
source - > CheckCaps ( MIDI - > GetTechnology ( ) ) ;
2018-11-01 09:22:21 +00:00
if ( ! MIDI - > CanHandleSysex ( ) ) source - > SkipSysex ( ) ;
2016-03-01 15:47:10 +00:00
2019-09-28 08:00:22 +00:00
auto streamInfo = MIDI - > GetStreamInfo ( ) ;
if ( streamInfo . mBufferSize > 0 )
{
2019-09-28 11:59:46 +00:00
Stream . reset ( GSnd - > CreateStream ( FillStream , streamInfo . mBufferSize , streamInfo . mNumChannels = = 1 ? SoundStream : : Float | SoundStream : : Mono : SoundStream : : Float , streamInfo . mSampleRate , MIDI . get ( ) ) ) ;
2019-09-28 08:00:22 +00:00
}
2019-09-28 14:50:00 +00:00
StartPlayback ( ) ;
if ( MIDI = = nullptr )
{ // The MIDI file had no content and has been automatically closed.
return false ;
2016-03-01 15:47:10 +00:00
}
2019-09-28 08:00:22 +00:00
int res = 1 ;
if ( Stream ) res = Stream - > Play ( true , 1 ) ;
if ( res ) res = MIDI - > Resume ( ) ;
if ( res )
2016-03-01 15:47:10 +00:00
{
2019-09-28 11:59:46 +00:00
throw std : : runtime_error ( " Starting MIDI playback failed " ) ;
2016-03-01 15:47:10 +00:00
}
else
{
2017-03-10 18:03:58 +00:00
m_Status = STATE_Playing ;
2018-02-24 17:55:42 +00:00
return true ;
2016-03-01 15:47:10 +00:00
}
}
//==========================================================================
//
// MIDIStreamer :: StartPlayback
//
//==========================================================================
void MIDIStreamer : : StartPlayback ( )
{
2018-02-23 11:40:43 +00:00
auto data = source - > PrecacheData ( ) ;
2019-09-28 11:59:46 +00:00
MIDI - > PrecacheInstruments ( data . data ( ) , ( int ) data . size ( ) ) ;
2018-02-23 11:40:43 +00:00
source - > StartPlayback ( m_Looping ) ;
2016-03-01 15:47:10 +00:00
// Set time division and tempo.
2018-02-23 11:40:43 +00:00
if ( 0 ! = MIDI - > SetTimeDiv ( source - > getDivision ( ) ) | |
0 ! = MIDI - > SetTempo ( source - > getInitialTempo ( ) ) )
2016-03-01 15:47:10 +00:00
{
2019-09-28 11:59:46 +00:00
throw std : : runtime_error ( " Setting MIDI stream speed failed " ) ;
2016-03-01 15:47:10 +00:00
}
MusicVolumeChanged ( ) ; // set volume to current music's properties
OutputVolume ( Volume ) ;
2017-03-10 18:03:58 +00:00
MIDI - > InitPlayback ( ) ;
2016-03-01 15:47:10 +00:00
// Fill the initial buffers for the song.
BufferNum = 0 ;
do
{
2018-02-23 11:40:43 +00:00
int res = FillBuffer ( BufferNum , MAX_MIDI_EVENTS , MAX_TIME ) ;
2016-03-01 15:47:10 +00:00
if ( res = = SONG_MORE )
{
if ( 0 ! = MIDI - > StreamOutSync ( & Buffer [ BufferNum ] ) )
{
2019-09-28 11:59:46 +00:00
throw std : : runtime_error ( " Initial midiStreamOut failed " ) ;
2016-03-01 15:47:10 +00:00
}
BufferNum ^ = 1 ;
}
else if ( res = = SONG_DONE )
{
// Do not play super short songs that can't fill the initial two buffers.
Stop ( ) ;
return ;
}
else
{
Stop ( ) ;
return ;
}
}
while ( BufferNum ! = 0 ) ;
}
//==========================================================================
//
// MIDIStreamer :: Pause
//
// "Pauses" the song by setting it to zero volume and filling subsequent
// buffers with NOPs until the song is unpaused. A MIDI device that
// supports real pauses will return true from its Pause() method.
//
//==========================================================================
void MIDIStreamer : : Pause ( )
{
if ( m_Status = = STATE_Playing )
{
m_Status = STATE_Paused ;
if ( ! MIDI - > Pause ( true ) )
{
OutputVolume ( 0 ) ;
}
2019-09-28 08:00:22 +00:00
if ( Stream ! = nullptr )
{
Stream - > SetPaused ( true ) ;
}
2016-03-01 15:47:10 +00:00
}
}
//==========================================================================
//
// 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 )
{
if ( ! MIDI - > Pause ( false ) )
{
OutputVolume ( Volume ) ;
}
2019-09-28 08:00:22 +00:00
if ( Stream ! = nullptr )
{
Stream - > SetPaused ( false ) ;
}
2016-03-01 15:47:10 +00:00
m_Status = STATE_Playing ;
}
}
//==========================================================================
//
// MIDIStreamer :: Stop
//
// Stops playback and closes the player thread and MIDI device.
//
//==========================================================================
void MIDIStreamer : : Stop ( )
{
EndQueued = 4 ;
2017-03-10 15:12:52 +00:00
2016-03-01 15:47:10 +00:00
if ( MIDI ! = NULL & & MIDI - > IsOpen ( ) )
{
MIDI - > Stop ( ) ;
MIDI - > UnprepareHeader ( & Buffer [ 0 ] ) ;
MIDI - > UnprepareHeader ( & Buffer [ 1 ] ) ;
MIDI - > Close ( ) ;
}
2019-09-28 08:00:22 +00:00
if ( MIDI ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2019-09-28 11:59:46 +00:00
MIDI . reset ( ) ;
2019-09-28 08:00:22 +00:00
}
if ( Stream ! = nullptr )
{
Stream - > Stop ( ) ;
2019-09-28 11:59:46 +00:00
Stream . reset ( ) ;
2016-03-01 15:47:10 +00:00
}
2019-09-28 08:00:22 +00:00
2016-03-01 15:47:10 +00:00
m_Status = STATE_Stopped ;
}
//==========================================================================
//
// MIDIStreamer :: IsPlaying
//
//==========================================================================
bool MIDIStreamer : : IsPlaying ( )
{
if ( m_Status ! = STATE_Stopped & & ( MIDI = = NULL | | ( EndQueued ! = 0 & & EndQueued < 4 ) ) )
{
Stop ( ) ;
}
if ( m_Status ! = STATE_Stopped & & ! MIDI - > IsOpen ( ) )
{
Stop ( ) ;
}
return m_Status ! = STATE_Stopped ;
}
//==========================================================================
//
// MIDIStreamer :: MusicVolumeChanged
//
// WinMM MIDI doesn't go through the sound system, so the normal volume
// changing procedure doesn't work for it.
//
//==========================================================================
void MIDIStreamer : : MusicVolumeChanged ( )
{
if ( MIDI ! = NULL & & MIDI - > FakeVolume ( ) )
{
2019-02-26 20:57:28 +00:00
float realvolume = clamp < float > ( snd_musicvolume * relative_volume * snd_mastervolume , 0.f , 1.f ) ;
2017-03-10 18:46:22 +00:00
Volume = clamp < uint32_t > ( ( uint32_t ) ( realvolume * 65535.f ) , 0 , 65535 ) ;
2016-03-01 15:47:10 +00:00
}
else
{
Volume = 0xFFFF ;
}
2018-02-23 11:40:43 +00:00
source - > setVolume ( Volume ) ;
2016-03-01 15:47:10 +00:00
if ( m_Status = = STATE_Playing )
{
OutputVolume ( Volume ) ;
}
}
//==========================================================================
//
2019-09-26 20:30:07 +00:00
// MIDIStreamer :: ChangeSettingInt
2016-03-01 15:47:10 +00:00
//
//==========================================================================
2019-09-26 20:30:07 +00:00
void MIDIStreamer : : ChangeSettingInt ( const char * setting , int value )
2016-03-01 15:47:10 +00:00
{
if ( MIDI ! = NULL )
{
2019-09-26 20:30:07 +00:00
MIDI - > ChangeSettingInt ( setting , value ) ;
2016-03-01 15:47:10 +00:00
}
}
//==========================================================================
//
2019-09-26 20:30:07 +00:00
// MIDIStreamer :: ChangeSettingNum
2016-03-01 15:47:10 +00:00
//
//==========================================================================
2019-09-26 20:30:07 +00:00
void MIDIStreamer : : ChangeSettingNum ( const char * setting , double value )
2016-03-01 15:47:10 +00:00
{
if ( MIDI ! = NULL )
{
2019-09-26 20:30:07 +00:00
MIDI - > ChangeSettingNum ( setting , value ) ;
2016-03-01 15:47:10 +00:00
}
}
//==========================================================================
//
2019-09-26 20:30:07 +00:00
// MIDIDeviceStreamer :: ChangeSettingString
2016-03-01 15:47:10 +00:00
//
//==========================================================================
2019-09-26 20:30:07 +00:00
void MIDIStreamer : : ChangeSettingString ( const char * setting , const char * value )
2016-03-01 15:47:10 +00:00
{
if ( MIDI ! = NULL )
{
2019-09-26 20:30:07 +00:00
MIDI - > ChangeSettingString ( setting , value ) ;
2016-03-01 15:47:10 +00:00
}
}
//==========================================================================
//
// MIDIStreamer :: OutputVolume
//
// Signals the buffer filler to send volume change events on all channels.
//
//==========================================================================
2017-03-10 15:12:52 +00:00
void MIDIStreamer : : OutputVolume ( uint32_t volume )
2016-03-01 15:47:10 +00:00
{
if ( MIDI ! = NULL & & MIDI - > FakeVolume ( ) )
{
NewVolume = volume ;
VolumeChanged = true ;
}
}
//==========================================================================
//
// MIDIStreamer :: Callback Static
//
//==========================================================================
2017-03-10 11:39:23 +00:00
void MIDIStreamer : : Callback ( void * userdata )
2016-03-01 15:47:10 +00:00
{
MIDIStreamer * self = ( MIDIStreamer * ) userdata ;
if ( self - > EndQueued > = 4 )
{
return ;
}
2017-03-10 18:03:58 +00:00
self - > ServiceEvent ( ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// MIDIStreamer :: Update
//
// Called periodically to see if the player thread is still alive. If it
// isn't, stop playback now.
//
//==========================================================================
void MIDIStreamer : : Update ( )
{
2017-03-12 23:38:59 +00:00
if ( MIDI ! = nullptr & & ! MIDI - > Update ( ) ) Stop ( ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// 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
// non-zero if a problem occured and playback should stop.
//
//==========================================================================
int MIDIStreamer : : ServiceEvent ( )
{
int res ;
if ( EndQueued = = 2 )
{
return 0 ;
}
if ( 0 ! = ( res = MIDI - > UnprepareHeader ( & Buffer [ BufferNum ] ) ) )
{
return res ;
}
fill :
if ( EndQueued = = 1 )
{
res = FillStopBuffer ( BufferNum ) ;
if ( ( res & 3 ) ! = SONG_ERROR )
{
EndQueued = 2 ;
}
}
else
{
2018-02-23 11:40:43 +00:00
res = FillBuffer ( BufferNum , MAX_MIDI_EVENTS , MAX_TIME ) ;
2016-03-01 15:47:10 +00:00
}
switch ( res & 3 )
{
case SONG_MORE :
2017-03-10 18:03:58 +00:00
res = MIDI - > StreamOut ( & Buffer [ BufferNum ] ) ;
if ( res ! = 0 )
2016-03-01 15:47:10 +00:00
{
return res ;
}
else
{
BufferNum ^ = 1 ;
}
break ;
case SONG_DONE :
if ( m_Looping )
{
Restarting = true ;
goto fill ;
}
EndQueued = 1 ;
break ;
default :
return res > > 2 ;
}
return 0 ;
}
//==========================================================================
//
// MIDIStreamer :: FillBuffer
//
// Copies MIDI events from the MIDI file 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.
//
//==========================================================================
2017-03-10 15:12:52 +00:00
int MIDIStreamer : : FillBuffer ( int buffer_num , int max_events , uint32_t max_time )
2016-03-01 15:47:10 +00:00
{
2018-02-23 11:40:43 +00:00
if ( ! Restarting & & source - > CheckDone ( ) )
2016-03-01 15:47:10 +00:00
{
return SONG_DONE ;
}
int i ;
2017-03-10 15:12:52 +00:00
uint32_t * events = Events [ buffer_num ] , * max_event_p ;
uint32_t tot_time = 0 ;
uint32_t time = 0 ;
2016-03-01 15:47:10 +00:00
// 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 ;
2018-02-28 08:46:28 +00:00
// Send the GS System Reset SysEx message.
events [ 0 ] = 0 ; // dwDeltaTime
events [ 1 ] = 0 ; // dwStreamID
events [ 2 ] = ( MEVENT_LONGMSG < < 24 ) | 6 ; // dwEvent
events [ 3 ] = MAKE_ID ( 0xf0 , 0x7e , 0x7f , 0x09 ) ; // dwParms[0]
events [ 4 ] = MAKE_ID ( 0x01 , 0xf7 , 0x00 , 0x00 ) ; // dwParms[1]
events + = 5 ;
2016-03-01 15:47:10 +00:00
// Send the full master volume SysEx message.
events [ 0 ] = 0 ; // dwDeltaTime
events [ 1 ] = 0 ; // dwStreamID
2017-03-10 11:39:23 +00:00
events [ 2 ] = ( MEVENT_LONGMSG < < 24 ) | 8 ; // dwEvent
2016-03-01 15:47:10 +00:00
events [ 3 ] = MAKE_ID ( 0xf0 , 0x7f , 0x7f , 0x04 ) ; // dwParms[0]
events [ 4 ] = MAKE_ID ( 0x01 , 0x7f , 0x7f , 0xf7 ) ; // dwParms[1]
events + = 5 ;
2018-02-23 11:40:43 +00:00
source - > DoInitialSetup ( ) ;
2016-03-01 15:47:10 +00:00
}
// 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 )
{
2018-02-23 11:40:43 +00:00
uint8_t courseVol = ( uint8_t ) ( ( ( source - > getChannelVolume ( i ) + 1 ) * NewVolume ) > > 16 ) ;
2016-03-01 15:47:10 +00:00
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.
2018-02-23 11:40:43 +00:00
events [ 0 ] = MAX < uint32_t > ( 1 , ( max_time / 3 ) * source - > getDivision ( ) / source - > getTempo ( ) ) ;
2016-03-01 15:47:10 +00:00
events [ 1 ] = 0 ;
2017-03-10 11:39:23 +00:00
events [ 2 ] = MEVENT_NOP < < 24 ;
2016-03-01 15:47:10 +00:00
events + = 3 ;
}
else
{
if ( Restarting )
{
Restarting = false ;
// Reset the tempo to the inital value.
events [ 0 ] = 0 ; // dwDeltaTime
events [ 1 ] = 0 ; // dwStreamID
2018-02-23 11:40:43 +00:00
events [ 2 ] = ( MEVENT_TEMPO < < 24 ) | source - > getInitialTempo ( ) ; // dwEvent
2016-03-01 15:47:10 +00:00
events + = 3 ;
// Stop all notes in case any were left hanging.
events = WriteStopNotes ( events ) ;
2018-02-23 11:40:43 +00:00
source - > DoRestart ( ) ;
2016-03-01 15:47:10 +00:00
}
2018-02-23 11:40:43 +00:00
events = source - > MakeEvents ( events , max_event_p , max_time ) ;
2016-03-01 15:47:10 +00:00
}
2017-03-10 11:01:29 +00:00
memset ( & Buffer [ buffer_num ] , 0 , sizeof ( MidiHeader ) ) ;
Buffer [ buffer_num ] . lpData = ( uint8_t * ) Events [ buffer_num ] ;
Buffer [ buffer_num ] . dwBufferLength = uint32_t ( ( uint8_t * ) events - Buffer [ buffer_num ] . lpData ) ;
2016-03-01 15:47:10 +00:00
Buffer [ buffer_num ] . dwBytesRecorded = Buffer [ buffer_num ] . dwBufferLength ;
if ( 0 ! = ( i = MIDI - > PrepareHeader ( & Buffer [ buffer_num ] ) ) )
{
return SONG_ERROR | ( i < < 2 ) ;
}
return SONG_MORE ;
}
//==========================================================================
//
// MIDIStreamer :: FillStopBuffer
//
// Fills a MIDI buffer with events to stop all channels.
//
//==========================================================================
int MIDIStreamer : : FillStopBuffer ( int buffer_num )
{
2017-03-10 15:12:52 +00:00
uint32_t * events = Events [ buffer_num ] ;
2016-03-01 15:47:10 +00:00
int i ;
events = WriteStopNotes ( events ) ;
// wait some tics, just so that this buffer takes some time
events [ 0 ] = 500 ;
events [ 1 ] = 0 ;
2017-03-10 11:39:23 +00:00
events [ 2 ] = MEVENT_NOP < < 24 ;
2016-03-01 15:47:10 +00:00
events + = 3 ;
2017-03-10 11:01:29 +00:00
memset ( & Buffer [ buffer_num ] , 0 , sizeof ( MidiHeader ) ) ;
Buffer [ buffer_num ] . lpData = ( uint8_t * ) Events [ buffer_num ] ;
2017-03-10 18:46:22 +00:00
Buffer [ buffer_num ] . dwBufferLength = uint32_t ( ( uint8_t * ) events - Buffer [ buffer_num ] . lpData ) ;
2016-03-01 15:47:10 +00:00
Buffer [ buffer_num ] . dwBytesRecorded = Buffer [ buffer_num ] . dwBufferLength ;
if ( 0 ! = ( i = MIDI - > PrepareHeader ( & Buffer [ buffer_num ] ) ) )
{
return SONG_ERROR | ( i < < 2 ) ;
}
return SONG_MORE ;
}
//==========================================================================
//
// MIDIStreamer :: WriteStopNotes
//
// Generates MIDI events to stop all notes and reset controllers on
// every channel.
//
//==========================================================================
2017-03-10 15:12:52 +00:00
uint32_t * MIDIStreamer : : WriteStopNotes ( uint32_t * events )
2016-03-01 15:47:10 +00:00
{
for ( int i = 0 ; i < 16 ; + + i )
{
events [ 0 ] = 0 ; // dwDeltaTime
events [ 1 ] = 0 ; // dwStreamID
events [ 2 ] = MIDI_CTRLCHANGE | i | ( 123 < < 8 ) ; // All notes off
events [ 3 ] = 0 ;
events [ 4 ] = 0 ;
events [ 5 ] = MIDI_CTRLCHANGE | i | ( 121 < < 8 ) ; // Reset controllers
events + = 6 ;
}
return events ;
}
//==========================================================================
//
//
//
//==========================================================================
2018-02-23 11:40:43 +00:00
void MIDIStreamer : : SetMIDISource ( MIDISource * _source )
2016-03-01 15:47:10 +00:00
{
2019-09-28 11:59:46 +00:00
source . reset ( _source ) ;
2018-02-26 11:22:36 +00:00
source - > setTempoCallback ( [ = ] ( int tempo ) { return ! ! MIDI - > SetTempo ( tempo ) ; } ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// MIDIStreamer :: GetStats
//
//==========================================================================
FString MIDIStreamer : : GetStats ( )
{
if ( MIDI = = NULL )
{
return " No MIDI device in use. " ;
}
2019-09-28 14:50:00 +00:00
auto s = MIDI - > GetStats ( ) ;
return s . c_str ( ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// MIDIStreamer :: SetSubsong
//
// Selects which subsong to play in an already-playing file. This is public.
//
//==========================================================================
bool MIDIStreamer : : SetSubsong ( int subsong )
{
2018-02-23 11:40:43 +00:00
if ( source - > SetMIDISubsong ( subsong ) )
2016-03-01 15:47:10 +00:00
{
Stop ( ) ;
Play ( m_Looping , subsong ) ;
return true ;
}
return false ;
}
2019-09-28 08:00:22 +00:00
//==========================================================================
//
// SoftSynthMIDIDevice :: FillStream static
//
//==========================================================================
2016-03-01 15:47:10 +00:00
2019-09-28 08:00:22 +00:00
bool MIDIStreamer : : FillStream ( SoundStream * stream , void * buff , int len , void * userdata )
{
SoftSynthMIDIDevice * device = ( SoftSynthMIDIDevice * ) userdata ;
return device - > ServiceStream ( buff , len ) ;
}