2019-12-08 11:28:51 +00:00
/*
* *
* * music . cpp
* *
2020-04-12 06:07:48 +00:00
* * music engine
2019-12-08 11:28:51 +00:00
* *
* * Copyright 1999 - 2016 Randy Heit
* * Copyright 2002 - 2016 Christoph Oelckers
2019-11-10 23:23:52 +00:00
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
* * 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 .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
2020-04-12 06:07:48 +00:00
# include <stdio.h>
# include <stdlib.h>
2020-05-23 10:59:03 +00:00
# include <stdexcept>
2020-04-12 06:07:48 +00:00
# include "i_sound.h"
2019-11-11 00:01:18 +00:00
# include "i_music.h"
2019-11-10 22:58:51 +00:00
# include "printf.h"
2020-04-12 06:07:48 +00:00
# include "s_playlist.h"
# include "c_dispatch.h"
2019-11-10 22:58:51 +00:00
# include "filesystem.h"
# include "cmdlib.h"
2020-04-12 06:07:48 +00:00
# include "s_music.h"
2019-11-10 22:58:51 +00:00
# include "filereadermusicinterface.h"
2020-04-12 06:07:48 +00:00
# include <zmusic.h>
2021-03-13 00:21:38 +00:00
# include "md5.h"
# include "gain_analysis.h"
# include "gameconfigfile.h"
# include "i_specialpaths.h"
2019-11-10 22:58:51 +00:00
2020-04-12 06:07:48 +00:00
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
extern float S_GetMusicVolume ( const char * music ) ;
static void S_ActivatePlayList ( bool goBack ) ;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
2020-01-27 19:53:41 +00:00
2020-05-26 21:12:04 +00:00
static bool MusicPaused ; // whether music is paused
MusPlayingInfo mus_playing ; // music currently being played
static FPlayList PlayList ;
float relative_volume = 1.f ;
float saved_relative_volume = 1.0f ; // this could be used to implement an ACS FadeMusic function
MusicVolumeMap MusicVolumes ;
MidiDeviceMap MidiDevices ;
2019-11-28 18:35:35 +00:00
2020-04-12 06:07:48 +00:00
static FileReader DefaultOpenMusic ( const char * fn )
2020-01-27 20:39:15 +00:00
{
2020-04-12 06:07:48 +00:00
// This is the minimum needed to make the music system functional.
FileReader fr ;
fr . OpenFile ( fn ) ;
return fr ;
2020-01-27 20:39:15 +00:00
}
2020-04-12 06:07:48 +00:00
static MusicCallbacks mus_cb = { nullptr , DefaultOpenMusic } ;
2020-01-27 20:39:15 +00:00
2020-04-12 06:07:48 +00:00
// PUBLIC DATA DEFINITIONS -------------------------------------------------
2021-03-13 00:21:38 +00:00
EXTERN_CVAR ( Int , snd_mididevice )
2021-03-14 08:05:28 +00:00
EXTERN_CVAR ( Float , mod_dumb_mastervolume )
EXTERN_CVAR ( Float , fluid_gain )
2021-03-13 00:21:38 +00:00
2021-03-14 08:05:28 +00:00
CVAR ( Bool , mus_calcgain , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // changing this will only take effect for the next song.
2021-03-13 00:21:38 +00:00
CVAR ( Bool , mus_usereplaygain , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // changing this will only take effect for the next song.
CUSTOM_CVAR ( Float , mus_gainoffset , 0.f , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // for customizing the base volume
{
if ( self > 10.f ) self = 10.f ;
mus_playing . replayGainFactor = dBToAmplitude ( mus_playing . replayGain + mus_gainoffset ) ;
}
2020-04-12 06:07:48 +00:00
// CODE --------------------------------------------------------------------
void S_SetMusicCallbacks ( MusicCallbacks * cb )
2020-01-27 20:39:15 +00:00
{
2020-04-12 06:07:48 +00:00
mus_cb = * cb ;
if ( mus_cb . OpenMusic = = nullptr ) mus_cb . OpenMusic = DefaultOpenMusic ; // without this we are dead in the water.
2020-01-27 20:39:15 +00:00
}
2021-05-21 23:34:00 +00:00
int MusicEnabled ( ) // int return is for scripting
{
return mus_enabled & & ! nomusic ;
}
2019-11-11 20:50:20 +00:00
//==========================================================================
//
//
//
// Create a sound system stream for the currently playing song
//==========================================================================
static std : : unique_ptr < SoundStream > musicStream ;
2021-04-16 20:14:11 +00:00
static TArray < SoundStream * > customStreams ;
2019-11-11 20:50:20 +00:00
2020-07-23 20:26:07 +00:00
SoundStream * S_CreateCustomStream ( size_t size , int samplerate , int numchannels , StreamCallback cb , void * userdata )
{
2020-07-23 21:22:09 +00:00
int flags = 0 ;
2020-07-23 20:26:07 +00:00
if ( numchannels < 2 ) flags | = SoundStream : : Mono ;
2020-10-07 14:02:49 +00:00
auto stream = GSnd - > CreateStream ( cb , int ( size ) , flags , samplerate , userdata ) ;
2021-04-16 20:14:11 +00:00
if ( stream )
{
stream - > Play ( true , 1 ) ;
customStreams . Push ( stream ) ;
}
2020-07-23 20:26:07 +00:00
return stream ;
}
void S_StopCustomStream ( SoundStream * stream )
{
2020-08-20 21:41:45 +00:00
if ( stream )
{
stream - > Stop ( ) ;
2021-04-16 20:14:11 +00:00
auto f = customStreams . Find ( stream ) ;
if ( f < customStreams . Size ( ) ) customStreams . Delete ( f ) ;
2020-08-20 21:41:45 +00:00
delete stream ;
}
2020-07-23 20:26:07 +00:00
}
2021-04-16 20:14:11 +00:00
void S_PauseAllCustomStreams ( bool on )
{
2021-04-26 22:01:25 +00:00
static bool paused = false ;
if ( paused = = on ) return ;
paused = on ;
2021-04-16 20:14:11 +00:00
for ( auto s : customStreams )
{
s - > SetPaused ( on ) ;
}
}
2020-07-23 20:26:07 +00:00
2021-03-13 00:21:38 +00:00
static TArray < int16_t > convert ;
2019-11-11 20:50:20 +00:00
static bool FillStream ( SoundStream * stream , void * buff , int len , void * userdata )
{
2021-03-13 00:21:38 +00:00
bool written ;
if ( mus_playing . isfloat )
{
written = ZMusic_FillStream ( mus_playing . handle , buff , len ) ;
if ( mus_playing . replayGainFactor ! = 1.f )
{
float * fbuf = ( float * ) buff ;
for ( int i = 0 ; i < len / 4 ; i + + )
{
fbuf [ i ] * = mus_playing . replayGainFactor ;
}
}
}
else
{
// To apply replay gain we need floating point streaming data, so 16 bit input needs to be converted here.
convert . Resize ( len / 2 ) ;
written = ZMusic_FillStream ( mus_playing . handle , convert . Data ( ) , len / 2 ) ;
float * fbuf = ( float * ) buff ;
for ( int i = 0 ; i < len / 4 ; i + + )
{
fbuf [ i ] = convert [ i ] * mus_playing . replayGainFactor * ( 1.f / 32768.f ) ;
}
}
2019-11-11 20:50:20 +00:00
if ( ! written )
{
memset ( ( char * ) buff , 0 , len ) ;
return false ;
}
return true ;
}
void S_CreateStream ( )
{
if ( ! mus_playing . handle ) return ;
2020-02-09 12:26:51 +00:00
SoundStreamInfo fmt ;
ZMusic_GetStreamInfo ( mus_playing . handle , & fmt ) ;
2021-03-13 00:21:38 +00:00
// always create a floating point streaming buffer so we can apply replay gain without risk of integer overflows.
mus_playing . isfloat = fmt . mNumChannels > 0 ;
if ( ! mus_playing . isfloat ) fmt . mBufferSize * = 2 ;
2020-02-09 12:26:51 +00:00
if ( fmt . mBufferSize > 0 ) // if buffer size is 0 the library will play the song itself (e.g. Windows system synth.)
2019-11-11 20:50:20 +00:00
{
2021-03-13 00:21:38 +00:00
int flags = SoundStream : : Float ;
2019-11-11 20:50:20 +00:00
if ( abs ( fmt . mNumChannels ) < 2 ) flags | = SoundStream : : Mono ;
musicStream . reset ( GSnd - > CreateStream ( FillStream , fmt . mBufferSize , flags , fmt . mSampleRate , nullptr ) ) ;
if ( musicStream ) musicStream - > Play ( true , 1 ) ;
}
}
2020-07-23 20:26:07 +00:00
2019-11-11 20:50:20 +00:00
void S_PauseStream ( bool paused )
{
if ( musicStream ) musicStream - > SetPaused ( paused ) ;
}
void S_StopStream ( )
{
if ( musicStream )
{
musicStream - > Stop ( ) ;
musicStream . reset ( ) ;
}
}
2019-11-10 22:58:51 +00:00
2019-11-10 23:23:52 +00:00
//==========================================================================
//
// starts playing this song
//
//==========================================================================
2020-02-09 12:26:51 +00:00
static bool S_StartMusicPlaying ( ZMusic_MusicStream song , bool loop , float rel_vol , int subsong )
2019-11-10 23:23:52 +00:00
{
2021-03-13 00:21:38 +00:00
if ( rel_vol > 0.f & & ! mus_usereplaygain )
2019-11-10 23:23:52 +00:00
{
float factor = relative_volume / saved_relative_volume ;
saved_relative_volume = rel_vol ;
I_SetRelativeVolume ( saved_relative_volume * factor ) ;
}
ZMusic_Stop ( song ) ;
2021-03-14 08:05:28 +00:00
// make sure the volume modifiers update properly in case replay gain settings have changed.
fluid_gain . Callback ( ) ;
mod_dumb_mastervolume . Callback ( ) ;
2020-02-09 12:26:51 +00:00
if ( ! ZMusic_Start ( song , subsong , loop ) )
{
return false ;
}
2019-11-10 23:23:52 +00:00
// Notify the sound system of the changed relative volume
2020-04-12 06:07:48 +00:00
snd_musicvolume . Callback ( ) ;
2020-02-09 12:26:51 +00:00
return true ;
2019-11-10 23:23:52 +00:00
}
//==========================================================================
//
2020-05-28 23:22:45 +00:00
// S_PauseMusic
2019-11-10 23:23:52 +00:00
//
2020-05-28 23:22:45 +00:00
// Stop music, during game PAUSE.
2019-11-10 23:23:52 +00:00
//==========================================================================
void S_PauseMusic ( )
{
if ( mus_playing . handle & & ! MusicPaused )
{
ZMusic_Pause ( mus_playing . handle ) ;
S_PauseStream ( true ) ;
MusicPaused = true ;
}
}
//==========================================================================
//
2020-05-28 23:22:45 +00:00
// S_ResumeMusic
2019-11-10 23:23:52 +00:00
//
2020-05-28 23:22:45 +00:00
// Resume music, after game PAUSE.
2019-11-10 23:23:52 +00:00
//==========================================================================
void S_ResumeMusic ( )
{
if ( mus_playing . handle & & MusicPaused )
{
ZMusic_Resume ( mus_playing . handle ) ;
S_PauseStream ( false ) ;
MusicPaused = false ;
}
}
//==========================================================================
//
// S_UpdateSound
//
//==========================================================================
void S_UpdateMusic ( )
{
if ( mus_playing . handle ! = nullptr )
{
ZMusic_Update ( mus_playing . handle ) ;
// [RH] Update music and/or playlist. IsPlaying() must be called
// to attempt to reconnect to broken net streams and to advance the
// playlist when the current song finishes.
if ( ! ZMusic_IsPlaying ( mus_playing . handle ) )
{
2020-04-12 06:07:48 +00:00
if ( PlayList . GetNumSongs ( ) )
{
PlayList . Advance ( ) ;
S_ActivatePlayList ( false ) ;
}
else
{
2020-05-26 21:12:04 +00:00
S_StopMusic ( true ) ;
}
2019-11-10 23:23:52 +00:00
}
}
2020-04-12 06:07:48 +00:00
}
//==========================================================================
//
// Resets the music player if music playback was paused.
//
//==========================================================================
void S_ResetMusic ( )
{
// stop the old music if it has been paused.
// This ensures that the new music is started from the beginning
// if it's the same as the last one and it has been paused.
if ( MusicPaused ) S_StopMusic ( true ) ;
// start new music for the level
MusicPaused = false ;
2019-11-10 23:23:52 +00:00
}
2020-04-12 06:07:48 +00:00
2019-11-10 23:23:52 +00:00
//==========================================================================
//
2020-04-12 06:07:48 +00:00
// S_ActivatePlayList
2019-11-10 23:23:52 +00:00
//
2020-04-12 06:07:48 +00:00
// Plays the next song in the playlist. If no songs in the playlist can be
// played, then it is deleted.
2019-11-10 23:23:52 +00:00
//==========================================================================
2020-04-12 06:07:48 +00:00
void S_ActivatePlayList ( bool goBack )
2019-11-10 23:23:52 +00:00
{
2020-04-12 06:07:48 +00:00
int startpos , pos ;
2019-11-10 23:23:52 +00:00
2020-04-12 06:07:48 +00:00
startpos = pos = PlayList . GetPosition ( ) ;
S_StopMusic ( true ) ;
while ( ! S_ChangeMusic ( PlayList . GetSong ( pos ) , 0 , false , true ) )
2019-11-10 23:23:52 +00:00
{
2020-04-12 06:07:48 +00:00
pos = goBack ? PlayList . Backup ( ) : PlayList . Advance ( ) ;
if ( pos = = startpos )
2020-05-26 21:12:04 +00:00
{
2020-04-12 06:07:48 +00:00
PlayList . Clear ( ) ;
Printf ( " Cannot play anything in the playlist. \n " ) ;
return ;
2020-05-26 21:12:04 +00:00
}
2019-11-10 23:23:52 +00:00
}
}
//==========================================================================
//
// S_StartMusic
//
// Starts some music with the given name.
//==========================================================================
bool S_StartMusic ( const char * m_id )
{
return S_ChangeMusic ( m_id , 0 , false ) ;
}
//==========================================================================
//
// S_ChangeMusic
//
2020-04-12 06:07:48 +00:00
// initiates playback of a song
2019-11-10 23:23:52 +00:00
//
//==========================================================================
2021-03-13 00:21:38 +00:00
static TMap < FString , float > gainMap ;
EXTERN_CVAR ( String , fluid_patchset )
EXTERN_CVAR ( String , timidity_config )
EXTERN_CVAR ( String , midi_config )
EXTERN_CVAR ( String , wildmidi_config )
EXTERN_CVAR ( String , adl_custom_bank )
EXTERN_CVAR ( Int , adl_bank )
EXTERN_CVAR ( Bool , adl_use_custom_bank )
EXTERN_CVAR ( String , opn_custom_bank )
EXTERN_CVAR ( Bool , opn_use_custom_bank )
EXTERN_CVAR ( Int , opl_core )
static FString ReplayGainHash ( ZMusicCustomReader * reader , int flength , int playertype , const char * _playparam )
{
std : : string playparam = _playparam ;
uint8_t buffer [ 50000 ] ; // for performance reasons only hash the start of the file. If we wanted to do this to large waveform songs it'd cause noticable lag.
uint8_t digest [ 16 ] ;
char digestout [ 33 ] ;
auto length = reader - > read ( reader , buffer , 50000 ) ;
reader - > seek ( reader , 0 , SEEK_SET ) ;
MD5Context md5 ;
md5 . Init ( ) ;
md5 . Update ( buffer , ( int ) length ) ;
md5 . Final ( digest ) ;
for ( size_t j = 0 ; j < sizeof ( digest ) ; + + j )
{
sprintf ( digestout + ( j * 2 ) , " %02X " , digest [ j ] ) ;
}
digestout [ 32 ] = 0 ;
auto type = ZMusic_IdentifyMIDIType ( ( uint32_t * ) buffer , 32 ) ;
if ( type = = MIDI_NOTMIDI ) return FStringf ( " %d:%s " , flength , digestout ) ;
// get the default for MIDI synth
if ( playertype = = - 1 )
{
switch ( snd_mididevice )
{
case - 1 : playertype = MDEV_FLUIDSYNTH ; break ;
case - 2 : playertype = MDEV_TIMIDITY ; break ;
case - 3 : playertype = MDEV_OPL ; break ;
case - 4 : playertype = MDEV_GUS ; break ;
case - 5 : playertype = MDEV_FLUIDSYNTH ; break ;
case - 6 : playertype = MDEV_WILDMIDI ; break ;
case - 7 : playertype = MDEV_ADL ; break ;
case - 8 : playertype = MDEV_OPN ; break ;
default : return " " ;
}
}
else if ( playertype = = MDEV_SNDSYS ) return " " ;
// get the default for used sound font.
if ( playparam . empty ( ) )
{
switch ( playertype )
{
case MDEV_FLUIDSYNTH : playparam = fluid_patchset ; break ;
case MDEV_TIMIDITY : playparam = timidity_config ; break ;
case MDEV_GUS : playparam = midi_config ; break ;
case MDEV_WILDMIDI : playparam = wildmidi_config ; break ;
case MDEV_ADL : playparam = adl_use_custom_bank ? * adl_custom_bank : std : : to_string ( adl_bank ) ; break ;
case MDEV_OPN : playparam = opn_use_custom_bank ? * opn_custom_bank : " " ; break ;
case MDEV_OPL : playparam = std : : to_string ( opl_core ) ; break ;
}
}
return FStringf ( " %d:%s:%d:%s " , flength , digestout , playertype , playparam . c_str ( ) ) . MakeUpper ( ) ;
}
static void SaveGains ( )
{
auto path = M_GetAppDataPath ( true ) ;
path < < " /replaygain.ini " ;
FConfigFile gains ( path ) ;
TMap < FString , float > : : Iterator it ( gainMap ) ;
TMap < FString , float > : : Pair * pair ;
if ( gains . SetSection ( " Gains " , true ) )
{
while ( it . NextPair ( pair ) )
{
gains . SetValueForKey ( pair - > Key , std : : to_string ( pair - > Value ) . c_str ( ) ) ;
}
}
gains . WriteConfigFile ( ) ;
}
static void ReadGains ( )
{
static bool done = false ;
if ( done ) return ;
done = true ;
auto path = M_GetAppDataPath ( true ) ;
path < < " /replaygain.ini " ;
FConfigFile gains ( path ) ;
if ( gains . SetSection ( " Gains " ) )
{
const char * key ;
const char * value ;
while ( gains . NextInSection ( key , value ) )
{
gainMap . Insert ( key , ( float ) strtod ( value , nullptr ) ) ;
}
}
}
CCMD ( setreplaygain )
{
// sets replay gain for current song to a fixed value
if ( ! mus_playing . handle | | mus_playing . hash . IsEmpty ( ) )
{
Printf ( " setreplaygain needs some music playing \n " ) ;
return ;
}
if ( argv . argc ( ) < 2 )
{
Printf ( " Usage: setreplaygain {dB} \n " ) ;
Printf ( " Current replay gain is %f dB \n " , mus_playing . replayGain ) ;
return ;
}
float dB = ( float ) strtod ( argv [ 1 ] , nullptr ) ;
if ( dB > 10 ) dB = 10 ; // don't blast the speakers. Values above 2 or 3 are very rare.
gainMap . Insert ( mus_playing . hash , dB ) ;
SaveGains ( ) ;
mus_playing . replayGain = dB ;
mus_playing . replayGainFactor = ( float ) dBToAmplitude ( mus_playing . replayGain + mus_gainoffset ) ;
}
static void CheckReplayGain ( const char * musicname , EMidiDevice playertype , const char * playparam )
{
mus_playing . replayGain = 0.f ;
mus_playing . replayGainFactor = dBToAmplitude ( mus_gainoffset ) ;
2021-03-14 08:05:28 +00:00
fluid_gain . Callback ( ) ;
mod_dumb_mastervolume . Callback ( ) ;
2021-03-13 00:21:38 +00:00
if ( ! mus_usereplaygain ) return ;
FileReader reader = mus_cb . OpenMusic ( musicname ) ;
if ( ! reader . isOpen ( ) ) return ;
int flength = ( int ) reader . GetLength ( ) ;
auto mreader = GetMusicReader ( reader ) ; // this passes the file reader to the newly created wrapper.
ReadGains ( ) ;
auto hash = ReplayGainHash ( mreader , flength , playertype , playparam ) ;
if ( hash . IsEmpty ( ) ) return ; // got nothing to measure.
mus_playing . hash = hash ;
auto entry = gainMap . CheckKey ( hash ) ;
if ( entry )
{
mus_playing . replayGain = * entry ;
mus_playing . replayGainFactor = dBToAmplitude ( mus_playing . replayGain + mus_gainoffset ) ;
return ;
}
if ( ! mus_calcgain ) return ;
auto handle = ZMusic_OpenSong ( mreader , playertype , playparam ) ;
if ( handle = = nullptr ) return ; // not a music file
if ( ! ZMusic_Start ( handle , 0 , false ) )
{
ZMusic_Close ( handle ) ;
return ; // unable to open
}
SoundStreamInfo fmt ;
ZMusic_GetStreamInfo ( handle , & fmt ) ;
if ( fmt . mBufferSize = = 0 )
{
ZMusic_Close ( handle ) ;
return ; // external player.
}
int flags = SoundStream : : Float ;
if ( abs ( fmt . mNumChannels ) < 2 ) flags | = SoundStream : : Mono ;
TArray < uint8_t > readbuffer ( fmt . mBufferSize , true ) ;
TArray < float > lbuffer ;
TArray < float > rbuffer ;
while ( ZMusic_FillStream ( handle , readbuffer . Data ( ) , fmt . mBufferSize ) )
{
unsigned index ;
// 4 cases, all with different preparation needs.
if ( fmt . mNumChannels = = - 2 ) // 16 bit stereo
{
int16_t * sbuf = ( int16_t * ) readbuffer . Data ( ) ;
int numsamples = fmt . mBufferSize / 4 ;
index = lbuffer . Reserve ( numsamples ) ;
rbuffer . Reserve ( numsamples ) ;
for ( int i = 0 ; i < numsamples ; i + + )
{
lbuffer [ index + i ] = sbuf [ i * 2 ] ;
rbuffer [ index + i ] = sbuf [ i * 2 + 1 ] ;
}
}
else if ( fmt . mNumChannels = = - 1 ) // 16 bit mono
{
int16_t * sbuf = ( int16_t * ) readbuffer . Data ( ) ;
int numsamples = fmt . mBufferSize / 2 ;
index = lbuffer . Reserve ( numsamples ) ;
for ( int i = 0 ; i < numsamples ; i + + )
{
lbuffer [ index + i ] = sbuf [ i ] ;
}
}
else if ( fmt . mNumChannels = = 1 ) // float mono
{
float * sbuf = ( float * ) readbuffer . Data ( ) ;
int numsamples = fmt . mBufferSize / 4 ;
index = lbuffer . Reserve ( numsamples ) ;
for ( int i = 0 ; i < numsamples ; i + + )
{
lbuffer [ index + i ] = sbuf [ i ] * 32768.f ;
}
}
else if ( fmt . mNumChannels = = 2 ) // float stereo
{
float * sbuf = ( float * ) readbuffer . Data ( ) ;
int numsamples = fmt . mBufferSize / 8 ;
auto index = lbuffer . Reserve ( numsamples ) ;
rbuffer . Reserve ( numsamples ) ;
for ( int i = 0 ; i < numsamples ; i + + )
{
lbuffer [ index + i ] = sbuf [ i * 2 ] * 32768.f ;
rbuffer [ index + i ] = sbuf [ i * 2 + 1 ] * 32768.f ;
}
}
float accTime = lbuffer . Size ( ) / ( float ) fmt . mSampleRate ;
if ( accTime > 8 * 60 ) break ; // do at most 8 minutes, if the song forces a loop.
}
ZMusic_Close ( handle ) ;
GainAnalyzer analyzer ;
2021-07-11 07:11:59 +00:00
int result = analyzer . InitGainAnalysis ( fmt . mSampleRate ) ;
2021-03-13 00:21:38 +00:00
if ( result = = GAIN_ANALYSIS_OK )
{
2021-07-11 07:11:59 +00:00
result = analyzer . AnalyzeSamples ( lbuffer . Data ( ) , rbuffer . Size ( ) = = 0 ? nullptr : rbuffer . Data ( ) , lbuffer . Size ( ) , rbuffer . Size ( ) = = 0 ? 1 : 2 ) ;
if ( result = = GAIN_ANALYSIS_OK )
{
auto gain = analyzer . GetTitleGain ( ) ;
Printf ( " Calculated replay gain for %s at %f dB \n " , hash . GetChars ( ) , gain ) ;
2021-03-13 00:21:38 +00:00
2021-07-11 07:11:59 +00:00
gainMap . Insert ( hash , gain ) ;
mus_playing . replayGain = gain ;
mus_playing . replayGainFactor = dBToAmplitude ( mus_playing . replayGain + mus_gainoffset ) ;
SaveGains ( ) ;
}
2021-03-13 00:21:38 +00:00
}
}
2019-11-10 23:23:52 +00:00
2019-11-10 22:58:51 +00:00
bool S_ChangeMusic ( const char * musicname , int order , bool looping , bool force )
{
2021-05-21 23:34:00 +00:00
if ( ! MusicEnabled ( ) ) return false ; // skip the entire procedure if music is globally disabled.
2020-04-12 06:07:48 +00:00
if ( ! force & & PlayList . GetNumSongs ( ) )
{ // Don't change if a playlist is active
2020-09-07 19:26:07 +00:00
return true ; // do not report an error here.
2020-04-12 06:07:48 +00:00
}
// Do game specific lookup.
FString musicname_ ;
if ( mus_cb . LookupFileName )
{
musicname_ = mus_cb . LookupFileName ( musicname , order ) ;
musicname = musicname_ . GetChars ( ) ;
}
2019-11-10 22:58:51 +00:00
if ( musicname = = nullptr | | musicname [ 0 ] = = 0 )
{
// Don't choke if the map doesn't have a song attached
2019-11-10 23:23:52 +00:00
S_StopMusic ( true ) ;
2019-11-10 22:58:51 +00:00
mus_playing . name = " " ;
mus_playing . LastSong = " " ;
return true ;
}
if ( ! mus_playing . name . IsEmpty ( ) & &
mus_playing . handle ! = nullptr & &
2020-04-12 06:07:48 +00:00
stricmp ( mus_playing . name , musicname ) = = 0 & &
2020-02-09 12:26:51 +00:00
ZMusic_IsLooping ( mus_playing . handle ) = = zmusic_bool ( looping ) )
2019-11-10 22:58:51 +00:00
{
if ( order ! = mus_playing . baseorder )
{
if ( ZMusic_SetSubsong ( mus_playing . handle , order ) )
{
mus_playing . baseorder = order ;
}
}
else if ( ! ZMusic_IsPlaying ( mus_playing . handle ) )
{
2020-02-22 15:12:40 +00:00
if ( ! ZMusic_Start ( mus_playing . handle , order , looping ) )
2019-11-10 22:58:51 +00:00
{
2020-02-09 12:26:51 +00:00
Printf ( " Unable to start %s: %s \n " , mus_playing . name . GetChars ( ) , ZMusic_GetLastError ( ) ) ;
2019-11-10 22:58:51 +00:00
}
2020-02-09 12:26:51 +00:00
S_CreateStream ( ) ;
2019-11-10 22:58:51 +00:00
}
return true ;
}
2020-05-26 21:12:04 +00:00
int lumpnum = - 1 ;
int length = 0 ;
ZMusic_MusicStream handle = nullptr ;
2020-04-12 06:07:48 +00:00
MidiDeviceSetting * devp = MidiDevices . CheckKey ( musicname ) ;
2019-11-10 22:58:51 +00:00
2020-05-26 21:12:04 +00:00
// Strip off any leading file:// component.
if ( strncmp ( musicname , " file:// " , 7 ) = = 0 )
{
musicname + = 7 ;
}
2019-11-10 22:58:51 +00:00
2020-04-12 06:07:48 +00:00
// opening the music must be done by the game because it's different depending on the game's file system use.
FileReader reader = mus_cb . OpenMusic ( musicname ) ;
2020-05-26 21:12:04 +00:00
if ( ! reader . isOpen ( ) ) return false ;
2019-11-10 22:58:51 +00:00
2020-05-26 21:12:04 +00:00
// shutdown old music
2020-04-12 06:07:48 +00:00
S_StopMusic ( true ) ;
2019-11-10 22:58:51 +00:00
2020-05-26 21:12:04 +00:00
// Just record it if volume is 0 or music was disabled
2020-04-12 06:07:48 +00:00
if ( snd_musicvolume < = 0 | | ! mus_enabled )
2020-05-26 21:12:04 +00:00
{
mus_playing . loop = looping ;
mus_playing . name = musicname ;
mus_playing . baseorder = order ;
mus_playing . LastSong = musicname ;
return true ;
}
2019-11-10 22:58:51 +00:00
2020-05-26 21:12:04 +00:00
// load & register it
if ( handle ! = nullptr )
{
mus_playing . handle = handle ;
}
else
{
2021-03-13 00:21:38 +00:00
CheckReplayGain ( musicname , devp ? ( EMidiDevice ) devp - > device : MDEV_DEFAULT , devp ? devp - > args . GetChars ( ) : " " ) ;
2020-05-26 21:12:04 +00:00
auto mreader = GetMusicReader ( reader ) ; // this passes the file reader to the newly created wrapper.
2020-04-12 06:07:48 +00:00
mus_playing . handle = ZMusic_OpenSong ( mreader , devp ? ( EMidiDevice ) devp - > device : MDEV_DEFAULT , devp ? devp - > args . GetChars ( ) : " " ) ;
2020-05-26 21:12:04 +00:00
if ( mus_playing . handle = = nullptr )
{
Printf ( " Unable to load %s: %s \n " , mus_playing . name . GetChars ( ) , ZMusic_GetLastError ( ) ) ;
2019-11-10 22:58:51 +00:00
}
2020-05-26 21:12:04 +00:00
}
2019-11-10 22:58:51 +00:00
mus_playing . loop = looping ;
mus_playing . name = musicname ;
mus_playing . baseorder = 0 ;
mus_playing . LastSong = " " ;
if ( mus_playing . handle ! = 0 )
{ // play it
2020-02-09 12:26:51 +00:00
auto volp = MusicVolumes . CheckKey ( musicname ) ;
float vol = volp ? * volp : 1.f ;
if ( ! S_StartMusicPlaying ( mus_playing . handle , looping , vol , order ) )
2019-11-10 22:58:51 +00:00
{
2020-02-09 12:26:51 +00:00
Printf ( " Unable to start %s: %s \n " , mus_playing . name . GetChars ( ) , ZMusic_GetLastError ( ) ) ;
return false ;
2019-11-10 22:58:51 +00:00
}
2020-02-09 12:26:51 +00:00
S_CreateStream ( ) ;
mus_playing . baseorder = order ;
2019-11-10 22:58:51 +00:00
return true ;
}
return false ;
}
2019-11-10 23:23:52 +00:00
//==========================================================================
//
// S_RestartMusic
//
//==========================================================================
void S_RestartMusic ( )
2019-11-10 22:58:51 +00:00
{
2020-04-12 06:07:48 +00:00
if ( snd_musicvolume < = 0 ) return ;
if ( ! mus_playing . LastSong . IsEmpty ( ) & & mus_enabled )
2019-11-10 23:23:52 +00:00
{
FString song = mus_playing . LastSong ;
mus_playing . LastSong = " " ;
S_ChangeMusic ( song , mus_playing . baseorder , mus_playing . loop , true ) ;
}
2020-02-25 20:15:25 +00:00
else
{
S_StopMusic ( true ) ;
}
2019-11-10 23:23:52 +00:00
}
2019-11-10 22:58:51 +00:00
2019-11-10 23:23:52 +00:00
//==========================================================================
//
// S_MIDIDeviceChanged
//
//==========================================================================
void S_MIDIDeviceChanged ( int newdev )
{
2020-02-09 12:26:51 +00:00
auto song = mus_playing . handle ;
2019-11-10 23:23:52 +00:00
if ( song ! = nullptr & & ZMusic_IsMIDI ( song ) & & ZMusic_IsPlaying ( song ) )
{
// Reload the song to change the device
auto mi = mus_playing ;
S_StopMusic ( true ) ;
S_ChangeMusic ( mi . name , mi . baseorder , mi . loop ) ;
}
}
//==========================================================================
//
// S_GetMusic
//
//==========================================================================
int S_GetMusic ( const char * * name )
{
int order ;
if ( mus_playing . name . IsNotEmpty ( ) )
{
* name = mus_playing . name ;
order = mus_playing . baseorder ;
}
else
{
* name = nullptr ;
order = 0 ;
}
return order ;
2019-11-10 22:58:51 +00:00
}
2019-11-10 23:23:52 +00:00
//==========================================================================
//
// S_StopMusic
//
//==========================================================================
void S_StopMusic ( bool force )
{
try
{
// [RH] Don't stop if a playlist is active.
2020-04-12 06:07:48 +00:00
if ( ( force | | PlayList . GetNumSongs ( ) = = 0 ) & & ! mus_playing . name . IsEmpty ( ) )
2019-11-10 23:23:52 +00:00
{
if ( mus_playing . handle ! = nullptr )
{
S_ResumeMusic ( ) ;
S_StopStream ( ) ;
ZMusic_Stop ( mus_playing . handle ) ;
auto h = mus_playing . handle ;
mus_playing . handle = nullptr ;
ZMusic_Close ( h ) ;
}
mus_playing . LastSong = std : : move ( mus_playing . name ) ;
}
}
2020-04-12 06:07:48 +00:00
catch ( const std : : runtime_error & )
2019-11-10 23:23:52 +00:00
{
//Printf("Unable to stop %s: %s\n", mus_playing.name.GetChars(), err.what());
if ( mus_playing . handle ! = nullptr )
{
auto h = mus_playing . handle ;
mus_playing . handle = nullptr ;
ZMusic_Close ( h ) ;
}
mus_playing . name = " " ;
}
}
//==========================================================================
//
// CCMD changemus
//
//==========================================================================
CCMD ( changemus )
{
2020-04-12 06:07:48 +00:00
if ( ! nomusic )
2019-11-10 23:23:52 +00:00
{
if ( argv . argc ( ) > 1 )
{
2020-04-12 06:07:48 +00:00
PlayList . Clear ( ) ;
2019-11-10 23:23:52 +00:00
S_ChangeMusic ( argv [ 1 ] , argv . argc ( ) > 2 ? atoi ( argv [ 2 ] ) : 0 ) ;
}
else
{
const char * currentmus = mus_playing . name . GetChars ( ) ;
if ( currentmus ! = nullptr & & * currentmus ! = 0 )
{
Printf ( " currently playing %s \n " , currentmus ) ;
}
else
{
Printf ( " no music playing \n " ) ;
}
}
}
else
{
Printf ( " Music is disabled \n " ) ;
}
}
//==========================================================================
//
// CCMD stopmus
//
//==========================================================================
CCMD ( stopmus )
{
2020-04-12 06:07:48 +00:00
PlayList . Clear ( ) ;
2019-11-10 23:23:52 +00:00
S_StopMusic ( false ) ;
mus_playing . LastSong = " " ; // forget the last played song so that it won't get restarted if some volume changes occur
}
2020-04-12 06:07:48 +00:00
//==========================================================================
//
// CCMD playlist
//
//==========================================================================
UNSAFE_CCMD ( playlist )
2019-11-10 22:58:51 +00:00
{
2020-04-12 06:07:48 +00:00
int argc = argv . argc ( ) ;
if ( argc < 2 | | argc > 3 )
2019-11-11 23:43:07 +00:00
{
2020-04-12 06:07:48 +00:00
Printf ( " playlist <playlist.m3u> [<position>|shuffle] \n " ) ;
2019-11-11 23:43:07 +00:00
}
2020-04-12 06:07:48 +00:00
else
2019-11-11 23:43:07 +00:00
{
2020-04-12 06:07:48 +00:00
if ( ! PlayList . ChangeList ( argv [ 1 ] ) )
{
Printf ( " Could not open " TEXTCOLOR_BOLD " %s " TEXTCOLOR_NORMAL " : %s \n " , argv [ 1 ] , strerror ( errno ) ) ;
return ;
2020-05-26 21:12:04 +00:00
}
2020-04-12 06:07:48 +00:00
if ( PlayList . GetNumSongs ( ) > 0 )
2019-11-28 18:35:35 +00:00
{
2020-05-26 21:12:04 +00:00
if ( argc = = 3 )
{
2020-04-12 06:07:48 +00:00
if ( stricmp ( argv [ 2 ] , " shuffle " ) = = 0 )
{
PlayList . Shuffle ( ) ;
2020-05-26 21:12:04 +00:00
}
2020-04-12 06:07:48 +00:00
else
2020-05-26 21:12:04 +00:00
{
2020-04-12 06:07:48 +00:00
PlayList . SetPosition ( atoi ( argv [ 2 ] ) ) ;
2020-05-26 21:12:04 +00:00
}
2020-04-12 06:07:48 +00:00
}
S_ActivatePlayList ( false ) ;
}
2019-11-28 18:35:35 +00:00
}
2019-11-10 22:58:51 +00:00
}
2020-04-12 06:07:48 +00:00
//==========================================================================
//
// CCMD playlistpos
//
//==========================================================================
2019-12-26 12:04:29 +00:00
2020-04-12 06:07:48 +00:00
static bool CheckForPlaylist ( )
2019-11-10 22:58:51 +00:00
{
2020-04-12 06:07:48 +00:00
if ( PlayList . GetNumSongs ( ) = = 0 )
{
Printf ( " No playlist is playing. \n " ) ;
return false ;
}
return true ;
2019-11-10 22:58:51 +00:00
}
2020-04-12 06:07:48 +00:00
CCMD ( playlistpos )
2019-12-07 17:28:30 +00:00
{
2020-04-12 06:07:48 +00:00
if ( CheckForPlaylist ( ) & & argv . argc ( ) > 1 )
{
PlayList . SetPosition ( atoi ( argv [ 1 ] ) - 1 ) ;
S_ActivatePlayList ( false ) ;
}
2019-12-07 17:28:30 +00:00
}
2020-04-12 06:07:48 +00:00
//==========================================================================
//
// CCMD playlistnext
//
//==========================================================================
CCMD ( playlistnext )
2019-11-10 22:58:51 +00:00
{
2020-04-12 06:07:48 +00:00
if ( CheckForPlaylist ( ) )
{
PlayList . Advance ( ) ;
S_ActivatePlayList ( false ) ;
}
2019-11-10 22:58:51 +00:00
}
2020-04-12 06:07:48 +00:00
//==========================================================================
//
// CCMD playlistprev
//
//==========================================================================
CCMD ( playlistprev )
2019-11-28 02:18:58 +00:00
{
2020-04-12 06:07:48 +00:00
if ( CheckForPlaylist ( ) )
2019-11-28 02:18:58 +00:00
{
2020-04-12 06:07:48 +00:00
PlayList . Backup ( ) ;
S_ActivatePlayList ( true ) ;
2020-05-26 21:12:04 +00:00
}
2020-04-12 06:07:48 +00:00
}
2019-11-28 02:18:58 +00:00
2020-04-12 06:07:48 +00:00
//==========================================================================
//
// CCMD playliststatus
//
//==========================================================================
2019-11-28 02:18:58 +00:00
2020-04-12 06:07:48 +00:00
CCMD ( playliststatus )
{
if ( CheckForPlaylist ( ) )
{
Printf ( " Song %d of %d: \n %s \n " ,
PlayList . GetPosition ( ) + 1 ,
PlayList . GetNumSongs ( ) ,
PlayList . GetSong ( PlayList . GetPosition ( ) ) ) ;
2019-11-28 02:18:58 +00:00
}
}
2020-04-12 06:07:48 +00:00
//==========================================================================
//
//
//
//==========================================================================
CCMD ( currentmusic )
2019-11-28 02:18:58 +00:00
{
2020-04-12 06:07:48 +00:00
if ( mus_playing . name . IsNotEmpty ( ) )
{
Printf ( " Currently playing music '%s' \n " , mus_playing . name . GetChars ( ) ) ;
}
else
{
Printf ( " Currently no music playing \n " ) ;
}
2019-11-28 02:18:58 +00:00
}