openclonk/engine/src/C4MusicSystem.cpp

577 lines
13 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, 2007 Matthes Bender
* Copyright (c) 2001, 2005, 2007 Sven Eberhardt
* Copyright (c) 2001 Carlo Teubner
* Copyright (c) 2001 Michael Käser
* Copyright (c) 2002-2003 Peter Wortmann
* Copyright (c) 2005-2006, 2008 Günther Brammer
* Copyright (c) 2009 Nicolas Hake
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
/* Handles Music.c4g and randomly plays songs */
#include <C4Include.h>
#include <C4MusicSystem.h>
#ifndef BIG_C4INCLUDE
#include <C4MusicFile.h>
#include <C4Application.h>
#include <C4Random.h>
#include <C4Log.h>
#include <C4Game.h>
#include <C4Wrappers.h>
#endif
#include <Midi.h>
#ifdef USE_FMOD
#include <fmod_errors.h>
#endif
#ifdef HAVE_LIBSDL_MIXER
#include <SDL.h>
#endif
// helper
const char *SGetRelativePath(const char *strPath)
{
const char *strEXEPath = Config.AtExePath("");
while(*strEXEPath == *strPath) { strEXEPath++; strPath++; }
return strPath;
}
C4MusicSystem::C4MusicSystem():
PlayMusicFile(NULL),
Songs(NULL),
SongCount(0),
Volume(100)
{
}
C4MusicSystem::~C4MusicSystem()
{
Clear();
}
bool C4MusicSystem::InitializeMOD()
{
#ifdef USE_FMOD
#ifdef _WIN32
// Debug code
switch(Config.Sound.FMMode)
{
case 0:
FSOUND_SetOutput(FSOUND_OUTPUT_WINMM);
break;
case 1:
FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
FSOUND_SetHWND(Application.pWindow->hWindow);
break;
case 2:
FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
FSOUND_SetDriver(0);
break;
}
FSOUND_SetMixer(FSOUND_MIXER_QUALITY_AUTODETECT);
#endif
if (FSOUND_GetVersion() < FMOD_VERSION)
{
LogF("FMod: You are using the wrong DLL version! You should be using %.02f", FMOD_VERSION);
return FALSE;
}
if (!FSOUND_Init(44100, 32, 0))
{
LogF("FMod: %s", FMOD_ErrorString(FSOUND_GetError()));
return FALSE;
}
// ok
MODInitialized = TRUE;
return TRUE;
#endif
#ifdef HAVE_LIBSDL_MIXER
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)",
link_version->major, link_version->minor, link_version->patch,
compile_version.major, compile_version.minor, compile_version.patch);
if (!SDL_WasInit(SDL_INIT_AUDIO) && SDL_Init(SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE))
{
LogF("SDL: %s", SDL_GetError());
return false;
}
//frequency, format, stereo, chunksize
if(Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 1024))
{
LogF("SDL_mixer: %s", SDL_GetError());
return false;
}
MODInitialized = true;
return true;
#endif
return false;
}
void C4MusicSystem::DeinitializeMOD()
{
#ifdef USE_FMOD
FSOUND_StopSound(FSOUND_ALL); /* to prevent some hangs in FMOD */
#ifdef DEBUG
Sleep(0);
#endif
FSOUND_Close();
#endif
#ifdef HAVE_LIBSDL_MIXER
Mix_CloseAudio();
SDL_Quit();
#endif
MODInitialized = FALSE;
}
bool C4MusicSystem::Init(const char * PlayList)
{
// init mod
if(!MODInitialized && !InitializeMOD()) return false;
// 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
if (PlayList) SetPlayList(PlayList); else SetPlayList(0);
// set initial volume
SetVolume(Config.Sound.MusicVolume);
// ok
return true;
}
bool C4MusicSystem::InitForScenario(C4Group & hGroup)
{
// 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
LogF(LoadResStr("IDS_PRC_LOCALMUSIC"), SGetRelativePath(MusicDir.getData()));
}
// check for music folders in group set
C4Group *pMusicFolder = NULL;
while(pMusicFolder = Game.GroupSet.FindGroup(C4GSCnt_Music, pMusicFolder))
{
if (!fLocalMusic)
{
// clear global songs
ClearSongs();
fLocalMusic = true;
}
// add songs
MusicDir.Take(pMusicFolder->GetFullName());
MusicDir.AppendChar(DirectorySeparator);
MusicDir.Append(C4CFN_Music);
LoadDir(MusicDir.getData());
// log
LogF(LoadResStr("IDS_PRC_LOCALMUSIC"), SGetRelativePath(MusicDir.getData()));
}
// no music?
if(!SongCount) return false;
// set play list
SetPlayList(0);
// ok
return true;
}
void C4MusicSystem::Load(const char *szFile)
{
// safety
if (!szFile || !*szFile) return;
C4MusicFile *NewSong=NULL;
// get extension
#ifdef USE_FMOD
const char *szExt = GetExtension(szFile);
// get type
switch (GetMusicFileTypeByExtension(GetExtension(szFile)))
{
case MUSICTYPE_MOD:
if (MODInitialized) NewSong = new C4MusicFileMOD;
break;
case MUSICTYPE_MP3:
if (MODInitialized) NewSong = new C4MusicFileMP3;
break;
case MUSICTYPE_OGG:
if (MODInitialized) NewSong = new C4MusicFileOgg;
break;
case MUSICTYPE_MID:
if (MODInitialized)
NewSong = new C4MusicFileMID;
break;
}
#endif
#ifdef HAVE_LIBSDL_MIXER
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;
while(pCurr && pCurr->pNext) pCurr = pCurr->pNext;
if(pCurr) pCurr->pNext = NewSong; else Songs = NewSong;
NewSong->pNext = NULL;
// count songs
SongCount++;
}
void C4MusicSystem::LoadDir(const char *szPath)
{
char Path[_MAX_FNAME + 1], File[_MAX_FNAME + 1];
C4Group *pDirGroup = NULL;
// split path
SCopy(szPath, Path, _MAX_FNAME);
char *pFileName = GetFilename(Path);
SCopy(pFileName, File);
*(pFileName - 1) = 0;
// no file name?
if(!File[0])
// -> add the whole directory
SCopy("*", File);
// no wildcard match?
else if(!SSearch(File, "*?"))
{
// then it's either a file or a directory - do the test with C4Group
pDirGroup = new C4Group();
if(!pDirGroup->Open(szPath))
{
// so it must be a file
if(!pDirGroup->Open(Path))
{
// -> file/dir doesn't exist
LogF("Music File not found: %s", szPath);
delete pDirGroup;
return;
}
// mother group is open... proceed with normal handling
}
else
{
// ok, set wildcard (load the whole directory)
SCopy(szPath, Path);
SCopy("*", File);
}
}
// open directory group, if not already done so
if(!pDirGroup)
{
pDirGroup = new C4Group();
if(!pDirGroup->Open(Path))
{
LogF("Music File not found: %s", szPath);
delete pDirGroup;
return;
}
}
// search file(s)
char szFile[_MAX_FNAME + 1];
pDirGroup->ResetSearch();
while(pDirGroup->FindNextEntry(File, szFile))
{
char strFullPath[_MAX_FNAME + 1];
sprintf(strFullPath, "%s%c%s", Path, DirectorySeparator, szFile);
Load(strFullPath);
}
// free it
delete pDirGroup;
}
void C4MusicSystem::LoadMoreMusic()
{
BYTE *szMoreMusic;
CStdFile MoreMusicFile;
// load MoreMusic.txt
if(!MoreMusicFile.Load(Config.AtUserDataPath(C4CFN_MoreMusic), &szMoreMusic, NULL, 1)) return;
// read contents
char *pPos = reinterpret_cast<char *>(szMoreMusic);
while(pPos && *pPos)
{
// get line
char szLine[1024 + 1];
SCopyUntil(pPos, szLine, '\n', 1024);
pPos = strchr(pPos, '\n'); if(pPos) pPos++;
// remove leading whitespace
char *pLine = szLine;
while(*pLine == ' ' || *pLine == '\t' || *pLine == '\r') pLine++;
// and whitespace at end
char *p = pLine + strlen(pLine) - 1;
while(*p == ' ' || *p == '\t' || *p == '\r') { *p = 0; --p; }
// comment?
if(*pLine == '#')
{
// might be a "directive"
if(SEqual(pLine, "#clear"))
ClearSongs();
continue;
}
// try to load file(s)
LoadDir(pLine);
}
delete [] szMoreMusic;
}
void C4MusicSystem::ClearSongs()
{
Stop();
while(Songs)
{
C4MusicFile *pFile = Songs;
Songs = pFile->pNext;
delete pFile;
}
SongCount = 0;
}
void C4MusicSystem::Clear()
{
#ifdef HAVE_LIBSDL_MIXER
// Stop a fadeout
Mix_HaltMusic();
#endif
ClearSongs();
if(MODInitialized) { DeinitializeMOD(); }
}
void C4MusicSystem::Execute()
{
#ifndef HAVE_LIBSDL_MIXER
if (!Tick35)
#endif
if (Game.IsRunning ? Config.Sound.RXMusic : Config.Sound.FEMusic)
if (!PlayMusicFile)
Play();
else
PlayMusicFile->CheckIfPlaying();
}
bool C4MusicSystem::Play(const char *szSongname, bool fLoop)
{
C4MusicFile* NewFile = NULL;
// Specified song name
if (szSongname && szSongname[0])
{
// Search in list
for (NewFile=Songs; NewFile; NewFile = NewFile->pNext)
{
char songname[_MAX_FNAME+1];
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;
}
}
// Random song
else
{
// try to find random song
for (int i = 0; i <= 1000; i++)
{
int nmb = SafeRandom(Max(ASongCount / 2 + ASongCount % 2, ASongCount - SCounter));
int j;
for(j = 0, NewFile = Songs; NewFile; NewFile = NewFile->pNext)
if(!NewFile->NoPlay)
if(NewFile->LastPlayed == -1 || NewFile->LastPlayed < SCounter - ASongCount / 2)
{
j++;
if(j > nmb) break;
}
if (NewFile) break;
}
}
// File found?
if (!NewFile)
return FALSE;
// Stop old music
Stop();
LogF(LoadResStr("IDS_PRC_PLAYMUSIC"), GetFilename(NewFile->FileName));
// Play new song
if(!NewFile->Play(fLoop)) return FALSE;
PlayMusicFile = NewFile;
NewFile->LastPlayed = SCounter++;
Loop = fLoop;
// Set volume
PlayMusicFile->SetVolume(Volume);
return TRUE;
}
void C4MusicSystem::NotifySuccess()
{
// nothing played?
if (!PlayMusicFile) return;
// loop?
if (Loop)
if (PlayMusicFile->Play())
return;
// stop
Stop();
}
void C4MusicSystem::FadeOut(int fadeout_ms)
{
if(PlayMusicFile)
{
PlayMusicFile->Stop(fadeout_ms);
}
}
bool C4MusicSystem::Stop()
{
if(PlayMusicFile)
{
PlayMusicFile->Stop();
PlayMusicFile=NULL;
}
return true;
}
int C4MusicSystem::SetVolume(int iLevel)
{
if(iLevel > 100) iLevel = 100;
if(iLevel < 0) iLevel = 0;
// Save volume for next file
Volume = iLevel;
// Tell it to the act file
if(PlayMusicFile)
PlayMusicFile->SetVolume(iLevel);
return iLevel;
}
MusicType GetMusicFileTypeByExtension(const char* ext)
{
if (SEqualNoCase(ext, "mid"))
return MUSICTYPE_MID;
#if defined USE_FMOD || defined HAVE_LIBSDL_MIXER
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;
}
bool C4MusicSystem::GrpContainsMusic(C4Group &rGrp)
{
// search for known file extensions
return rGrp.FindEntry("*.mid")
#ifdef USE_MP3
|| rGrp.FindEntry("*.mp3")
#endif
|| rGrp.FindEntry("*.xm")
|| rGrp.FindEntry("*.it")
|| rGrp.FindEntry("*.s3m")
|| rGrp.FindEntry("*.mod")
|| rGrp.FindEntry("*.ogg");
}
int C4MusicSystem::SetPlayList(const char *szPlayList)
{
// copy
if(!szPlayList) szPlayList = "";
// reset
C4MusicFile *pFile;
for(pFile = Songs; pFile; pFile = pFile->pNext)
{
pFile->NoPlay = TRUE;
pFile->LastPlayed = -1;
}
ASongCount = 0;
SCounter = 0;
if(*szPlayList)
{
// match
char szFileName[_MAX_FNAME + 1];
for(int cnt = 0; SGetModule(szPlayList, cnt, szFileName, _MAX_FNAME); cnt++)
for(pFile = Songs; pFile; pFile = pFile->pNext)
if(pFile->NoPlay && WildcardMatch(szFileName, GetFilename(pFile->FileName)))
{
ASongCount++;
pFile->NoPlay = FALSE;
}
}
else
{
// default: all files except the ones beginning with an at ('@')
// Ignore frontend and credits music
for(pFile = Songs; pFile; pFile = pFile->pNext)
if(*GetFilename(pFile->FileName) != '@' &&
!SEqual2(GetFilename(pFile->FileName), "Credits.") &&
!SEqual2(GetFilename(pFile->FileName), "Frontend."))
{
ASongCount++;
pFile->NoPlay = FALSE;
}
}
return ASongCount;
}
bool C4MusicSystem::ToggleOnOff()
{
// // 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)
{
// game music
Config.Sound.RXMusic = !Config.Sound.RXMusic;
if (!Config.Sound.RXMusic) Stop(); else Play();
::GraphicsSystem.FlashMessageOnOff(LoadResStr("IDS_CTL_MUSIC"), !!Config.Sound.RXMusic);
}
else
{
// game menu
Config.Sound.FEMusic = !Config.Sound.FEMusic;
if (!Config.Sound.FEMusic) Stop(); else Play();
}
// key processed
return true;
}