2009-05-08 13:28:41 +00:00
/*
* OpenClonk , http : //www.openclonk.org
*
2013-12-17 20:01:09 +00:00
* Copyright ( c ) 1998 - 2000 , Matthes Bender
* Copyright ( c ) 2001 - 2009 , RedWolf Design GmbH , http : //www.clonk.de/
2016-04-03 18:18:29 +00:00
* Copyright ( c ) 2009 - 2016 , The OpenClonk Team and contributors
2009-05-08 13:28:41 +00:00
*
2013-12-17 20:01:09 +00:00
* Distributed under the terms of the ISC license ; see accompanying file
* " COPYING " for details .
2009-05-08 13:28:41 +00:00
*
2013-12-17 20:01:09 +00:00
* " Clonk " is a registered trademark of Matthes Bender , used with permission .
* See accompanying file " TRADEMARK " for details .
2009-05-08 13:28:41 +00:00
*
2013-12-17 20:01:09 +00:00
* To redistribute this file separately , substitute the full license texts
* for the above references .
2009-05-08 13:28:41 +00:00
*/
2011-03-13 15:16:45 +00:00
/* Handles Music.ocg and randomly plays songs */
2009-05-08 13:28:41 +00:00
2016-04-03 18:07:56 +00:00
# include "C4Include.h"
# include "platform/C4MusicSystem.h"
2009-05-08 13:28:41 +00:00
2016-04-03 18:07:56 +00:00
# include "game/C4Application.h"
# include "game/C4GraphicsSystem.h"
2017-04-30 08:49:09 +00:00
# include "lib/C4Random.h"
# include "platform/C4MusicFile.h"
# include "platform/C4Window.h"
2009-05-08 13:28:41 +00:00
C4MusicSystem : : C4MusicSystem ( ) :
2015-10-01 02:49:47 +00:00
playlist ( ) ,
music_break_min ( DefaultMusicBreak ) , music_break_max ( DefaultMusicBreak ) ,
2017-05-07 11:50:00 +00:00
wait_time_end ( )
2010-03-28 18:58:01 +00:00
{
}
2009-05-08 13:28:41 +00:00
C4MusicSystem : : ~ C4MusicSystem ( )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
Clear ( ) ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
2014-08-01 19:05:55 +00:00
# if AUDIO_TK == AUDIO_TK_OPENAL
2010-09-26 00:36:35 +00:00
void C4MusicSystem : : SelectContext ( )
{
alcMakeContextCurrent ( alcContext ) ;
}
# endif
2009-05-08 13:28:41 +00:00
bool C4MusicSystem : : InitializeMOD ( )
{
2015-10-12 12:17:24 +00:00
# if AUDIO_TK == AUDIO_TK_SDL_MIXER
2009-05-08 13:28:41 +00:00
SDL_version compile_version ;
const SDL_version * link_version ;
MIX_VERSION ( & compile_version ) ;
link_version = Mix_Linked_Version ( ) ;
LogF ( " SDL_mixer runtime version is %d.%d.%d (compiled with %d.%d.%d) " ,
2010-03-28 18:58:01 +00:00
link_version - > major , link_version - > minor , link_version - > patch ,
compile_version . major , compile_version . minor , compile_version . patch ) ;
2016-02-06 20:12:56 +00:00
if ( SDL_InitSubSystem ( SDL_INIT_AUDIO ) ! = 0 )
2010-03-28 18:58:01 +00:00
{
2016-02-06 20:12:56 +00:00
LogF ( " SDL_InitSubSystem(SDL_INIT_AUDIO): %s " , SDL_GetError ( ) ) ;
2009-05-08 13:28:41 +00:00
return false ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
//frequency, format, stereo, chunksize
2010-03-28 18:58:01 +00:00
if ( Mix_OpenAudio ( 44100 , AUDIO_S16SYS , 2 , 1024 ) )
{
2009-05-08 13:28:41 +00:00
LogF ( " SDL_mixer: %s " , SDL_GetError ( ) ) ;
return false ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
MODInitialized = true ;
return true ;
2014-08-01 19:05:55 +00:00
# elif AUDIO_TK == AUDIO_TK_OPENAL
2016-11-02 23:58:02 +00:00
alcDevice = alcOpenDevice ( nullptr ) ;
2010-09-26 00:36:35 +00:00
if ( ! alcDevice )
2012-10-07 11:18:28 +00:00
{
LogF ( " Sound system: OpenAL create context error " ) ;
2010-09-26 00:36:35 +00:00
return false ;
2012-10-07 11:18:28 +00:00
}
2016-11-02 23:58:02 +00:00
alcContext = alcCreateContext ( alcDevice , nullptr ) ;
2010-09-26 00:36:35 +00:00
if ( ! alcContext )
2012-10-07 11:18:28 +00:00
{
LogF ( " Sound system: OpenAL create context error " ) ;
return false ;
}
# ifndef __APPLE__
2016-11-02 23:58:02 +00:00
if ( ! alutInitWithoutContext ( nullptr , nullptr ) )
2012-10-07 11:18:28 +00:00
{
LogF ( " Sound system: ALUT init error " ) ;
2010-09-26 00:36:35 +00:00
return false ;
2012-10-07 11:18:28 +00:00
}
# endif
MODInitialized = true ;
2010-09-26 00:36:35 +00:00
return true ;
2009-05-08 13:28:41 +00:00
# endif
return false ;
}
void C4MusicSystem : : DeinitializeMOD ( )
{
2015-10-12 12:17:24 +00:00
# if AUDIO_TK == AUDIO_TK_SDL_MIXER
2009-05-08 13:28:41 +00:00
Mix_CloseAudio ( ) ;
2016-02-06 20:12:56 +00:00
SDL_QuitSubSystem ( SDL_INIT_AUDIO ) ;
2014-08-01 19:05:55 +00:00
# elif AUDIO_TK == AUDIO_TK_OPENAL
2012-10-07 11:18:28 +00:00
# ifndef __APPLE__
alutExit ( ) ;
# endif
2010-09-26 00:36:35 +00:00
alcDestroyContext ( alcContext ) ;
alcCloseDevice ( alcDevice ) ;
2016-11-02 23:58:02 +00:00
alcContext = nullptr ;
alcDevice = nullptr ;
2009-05-08 13:28:41 +00:00
# endif
2009-08-15 18:50:32 +00:00
MODInitialized = false ;
2009-05-08 13:28:41 +00:00
}
bool C4MusicSystem : : Init ( const char * PlayList )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// init mod
2010-03-28 18:58:01 +00:00
if ( ! MODInitialized & & ! InitializeMOD ( ) ) return false ;
2009-05-08 13:28:41 +00:00
// Might be reinitialisation
ClearSongs ( ) ;
// Global music file
LoadDir ( Config . AtSystemDataPath ( C4CFN_Music ) ) ;
// User music file
LoadDir ( Config . AtUserDataPath ( C4CFN_Music ) ) ;
// read MoreMusic.txt
LoadMoreMusic ( ) ;
// set play list
2015-12-10 05:31:20 +00:00
SCounter = 0 ;
2017-05-03 18:28:00 +00:00
if ( PlayList ) SetPlayList ( PlayList ) ; else SetPlayList ( nullptr ) ;
2009-05-08 13:28:41 +00:00
// set initial volume
2015-10-01 02:49:47 +00:00
UpdateVolume ( ) ;
2009-05-08 13:28:41 +00:00
// ok
return true ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4MusicSystem : : InitForScenario ( C4Group & hGroup )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// check if the scenario contains music
bool fLocalMusic = false ;
StdStrBuf MusicDir ;
if ( GrpContainsMusic ( hGroup ) )
{
// clear global songs
ClearSongs ( ) ;
fLocalMusic = true ;
// add songs
MusicDir . Take ( Game . ScenarioFile . GetFullName ( ) ) ;
LoadDir ( MusicDir . getData ( ) ) ;
// log
2011-10-10 21:28:01 +00:00
LogF ( LoadResStr ( " IDS_PRC_LOCALMUSIC " ) , MusicDir . getData ( ) ) ;
2009-05-08 13:28:41 +00:00
}
// check for music folders in group set
2016-11-02 23:58:02 +00:00
C4Group * pMusicFolder = nullptr ;
2010-03-28 18:58:01 +00:00
while ( ( pMusicFolder = Game . GroupSet . FindGroup ( C4GSCnt_Music , pMusicFolder ) ) )
{
2009-05-08 13:28:41 +00:00
if ( ! fLocalMusic )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// clear global songs
ClearSongs ( ) ;
fLocalMusic = true ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// add songs
MusicDir . Take ( pMusicFolder - > GetFullName ( ) ) ;
MusicDir . AppendChar ( DirectorySeparator ) ;
MusicDir . Append ( C4CFN_Music ) ;
LoadDir ( MusicDir . getData ( ) ) ;
// log
2011-10-10 21:28:01 +00:00
LogF ( LoadResStr ( " IDS_PRC_LOCALMUSIC " ) , MusicDir . getData ( ) ) ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// no music?
2010-03-28 18:58:01 +00:00
if ( ! SongCount ) return false ;
2009-05-08 13:28:41 +00:00
// set play list
2017-05-03 18:28:00 +00:00
SetPlayList ( nullptr ) ;
2009-05-08 13:28:41 +00:00
// ok
return true ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4MusicSystem : : Load ( const char * szFile )
{
// safety
if ( ! szFile | | ! * szFile ) return ;
2016-11-02 23:58:02 +00:00
C4MusicFile * NewSong = nullptr ;
2014-08-07 11:46:45 +00:00
# if AUDIO_TK == AUDIO_TK_OPENAL
// openal: Only ogg supported
const char * szExt = GetExtension ( szFile ) ;
if ( SEqualNoCase ( szExt , " ogg " ) ) NewSong = new C4MusicFileOgg ;
2014-08-01 19:05:55 +00:00
# elif AUDIO_TK == AUDIO_TK_SDL_MIXER
2009-05-08 13:28:41 +00:00
if ( GetMusicFileTypeByExtension ( GetExtension ( szFile ) ) = = MUSICTYPE_UNKNOWN ) return ;
NewSong = new C4MusicFileSDL ;
# endif
// unrecognized type/mod not initialized?
if ( ! NewSong ) return ;
// init music file
NewSong - > Init ( szFile ) ;
// add song to list (push back)
C4MusicFile * pCurr = Songs ;
2010-03-28 18:58:01 +00:00
while ( pCurr & & pCurr - > pNext ) pCurr = pCurr - > pNext ;
if ( pCurr ) pCurr - > pNext = NewSong ; else Songs = NewSong ;
2016-11-02 23:58:02 +00:00
NewSong - > pNext = nullptr ;
2009-05-08 13:28:41 +00:00
// count songs
SongCount + + ;
2015-12-03 04:08:50 +00:00
playlist_valid = false ;
2009-05-08 13:28:41 +00:00
}
void C4MusicSystem : : LoadDir ( const char * szPath )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
char Path [ _MAX_FNAME + 1 ] , File [ _MAX_FNAME + 1 ] ;
2016-11-02 23:58:02 +00:00
C4Group * pDirGroup = nullptr ;
2009-05-08 13:28:41 +00:00
// split path
SCopy ( szPath , Path , _MAX_FNAME ) ;
char * pFileName = GetFilename ( Path ) ;
SCopy ( pFileName , File ) ;
* ( pFileName - 1 ) = 0 ;
// no file name?
2010-03-28 18:58:01 +00:00
if ( ! File [ 0 ] )
2009-05-08 13:28:41 +00:00
// -> add the whole directory
SCopy ( " * " , File ) ;
// no wildcard match?
2010-03-28 18:58:01 +00:00
else if ( ! SSearch ( File , " *? " ) )
{
2009-05-08 13:28:41 +00:00
// then it's either a file or a directory - do the test with C4Group
pDirGroup = new C4Group ( ) ;
2010-03-28 18:58:01 +00:00
if ( ! pDirGroup - > Open ( szPath ) )
{
2009-05-08 13:28:41 +00:00
// so it must be a file
2010-03-28 18:58:01 +00:00
if ( ! pDirGroup - > Open ( Path ) )
{
2009-05-08 13:28:41 +00:00
// -> file/dir doesn't exist
LogF ( " Music File not found: %s " , szPath ) ;
delete pDirGroup ;
return ;
}
2010-03-28 18:58:01 +00:00
// mother group is open... proceed with normal handling
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// ok, set wildcard (load the whole directory)
SCopy ( szPath , Path ) ;
SCopy ( " * " , File ) ;
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// open directory group, if not already done so
2010-03-28 18:58:01 +00:00
if ( ! pDirGroup )
2009-05-08 13:28:41 +00:00
{
pDirGroup = new C4Group ( ) ;
2010-03-28 18:58:01 +00:00
if ( ! pDirGroup - > Open ( Path ) )
2009-05-08 13:28:41 +00:00
{
LogF ( " Music File not found: %s " , szPath ) ;
delete pDirGroup ;
return ;
}
}
// search file(s)
char szFile [ _MAX_FNAME + 1 ] ;
pDirGroup - > ResetSearch ( ) ;
2010-03-28 18:58:01 +00:00
while ( pDirGroup - > FindNextEntry ( File , szFile ) )
2009-05-08 13:28:41 +00:00
{
char strFullPath [ _MAX_FNAME + 1 ] ;
sprintf ( strFullPath , " %s%c%s " , Path , DirectorySeparator , szFile ) ;
Load ( strFullPath ) ;
}
// free it
delete pDirGroup ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4MusicSystem : : LoadMoreMusic ( )
2010-03-28 18:58:01 +00:00
{
2011-06-19 14:35:06 +00:00
StdStrBuf MoreMusicFile ;
2009-05-08 13:28:41 +00:00
// load MoreMusic.txt
2011-06-19 14:35:06 +00:00
if ( ! MoreMusicFile . LoadFromFile ( Config . AtUserDataPath ( C4CFN_MoreMusic ) ) ) return ;
2009-05-08 13:28:41 +00:00
// read contents
2011-06-19 14:35:06 +00:00
char * pPos = MoreMusicFile . getMData ( ) ;
2010-03-28 18:58:01 +00:00
while ( pPos & & * pPos )
{
2009-05-08 13:28:41 +00:00
// get line
char szLine [ 1024 + 1 ] ;
SCopyUntil ( pPos , szLine , ' \n ' , 1024 ) ;
2010-03-28 18:58:01 +00:00
pPos = strchr ( pPos , ' \n ' ) ; if ( pPos ) pPos + + ;
2009-05-08 13:28:41 +00:00
// remove leading whitespace
char * pLine = szLine ;
2010-03-28 18:58:01 +00:00
while ( * pLine = = ' ' | | * pLine = = ' \t ' | | * pLine = = ' \r ' ) pLine + + ;
2009-05-08 13:28:41 +00:00
// and whitespace at end
char * p = pLine + strlen ( pLine ) - 1 ;
2010-03-28 18:58:01 +00:00
while ( * p = = ' ' | | * p = = ' \t ' | | * p = = ' \r ' ) { * p = 0 ; - - p ; }
2009-05-08 13:28:41 +00:00
// comment?
2010-03-28 18:58:01 +00:00
if ( * pLine = = ' # ' )
2009-05-08 13:28:41 +00:00
{
// might be a "directive"
2010-03-28 18:58:01 +00:00
if ( SEqual ( pLine , " #clear " ) )
2009-05-08 13:28:41 +00:00
ClearSongs ( ) ;
continue ;
}
// try to load file(s)
LoadDir ( pLine ) ;
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4MusicSystem : : ClearSongs ( )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
Stop ( ) ;
2010-03-28 18:58:01 +00:00
while ( Songs )
{
2009-05-08 13:28:41 +00:00
C4MusicFile * pFile = Songs ;
Songs = pFile - > pNext ;
delete pFile ;
}
2010-03-28 18:58:01 +00:00
SongCount = 0 ;
2016-11-02 23:58:02 +00:00
FadeMusicFile = upcoming_music_file = PlayMusicFile = nullptr ;
2015-12-03 04:08:50 +00:00
playlist_valid = false ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4MusicSystem : : Clear ( )
2010-03-28 18:58:01 +00:00
{
2014-08-01 19:05:55 +00:00
# if AUDIO_TK == AUDIO_TK_SDL_MIXER
2009-05-08 13:28:41 +00:00
// Stop a fadeout
Mix_HaltMusic ( ) ;
# endif
ClearSongs ( ) ;
2010-03-28 18:58:01 +00:00
if ( MODInitialized ) { DeinitializeMOD ( ) ; }
}
2009-05-08 13:28:41 +00:00
2015-10-01 02:49:47 +00:00
void C4MusicSystem : : ClearGame ( )
{
game_music_level = 100 ;
music_break_min = music_break_max = DefaultMusicBreak ;
music_break_chance = DefaultMusicBreakChance ;
2015-12-04 00:17:14 +00:00
music_max_position_memory = DefaultMusicMaxPositionMemory ;
2016-11-02 23:58:02 +00:00
SetPlayList ( nullptr ) ;
2015-10-01 02:49:47 +00:00
is_waiting = false ;
2016-11-02 23:58:02 +00:00
upcoming_music_file = nullptr ;
2015-10-01 02:49:47 +00:00
}
2015-10-01 04:08:35 +00:00
void C4MusicSystem : : Execute ( bool force_song_execution )
2010-03-28 18:58:01 +00:00
{
2014-12-06 17:04:55 +00:00
// Execute music fading
if ( FadeMusicFile )
{
C4TimeMilliseconds tNow = C4TimeMilliseconds : : Now ( ) ;
// Fading done?
if ( tNow > = FadeTimeEnd )
{
FadeMusicFile - > Stop ( ) ;
2016-11-02 23:58:02 +00:00
FadeMusicFile = nullptr ;
2015-10-01 04:08:35 +00:00
if ( PlayMusicFile )
{
PlayMusicFile - > SetVolume ( Volume ) ;
}
else if ( upcoming_music_file )
{
// Fade end -> start desired next immediately
force_song_execution = true ;
}
2014-12-06 17:04:55 +00:00
}
else
{
// Fade process
int fade_volume = 1000 * ( tNow - FadeTimeStart ) / ( FadeTimeEnd - FadeTimeStart ) ;
FadeMusicFile - > SetVolume ( Volume * ( 1000 - fade_volume ) / 1000 ) ;
if ( PlayMusicFile ) PlayMusicFile - > SetVolume ( Volume * fade_volume / 1000 ) ;
}
}
// Ensure a piece is played
2014-08-01 19:05:55 +00:00
# if AUDIO_TK != AUDIO_TK_SDL_MIXER
2015-10-01 04:08:35 +00:00
if ( ! : : Game . iTick35 | | ! : : Game . IsRunning | | force_song_execution | | : : Game . IsPaused ( ) )
2015-10-04 13:23:15 +00:00
# else
( void ) force_song_execution ;
2009-05-08 13:28:41 +00:00
# endif
2010-04-28 21:43:25 +00:00
{
2010-01-02 16:33:58 +00:00
if ( ! PlayMusicFile )
2015-10-01 02:49:47 +00:00
{
if ( ! is_waiting | | ( C4TimeMilliseconds : : Now ( ) > = wait_time_end ) )
{
// Play a song if no longer in silence mode and nothing is playing right now
2015-10-01 04:08:35 +00:00
C4MusicFile * next_file = upcoming_music_file ;
is_waiting = false ;
2016-11-02 23:58:02 +00:00
upcoming_music_file = nullptr ;
2015-10-01 04:08:35 +00:00
if ( next_file )
Play ( next_file , false , 0.0 ) ;
else
Play ( ) ;
2015-10-01 02:49:47 +00:00
}
}
2010-01-02 16:33:58 +00:00
else
2015-10-01 02:49:47 +00:00
{
// Calls NotifySuccess if a new piece had been selected.
2010-01-02 16:33:58 +00:00
PlayMusicFile - > CheckIfPlaying ( ) ;
2015-10-01 02:49:47 +00:00
}
2010-04-28 21:43:25 +00:00
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
2015-10-01 02:49:47 +00:00
bool C4MusicSystem : : Play ( const char * szSongname , bool fLoop , int fadetime_ms , double max_resume_time , bool allow_break )
2010-03-28 18:58:01 +00:00
{
2015-10-01 04:08:35 +00:00
// pause is done
is_waiting = false ;
2016-11-02 23:58:02 +00:00
upcoming_music_file = nullptr ;
2015-10-01 04:08:35 +00:00
// music off?
2010-01-02 16:33:58 +00:00
if ( Game . IsRunning ? ! Config . Sound . RXMusic : ! Config . Sound . FEMusic )
return false ;
2015-12-03 04:08:50 +00:00
// info
if ( : : Config . Sound . Verbose )
{
2017-05-03 18:28:00 +00:00
LogF ( R " (MusicSystem: Play( " % s " , %s, %d, %.3lf, %s)) " , szSongname ? szSongname : " (null) " , fLoop ? " true " : " false " , fadetime_ms , max_resume_time , allow_break ? " true " : " false " ) ;
2015-12-03 04:08:50 +00:00
}
2016-11-02 23:58:02 +00:00
C4MusicFile * NewFile = nullptr ;
2009-05-08 13:28:41 +00:00
// Specified song name
if ( szSongname & & szSongname [ 0 ] )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Search in list
2015-10-01 04:08:35 +00:00
for ( NewFile = Songs ; NewFile ; NewFile = NewFile - > pNext )
2010-03-28 18:58:01 +00:00
{
2015-10-01 04:08:35 +00:00
char songname [ _MAX_FNAME + 1 ] ;
2010-03-28 18:58:01 +00:00
SCopy ( szSongname , songname ) ; DefaultExtension ( songname , " mid " ) ;
if ( SEqual ( GetFilename ( NewFile - > FileName ) , songname ) )
break ;
SCopy ( szSongname , songname ) ; DefaultExtension ( songname , " ogg " ) ;
if ( SEqual ( GetFilename ( NewFile - > FileName ) , songname ) )
break ;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2015-09-28 20:22:25 +00:00
// When resuming, prefer songs that were interrupted before
if ( max_resume_time > 0 )
2010-03-28 18:58:01 +00:00
{
2015-12-04 00:17:14 +00:00
C4TimeMilliseconds t_now = C4TimeMilliseconds : : Now ( ) ;
2015-09-28 20:22:25 +00:00
for ( C4MusicFile * check_file = Songs ; check_file ; check_file = check_file - > pNext )
if ( ! check_file - > NoPlay )
2015-12-04 00:17:14 +00:00
{
2015-09-28 20:22:25 +00:00
if ( check_file - > HasResumePos ( ) & & check_file - > GetRemainingTime ( ) > max_resume_time )
2015-12-04 00:17:14 +00:00
if ( ! music_max_position_memory | | ( t_now - check_file - > GetLastInterruptionTime ( ) < = music_max_position_memory * 1000 ) )
if ( ! NewFile | | NewFile - > LastPlayed < check_file - > LastPlayed )
NewFile = check_file ;
}
2009-05-08 13:28:41 +00:00
}
2015-09-28 20:22:25 +00:00
// Random song
if ( ! NewFile )
{
2015-10-01 02:49:47 +00:00
// Intead of a new song, is a break also allowed?
if ( allow_break ) ScheduleWaitTime ( ) ;
if ( ! is_waiting )
2015-09-28 20:22:25 +00:00
{
2015-12-10 05:31:20 +00:00
if ( : : Config . Sound . Verbose ) LogF ( " ASongCount=%d SCounter=%d " , ASongCount , SCounter ) ;
2015-10-01 04:08:35 +00:00
// try to find random song
2015-12-10 05:31:20 +00:00
int32_t new_file_playability = 0 , new_file_num_rolls = 0 ;
for ( C4MusicFile * check_file = Songs ; check_file ; check_file = check_file - > pNext )
2015-10-01 02:49:47 +00:00
{
2015-12-10 05:31:20 +00:00
if ( ! check_file - > NoPlay )
{
// Categorize song playability:
// 0 = no song found yet
// 1 = song was played recently
// 2 = song not played recently
// 3 = song was not played yet
int32_t check_file_playability = ( check_file - > LastPlayed < 0 ) ? 3 : ( SCounter - check_file - > LastPlayed < = ASongCount / 2 ) ? 1 : 2 ;
if ( : : Config . Sound . Verbose ) LogF ( " Song LastPlayed %d [%d] (%s) " , int ( check_file - > LastPlayed ) , int ( check_file_playability ) , check_file - > GetDebugInfo ( ) . getData ( ) ) ;
if ( check_file_playability > new_file_playability )
{
// Found much better fit. Play this and reset number of songs found in same plyability
new_file_num_rolls = 1 ;
NewFile = check_file ;
new_file_playability = check_file_playability ;
}
else if ( check_file_playability = = new_file_playability )
{
// Found a fit in the same playability category: Roll for it
2016-04-25 15:32:23 +00:00
if ( ! UnsyncedRandom ( + + new_file_num_rolls ) ) NewFile = check_file ;
2015-12-10 05:31:20 +00:00
}
else
{
// Worse playability - ignore this song
}
}
2015-10-01 02:49:47 +00:00
}
2015-09-28 20:22:25 +00:00
}
}
2010-03-28 18:58:01 +00:00
}
2015-10-01 02:49:47 +00:00
// File (or wait time) found?
if ( ! NewFile & & ! is_waiting )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
2014-12-06 17:04:55 +00:00
// Stop/Fade out old music
2015-10-01 02:49:47 +00:00
bool is_fading = ( fadetime_ms & & NewFile ! = PlayMusicFile & & PlayMusicFile ) ;
2014-12-06 17:04:55 +00:00
if ( ! is_fading )
{
Stop ( ) ;
}
else
{
2014-12-09 22:37:03 +00:00
C4TimeMilliseconds tNow = C4TimeMilliseconds : : Now ( ) ;
if ( FadeMusicFile )
{
if ( FadeMusicFile = = NewFile & & FadeMusicFile - > IsLooping ( ) = = fLoop & & tNow < FadeTimeEnd )
{
// Fading back to a song while it wasn't fully faded out yet. Just swap our pointers and fix timings for that.
FadeMusicFile = PlayMusicFile ;
PlayMusicFile = NewFile ;
FadeTimeEnd = tNow + fadetime_ms * ( tNow - FadeTimeStart ) / ( FadeTimeEnd - FadeTimeStart ) ;
FadeTimeStart = FadeTimeEnd - fadetime_ms ;
return true ;
}
else
{
// Fading to a third song while the previous wasn't faded out yet
// That's pretty chaotic anyway, so just cancel the last song
// Also happens if fading should already be done, in which case it won't harm to stop now
// (It would stop on next call to Execute() anyway)
// Also happens when fading back to the same song but loop status changes, but that should be really uncommon.
FadeMusicFile - > Stop ( ) ;
}
2015-10-01 04:08:35 +00:00
2014-12-09 22:37:03 +00:00
}
2014-12-06 17:04:55 +00:00
FadeMusicFile = PlayMusicFile ;
2016-11-02 23:58:02 +00:00
PlayMusicFile = nullptr ;
2014-12-09 22:37:03 +00:00
FadeTimeStart = tNow ;
2014-12-06 17:04:55 +00:00
FadeTimeEnd = FadeTimeStart + fadetime_ms ;
}
2009-05-08 13:28:41 +00:00
2015-10-01 02:49:47 +00:00
// Waiting?
if ( ! NewFile ) return false ;
2015-10-01 04:08:35 +00:00
// If the old file is being faded out and a new file would just start, start delayed and without fading
// so the beginning of a song isn't faded unnecesserily (because our songs often start very abruptly)
if ( is_fading & & ( ! NewFile - > HasResumePos ( ) | | NewFile - > GetRemainingTime ( ) < = max_resume_time ) )
{
upcoming_music_file = NewFile ;
is_waiting = true ;
wait_time_end = FadeTimeEnd ;
return false ;
}
if ( ! Play ( NewFile , fLoop , max_resume_time ) ) return false ;
if ( is_fading ) PlayMusicFile - > SetVolume ( 0 ) ;
return true ;
}
bool C4MusicSystem : : Play ( C4MusicFile * NewFile , bool fLoop , double max_resume_time )
{
2015-12-03 04:08:50 +00:00
// info
if ( : : Config . Sound . Verbose )
{
2017-05-03 18:28:00 +00:00
LogF ( R " (MusicSystem: PlaySong( " % s " , %s, %.3lf)) " , NewFile - > GetDebugInfo ( ) . getData ( ) , fLoop ? " true " : " false " , max_resume_time ) ;
2015-12-03 04:08:50 +00:00
}
2015-10-01 04:08:35 +00:00
// Play new song directly
2015-09-28 00:58:37 +00:00
if ( ! NewFile - > Play ( fLoop , max_resume_time ) ) return false ;
2009-05-08 13:28:41 +00:00
PlayMusicFile = NewFile ;
NewFile - > LastPlayed = SCounter + + ;
Loop = fLoop ;
// Set volume
2015-10-01 04:08:35 +00:00
PlayMusicFile - > SetVolume ( Volume ) ;
2009-05-08 13:28:41 +00:00
2015-09-28 20:29:12 +00:00
// Message first time a piece is played
if ( ! NewFile - > HasBeenAnnounced ( ) )
NewFile - > Announce ( ) ;
2009-08-15 18:50:32 +00:00
return true ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4MusicSystem : : NotifySuccess ( )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// nothing played?
if ( ! PlayMusicFile ) return ;
// loop?
if ( Loop )
if ( PlayMusicFile - > Play ( ) )
return ;
2015-10-01 02:49:47 +00:00
// clear last played piece
2009-05-08 13:28:41 +00:00
Stop ( ) ;
2015-10-01 02:49:47 +00:00
// force a wait time after this song?
ScheduleWaitTime ( ) ;
}
bool C4MusicSystem : : ScheduleWaitTime ( )
{
// Roll for scheduling a break after the next piece.
if ( SCounter < 3 ) return false ; // But not right away.
2016-09-07 01:47:55 +00:00
if ( int32_t ( UnsyncedRandom ( 100 ) ) > = music_break_chance ) return false ;
2015-10-01 02:49:47 +00:00
if ( music_break_max > 0 )
{
int32_t music_break = music_break_min ;
2016-04-25 15:32:23 +00:00
if ( music_break_max > music_break_min ) music_break + = UnsyncedRandom ( music_break_max - music_break_min ) ; // note that UnsyncedRandom has limited range
2015-10-01 02:49:47 +00:00
if ( music_break > 0 )
{
is_waiting = true ;
wait_time_end = C4TimeMilliseconds : : Now ( ) + music_break ;
2015-12-03 04:08:50 +00:00
if ( : : Config . Sound . Verbose )
{
2015-12-09 04:42:08 +00:00
LogF ( " MusicSystem: Pause (%d msecs) " , ( int ) music_break ) ;
2015-12-03 04:08:50 +00:00
}
2015-10-01 02:49:47 +00:00
// After wait, do not resume previously started songs
for ( C4MusicFile * check_file = Songs ; check_file ; check_file = check_file - > pNext )
check_file - > ClearResumePos ( ) ;
}
}
return is_waiting ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4MusicSystem : : FadeOut ( int fadeout_ms )
2010-03-28 18:58:01 +00:00
{
2014-12-06 17:04:55 +00:00
// Kill any previous fading music and schedule current piece to fade
2010-03-28 18:58:01 +00:00
if ( PlayMusicFile )
2009-05-08 13:28:41 +00:00
{
2014-12-06 17:04:55 +00:00
if ( FadeMusicFile ) FadeMusicFile - > Stop ( ) ;
FadeMusicFile = PlayMusicFile ;
2016-11-02 23:58:02 +00:00
PlayMusicFile = nullptr ;
2014-12-06 17:04:55 +00:00
FadeTimeStart = C4TimeMilliseconds : : Now ( ) ;
FadeTimeEnd = FadeTimeStart + fadeout_ms ;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4MusicSystem : : Stop ( )
2010-03-28 18:58:01 +00:00
{
if ( PlayMusicFile )
2009-05-08 13:28:41 +00:00
{
PlayMusicFile - > Stop ( ) ;
2016-11-02 23:58:02 +00:00
PlayMusicFile = nullptr ;
2009-05-08 13:28:41 +00:00
}
2014-12-06 17:04:55 +00:00
if ( FadeMusicFile )
{
FadeMusicFile - > Stop ( ) ;
2016-11-02 23:58:02 +00:00
FadeMusicFile = nullptr ;
2014-12-06 17:04:55 +00:00
}
2010-03-28 18:58:01 +00:00
return true ;
}
2009-05-08 13:28:41 +00:00
2015-10-01 02:49:47 +00:00
void C4MusicSystem : : UpdateVolume ( )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Save volume for next file
2015-10-01 02:49:47 +00:00
int32_t config_volume = Clamp < int32_t > ( Config . Sound . MusicVolume , 0 , 100 ) ;
Volume = config_volume * game_music_level / 100 ;
2009-05-08 13:28:41 +00:00
// Tell it to the act file
2010-03-28 18:58:01 +00:00
if ( PlayMusicFile )
2015-10-01 02:49:47 +00:00
PlayMusicFile - > SetVolume ( Volume ) ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
MusicType GetMusicFileTypeByExtension ( const char * ext )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
if ( SEqualNoCase ( ext , " mid " ) )
return MUSICTYPE_MID ;
2015-10-12 12:17:24 +00:00
# if AUDIO_TK == AUDIO_TK_SDL_MIXER
2009-05-08 13:28:41 +00:00
else if ( SEqualNoCase ( ext , " xm " ) | | SEqualNoCase ( ext , " it " ) | | SEqualNoCase ( ext , " s3m " ) | | SEqualNoCase ( ext , " mod " ) )
return MUSICTYPE_MOD ;
# ifdef USE_MP3
else if ( SEqualNoCase ( ext , " mp3 " ) )
return MUSICTYPE_MP3 ;
# endif
# endif
else if ( SEqualNoCase ( ext , " ogg " ) )
return MUSICTYPE_OGG ;
return MUSICTYPE_UNKNOWN ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4MusicSystem : : GrpContainsMusic ( C4Group & rGrp )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// search for known file extensions
return rGrp . FindEntry ( " *.mid " )
# ifdef USE_MP3
2010-03-28 18:58:01 +00:00
| | rGrp . FindEntry ( " *.mp3 " )
2009-05-08 13:28:41 +00:00
# endif
2010-03-28 18:58:01 +00:00
| | rGrp . FindEntry ( " *.xm " )
| | rGrp . FindEntry ( " *.it " )
| | rGrp . FindEntry ( " *.s3m " )
| | rGrp . FindEntry ( " *.mod " )
| | rGrp . FindEntry ( " *.ogg " ) ;
}
2009-05-08 13:28:41 +00:00
2015-09-28 00:58:37 +00:00
int C4MusicSystem : : SetPlayList ( const char * szPlayList , bool fForceSwitch , int fadetime_ms , double max_resume_time )
2010-03-28 18:58:01 +00:00
{
2015-12-03 04:08:50 +00:00
// Shortcut if no change
if ( playlist_valid & & playlist = = szPlayList ) return 0 ;
// info
if ( : : Config . Sound . Verbose )
{
2017-05-03 18:28:00 +00:00
LogF ( R " (MusicSystem: SetPlayList( " % s " , %s, %d, %.3lf)) " , szPlayList ? szPlayList : " (null) " , fForceSwitch ? " true " : " false " , fadetime_ms , max_resume_time ) ;
2015-12-03 04:08:50 +00:00
}
2009-05-08 13:28:41 +00:00
// reset
C4MusicFile * pFile ;
2010-03-28 18:58:01 +00:00
for ( pFile = Songs ; pFile ; pFile = pFile - > pNext )
{
2009-08-15 18:50:32 +00:00
pFile - > NoPlay = true ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
ASongCount = 0 ;
2010-03-28 18:58:01 +00:00
if ( szPlayList & & * szPlayList )
{
2009-05-08 13:28:41 +00:00
// match
char szFileName [ _MAX_FNAME + 1 ] ;
2010-03-28 18:58:01 +00:00
for ( int cnt = 0 ; SGetModule ( szPlayList , cnt , szFileName , _MAX_FNAME ) ; cnt + + )
2014-12-05 23:04:00 +00:00
for ( pFile = Songs ; pFile ; pFile = pFile - > pNext ) if ( pFile - > NoPlay )
if ( WildcardMatch ( szFileName , GetFilename ( pFile - > FileName ) ) | | pFile - > HasCategory ( szFileName ) )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
ASongCount + + ;
2009-08-15 18:50:32 +00:00
pFile - > NoPlay = false ;
2010-03-28 18:58:01 +00:00
}
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// default: all files except the ones beginning with an at ('@')
// Ignore frontend and credits music
2010-03-28 18:58:01 +00:00
for ( pFile = Songs ; pFile ; pFile = pFile - > pNext )
if ( * GetFilename ( pFile - > FileName ) ! = ' @ ' & &
2015-12-12 17:08:45 +00:00
! pFile - > HasCategory ( " frontend " ) & &
! pFile - > HasCategory ( " credits " ) )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
ASongCount + + ;
2009-08-15 18:50:32 +00:00
pFile - > NoPlay = false ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
}
2015-10-01 02:49:47 +00:00
// Force switch of music if currently playing piece is not in list or idle because no music file matched
if ( fForceSwitch )
2014-12-06 17:04:55 +00:00
{
2015-10-01 02:49:47 +00:00
if ( PlayMusicFile )
{
fForceSwitch = PlayMusicFile - > NoPlay ;
}
else
{
fForceSwitch = ( ! is_waiting | | C4TimeMilliseconds : : Now ( ) > = wait_time_end ) ;
}
if ( fForceSwitch )
{
// Switch music. Switching to a break is also allowed, but won't be done if there is a piece to resume
// Otherwise breaks would never occur if the playlist changes often.
2016-11-02 23:58:02 +00:00
Play ( nullptr , false , fadetime_ms , max_resume_time , PlayMusicFile ! = nullptr ) ;
2015-10-01 02:49:47 +00:00
}
2014-12-06 17:04:55 +00:00
}
2015-10-01 02:49:47 +00:00
// Remember setting (e.g. to be saved in savegames)
playlist . Copy ( szPlayList ) ;
2015-12-03 04:08:50 +00:00
playlist_valid = true ; // do not re-calculate available song if playlist is reset to same value in the future
2010-03-28 18:58:01 +00:00
return ASongCount ;
}
2009-05-08 13:28:41 +00:00
bool C4MusicSystem : : ToggleOnOff ( )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// // command key for music toggle pressed
// use different settings for game/menu (lobby also counts as "menu", so go by Game.IsRunning-flag rather than startup)
if ( Game . IsRunning )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// game music
Config . Sound . RXMusic = ! Config . Sound . RXMusic ;
if ( ! Config . Sound . RXMusic ) Stop ( ) ; else Play ( ) ;
2009-06-05 15:14:20 +00:00
: : GraphicsSystem . FlashMessageOnOff ( LoadResStr ( " IDS_CTL_MUSIC " ) , ! ! Config . Sound . RXMusic ) ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// game menu
Config . Sound . FEMusic = ! Config . Sound . FEMusic ;
if ( ! Config . Sound . FEMusic ) Stop ( ) ; else Play ( ) ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// key processed
return true ;
2010-03-28 18:58:01 +00:00
}
2015-10-01 02:49:47 +00:00
void C4MusicSystem : : CompileFunc ( StdCompiler * comp )
{
comp - > Value ( mkNamingAdapt ( playlist , " PlayList " , StdCopyStrBuf ( ) ) ) ;
comp - > Value ( mkNamingAdapt ( game_music_level , " Volume " , 100 ) ) ;
comp - > Value ( mkNamingAdapt ( music_break_min , " MusicBreakMin " , DefaultMusicBreak ) ) ;
comp - > Value ( mkNamingAdapt ( music_break_max , " MusicBreakMax " , DefaultMusicBreak ) ) ;
comp - > Value ( mkNamingAdapt ( music_break_chance , " MusicBreakChance " , DefaultMusicBreakChance ) ) ;
2015-12-04 00:17:14 +00:00
comp - > Value ( mkNamingAdapt ( music_max_position_memory , " MusicMaxPositionMemory " , DefaultMusicMaxPositionMemory ) ) ;
2015-10-01 02:49:47 +00:00
// Wait time is not saved - begin savegame resume with a fresh song!
// Reflect loaded values immediately
2017-03-11 14:05:41 +00:00
if ( comp - > isDeserializer ( ) )
2015-10-01 02:49:47 +00:00
{
SetGameMusicLevel ( game_music_level ) ;
SetPlayList ( playlist . getData ( ) ) ;
}
}
int32_t C4MusicSystem : : SetGameMusicLevel ( int32_t volume_percent )
{
game_music_level = Clamp < int32_t > ( volume_percent , 0 , 500 ) ; // allow max 5x the user setting
UpdateVolume ( ) ;
return game_music_level ;
}
2015-10-01 04:22:43 +00:00
const int32_t C4MusicSystem : : DefaultMusicBreak = 120000 ; // two minutes default music break time
2015-10-04 13:23:15 +00:00
const int32_t C4MusicSystem : : DefaultMusicBreakChance = 50 ; // ...with a 50% chance
2015-12-04 00:17:14 +00:00
const int32_t C4MusicSystem : : DefaultMusicMaxPositionMemory = 420 ; // after this time (in seconds) a piece is no longer continued at the position where it was interrupted