//----------------------------------------------------------------------------- // // Copyright 1993-1996 id Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/ // //----------------------------------------------------------------------------- // // DESCRIPTION: none // //----------------------------------------------------------------------------- /* For code that originates from ZDoom the following applies: ** **--------------------------------------------------------------------------- ** ** 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. **--------------------------------------------------------------------------- ** */ #include #include #ifdef _WIN32 #include #endif #include "i_system.h" #include "i_sound.h" #include "i_music.h" #include "i_cd.h" #include "s_sound.h" #include "s_sndseq.h" #include "s_playlist.h" #include "c_dispatch.h" #include "m_random.h" #include "w_wad.h" #include "p_local.h" #include "doomstat.h" #include "cmdlib.h" #include "v_video.h" #include "v_text.h" #include "a_sharedglobal.h" #include "gstrings.h" #include "gi.h" #include "po_man.h" #include "serializer.h" #include "d_player.h" #include "g_levellocals.h" #include "vm.h" #include "g_game.h" #include "atterm.h" #include "s_music.h" // MACROS ------------------------------------------------------------------ // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- extern float S_GetMusicVolume (const char *music); static void S_ActivatePlayList(bool goBack); // PRIVATE DATA DEFINITIONS ------------------------------------------------ static bool MusicPaused; // whether music is paused MusPlayingInfo mus_playing; // music currently being played static FString LastSong; // last music that was played static FPlayList *PlayList; DEFINE_GLOBAL_NAMED(mus_playing, musplaying); DEFINE_FIELD_X(MusPlayingInfo, MusPlayingInfo, name); DEFINE_FIELD_X(MusPlayingInfo, MusPlayingInfo, baseorder); DEFINE_FIELD_X(MusPlayingInfo, MusPlayingInfo, loop); // PUBLIC DATA DEFINITIONS ------------------------------------------------- void S_ShutdownMusic (); // CODE -------------------------------------------------------------------- //========================================================================== // // S_Init // // Initializes sound stuff, including volume. Sets channels, SFX and // music volume, allocates channel buffer, and sets S_sfx lookup. //========================================================================== void S_InitMusic () { // no sounds are playing, and they are not paused mus_playing.name = ""; LastSong = ""; mus_playing.handle = nullptr; mus_playing.baseorder = 0; MusicPaused = false; atterm(S_ShutdownMusic); } //========================================================================== // // S_Shutdown // //========================================================================== void S_ShutdownMusic () { if (PlayList != nullptr) { delete PlayList; PlayList = nullptr; } S_StopMusic (true); mus_playing.name = ""; LastSong = ""; } //========================================================================== // // S_PauseSound // // Stop music and sound effects, during game PAUSE. //========================================================================== void S_PauseMusic () { if (mus_playing.handle && !MusicPaused) { mus_playing.handle->Pause(); MusicPaused = true; } } //========================================================================== // // S_ResumeSound // // Resume music and sound effects, after game PAUSE. //========================================================================== void S_ResumeMusic () { if (mus_playing.handle && MusicPaused) { mus_playing.handle->Resume(); MusicPaused = false; } } //========================================================================== // // S_UpdateSound // //========================================================================== void S_UpdateMusic () { I_UpdateMusic(); // [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 (mus_playing.handle != nullptr && !mus_playing.handle->IsPlaying() && PlayList) { PlayList->Advance(); S_ActivatePlayList(false); } } //========================================================================== // // S_Start // // Per level startup code. Kills playing sounds at start of level // and starts new music. //========================================================================== void S_StartMusic () { // 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; // Don't start the music if loading a savegame, because the music is stored there. // Don't start the music if revisiting a level in a hub for the same reason. if (!level.IsReentering()) { level.SetMusic(); } } //========================================================================== // // S_ActivatePlayList // // Plays the next song in the playlist. If no songs in the playlist can be // played, then it is deleted. //========================================================================== void S_ActivatePlayList (bool goBack) { int startpos, pos; startpos = pos = PlayList->GetPosition (); S_StopMusic (true); while (!S_ChangeMusic (PlayList->GetSong (pos), 0, false, true)) { pos = goBack ? PlayList->Backup () : PlayList->Advance (); if (pos == startpos) { delete PlayList; PlayList = nullptr; Printf ("Cannot play anything in the playlist.\n"); return; } } } //========================================================================== // // S_ChangeCDMusic // // Starts a CD track as music. //========================================================================== bool S_ChangeCDMusic (int track, unsigned int id, bool looping) { char temp[32]; if (id != 0) { mysnprintf (temp, countof(temp), ",CD,%d,%x", track, id); } else { mysnprintf (temp, countof(temp), ",CD,%d", track); } return S_ChangeMusic (temp, 0, looping); } //========================================================================== // // 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 // // Starts playing a music, possibly looping. // // [RH] If music is a MOD, starts it at position order. If name is of the // format ",CD,,[cd id]" song is a CD track, and if [cd id] is // specified, it will only be played if the specified CD is in a drive. //========================================================================== bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force) { if (!force && PlayList) { // Don't change if a playlist is active return false; } // allow specifying "*" as a placeholder to play the level's default music. if (musicname != nullptr && !strcmp(musicname, "*")) { if (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL) { musicname = level.Music; order = level.musicorder; } else { musicname = nullptr; } } if (musicname == nullptr || musicname[0] == 0) { // Don't choke if the map doesn't have a song attached S_StopMusic (true); mus_playing.name = ""; LastSong = ""; return true; } FString DEH_Music; if (musicname[0] == '$') { // handle dehacked replacement. // Any music name defined this way needs to be prefixed with 'D_' because // Doom.exe does not contain the prefix so these strings don't either. const char * mus_string = GStrings[musicname+1]; if (mus_string != nullptr) { DEH_Music << "D_" << mus_string; musicname = DEH_Music; } } FName *aliasp = MusicAliases.CheckKey(musicname); if (aliasp != nullptr) { if (*aliasp == NAME_None) { return true; // flagged to be ignored } musicname = aliasp->GetChars(); } if (!mus_playing.name.IsEmpty() && mus_playing.handle != nullptr && stricmp (mus_playing.name, musicname) == 0 && mus_playing.handle->m_Looping == looping) { if (order != mus_playing.baseorder) { if (mus_playing.handle->SetSubsong(order)) { mus_playing.baseorder = order; } } else if (!mus_playing.handle->IsPlaying()) { mus_playing.handle->Play(looping, order); } return true; } if (strnicmp (musicname, ",CD,", 4) == 0) { int track = strtoul (musicname+4, nullptr, 0); const char *more = strchr (musicname+4, ','); unsigned int id = 0; if (more != nullptr) { id = strtoul (more+1, nullptr, 16); } S_StopMusic (true); mus_playing.handle = I_RegisterCDSong (track, id); } else { int lumpnum = -1; int length = 0; MusInfo *handle = nullptr; MidiDeviceSetting *devp = MidiDevices.CheckKey(musicname); // Strip off any leading file:// component. if (strncmp(musicname, "file://", 7) == 0) { musicname += 7; } FileReader reader; if (!FileExists (musicname)) { if ((lumpnum = Wads.CheckNumForFullName (musicname, true, ns_music)) == -1) { Printf ("Music \"%s\" not found\n", musicname); return false; } if (handle == nullptr) { if (Wads.LumpLength (lumpnum) == 0) { return false; } reader = Wads.ReopenLumpReader(lumpnum); } } else { // Load an external file. if (!reader.OpenFile(musicname)) { return false; } } // shutdown old music S_StopMusic (true); // Just record it if volume is 0 if (snd_musicvolume <= 0) { mus_playing.loop = looping; mus_playing.name = musicname; mus_playing.baseorder = order; LastSong = musicname; return true; } // load & register it if (handle != nullptr) { mus_playing.handle = handle; } else { mus_playing.handle = I_RegisterSong (reader, devp); } } mus_playing.loop = looping; mus_playing.name = musicname; mus_playing.baseorder = 0; LastSong = ""; if (mus_playing.handle != 0) { // play it mus_playing.handle->Start(looping, S_GetMusicVolume (musicname), order); mus_playing.baseorder = order; return true; } return false; } DEFINE_ACTION_FUNCTION(DObject, S_ChangeMusic) { PARAM_PROLOGUE; PARAM_STRING(music); PARAM_INT(order); PARAM_BOOL(looping); PARAM_BOOL(force); ACTION_RETURN_BOOL(S_ChangeMusic(music, order, looping, force)); } //========================================================================== // // S_RestartMusic // // Must only be called from snd_reset in i_sound.cpp! //========================================================================== void S_RestartMusic () { if (!LastSong.IsEmpty()) { FString song = LastSong; LastSong = ""; S_ChangeMusic (song, mus_playing.baseorder, mus_playing.loop, true); } } //========================================================================== // // S_MIDIDeviceChanged // //========================================================================== void S_MIDIDeviceChanged() { if (mus_playing.handle != nullptr && mus_playing.handle->IsMIDI()) { mus_playing.handle->Stop(); mus_playing.handle->Start(mus_playing.loop, -1, mus_playing.baseorder); } } //========================================================================== // // 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; } //========================================================================== // // S_StopMusic // //========================================================================== void S_StopMusic (bool force) { // [RH] Don't stop if a playlist is active. if ((force || PlayList == nullptr) && !mus_playing.name.IsEmpty()) { if (mus_playing.handle != nullptr) { if (MusicPaused) mus_playing.handle->Resume(); mus_playing.handle->Stop(); delete mus_playing.handle; mus_playing.handle = nullptr; } LastSong = mus_playing.name; mus_playing.name = ""; } } //========================================================================== // // CCMD idmus // //========================================================================== CCMD (idmus) { level_info_t *info; FString map; int l; if (!nomusic) { if (argv.argc() > 1) { if (gameinfo.flags & GI_MAPxx) { l = atoi (argv[1]); if (l <= 99) { map = CalcMapName (0, l); } else { Printf ("%s\n", GStrings("STSTR_NOMUS")); return; } } else { map = CalcMapName (argv[1][0] - '0', argv[1][1] - '0'); } if ( (info = FindLevelInfo (map)) ) { if (info->Music.IsNotEmpty()) { S_ChangeMusic (info->Music, info->musicorder); Printf ("%s\n", GStrings("STSTR_MUS")); } } else { Printf ("%s\n", GStrings("STSTR_NOMUS")); } } } } //========================================================================== // // CCMD changemus // //========================================================================== CCMD (changemus) { if (!nomusic) { if (argv.argc() > 1) { if (PlayList) { delete PlayList; PlayList = nullptr; } 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"); } } } } //========================================================================== // // CCMD stopmus // //========================================================================== CCMD (stopmus) { if (PlayList) { delete PlayList; PlayList = nullptr; } S_StopMusic (false); LastSong = ""; // forget the last played song so that it won't get restarted if some volume changes occur } //========================================================================== // // CCMD cd_play // // Plays a specified track, or the entire CD if no track is specified. //========================================================================== CCMD (cd_play) { char musname[16]; if (argv.argc() == 1) { strcpy (musname, ",CD,"); } else { mysnprintf (musname, countof(musname), ",CD,%d", atoi(argv[1])); } S_ChangeMusic (musname, 0, true); } //========================================================================== // // CCMD cd_stop // //========================================================================== CCMD (cd_stop) { CD_Stop (); } //========================================================================== // // CCMD cd_eject // //========================================================================== CCMD (cd_eject) { CD_Eject (); } //========================================================================== // // CCMD cd_close // //========================================================================== CCMD (cd_close) { CD_UnEject (); } //========================================================================== // // CCMD cd_pause // //========================================================================== CCMD (cd_pause) { CD_Pause (); } //========================================================================== // // CCMD cd_resume // //========================================================================== CCMD (cd_resume) { CD_Resume (); } //========================================================================== // // CCMD playlist // //========================================================================== UNSAFE_CCMD (playlist) { int argc = argv.argc(); if (argc < 2 || argc > 3) { Printf ("playlist [|shuffle]\n"); } else { if (PlayList != nullptr) { PlayList->ChangeList (argv[1]); } else { PlayList = new FPlayList (argv[1]); } if (PlayList->GetNumSongs () == 0) { delete PlayList; PlayList = nullptr; } else { if (argc == 3) { if (stricmp (argv[2], "shuffle") == 0) { PlayList->Shuffle (); } else { PlayList->SetPosition (atoi (argv[2])); } } S_ActivatePlayList (false); } } } //========================================================================== // // CCMD playlistpos // //========================================================================== static bool CheckForPlaylist () { if (PlayList == nullptr) { Printf ("No playlist is playing.\n"); return false; } return true; } CCMD (playlistpos) { if (CheckForPlaylist() && argv.argc() > 1) { PlayList->SetPosition (atoi (argv[1]) - 1); S_ActivatePlayList (false); } } //========================================================================== // // CCMD playlistnext // //========================================================================== CCMD (playlistnext) { if (CheckForPlaylist()) { PlayList->Advance (); S_ActivatePlayList (false); } } //========================================================================== // // CCMD playlistprev // //========================================================================== CCMD (playlistprev) { if (CheckForPlaylist()) { PlayList->Backup (); S_ActivatePlayList (true); } } //========================================================================== // // CCMD playliststatus // //========================================================================== CCMD (playliststatus) { if (CheckForPlaylist ()) { Printf ("Song %d of %d:\n%s\n", PlayList->GetPosition () + 1, PlayList->GetNumSongs (), PlayList->GetSong (PlayList->GetPosition ())); } } //========================================================================== // // // //========================================================================== CCMD(currentmusic) { if (mus_playing.name.IsNotEmpty()) { Printf("Currently playing music '%s'\n", mus_playing.name.GetChars()); } else { Printf("Currently no music playing\n"); } }