diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index caac5bdbb..9e9dc4b59 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -698,6 +698,7 @@ set (PCH_SOURCES core/quotes.cpp core/screenshot.cpp core/serializer.cpp + core/raze_music.cpp core/2d/v_2ddrawer.cpp core/2d/v_draw.cpp diff --git a/source/blood/src/loadsave.cpp b/source/blood/src/loadsave.cpp index 78313ea37..7d2229ea2 100644 --- a/source/blood/src/loadsave.cpp +++ b/source/blood/src/loadsave.cpp @@ -48,7 +48,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "nnexts.h" #include "savegamehelp.h" -#include "z_music.h" +#include "raze_music.h" #include "mapinfo.h" #include "aibat.h" diff --git a/source/blood/src/sound.cpp b/source/blood/src/sound.cpp index e8dc02298..9b491d525 100644 --- a/source/blood/src/sound.cpp +++ b/source/blood/src/sound.cpp @@ -30,7 +30,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "resource.h" #include "sound.h" #include "baselayer.h" -#include "z_music.h" +#include "raze_music.h" #include "sfx.h" #include "sound/s_soundinternal.h" diff --git a/source/blood/src/sound.h b/source/blood/src/sound.h index c5aa22d1a..7969768d1 100644 --- a/source/blood/src/sound.h +++ b/source/blood/src/sound.h @@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #pragma once #include "resource.h" -#include "z_music.h" +#include "raze_music.h" BEGIN_BLD_NS diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp index f2a19ce5d..0fba34a41 100644 --- a/source/core/gamecontrol.cpp +++ b/source/core/gamecontrol.cpp @@ -39,7 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "c_console.h" #include "c_dispatch.h" #include "i_specialpaths.h" -#include "z_music.h" +#include "raze_music.h" #include "statistics.h" #include "menu.h" #include "gstrings.h" @@ -96,6 +96,8 @@ void InitFileSystem(TArray&); void I_SetWindowTitle(const char* caption); void InitENet(); void ShutdownENet(); +void S_ParseSndInfo(); + bool AppActive; int chatmodeon; // needed by the common console code. @@ -273,9 +275,7 @@ void UserConfig::ProcessOptions() AddArt.reset(Args->GatherFiles("-art")); nologo = Args->CheckParm("-nologo") || Args->CheckParm("-quick"); - nomusic = Args->CheckParm("-nomusic"); - nosound = Args->CheckParm("-nosfx"); - if (Args->CheckParm("-nosound")) nomusic = nosound = true; + nosound = Args->CheckParm("-nosfx") || Args->CheckParm("-nosound"); if (Args->CheckParm("-setup")) queryiwad = 1; else if (Args->CheckParm("-nosetup")) queryiwad = 0; @@ -714,7 +714,10 @@ int RunGame() V_InitFonts(); C_CON_SetAliases(); sfx_empty = fileSystem.FindFile("engine/dsempty.lmp"); // this must be done outside the sound code because it's initialized late. - Mus_Init(); + I_InitSound(); + Mus_InitMusic(); + timerSetCallback(Mus_UpdateMusic); + S_ParseSndInfo(); InitStatistics(); M_Init(); SetDefaultStrings(); diff --git a/source/core/gamecontrol.h b/source/core/gamecontrol.h index 2c0ae6a11..85ab1a24e 100644 --- a/source/core/gamecontrol.h +++ b/source/core/gamecontrol.h @@ -63,7 +63,7 @@ struct UserConfig bool nomonsters = false; bool nosound = false; - bool nomusic = false; + //bool nomusic = false; bool nologo = false; int setupstate = -1; @@ -77,9 +77,10 @@ struct UserConfig extern UserConfig userConfig; +extern int nomusic; inline bool MusicEnabled() { - return !userConfig.nomusic; + return !nomusic; } inline bool SoundEnabled() diff --git a/source/core/gamecvars.cpp b/source/core/gamecvars.cpp index a3d7834fe..2f38c7a1b 100644 --- a/source/core/gamecvars.cpp +++ b/source/core/gamecvars.cpp @@ -42,7 +42,7 @@ #include "m_argv.h" #include "rts.h" #include "stats.h" -#include "z_music.h" +#include "raze_music.h" #include "c_dispatch.h" #include "gstrings.h" #include "quotemgr.h" diff --git a/source/core/music/i_music.cpp b/source/core/music/i_music.cpp index 1e9479672..b73c630ca 100644 --- a/source/core/music/i_music.cpp +++ b/source/core/music/i_music.cpp @@ -39,6 +39,7 @@ #include +#include #include "m_argv.h" #include "filesystem.h" #include "c_dispatch.h" @@ -48,69 +49,28 @@ #include "c_cvars.h" #include "c_console.h" #include "v_text.h" +#include "i_sound.h" #include "i_soundfont.h" #include "s_music.h" -#include "printf.h" -#include "timer.h" -#include "backend/i_sound.h" -#include +#include "filereadermusicinterface.h" void I_InitSoundFonts(); -void S_SetStreamVolume(float); -void S_ParseSndInfo(); EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Int, snd_mididevice) EXTERN_CVAR(Float, snd_mastervolume) - -float relative_volume = 1.f, saved_relative_volume = 1.f; - -#ifdef _WIN32 +int nomusic = 0; //========================================================================== // -// CVAR: cd_drive -// -// Which drive (letter) to use for CD audio. If not a valid drive letter, -// let the operating system decide for us. -// -//========================================================================== -EXTERN_CVAR(Bool, cd_enabled); - -CUSTOM_CVAR(String, cd_drive, "", CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) -{ - if (cd_enabled && !Args->CheckParm("-nocdaudio")) CD_Enable(self); -} - -//========================================================================== -// -// CVAR: cd_enabled -// -// Use the CD device? Can be overridden with -nocdaudio on the command line -// -//========================================================================== - -CUSTOM_CVAR(Bool, cd_enabled, true, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) -{ - if (self && !Args->CheckParm("-nocdaudio")) - CD_Enable(cd_drive); - else - CD_Enable(nullptr); -} - - -#endif - -//========================================================================== -// -// CVAR mus_volume +// CVAR snd_musicvolume // // Maximum volume of MOD/stream music. //========================================================================== -CUSTOM_CVARD(Float, mus_volume, 0.5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "controls music volume") +CUSTOM_CVARD(Float, snd_musicvolume, 0.5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "controls music volume") { if (self < 0.f) self = 0.f; @@ -202,20 +162,68 @@ static void mus_sfclose(void* handle) reinterpret_cast(handle)->close(); } - +#ifndef ZMUSIC_LITE //========================================================================== // -// +// Pass some basic working data to the music backend +// We do this once at startup for everything available. // //========================================================================== -void Mus_Init(void) +static void SetupGenMidi() { - I_InitSound(); - I_InitSoundFonts(); - S_ParseSndInfo(); + // The OPL renderer should not care about where this comes from. + // Note: No I_Error here - this needs to be consistent with the rest of the music code. + auto lump = fileSystem.CheckNumForName("GENMIDI", ns_global); + if (lump < 0) + { + Printf("No GENMIDI lump found. OPL playback not available.\n"); + return; + } + auto data = fileSystem.OpenFileReader(lump); - mus_volume.Callback (); + auto genmidi = data.Read(); + if (genmidi.Size() < 8 + 175 * 36 || memcmp(genmidi.Data(), "#OPL_II#", 8)) return; + ZMusic_SetGenMidi(genmidi.Data()+8); +} + +static void SetupWgOpn() +{ + int lump = fileSystem.CheckNumForFullName("xg.wopn"); + if (lump < 0) + { + return; + } + FileData data = fileSystem.ReadFile(lump); + ZMusic_SetWgOpn(data.GetMem(), (uint32_t)data.GetSize()); +} + +static void SetupDMXGUS() +{ + int lump = fileSystem.CheckNumForFullName("DMXGUS"); + if (lump < 0) + { + return; + } + FileData data = fileSystem.ReadFile(lump); + ZMusic_SetDmxGus(data.GetMem(), (uint32_t)data.GetSize()); +} + +#endif + +//========================================================================== +// +// +// +//========================================================================== + +void I_InitMusic(void) +{ + I_InitSoundFonts(); + + snd_musicvolume.Callback (); + + nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound"); snd_mididevice.Callback(); @@ -230,7 +238,11 @@ void Mus_Init(void) callbacks.SF_Close = mus_sfclose; ZMusic_SetCallbacks(&callbacks); - timerSetCallback(S_UpdateMusic); +#ifndef ZMUSIC_LITE + SetupGenMidi(); + SetupDMXGUS(); + SetupWgOpn(); +#endif } @@ -244,7 +256,7 @@ void I_SetRelativeVolume(float vol) { relative_volume = (float)vol; ChangeMusicSetting(zmusic_relative_volume, nullptr, (float)vol); - mus_volume.Callback(); + snd_musicvolume.Callback(); } //========================================================================== // @@ -300,7 +312,8 @@ static ZMusic_MidiSource GetMIDISource(const char *fn) FString src = fn; if (src.Compare("*") == 0) src = mus_playing.name; - auto lump = fileSystem.FindFile(src); + auto lump = fileSystem.CheckNumForName(src, ns_music); + if (lump < 0) lump = fileSystem.CheckNumForFullName(src); if (lump < 0) { Printf("Cannot find MIDI lump %s.\n", src.GetChars()); @@ -344,7 +357,7 @@ static ZMusic_MidiSource GetMIDISource(const char *fn) // //========================================================================== -CCMD (writewave) +UNSAFE_CCMD (writewave) { if (argv.argc() >= 3 && argv.argc() <= 7) { @@ -352,18 +365,23 @@ CCMD (writewave) if (source == nullptr) return; EMidiDevice dev = MDEV_DEFAULT; - +#ifndef ZMUSIC_LITE if (argv.argc() >= 6) { - if (!stricmp(argv[5], "Timidity") || !stricmp(argv[5], "Timidity++")) dev = MDEV_TIMIDITY; + if (!stricmp(argv[5], "WildMidi")) dev = MDEV_WILDMIDI; + else if (!stricmp(argv[5], "GUS")) dev = MDEV_GUS; + else if (!stricmp(argv[5], "Timidity") || !stricmp(argv[5], "Timidity++")) dev = MDEV_TIMIDITY; else if (!stricmp(argv[5], "FluidSynth")) dev = MDEV_FLUIDSYNTH; else if (!stricmp(argv[5], "OPL")) dev = MDEV_OPL; + else if (!stricmp(argv[5], "OPN")) dev = MDEV_OPN; + else if (!stricmp(argv[5], "ADL")) dev = MDEV_ADL; else { Printf("%s: Unknown MIDI device\n", argv[5]); return; } } +#endif // We must stop the currently playing music to avoid interference between two synths. auto savedsong = mus_playing; S_StopMusic(true); @@ -392,7 +410,7 @@ CCMD (writewave) // //========================================================================== -CCMD(writemidi) +UNSAFE_CCMD(writemidi) { if (argv.argc() != 3) { diff --git a/source/core/music/i_music.h b/source/core/music/i_music.h index 5574f58c8..b552f5c23 100644 --- a/source/core/music/i_music.h +++ b/source/core/music/i_music.h @@ -34,14 +34,26 @@ #ifndef __I_MUSIC_H__ #define __I_MUSIC_H__ +#include "c_cvars.h" + class FileReader; +struct FOptionValues; // // MUSIC I/O // +void I_InitMusic (); +void I_BuildMIDIMenuList (FOptionValues *); // Volume. void I_SetRelativeVolume(float); void I_SetMusicVolume (double volume); + +extern int nomusic; + +EXTERN_CVAR(Bool, mus_enabled) +EXTERN_CVAR(Float, snd_musicvolume) + + #endif //__I_MUSIC_H__ diff --git a/source/core/music/i_soundfont.cpp b/source/core/music/i_soundfont.cpp index 7ecb354b2..a8b4e9fdf 100644 --- a/source/core/music/i_soundfont.cpp +++ b/source/core/music/i_soundfont.cpp @@ -35,7 +35,9 @@ #include #include #include "i_soundfont.h" +#include "i_soundinternal.h" #include "cmdlib.h" +#include "i_system.h" #include "gameconfigfile.h" #include "filereadermusicinterface.h" #include @@ -292,7 +294,7 @@ FileReader FPatchSetReader::OpenFile(const char *name) FLumpPatchSetReader::FLumpPatchSetReader(const char *filename) { - mLumpIndex = fileSystem.FindFile(filename); + mLumpIndex = fileSystem.CheckNumForFullName(filename); mBasePath = filename; FixPathSeperator(mBasePath); @@ -310,7 +312,7 @@ FileReader FLumpPatchSetReader::OpenFile(const char *name) FString path; if (IsAbsPath(name)) return FileReader(); // no absolute paths in the lump directory. path = mBasePath + name; - auto index = fileSystem.FindFile(path); + auto index = fileSystem.CheckNumForFullName(path); if (index < 0) return FileReader(); return fileSystem.ReopenFileReader(index); } @@ -470,7 +472,7 @@ FSoundFontReader *FSoundFontManager::OpenSoundFont(const char *name, int allowed if (name != nullptr) { const char *p = name + strlen(name) - 4; - if (p > name && !stricmp(p, ".cfg") && fileSystem.FindFile(name) >= 0) + if (p > name && !stricmp(p, ".cfg") && fileSystem.CheckNumForFullName(name) >= 0) { return new FLumpPatchSetReader(name); } diff --git a/source/core/music/music.cpp b/source/core/music/music.cpp index 8c08beb6c..ae0174f10 100644 --- a/source/core/music/music.cpp +++ b/source/core/music/music.cpp @@ -2,7 +2,7 @@ ** ** music.cpp ** -** music engine - borrowed from GZDoom +** music engine ** ** Copyright 1999-2016 Randy Heit ** Copyright 2002-2016 Christoph Oelckers @@ -35,51 +35,35 @@ ** */ -#include -#include "z_music.h" -#include "zstring.h" -#include "backend/i_sound.h" -#include "name.h" -#include "s_music.h" +#include +#include + + +#include "i_sound.h" #include "i_music.h" #include "printf.h" -#include "files.h" +#include "s_playlist.h" +#include "c_dispatch.h" #include "filesystem.h" #include "cmdlib.h" -#include "gamecvars.h" -#include "c_dispatch.h" -#include "gamecontrol.h" +#include "s_music.h" #include "filereadermusicinterface.h" -#include "v_text.h" -#include "mapinfo.h" -#include "serializer.h" +#include -MusPlayingInfo mus_playing; -MusicAliasMap MusicAliases; -MidiDeviceMap MidiDevices; +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; -MusicAliasMap LevelMusicAliases; -bool MusicPaused; -static bool mus_blocked; -static FString lastStartedMusic; -EXTERN_CVAR(Float, mus_volume) -CVAR(Bool, printmusicinfo, false, 0) -CVAR(Bool, mus_extendedlookup, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +MidiDeviceMap MidiDevices; +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- -// Order is: streaming formats, module formats, emulated formats and MIDI formats - for external files the first one found wins so ambiguous names should be avoided -static const char* knownMusicExts[] = { - "OGG", "FLAC", "MP3", "MP2", "XA", "XM", "MOD", - "IT", "S3M", "MTM", "STM", "669", "PTM", "AMF", - "OKT", "DSM", "AMFF", "SPC", "VGM", "VGZ", "AY", - "GBS", "GYM", "HES", "KSS", "NSF", "NSFE", "SAP", - "MID", "HMP", "HMI", "XMI", "VOC" -}; +extern float S_GetMusicVolume (const char *music); +static void S_ActivatePlayList(bool goBack); -FString G_SetupFilenameBasedMusic(const char* fn, const char* defmusic) -{ - FString name = StripExtension(fn); - FString test; +// PRIVATE DATA DEFINITIONS ------------------------------------------------ // Test if a real file with this name exists with all known extensions for music. for (auto& ext : knownMusicExts) @@ -94,77 +78,25 @@ FString G_SetupFilenameBasedMusic(const char* fn, const char* defmusic) return defmusic; } -FString MusicFileExists(const char* fn) + +static FileReader DefaultOpenMusic(const char* fn) { - if (mus_extendedlookup) return G_SetupFilenameBasedMusic(fn, nullptr); - if (FileExists(fn)) return fn; - return FString(); + // This is the minimum needed to make the music system functional. + FileReader fr; + fr.OpenFile(fn); + return fr; } +static MusicCallbacks mus_cb = { nullptr, DefaultOpenMusic }; -int LookupMusicLump(const char* fn) + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// CODE -------------------------------------------------------------------- + +void S_SetMusicCallbacks(MusicCallbacks* cb) { - if (mus_extendedlookup) - { - FString name = StripExtension(fn); - int l = fileSystem.FindFileWithExtensions(name, knownMusicExts, countof(knownMusicExts)); - if (l >= 0) return l; - } - return fileSystem.FindFile(fn); -} - -//========================================================================== -// -// Music lookup in various places. -// -//========================================================================== - -FileReader LookupMusic(const char* musicname) -{ - FileReader reader; - FString mus = MusicFileExists(musicname); - if (mus.IsNotEmpty()) - { - // Load an external file. - reader.OpenFile(mus); - } - if (!reader.isOpen()) - { - int lumpnum = LookupMusicLump(musicname); - if (mus_extendedlookup && lumpnum >= 0) - { - // EDuke also looks in a subfolder named after the main game resource. Do this as well if extended lookup is active. - auto rfn = fileSystem.GetResourceFileName(fileSystem.GetFileContainer(lumpnum)); - auto rfbase = ExtractFileBase(rfn); - FStringf aliasMusicname("music/%s/%s", rfbase.GetChars(), musicname); - lumpnum = LookupMusicLump(aliasMusicname); - } - if (lumpnum == -1) - { - // Always look in the 'music' subfolder as well. This gets used by multiple setups to store ripped CD tracks. - FStringf aliasMusicname("music/%s", musicname); - lumpnum = LookupMusicLump(aliasMusicname); - } - if (lumpnum == -1 && (g_gameType & GAMEFLAG_SW)) - { - // Some Shadow Warrioe distributions have the music in a subfolder named 'classic'. Check that, too. - FStringf aliasMusicname("classic/music/%s", musicname); - lumpnum = fileSystem.FindFile(aliasMusicname); - } - if (lumpnum > -1) - { - if (fileSystem.FileLength(lumpnum) >= 0) - { - reader = fileSystem.ReopenFileReader(lumpnum); - if (!reader.isOpen()) - { - Printf(TEXTCOLOR_RED "Unable to play music " TEXTCOLOR_WHITE "\"%s\"\n", musicname); - } - else if (printmusicinfo) Printf("Playing music from file system %s:%s\n", fileSystem.GetResourceFileFullName(fileSystem.GetFileContainer(lumpnum)), fileSystem.GetFileFullPath(lumpnum).GetChars()); - } - } - } - else if (printmusicinfo) Printf("Playing music from external file %s\n", musicname); - return reader; + mus_cb = *cb; + if (mus_cb.OpenMusic == nullptr) mus_cb.OpenMusic = DefaultOpenMusic; // without this we are dead in the water. } //========================================================================== @@ -240,7 +172,7 @@ static bool S_StartMusicPlaying(ZMusic_MusicStream song, bool loop, float rel_vo } // Notify the sound system of the changed relative volume - mus_volume.Callback(); + snd_musicvolume.Callback(); return true; } @@ -287,7 +219,6 @@ void S_ResumeMusic () void S_UpdateMusic () { - mus_blocked = false; if (mus_playing.handle != nullptr) { ZMusic_Update(mus_playing.handle); @@ -297,31 +228,61 @@ void S_UpdateMusic () // playlist when the current song finishes. if (!ZMusic_IsPlaying(mus_playing.handle)) { + if (PlayList.GetNumSongs()) + { + PlayList.Advance(); + S_ActivatePlayList(false); + } + else + { S_StopMusic(true); } } + } } //========================================================================== // -// S_ChangeCDMusic +// Resets the music player if music playback was paused. // -// Starts a CD track as music. //========================================================================== -bool S_ChangeCDMusic (int track, unsigned int id, bool looping) +void S_ResetMusic () { - char temp[32]; + // 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); - if (id != 0) + // start new music for the level + MusicPaused = false; +} + + +//========================================================================== +// +// 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)) { - mysnprintf (temp, countof(temp), ",CD,%d,%x", track, id); - } - else + pos = goBack ? PlayList.Backup () : PlayList.Advance (); + if (pos == startpos) { - mysnprintf (temp, countof(temp), ",CD,%d", track); + PlayList.Clear(); + Printf ("Cannot play anything in the playlist.\n"); + return; + } } - return S_ChangeMusic (temp, 0, looping); } //========================================================================== @@ -340,16 +301,26 @@ bool S_StartMusic (const char *m_id) // // S_ChangeMusic // -// Starts playing a music, possibly looping. +// initiates playback of a song // -// [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) { - lastStartedMusic = musicname; // remember the last piece of music that was requested to be played. + if (nomusic) return false; // skip the entire procedure if music is globally disabled. + + if (!force && PlayList.GetNumSongs()) + { // Don't change if a playlist is active + return false; + } + // Do game specific lookup. + FString musicname_; + if (mus_cb.LookupFileName) + { + musicname_ = mus_cb.LookupFileName(musicname, order); + musicname = musicname_.GetChars(); + } + if (musicname == nullptr || musicname[0] == 0) { // Don't choke if the map doesn't have a song attached @@ -358,13 +329,10 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) mus_playing.LastSong = ""; return true; } - if (*musicname == '/') musicname++; - - FString DEH_Music; if (!mus_playing.name.IsEmpty() && mus_playing.handle != nullptr && - stricmp (mus_playing.name, musicname) == 0 && + stricmp(mus_playing.name, musicname) == 0 && ZMusic_IsLooping(mus_playing.handle) == zmusic_bool(looping)) { if (order != mus_playing.baseorder) @@ -386,29 +354,10 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) 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 = ZMusic_OpenCDSong (track, id); - if (mus_playing.handle == nullptr) - { - Printf("Unable to start CD Audio for track #%d, ID %d\n", track, id); - } - } - else - { int lumpnum = -1; int length = 0; ZMusic_MusicStream handle = nullptr; - MidiDeviceSetting *devp = MidiDevices.CheckKey(musicname); + MidiDeviceSetting* devp = MidiDevices.CheckKey(musicname); // Strip off any leading file:// component. if (strncmp(musicname, "file://", 7) == 0) @@ -416,14 +365,15 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) musicname += 7; } - FileReader reader = LookupMusic(musicname); + // 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); if (!reader.isOpen()) return false; // shutdown old music - S_StopMusic (true); + S_StopMusic(true); // Just record it if volume is 0 or music was disabled - if (mus_volume <= 0 || !mus_enabled) + if (snd_musicvolume <= 0 || !mus_enabled) { mus_playing.loop = looping; mus_playing.name = musicname; @@ -440,13 +390,12 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) else { auto mreader = GetMusicReader(reader); // this passes the file reader to the newly created wrapper. - mus_playing.handle = ZMusic_OpenSong(mreader, devp? (EMidiDevice)devp->device : MDEV_DEFAULT, devp? devp->args.GetChars() : ""); + mus_playing.handle = ZMusic_OpenSong(mreader, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : ""); if (mus_playing.handle == nullptr) { Printf("Unable to load %s: %s\n", mus_playing.name.GetChars(), ZMusic_GetLastError()); } } - } mus_playing.loop = looping; mus_playing.name = musicname; @@ -477,7 +426,8 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) void S_RestartMusic () { - if (!mus_playing.LastSong.IsEmpty() && mus_volume > 0 && mus_enabled) + if (snd_musicvolume <= 0) return; + if (!mus_playing.LastSong.IsEmpty() && mus_enabled) { FString song = mus_playing.LastSong; mus_playing.LastSong = ""; @@ -542,7 +492,7 @@ void S_StopMusic (bool force) try { // [RH] Don't stop if a playlist is active. - if (!mus_playing.name.IsEmpty()) + if ((force || PlayList.GetNumSongs() == 0) && !mus_playing.name.IsEmpty()) { if (mus_playing.handle != nullptr) { @@ -556,7 +506,7 @@ void S_StopMusic (bool force) mus_playing.LastSong = std::move(mus_playing.name); } } - catch (const CRecoverableError& ) + catch (const std::runtime_error& ) { //Printf("Unable to stop %s: %s\n", mus_playing.name.GetChars(), err.what()); if (mus_playing.handle != nullptr) @@ -577,10 +527,11 @@ void S_StopMusic (bool force) CCMD (changemus) { - if (MusicEnabled()) + if (!nomusic) { if (argv.argc() > 1) { + PlayList.Clear(); S_ChangeMusic (argv[1], argv.argc() > 2 ? atoi (argv[2]) : 0); } else @@ -610,104 +561,136 @@ CCMD (changemus) CCMD (stopmus) { + PlayList.Clear(); S_StopMusic (false); mus_playing.LastSong = ""; // forget the last played song so that it won't get restarted if some volume changes occur } -static FString lastMusicLevel, lastMusic; -int Mus_Play(const char *mapname, const char *fn, bool loop) +//========================================================================== +// +// CCMD playlist +// +//========================================================================== + +UNSAFE_CCMD (playlist) { - if (mus_blocked) return 1; // Caller should believe it succeeded. - // Store the requested names for resuming. - lastMusicLevel = mapname; - lastMusic = fn; - - if (!MusicEnabled()) - { - return 0; - } + int argc = argv.argc(); - // Allow per level music substitution. - // For most cases using $musicalias would be sufficient, but that method only works if a level actually has some music defined at all. - // This way it can be done with an add-on definition lump even in cases like Redneck Rampage where no music definitions exist - // or where music gets reused for multiple levels but replacement is wanted individually. - if (mapname && *mapname) + if (argc < 2 || argc > 3) { - if (*mapname == '/') mapname++; - FName *check = LevelMusicAliases.CheckKey(FName(mapname, true)); - if (check) fn = check->GetChars(); + Printf ("playlist [|shuffle]\n"); } - - // Now perform music aliasing. This also needs to be done before checking identities because multiple names can map to the same song. - FName* aliasp = MusicAliases.CheckKey(fn); - if (aliasp != nullptr) + else { - if (*aliasp == NAME_None) + if (!PlayList.ChangeList(argv[1])) { - return true; // flagged to be ignored - } - fn = aliasp->GetChars(); + Printf("Could not open " TEXTCOLOR_BOLD "%s" TEXTCOLOR_NORMAL ": %s\n", argv[1], strerror(errno)); + return; } - - if (!mus_restartonload) + if (PlayList.GetNumSongs () > 0) { - // If the currently playing piece of music is the same, do not restart. Note that there's still edge cases where this may fail to detect identities. - if (mus_playing.handle != nullptr && lastStartedMusic.CompareNoCase(fn) == 0 && mus_playing.loop) - return true; - } - - return S_ChangeMusic(fn, 0, loop, true); -} - -bool Mus_IsPlaying() -{ - return mus_playing.handle != nullptr; -} - -void Mus_Stop() -{ - if (mus_blocked) return; - S_StopMusic(true); -} - -void Mus_Fade(double seconds) -{ - // Todo: Blood uses this, but the streamer cannot currently fade the volume. - Mus_Stop(); -} - -void Mus_SetPaused(bool on) -{ - if (on) S_PauseMusic(); - else S_ResumeMusic(); -} - -void Mus_Serialize(FSerializer &arc) -{ - if (arc.BeginObject("music")) - { - if (arc.isWriting()) + if (argc == 3) { - FString music = mus_playing.name; - if (music.IsEmpty()) music = mus_playing.LastSong; - - arc.AddString("music", music); + if (stricmp (argv[2], "shuffle") == 0) + { + PlayList.Shuffle (); + } + else + { + PlayList.SetPosition (atoi (argv[2])); + } + } + S_ActivatePlayList (false); } - else arc("music", mus_playing.LastSong); - - arc("baseorder", mus_playing.baseorder) - ("loop", mus_playing.loop) - .EndObject(); - - // this is to prevent scripts from resetting the music after it has been loaded from the savegame. - if (arc.isReading()) mus_blocked = true; - // Actual music resuming cannot be performed here, it must be done in the game code. } } -void Mus_ResumeSaved() +//========================================================================== +// +// CCMD playlistpos +// +//========================================================================== + +static bool CheckForPlaylist () { - S_RestartMusic(); + if (PlayList.GetNumSongs() == 0) + { + 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"); + } +} diff --git a/source/core/music/music_config.cpp b/source/core/music/music_config.cpp index 4c5e7dd08..af3f7430a 100644 --- a/source/core/music/music_config.cpp +++ b/source/core/music/music_config.cpp @@ -43,7 +43,7 @@ //========================================================================== // -// +// ADL Midi device // //========================================================================== @@ -61,8 +61,49 @@ #define FORWARD_STRING_CVAR(key) \ auto ret = ChangeMusicSetting(zmusic_##key, mus_playing.handle,*self); \ - if (ret) S_MIDIDeviceChanged(-1); + if (ret) S_MIDIDeviceChanged(-1); +#ifndef ZMUSIC_LITE +CUSTOM_CVAR(Int, adl_chips_count, 6, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(adl_chips_count); +} + +CUSTOM_CVAR(Int, adl_emulator_id, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(adl_emulator_id); +} + +CUSTOM_CVAR(Bool, adl_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(adl_run_at_pcm_rate); +} + +CUSTOM_CVAR(Bool, adl_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(adl_fullpan); +} + +CUSTOM_CVAR(Int, adl_bank, 14, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(adl_bank); +} + +CUSTOM_CVAR(Bool, adl_use_custom_bank, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(adl_use_custom_bank); +} + +CUSTOM_CVAR(String, adl_custom_bank, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(adl_custom_bank); +} + +CUSTOM_CVAR(Int, adl_volume_model, 3/*ADLMIDI_VolumeModel_DMX*/, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(adl_bank); +} +#endif //========================================================================== // // Fluidsynth MIDI device @@ -182,10 +223,194 @@ CUSTOM_CVAR(Bool, opl_fullpan, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIR FORWARD_BOOL_CVAR(opl_fullpan); } +#ifndef ZMUSIC_LITE +//========================================================================== +// +// OPN MIDI device +// +//========================================================================== + + +CUSTOM_CVAR(Int, opn_chips_count, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(opn_chips_count); +} + +CUSTOM_CVAR(Int, opn_emulator_id, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(opn_emulator_id); +} + +CUSTOM_CVAR(Bool, opn_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(opn_run_at_pcm_rate); +} + +CUSTOM_CVAR(Bool, opn_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(opn_fullpan); +} + +CUSTOM_CVAR(Bool, opn_use_custom_bank, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(opn_use_custom_bank); +} + +CUSTOM_CVAR(String, opn_custom_bank, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(opn_custom_bank); +} + +//========================================================================== +// +// GUS MIDI device +// +//========================================================================== + + +CUSTOM_CVAR(String, midi_config, GAMENAMELOWERCASE, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(gus_config); +} + +CUSTOM_CVAR(Bool, midi_dmxgus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) // This was 'true' but since it requires special setup that's not such a good idea. +{ + FORWARD_BOOL_CVAR(gus_dmxgus); +} + +CUSTOM_CVAR(String, gus_patchdir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(gus_patchdir); +} + +CUSTOM_CVAR(Int, midi_voices, 32, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(gus_midi_voices); +} + +CUSTOM_CVAR(Int, gus_memsize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(gus_memsize); +} + +//========================================================================== +// +// Timidity++ device +// +//========================================================================== + +CUSTOM_CVAR(Bool, timidity_modulation_wheel, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_modulation_wheel); +} + +CUSTOM_CVAR(Bool, timidity_portamento, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_portamento); +} + +CUSTOM_CVAR(Int, timidity_reverb, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_reverb); +} + +CUSTOM_CVAR(Int, timidity_reverb_level, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_reverb_level); +} + +CUSTOM_CVAR(Int, timidity_chorus, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_chorus); +} + +CUSTOM_CVAR(Bool, timidity_surround_chorus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_surround_chorus); +} + +CUSTOM_CVAR(Bool, timidity_channel_pressure, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_channel_pressure); +} + +CUSTOM_CVAR(Int, timidity_lpf_def, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_lpf_def); +} + +CUSTOM_CVAR(Bool, timidity_temper_control, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_temper_control); +} + +CUSTOM_CVAR(Bool, timidity_modulation_envelope, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_modulation_envelope); +} + +CUSTOM_CVAR(Bool, timidity_overlap_voice_allow, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_overlap_voice_allow); +} + +CUSTOM_CVAR(Bool, timidity_drum_effect, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_drum_effect); +} + +CUSTOM_CVAR(Bool, timidity_pan_delay, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(timidity_pan_delay); +} + +CUSTOM_CVAR(Float, timidity_drum_power, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) /* coef. of drum amplitude */ +{ + FORWARD_CVAR(timidity_drum_power); +} + +CUSTOM_CVAR(Int, timidity_key_adjust, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_key_adjust); +} + +CUSTOM_CVAR(Float, timidity_tempo_adjust, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_tempo_adjust); +} + +CUSTOM_CVAR(Float, timidity_min_sustain_time, 5000, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(timidity_min_sustain_time); +} +#endif + CUSTOM_CVAR(String, timidity_config, GAMENAMELOWERCASE, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) { FORWARD_STRING_CVAR(timidity_config); } +#ifndef ZMUSIC_LITE +//========================================================================== +// +// WildMidi +// +//========================================================================== + +CUSTOM_CVAR(String, wildmidi_config, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(wildmidi_config); +} + +CUSTOM_CVAR(Bool, wildmidi_reverb, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(wildmidi_reverb); +} + +CUSTOM_CVAR(Bool, wildmidi_enhanced_resampling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(wildmidi_enhanced_resampling); +} +#endif //========================================================================== // diff --git a/source/core/music/s_advsound.cpp b/source/core/music/s_advsound.cpp index 1c0a831fa..8270d5386 100644 --- a/source/core/music/s_advsound.cpp +++ b/source/core/music/s_advsound.cpp @@ -43,6 +43,8 @@ #include "sc_man.h" #include +#include "raze_music.h" + // MACROS ------------------------------------------------------------------ enum SICommands diff --git a/source/core/music/s_music.h b/source/core/music/s_music.h index 497e4b640..4138dc7da 100644 --- a/source/core/music/s_music.h +++ b/source/core/music/s_music.h @@ -7,6 +7,15 @@ #include "name.h" #include +class FileReader; + +struct MusicCallbacks +{ + FString(*LookupFileName)(const char* fn, int &order); + FileReader(*OpenMusic)(const char* fn); +}; +void S_SetMusicCallbacks(MusicCallbacks* cb); + void S_CreateStream(); void S_PauseStream(bool pause); void S_StopStream(); @@ -15,7 +24,7 @@ void S_SetStreamVolume(float vol); // void S_InitMusic (); -void S_StartMusic (); +void S_ResetMusic (); // Start music using @@ -24,9 +33,6 @@ bool S_StartMusic (const char *music_name); // Start music using , and set whether looping bool S_ChangeMusic (const char *music_name, int order=0, bool looping=true, bool force=false); -// Start playing a cd track as music -bool S_ChangeCDMusic (int track, unsigned int id=0, bool looping=true); - void S_RestartMusic (); void S_MIDIDeviceChanged(int newdev); @@ -50,11 +56,9 @@ struct MidiDeviceSetting FString args; }; -typedef TMap MusicAliasMap; typedef TMap MidiDeviceMap; typedef TMap MusicVolumeMap; -extern MusicAliasMap MusicAliases; extern MidiDeviceMap MidiDevices; extern MusicVolumeMap MusicVolumes; @@ -71,8 +75,5 @@ extern MusPlayingInfo mus_playing; extern float relative_volume, saved_relative_volume; -// Note for later when the OPL player is ported. -// DN3D and related games use "d3dtimbr.tmb" - #endif diff --git a/source/core/raze_music.cpp b/source/core/raze_music.cpp new file mode 100644 index 000000000..5e1743fcd --- /dev/null +++ b/source/core/raze_music.cpp @@ -0,0 +1,282 @@ +/* +** +** raze_music.cpp +** music player for Build games +** +** Copyright 2019-2020 Christoph Oelckers +** +**--------------------------------------------------------------------------- +** +** 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 "raze_music.h" +#include "s_music.h" +#include "c_cvars.h" +#include "cmdlib.h" +#include "filesystem.h" +#include "files.h" +#include "i_music.h" + +#include "gamecontrol.h" +#include "serializer.h" + +static bool mus_blocked; +static FString lastStartedMusic; + +MusicAliasMap MusicAliases; +MusicAliasMap LevelMusicAliases; + +CVAR(Bool, printmusicinfo, false, 0) +CVAR(Bool, mus_extendedlookup, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + + +// Order is: streaming formats, module formats, emulated formats and MIDI formats - for external files the first one found wins so ambiguous names should be avoided +static const char* knownMusicExts[] = { + "OGG", "FLAC", "MP3", "MP2", "XA", "XM", "MOD", + "IT", "S3M", "MTM", "STM", "669", "PTM", "AMF", + "OKT", "DSM", "AMFF", "SPC", "VGM", "VGZ", "AY", + "GBS", "GYM", "HES", "KSS", "NSF", "NSFE", "SAP", + "MID", "HMP", "HMI", "XMI", "VOC" +}; + + +//========================================================================== +// +// Music file name lookup +// +//========================================================================== + +FString G_SetupFilenameBasedMusic(const char* fn, const char* defmusic) +{ + FString name = StripExtension(fn); + FString test; + + // Test if a real file with this name exists with all known extensions for music. + for (auto& ext : knownMusicExts) + { + test.Format("%s.%s", name.GetChars(), ext); + if (FileExists(test)) return test; +#ifdef __unix__ + test.Format("%s.%s", name.GetChars(), FString(ext.GetChars()).MakeLower().GetChars()); + if (FileExists(test)) return test; +#endif + } + return defmusic; +} + +FString MusicFileExists(const char* fn) +{ + if (mus_extendedlookup) return G_SetupFilenameBasedMusic(fn, nullptr); + if (FileExists(fn)) return fn; + return FString(); +} + +int LookupMusicLump(const char* fn) +{ + if (mus_extendedlookup) + { + FString name = StripExtension(fn); + int l = fileSystem.FindFileWithExtensions(name, knownMusicExts, countof(knownMusicExts)); + if (l >= 0) return l; + } + return fileSystem.CheckNumForFullName(fn, true, ns_music); +} + +//========================================================================== +// +// Music lookup in various places. +// +//========================================================================== + +FileReader OpenMusic(const char* musicname) +{ + FileReader reader; + if (!mus_restartonload) + { + // If the currently playing piece of music is the same, do not restart. Note that there's still edge cases where this may fail to detect identities. + if (mus_playing.handle != nullptr && lastStartedMusic.CompareNoCase(musicname) == 0 && mus_playing.loop) + return reader; + } + lastStartedMusic = musicname; // remember the last piece of music that was requested to be played. + + FString mus = MusicFileExists(musicname); + if (mus.IsNotEmpty()) + { + // Load an external file. + reader.OpenFile(mus); + } + if (!reader.isOpen()) + { + int lumpnum = LookupMusicLump(musicname); + if (mus_extendedlookup && lumpnum >= 0) + { + // EDuke also looks in a subfolder named after the main game resource. Do this as well if extended lookup is active. + auto rfn = fileSystem.GetResourceFileName(fileSystem.GetFileContainer(lumpnum)); + auto rfbase = ExtractFileBase(rfn); + FStringf aliasMusicname("music/%s/%s", rfbase.GetChars(), musicname); + lumpnum = LookupMusicLump(aliasMusicname); + } + if (lumpnum == -1) + { + // Always look in the 'music' subfolder as well. This gets used by multiple setups to store ripped CD tracks. + FStringf aliasMusicname("music/%s", musicname); + lumpnum = LookupMusicLump(aliasMusicname); + } + if (lumpnum == -1 && (g_gameType & GAMEFLAG_SW)) + { + // Some Shadow Warrioe distributions have the music in a subfolder named 'classic'. Check that, too. + FStringf aliasMusicname("classic/music/%s", musicname); + lumpnum = fileSystem.FindFile(aliasMusicname); + } + if (lumpnum > -1) + { + if (fileSystem.FileLength(lumpnum) >= 0) + { + reader = fileSystem.ReopenFileReader(lumpnum); + if (!reader.isOpen()) + { + Printf(TEXTCOLOR_RED "Unable to play music " TEXTCOLOR_WHITE "\"%s\"\n", musicname); + } + else if (printmusicinfo) Printf("Playing music from file system %s:%s\n", fileSystem.GetResourceFileFullName(fileSystem.GetFileContainer(lumpnum)), fileSystem.GetFileFullPath(lumpnum).GetChars()); + } + } + } + else if (printmusicinfo) Printf("Playing music from external file %s\n", musicname); + return reader; +} + + +static FString LookupMusic(const char* musicname, int& order) +{ + // Now perform music aliasing. This also needs to be done before checking identities because multiple names can map to the same song. + FName* aliasp = MusicAliases.CheckKey(musicname); + if (aliasp != nullptr) + { + if (*aliasp == NAME_None) + { + return true; // flagged to be ignored + } + return aliasp->GetChars(); + } + return musicname; +} + + +static FString lastMusicLevel, lastMusic; +int Mus_Play(const char *mapname, const char *fn, bool loop) +{ + if (mus_blocked) return 1; // Caller should believe it succeeded. + // Store the requested names for resuming. + lastMusicLevel = mapname; + lastMusic = fn; + + if (!MusicEnabled()) + { + return 0; + } + + // Allow per level music substitution. + // For most cases using $musicalias would be sufficient, but that method only works if a level actually has some music defined at all. + // This way it can be done with an add-on definition lump even in cases like Redneck Rampage where no music definitions exist + // or where music gets reused for multiple levels but replacement is wanted individually. + if (mapname && *mapname) + { + if (*mapname == '/') mapname++; + FName *check = LevelMusicAliases.CheckKey(FName(mapname, true)); + if (check) fn = check->GetChars(); + } + + return S_ChangeMusic(fn, 0, loop, true); +} + +bool Mus_IsPlaying() +{ + return mus_playing.handle != nullptr; +} + +void Mus_Stop() +{ + if (mus_blocked) return; + S_StopMusic(true); +} + +void Mus_Fade(double seconds) +{ + // Todo: Blood uses this, but the streamer cannot currently fade the volume. + Mus_Stop(); +} + +void Mus_SetPaused(bool on) +{ + if (on) S_PauseMusic(); + else S_ResumeMusic(); +} + +void Mus_Serialize(FSerializer &arc) +{ + if (arc.BeginObject("music")) + { + if (arc.isWriting()) + { + FString music = mus_playing.name; + if (music.IsEmpty()) music = mus_playing.LastSong; + + arc.AddString("music", music); + } + else arc("music", mus_playing.LastSong); + + arc("baseorder", mus_playing.baseorder) + ("loop", mus_playing.loop) + .EndObject(); + + // this is to prevent scripts from resetting the music after it has been loaded from the savegame. + if (arc.isReading()) mus_blocked = true; + // Actual music resuming cannot be performed here, it must be done in the game code. + } +} + +void Mus_ResumeSaved() +{ + S_RestartMusic(); +} + +void Mus_UpdateMusic() +{ + mus_blocked = false; + S_UpdateMusic(); +} + +void Mus_InitMusic() +{ + I_InitMusic(); + static MusicCallbacks mus_cb = + { + LookupMusic, + OpenMusic + }; + S_SetMusicCallbacks(&mus_cb); +} diff --git a/source/core/music/z_music.h b/source/core/raze_music.h similarity index 72% rename from source/core/music/z_music.h rename to source/core/raze_music.h index 544b67a6d..57e492c67 100644 --- a/source/core/music/z_music.h +++ b/source/core/raze_music.h @@ -1,10 +1,15 @@ #pragma once #include "zstring.h" +#include "tarray.h" +#include "name.h" + +typedef TMap MusicAliasMap; +extern MusicAliasMap MusicAliases; // Totally minimalistic interface - should be all the game modules need. - -void Mus_Init(); +void Mus_InitMusic(); +void Mus_UpdateMusic(); int Mus_Play(const char *mapname, const char *fn, bool loop); void Mus_Stop(); bool Mus_IsPlaying(); diff --git a/source/core/savegamehelp.cpp b/source/core/savegamehelp.cpp index 3f7c2a671..048000002 100644 --- a/source/core/savegamehelp.cpp +++ b/source/core/savegamehelp.cpp @@ -49,7 +49,7 @@ #include "m_argv.h" #include "serializer.h" #include "version.h" -#include "z_music.h" +#include "raze_music.h" #include "s_soundinternal.h" static CompositeSavegameWriter savewriter; diff --git a/source/core/sound/backend/i_sound.cpp b/source/core/sound/backend/i_sound.cpp index a0647160c..c0d6c8c9c 100644 --- a/source/core/sound/backend/i_sound.cpp +++ b/source/core/sound/backend/i_sound.cpp @@ -48,13 +48,13 @@ #include "c_cvars.h" #include "stats.h" #include "s_music.h" -#include "z_music.h" +#include "raze_music.h" #include "gamecvars.h" #include "gamecontrol.h" #include EXTERN_CVAR (Float, snd_sfxvolume) -EXTERN_CVAR(Float, mus_volume) +EXTERN_CVAR(Float, snd_musicvolume) CVAR (Int, snd_samplerate, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Int, snd_buffersize, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Int, snd_hrtf, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -95,7 +95,7 @@ CUSTOM_CVAR(Float, snd_mastervolume, 1.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVA ChangeMusicSetting(zmusic_snd_mastervolume, nullptr, self); snd_sfxvolume.Callback(); - mus_volume.Callback(); + snd_musicvolume.Callback(); } //========================================================================== diff --git a/source/duke3d/src/savegame.cpp b/source/duke3d/src/savegame.cpp index c4fdf6f32..b5664de4d 100644 --- a/source/duke3d/src/savegame.cpp +++ b/source/duke3d/src/savegame.cpp @@ -33,7 +33,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "savegamehelp.h" #include "menu/menu.h" #include "mapinfo.h" -#include "z_music.h" +#include "raze_music.h" BEGIN_DUKE_NS diff --git a/source/duke3d/src/screens.cpp b/source/duke3d/src/screens.cpp index 0314ebd68..f6fe594e6 100644 --- a/source/duke3d/src/screens.cpp +++ b/source/duke3d/src/screens.cpp @@ -46,6 +46,13 @@ double g_moveActorsTime, g_moveWorldTime; // in ms, smoothed int32_t g_noLogoAnim = 0; int32_t g_noLogo = 0; +void PlayBonusMusic() +{ + if (MusicEnabled() && mus_enabled) + S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + +} + ////////// OFTEN-USED FEW-LINERS ////////// #ifndef EDUKE32_STANDALONE static void G_HandleEventsWhileNoInput(void) @@ -1921,8 +1928,7 @@ void G_BonusScreen(int32_t bonusonly) videoClearScreen(0); G_DisplayMPResultsScreen(); - if (MusicEnabled() && mus_enabled) - S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + PlayBonusMusic(); videoNextPage(); inputState.ClearAllInput(); @@ -1960,8 +1966,7 @@ void G_BonusScreen(int32_t bonusonly) gametext_center_shade(192, GStrings("PRESSKEY"), quotepulseshade); - if (MusicEnabled() && mus_enabled) - S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + PlayBonusMusic(); videoNextPage(); inputState.ClearAllInput(); diff --git a/source/duke3d/src/sounds.cpp b/source/duke3d/src/sounds.cpp index 8ca14b3b4..594fc7a13 100644 --- a/source/duke3d/src/sounds.cpp +++ b/source/duke3d/src/sounds.cpp @@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "compat.h" #include "duke3d.h" -#include "z_music.h" +#include "raze_music.h" #include "mapinfo.h" #include "sound/s_soundinternal.h" diff --git a/source/duke3d/src/sounds.h b/source/duke3d/src/sounds.h index 54c336e32..33c93f0cc 100644 --- a/source/duke3d/src/sounds.h +++ b/source/duke3d/src/sounds.h @@ -31,7 +31,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "sounds_common.h" #include "sound/s_soundinternal.h" -#include "z_music.h" +#include "raze_music.h" BEGIN_DUKE_NS diff --git a/source/exhumed/src/cd.cpp b/source/exhumed/src/cd.cpp index 7f8eefec7..4ef0f7864 100644 --- a/source/exhumed/src/cd.cpp +++ b/source/exhumed/src/cd.cpp @@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "exhumed.h" #include #include -#include "z_music.h" +#include "raze_music.h" BEGIN_PS_NS diff --git a/source/rr/src/savegame.cpp b/source/rr/src/savegame.cpp index 42fb3f392..0009521bd 100644 --- a/source/rr/src/savegame.cpp +++ b/source/rr/src/savegame.cpp @@ -28,7 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "i_specialpaths.h" #include "gamecontrol.h" #include "version.h" -#include "z_music.h" +#include "raze_music.h" #include "mapinfo.h" #include "savegamehelp.h" diff --git a/source/rr/src/screens.cpp b/source/rr/src/screens.cpp index 1b60ac870..e4be788b7 100644 --- a/source/rr/src/screens.cpp +++ b/source/rr/src/screens.cpp @@ -46,6 +46,13 @@ double g_moveActorsTime, g_moveWorldTime; // in ms, smoothed int32_t g_noLogoAnim = 0; int32_t g_noLogo = 0; +void PlayBonusMusic() +{ + if (MusicEnabled() && mus_enabled) + S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + +} + ////////// OFTEN-USED FEW-LINERS ////////// static void G_HandleEventsWhileNoInput(void) { @@ -1930,8 +1937,7 @@ void G_BonusScreen(int32_t bonusonly) videoClearScreen(0); G_DisplayMPResultsScreen(); - if (MusicEnabled() && mus_enabled) - S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + PlayBonusMusic(); videoNextPage(); inputState.ClearAllInput(); @@ -1970,10 +1976,9 @@ void G_BonusScreen(int32_t bonusonly) menutext_center(20-6, lastmapname); menutext_center(36-6, GStrings("Completed")); - gametext_center_shade(192, GStrings("PRESSKEY"), quotepulseshade); + gametext_center_shade(192, GStrings("PRESSKEY"), quotepulseshade); - if (MusicEnabled() && mus_enabled) - S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + PlayBonusMusic(); } else { @@ -2504,8 +2509,7 @@ void G_BonusScreenRRRA(int32_t bonusonly) videoClearScreen(0); G_DisplayMPResultsScreen(); - if (MusicEnabled() && mus_enabled) - S_PlaySound(BONUSMUSIC, CHAN_AUTO, CHANF_UI); + PlayBonusMusic(); videoNextPage(); inputState.ClearAllInput(); diff --git a/source/rr/src/sounds.cpp b/source/rr/src/sounds.cpp index 4478423cb..827b2077b 100644 --- a/source/rr/src/sounds.cpp +++ b/source/rr/src/sounds.cpp @@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "compat.h" #include "duke3d.h" -#include "z_music.h" +#include "raze_music.h" #include "mapinfo.h" #include "sound/s_soundinternal.h" diff --git a/source/rr/src/sounds.h b/source/rr/src/sounds.h index 719ae0beb..963b14b07 100644 --- a/source/rr/src/sounds.h +++ b/source/rr/src/sounds.h @@ -31,7 +31,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "sounds_common.h" #include "sound/s_soundinternal.h" -#include "z_music.h" +#include "raze_music.h" BEGIN_RR_NS diff --git a/source/sw/src/game.cpp b/source/sw/src/game.cpp index 2dc38aea3..ce2fa0687 100644 --- a/source/sw/src/game.cpp +++ b/source/sw/src/game.cpp @@ -94,7 +94,7 @@ Things required to make savegames work: #include "m_argv.h" #include "debugbreak.h" #include "menu/menu.h" -#include "z_music.h" +#include "raze_music.h" #include "statistics.h" #include "gstrings.h" #include "mapinfo.h" diff --git a/source/sw/src/player.cpp b/source/sw/src/player.cpp index f91e1ef37..22fff4782 100644 --- a/source/sw/src/player.cpp +++ b/source/sw/src/player.cpp @@ -65,7 +65,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "interpso.h" #include "menu/menu.h" #include "gstrings.h" -#include "z_music.h" +#include "raze_music.h" BEGIN_SW_NS diff --git a/source/sw/src/save.cpp b/source/sw/src/save.cpp index 393ddecd7..b4ed91438 100644 --- a/source/sw/src/save.cpp +++ b/source/sw/src/save.cpp @@ -54,7 +54,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "player.h" #include "i_specialpaths.h" #include "savegamehelp.h" -#include "z_music.h" +#include "raze_music.h" #include "mapinfo.h" //void TimerFunc(task * Task); diff --git a/source/sw/src/sounds.cpp b/source/sw/src/sounds.cpp index 5bf6e71b3..ff1c1a653 100644 --- a/source/sw/src/sounds.cpp +++ b/source/sw/src/sounds.cpp @@ -49,7 +49,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "menus.h" #include "config.h" #include "menu/menu.h" -#include "z_music.h" +#include "raze_music.h" #include "sound/s_soundinternal.h" #include "filesystem.h" #include "serializer.h" diff --git a/wadsrc/static/engine/menudef.txt b/wadsrc/static/engine/menudef.txt index 09677cad1..1ea359b0c 100644 --- a/wadsrc/static/engine/menudef.txt +++ b/wadsrc/static/engine/menudef.txt @@ -1387,7 +1387,7 @@ OptionMenu SoundOptions //protected Option "$SNDMNU_SNDENABLED", "snd_enabled", "YesNo" Slider "$SNDMNU_SFXVOLUME", "snd_sfxvolume", 0, 1, 0.05, 2 Option "$SNDMNU_MUSENABLED", "mus_enabled", "YesNo" - Slider "$SNDMNU_MUSICVOLUME", "mus_volume", 0, 1, 0.05, 2 + Slider "$SNDMNU_MUSICVOLUME", "snd_musicvolume", 0, 1, 0.05, 2 Option "$SNDMNU_MENUSOUND", "menu_sounds", "OnOff" // placeholder until the slider can be made to work //Slider "$SNDMNU_MENUVOLUME", "snd_menuvolume", 0, 1, 0.05, 2 StaticText " "