diff --git a/source/common/gamecvars.cpp b/source/common/gamecvars.cpp index e602315de..181c55b7a 100644 --- a/source/common/gamecvars.cpp +++ b/source/common/gamecvars.cpp @@ -168,13 +168,6 @@ CUSTOM_CVARD(Int, snd_speech, 5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "enables/disabl else if (self > 5) self = 5; } -CUSTOM_CVARD(Int, mus_volume, 255, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "controls music volume") -{ - if (self < 0) self = 0; - if (self > 255) self = 255; - Mus_SetVolume(self/255.f); -} - int MusicDevice = ASS_WinMM; CUSTOM_CVARD(Int, mus_device, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "selects music device") { diff --git a/source/common/music/i_music.cpp b/source/common/music/i_music.cpp new file mode 100644 index 000000000..1d0b05b9a --- /dev/null +++ b/source/common/music/i_music.cpp @@ -0,0 +1,452 @@ +/* +** i_music.cpp +** Plays music +** +**--------------------------------------------------------------------------- +** Copyright 1998-2010 Randy Heit +** All rights reserved. +** +** 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. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef _WIN32 +#include +#include +#endif + +#include + +#include "m_argv.h" +#include "w_wad.h" +#include "c_dispatch.h" +#include "templates.h" +#include "stats.h" +#include "c_cvars.h" +#include "c_console.h" +#include "vm.h" +#include "v_text.h" +#include "i_sound.h" +#include "i_soundfont.h" +#include "s_music.h" +#include "doomstat.h" +#include "zmusic/zmusic.h" +#include "streamsources/streamsource.h" +#include "filereadermusicinterface.h" +#include "../libraries/zmusic/midisources/midisource.h" + + + +void I_InitSoundFonts(); + +EXTERN_CVAR (Int, snd_samplerate) +EXTERN_CVAR (Int, snd_mididevice) + +int nomusic = 0; + +#ifdef _WIN32 + +void I_InitMusicWin32(); + +#include "musicformats/win32/i_cd.h" +//========================================================================== +// +// 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 +// +// Maximum volume of MOD/stream music. +//========================================================================== + +CUSTOM_CVARD(Int, mus_volume, 255, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "controls music volume") +{ + if (self < 0) self = 0; + else if (self > 255) self = 255; + else + { + // Set general music volume. + ChangeMusicSetting(ZMusic::mus_volume, nullptr, self / 255.f); + if (GSnd != nullptr) + { + GSnd->SetMusicVolume(clamp(self * relative_volume * snd_mastervolume, 0, 1)); + } + // For music not implemented through the digital sound system, + // let them know about the change. + if (mus_playing.handle != nullptr) + { + ZMusic_VolumeChanged(mus_playing.handle); + } + else + { // If the music was stopped because volume was 0, start it now. + S_RestartMusic(); + } + } +} + +//========================================================================== +// +// Callbacks for the music system. +// +//========================================================================== + +static void tim_printfunc(int type, int verbosity_level, const char* fmt, ...) +{ + if (verbosity_level >= 3/*Timidity::VERB_DEBUG*/) return; // Don't waste time on diagnostics. + + va_list args; + va_start(args, fmt); + FString msg; + msg.VFormat(fmt, args); + va_end(args); + + switch (type) + { + case 2:// Timidity::CMSG_ERROR: + Printf(TEXTCOLOR_RED "%s\n", msg.GetChars()); + break; + + case 1://Timidity::CMSG_WARNING: + Printf(TEXTCOLOR_YELLOW "%s\n", msg.GetChars()); + break; + + case 0://Timidity::CMSG_INFO: + DPrintf(DMSG_SPAMMY, "%s\n", msg.GetChars()); + break; + } +} + +static void wm_printfunc(const char* wmfmt, va_list args) +{ + Printf(TEXTCOLOR_RED); + VPrintf(PRINT_HIGH, wmfmt, args); +} + +//========================================================================== +// +// other callbacks +// +//========================================================================== + +static short* dumb_decode_vorbis_(int outlen, const void* oggstream, int sizebytes) +{ + // This really should be done internally in zmusic... + return nullptr;// GSnd->DecodeSample(outlen, oggstream, sizebytes, CODEC_Vorbis); +} + +static std::string mus_NicePath(const char* str) +{ + FString strv = NicePath(str); + return strv.GetChars(); +} + +static const char* mus_pathToSoundFont(const char* sfname, int type) +{ + auto info = sfmanager.FindSoundFont(sfname, type); + return info ? info->mFilename.GetChars() : nullptr; +} + +static MusicIO::SoundFontReaderInterface* mus_openSoundFont(const char* sfname, int type) +{ + return sfmanager.OpenSoundFont(sfname, type); +} + + +//========================================================================== +// +// Pass some basic working data to the music backend +// We do this once at startup for everything available. +// +//========================================================================== + +static void SetupGenMidi() +{ + // 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.FindFile("demolition/genmidi.txt"); + if (lump < 0) + { + Printf("No GENMIDI lump found. OPL playback not available."); + return; + } + auto data = Wads.OpenLumpReader(lump); + + auto genmidi = data.Read(); + if (genmidi.Size() < 8 + 175 * 36 || memcmp(genmidi.Data(), "#OPL_II#", 8)) return; + ZMusic_SetGenMidi(genmidi.Data()+8); +} + +//========================================================================== +// +// +// +//========================================================================== + +void I_InitMusic (void) +{ + I_InitSoundFonts(); + + mus_volume.Callback (); + +#ifdef _WIN32 + I_InitMusicWin32 (); +#endif // _WIN32 + snd_mididevice.Callback(); + + Callbacks callbacks; + + callbacks.Fluid_MessageFunc = Printf; + callbacks.Timidity_Messagefunc = tim_printfunc; + callbacks.NicePath = mus_NicePath; + callbacks.PathForSoundfont = mus_pathToSoundFont; + callbacks.OpenSoundFont = mus_openSoundFont; + //callbacks.DumbVorbisDecode = dumb_decode_vorbis_; + + ZMusic_SetCallbacks(&callbacks); + SetupGenMidi(); +} + + +//========================================================================== +// +// +// +//========================================================================== + +void I_SetRelativeVolume(float vol) +{ + relative_volume = (float)vol; + ChangeMusicSetting(ZMusic::relative_volume, nullptr, (float)vol); + mus_volume.Callback(); +} +//========================================================================== +// +// Sets relative music volume. Takes $musicvolume in SNDINFO into consideration +// +//========================================================================== + +void I_SetMusicVolume (double factor) +{ + factor = clamp(factor, 0., 2.0); + I_SetRelativeVolume((float)factor); +} + +//========================================================================== +// +// test a relative music volume +// +//========================================================================== + +CCMD(testmusicvol) +{ + if (argv.argc() > 1) + { + I_SetRelativeVolume((float)strtod(argv[1], nullptr)); + } + else + Printf("Current relative volume is %1.2f\n", relative_volume); +} + +//========================================================================== +// +// STAT music +// +//========================================================================== + +ADD_STAT(music) +{ + if (mus_playing.handle != nullptr) + { + return FString(ZMusic_GetStats(mus_playing.handle).c_str()); + } + return "No song playing"; +} + +//========================================================================== +// +// Common loader for the dumpers. +// +//========================================================================== + +static MIDISource *GetMIDISource(const char *fn) +{ + FString src = fn; + if (src.Compare("*") == 0) src = mus_playing.name; + + auto lump = fileSystem.FindFile(src); + if (lump < 0) + { + Printf("Cannot find MIDI lump %s.\n", src.GetChars()); + return nullptr; + } + + auto wlump = fileSystem.OpenFile(lump); + + uint32_t id[32 / 4]; + + if (wlump.Read(id, 32) != 32 || wlump.Seek(-32, FileReader::SeekCur) != 0) + { + Printf("Unable to read lump %s\n", src.GetChars()); + return nullptr; + } + auto type = IdentifyMIDIType(id, 32); + if (type == MIDI_NOTMIDI) + { + Printf("%s is not MIDI-based.\n", src.GetChars()); + return nullptr; + } + + auto data = wlump.Read(); + auto source = CreateMIDISource(data.Data(), data.Size(), type); + + if (source == nullptr) + { + Printf("%s is not MIDI-based.\n", src.GetChars()); + return nullptr; + } + return source; +} + +//========================================================================== +// +// CCMD writewave +// +// If the current song can be represented as a waveform, dump it to +// the specified file on disk. The sample rate parameter is merely a +// suggestion, and the dumper is free to ignore it. +// +//========================================================================== + +CCMD (writewave) +{ + if (argv.argc() >= 3 && argv.argc() <= 7) + { + auto source = GetMIDISource(argv[1]); + if (source == nullptr) return; + + EMidiDevice dev = MDEV_DEFAULT; + + if (argv.argc() >= 6) + { + 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 + { + Printf("%s: Unknown MIDI device\n", argv[5]); + return; + } + } + // We must stop the currently playing music to avoid interference between two synths. + auto savedsong = mus_playing; + S_StopMusic(true); + if (dev == MDEV_DEFAULT && snd_mididevice >= 0) dev = MDEV_FLUIDSYNTH; // The Windows system synth cannot dump a wave. + try + { + MIDIDumpWave(source, dev, argv.argc() < 6 ? nullptr : argv[6], argv[2], argv.argc() < 4 ? 0 : (int)strtol(argv[3], nullptr, 10), argv.argc() < 5 ? 0 : (int)strtol(argv[4], nullptr, 10)); + } + catch (const std::runtime_error& err) + { + Printf("MIDI dump failed: %s\n", err.what()); + } + + S_ChangeMusic(savedsong.name, savedsong.baseorder, savedsong.loop, true); + } + else + { + Printf ("Usage: writewave [subsong] [sample rate] [synth] [soundfont]\n" + " - use '*' as song name to dump the currently playing song\n" + " - use 0 for subsong and sample rate to play the default\n"); + } +} + +//========================================================================== +// +// CCMD writemidi +// +// Writes a given MIDI song to disk. This does not affect playback anymore, +// like older versions did. +// +//========================================================================== + +CCMD(writemidi) +{ + if (argv.argc() != 3) + { + Printf("Usage: writemidi - use '*' as song name to dump the currently playing song\n"); + return; + } + auto source = GetMIDISource(argv[1]); + if (source == nullptr) return; + + std::vector midi; + bool success; + + source->CreateSMF(midi, 1); + auto f = FileWriter::Open(argv[2]); + if (f == nullptr) + { + Printf("Could not open %s.\n", argv[2]); + return; + } + success = (f->Write(&midi[0], midi.size()) == midi.size()); + delete f; + + if (!success) + { + Printf("Could not write to music file %s.\n", argv[2]); + } +} diff --git a/source/common/music/i_music.h b/source/common/music/i_music.h new file mode 100644 index 000000000..958ed4511 --- /dev/null +++ b/source/common/music/i_music.h @@ -0,0 +1,48 @@ +/* +** i_music.h +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** All rights reserved. +** +** 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. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef __I_MUSIC_H__ +#define __I_MUSIC_H__ + +class FileReader; + +// +// MUSIC I/O +// +void I_InitMusic (); + +// Volume. +void I_SetRelativeVolume(float); +void I_SetMusicVolume (double volume); + +#endif //__I_MUSIC_H__ diff --git a/source/common/music/i_soundfont.cpp b/source/common/music/i_soundfont.cpp new file mode 100644 index 000000000..7e634e793 --- /dev/null +++ b/source/common/music/i_soundfont.cpp @@ -0,0 +1,545 @@ +/* +** i_soundfont.cpp +** The sound font manager for the MIDI synths +** +**--------------------------------------------------------------------------- +** Copyright 2018 Christoph Oelckers +** All rights reserved. +** +** 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 +#include "i_soundfont.h" +#include "cmdlib.h" +#include "i_system.h" +#include "gameconfigfile.h" +#include "filereadermusicinterface.h" +#include "zmusic/zmusic.h" +#include "resourcefile.h" + +//========================================================================== +// +// +// +//========================================================================== + +FSoundFontManager sfmanager; + +//========================================================================== +// +// returns both a file reader and the full name of the looked up file +// +//========================================================================== + +std::pair FSoundFontReader::LookupFile(const char *name) +{ + if (!IsAbsPath(name)) + { + for(int i = mPaths.Size()-1; i>=0; i--) + { + FString fullname = mPaths[i] + name; + auto fr = OpenFile(fullname); + if (fr.isOpen()) return std::make_pair(std::move(fr), fullname); + } + } + auto fr = OpenFile(name); + if (!fr.isOpen()) name = ""; + return std::make_pair(std::move(fr), name); +} + +//========================================================================== +// +// This adds a directory to the path list +// +//========================================================================== + +void FSoundFontReader::AddPath(const char *strp) +{ + if (*strp == 0) return; + if (!mAllowAbsolutePaths && IsAbsPath(strp)) return; // of no use so we may just discard it right away + int i = 0; + FString str = strp; + str.Substitute("\\", "/"); + if (str.Back() != '/') str += '/'; // always let it end with a slash. + for (auto &s : mPaths) + { + if (pathcmp(s.GetChars(), str) == 0) + { + // move string to the back. + mPaths.Delete(i); + mPaths.Push(str); + return; + } + i++; + } + mPaths.Push(str); +} + +int FSoundFontReader::pathcmp(const char *p1, const char *p2) +{ + return mCaseSensitivePaths? strcmp(p1, p2) : stricmp(p1, p2); +} + +//========================================================================== +// +// +// +//========================================================================== + +FileReader FSoundFontReader::Open(const char *name, std::string& filename) +{ + FileReader fr; + if (name == nullptr) + { + fr = OpenMainConfigFile(); + filename = MainConfigFileName(); + } + else + { + auto res = LookupFile(name); + fr = std::move(res.first); + filename = res.second; + } + return fr; +} + +//========================================================================== +// +// +// +//========================================================================== + +MusicIO::FileInterface* FSoundFontReader::open_interface(const char* name) +{ + std::string filename; + + FileReader fr = Open(name, filename); + if (!fr.isOpen()) return nullptr; + auto fri = new FileReaderMusicInterface(fr); + fri->filename = std::move(filename); + return fri; +} + + +//========================================================================== +// +// The file interface for the backend +// +//========================================================================== + +struct MusicIO::FileInterface* FSoundFontReader::open_file(const char* name) +{ + return open_interface(name); +} + + +//========================================================================== +// +// Note that the file type has already been checked +// +//========================================================================== + +FSF2Reader::FSF2Reader(const char *fn) +{ + mMainConfigForSF2.Format("soundfont \"%s\"\n", fn); + mFilename = fn; +} + +//========================================================================== +// +// +// +//========================================================================== + +FileReader FSF2Reader::OpenMainConfigFile() +{ + FileReader fr; + if (mMainConfigForSF2.IsNotEmpty()) + { + fr.OpenMemory(mMainConfigForSF2.GetChars(), mMainConfigForSF2.Len()); + } + return fr; +} + +FileReader FSF2Reader::OpenFile(const char *name) +{ + FileReader fr; + if (mFilename.CompareNoCase(name) == 0) + { + fr.OpenFile(name); + } + return fr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FZipPatReader::FZipPatReader(const char *filename) +{ + resf = FResourceFile::OpenResourceFile(filename, true); +} + +FZipPatReader::~FZipPatReader() +{ + if (resf != nullptr) delete resf; +} + +FileReader FZipPatReader::OpenMainConfigFile() +{ + return OpenFile("timidity.cfg"); +} + +FileReader FZipPatReader::OpenFile(const char *name) +{ + FileReader fr; + if (resf != nullptr) + { + auto lump = resf->FindLump(name); + if (lump != nullptr) + { + return lump->NewReader(); + } + } + return fr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FPatchSetReader::FPatchSetReader(const char *filename) +{ +#ifndef _WIN32 + mCaseSensitivePaths = true; + const char *paths[] = { + "/usr/local/lib/timidity", + "/etc/timidity", + "/etc" + }; +#else + const char *paths[] = { + "C:/TIMIDITY", + "/TIMIDITY", + progdir + }; +#endif + mAllowAbsolutePaths = true; + FileReader fr; + if (fr.OpenFile(filename)) + { + mFullPathToConfig = filename; + } + else if (!IsAbsPath(filename)) + { + for(auto c : paths) + { + FStringf fullname("%s/%s", c, filename); + if (fr.OpenFile(fullname)) + { + mFullPathToConfig = fullname; + } + } + } + if (mFullPathToConfig.Len() > 0) + { + mFullPathToConfig.Substitute("\\", "/"); + mBasePath = ExtractFilePath(mFullPathToConfig); + if (mBasePath.Len() > 0 && mBasePath.Back() != '/') mBasePath += '/'; + } +} + + +FileReader FPatchSetReader::OpenMainConfigFile() +{ + FileReader fr; + fr.OpenFile(mFullPathToConfig); + return fr; +} + +FileReader FPatchSetReader::OpenFile(const char *name) +{ + FString path; + if (IsAbsPath(name)) path = name; + else path = mBasePath + name; + FileReader fr; + fr.OpenFile(path); + return fr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FLumpPatchSetReader::FLumpPatchSetReader(const char *filename) +{ + mLumpIndex = fileSystem.FindFile(filename); + + mBasePath = filename; + mBasePath.Substitute("\\", "/"); + mBasePath = ExtractFilePath(mBasePath); + if (mBasePath.Len() > 0 && mBasePath.Back() != '/') mBasePath += '/'; +} + +FileReader FLumpPatchSetReader::OpenMainConfigFile() +{ + return Wads.ReopenLumpReader(mLumpIndex); +} + +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); + if (index < 0) return FileReader(); + return Wads.ReopenLumpReader(index); +} + +//========================================================================== +// +// collects everything out of the soundfonts directory. +// This may either be .sf2 files or zipped GUS patch sets with a +// 'timidity.cfg' in the root directory. +// Other compression types are not supported, in particular not 7z because +// due to the solid nature of its archives would be too slow. +// +//========================================================================== + +void FSoundFontManager::ProcessOneFile(const FString &fn) +{ + auto fb = ExtractFileBase(fn, false); + auto fbe = ExtractFileBase(fn, true); + for (auto &sfi : soundfonts) + { + // We already got a soundfont with this name. Do not add again. + if (!sfi.mName.CompareNoCase(fb)) return; + } + + FileReader fr; + if (fr.OpenFile(fn)) + { + // Try to identify. We only accept .sf2 and .zip by content. All other archives are intentionally ignored. + char head[16] = { 0}; + fr.Read(head, 16); + if (!memcmp(head, "RIFF", 4) && !memcmp(head+8, "sfbkLIST", 8)) + { + FSoundFontInfo sft = { fb, fbe, fn, SF_SF2 }; + soundfonts.Push(sft); + } + if (!memcmp(head, "WOPL3-BANK\0", 11)) + { + FSoundFontInfo sft = { fb, fbe, fn, SF_WOPL }; + soundfonts.Push(sft); + } + if (!memcmp(head, "WOPN2-BANK\0", 11) || !memcmp(head, "WOPN2-B2NK\0", 11)) + { + FSoundFontInfo sft = { fb, fbe, fn, SF_WOPN }; + soundfonts.Push(sft); + } + else if (!memcmp(head, "PK", 2)) + { + auto zip = FResourceFile::OpenResourceFile(fn, true); + if (zip != nullptr) + { + if (zip->LumpCount() > 1) // Anything with just one lump cannot possibly be a packed GUS patch set so skip it right away and simplify the lookup code + { + auto zipl = zip->FindLump("timidity.cfg"); + if (zipl != nullptr) + { + // It seems like this is what we are looking for + FSoundFontInfo sft = { fb, fbe, fn, SF_GUS }; + soundfonts.Push(sft); + } + } + delete zip; + } + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FSoundFontManager::CollectSoundfonts() +{ + findstate_t c_file; + void *file; + + if (GameConfig != NULL && GameConfig->SetSection ("SoundfontSearch.Directories")) + { + const char *key; + const char *value; + + while (GameConfig->NextInSection (key, value)) + { + if (stricmp (key, "Path") == 0) + { + FString dir; + + dir = NicePath(value); + dir.Substitute("\\", "/"); + if (dir.IsNotEmpty()) + { + if (dir.Back() != '/') dir += '/'; + FString mask = dir + '*'; + if ((file = I_FindFirst(mask, &c_file)) != ((void *)(-1))) + { + do + { + if (!(I_FindAttr(&c_file) & FA_DIREC)) + { + FStringf name("%s%s", dir.GetChars(), I_FindName(&c_file)); + ProcessOneFile(name); + } + } while (I_FindNext(file, &c_file) == 0); + I_FindClose(file); + } + } + } + } + } + + if (soundfonts.Size() == 0) + { + ProcessOneFile(NicePath("$PROGDIR/soundfonts/demolition.sf2")); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +const FSoundFontInfo *FSoundFontManager::FindSoundFont(const char *name, int allowed) const +{ + for(auto &sfi : soundfonts) + { + // an empty name will pick the first one in a compatible format. + if (allowed & sfi.type && (name == nullptr || *name == 0 || !sfi.mName.CompareNoCase(name) || !sfi.mNameExt.CompareNoCase(name))) + { + return &sfi; + } + } + // We did not find what we were looking for. Let's just return the first valid item that works with the given device. + for (auto &sfi : soundfonts) + { + if (allowed & sfi.type) + { + return &sfi; + } + } + return nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSoundFontReader *FSoundFontManager::OpenSoundFont(const char *name, int allowed) +{ + + // First check if the given name is inside the loaded resources. + // To avoid clashes this will only be done if the name has the '.cfg' extension. + // Sound fonts cannot be loaded this way. + if (name != nullptr) + { + const char *p = name + strlen(name) - 4; + if (p > name && !stricmp(p, ".cfg") && fileSystem.FindFile(name) >= 0) + { + return new FLumpPatchSetReader(name); + } + } + + auto sfi = FindSoundFont(name, allowed); + if (sfi != nullptr) + { + if (sfi->type == SF_SF2) return new FSF2Reader(sfi->mFilename); + else return new FZipPatReader(sfi->mFilename); + } + // The sound font collection did not yield any good results. + // Next check if the file is a .sf file + if (allowed & SF_SF2) + { + FileReader fr; + if (fr.OpenFile(name)) + { + char head[16] = { 0}; + fr.Read(head, 16); + fr.Close(); + if (!memcmp(head, "RIFF", 4) && !memcmp(head+8, "sfbkLIST", 8)) + { + return new FSF2Reader(name); + } + } + } + if (allowed & SF_GUS) + { + FileReader fr; + if (fr.OpenFile(name)) + { + char head[16] = { 0 }; + fr.Read(head, 2); + fr.Close(); + if (!memcmp(head, "PK", 2)) // The only reason for this check is to block non-Zips. The actual validation will be done by FZipFile. + { + auto r = new FZipPatReader(name); + if (r->isOk()) return r; + delete r; + } + } + + // Config files are only accepted if they are named '.cfg', because they are impossible to validate. + const char *p = name + strlen(name) - 4; + if (p > name && !stricmp(p, ".cfg") && FileExists(name)) + { + return new FPatchSetReader(name); + } + } + return nullptr; + +} + +void I_InitSoundFonts() +{ + sfmanager.CollectSoundfonts(); +} + + diff --git a/source/common/music/i_soundfont.h b/source/common/music/i_soundfont.h new file mode 100644 index 000000000..0be51c2dd --- /dev/null +++ b/source/common/music/i_soundfont.h @@ -0,0 +1,166 @@ +#pragma once + +#include "zstring.h" +#include "tarray.h" +#include "filesystem.h" +#include "files.h" +#include "filereadermusicinterface.h" + +struct FSoundFontInfo +{ + FString mName; // This is what the sounfont is identified with. It's the extension-less base file name + FString mNameExt; // Same with extension. Used for comparing with input names so they can be done with or without extension. + FString mFilename; // Full path to the backing file - this is needed by FluidSynth to load the sound font. + int type; +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FSoundFontReader : public MusicIO::SoundFontReaderInterface +// Yes, it's 3 copies of essentially the same interface, but since we want to keep the 3 renderers as isolated modules we have to pull in their own implementations here. +{ +protected: + // This is only doable for loose config files that get set as sound fonts. All other cases read from a contained environment where this does not apply. + bool mAllowAbsolutePaths = false; + // This has only meaning if being run on a platform with a case sensitive file system and loose files. + // When reading from an archive it will always be case insensitive, just like the lump manager. + bool mCaseSensitivePaths = false; + TArray mPaths; + + + int pathcmp(const char *p1, const char *p2); + + +public: + + virtual ~FSoundFontReader() {} + virtual FileReader OpenMainConfigFile() = 0; // this is special because it needs to be synthesized for .sf files and set some restrictions for patch sets + virtual FString MainConfigFileName() + { + return basePath() + "timidity.cfg"; + } + + virtual FileReader OpenFile(const char *name) = 0; + std::pair LookupFile(const char *name); + void AddPath(const char *str); + virtual FString basePath() const + { + return ""; // archived patch sets do not use paths + } + + virtual FileReader Open(const char* name, std::string &filename); + + // Timidity++ interface + struct MusicIO::FileInterface* open_file(const char* name) override; + void add_search_path(const char* name) override + { + return AddPath(name); + } + + MusicIO::FileInterface* open_interface(const char* name); + +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FSF2Reader : public FSoundFontReader +{ + FString mMainConfigForSF2; + FString mFilename; +public: + FSF2Reader(const char *filename); + virtual FileReader OpenMainConfigFile() override; + virtual FileReader OpenFile(const char *name) override; +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FZipPatReader : public FSoundFontReader +{ + FResourceFile *resf; +public: + FZipPatReader(const char *filename); + ~FZipPatReader(); + virtual FileReader OpenMainConfigFile() override; + virtual FileReader OpenFile(const char *name) override; + bool isOk() { return resf != nullptr; } +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FLumpPatchSetReader : public FSoundFontReader +{ + int mLumpIndex;; + FString mBasePath; + +public: + FLumpPatchSetReader(const char *filename); + virtual FileReader OpenMainConfigFile() override; + virtual FileReader OpenFile(const char *name) override; + virtual FString basePath() const override + { + return mBasePath; + } + +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FPatchSetReader : public FSoundFontReader +{ + FString mBasePath; + FString mFullPathToConfig; + +public: + FPatchSetReader(FileReader &reader); + FPatchSetReader(const char *filename); + virtual FileReader OpenMainConfigFile() override; + virtual FileReader OpenFile(const char *name) override; + virtual FString basePath() const override + { + return mBasePath; + } +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FSoundFontManager +{ + TArray soundfonts; + + void ProcessOneFile(const FString & fn); + +public: + void CollectSoundfonts(); + const FSoundFontInfo *FindSoundFont(const char *name, int allowedtypes) const; + FSoundFontReader *OpenSoundFont(const char *name, int allowedtypes); + const auto &GetList() const { return soundfonts; } // This is for the menu + +}; + + +extern FSoundFontManager sfmanager; diff --git a/source/common/music/music.cpp b/source/common/music/music.cpp index f224864a8..e3665ba86 100644 --- a/source/common/music/music.cpp +++ b/source/common/music/music.cpp @@ -1,3 +1,58 @@ +//----------------------------------------------------------------------------- +// +// 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 "zmusic/zmusic.h" #include "z_music.h" #include "zstring.h" @@ -15,12 +70,171 @@ MusicAliasMap MusicAliases; MidiDeviceMap MidiDevices; +//========================================================================== +// +// starts playing this song +// +//========================================================================== + +static void S_StartMusicPlaying(MusInfo* song, bool loop, float rel_vol, int subsong) +{ + if (rel_vol > 0.f) + { + float factor = relative_volume / saved_relative_volume; + saved_relative_volume = rel_vol; + I_SetRelativeVolume(saved_relative_volume * factor); + } + ZMusic_Stop(song); + ZMusic_Start(song, subsong, loop); + + // Notify the sound system of the changed relative volume + snd_musicvolume.Callback(); +} + + +//========================================================================== +// +// S_PauseSound +// +// Stop music and sound effects, during game PAUSE. +//========================================================================== + +void S_PauseMusic () +{ + if (mus_playing.handle && !MusicPaused) + { + ZMusic_Pause(mus_playing.handle); + S_PauseStream(true); + MusicPaused = true; + } +} + +//========================================================================== +// +// S_ResumeSound +// +// Resume music and sound effects, after game PAUSE. +//========================================================================== + +void S_ResumeMusic () +{ + if (mus_playing.handle && MusicPaused) + { + ZMusic_Resume(mus_playing.handle); + S_PauseStream(false); + MusicPaused = false; + } +} + +//========================================================================== +// +// S_UpdateSound +// +//========================================================================== + +void S_UpdateMusic () +{ + if (mus_playing.handle != nullptr) + { + ZMusic_Update(mus_playing.handle); + + // [RH] Update music and/or playlist. IsPlaying() must be called + // to attempt to reconnect to broken net streams and to advance the + // playlist when the current song finishes. + if (!ZMusic_IsPlaying(mus_playing.handle)) + { + if (PlayList.GetNumSongs()) + { + PlayList.Advance(); + S_ActivatePlayList(false); + } + else + { + S_StopMusic(true); + } + } + } +} + +//========================================================================== +// +// 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 (!primaryLevel->IsReentering()) + { + primaryLevel->SetMusic(); + } +} + + + +//========================================================================== +// +// 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 (musicname == nullptr || musicname[0] == 0) { // Don't choke if the map doesn't have a song attached - //S_StopMusic(true); + S_StopMusic (true); mus_playing.name = ""; mus_playing.LastSong = ""; return true; @@ -55,7 +269,7 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) try { ZMusic_Start(mus_playing.handle, looping, order); - //S_CreateStream(); + S_CreateStream(); } catch (const std::runtime_error & err) { @@ -76,8 +290,8 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) { id = strtoul(more + 1, nullptr, 16); } - //S_StopMusic(true); - mus_playing.handle = ZMusic_OpenCDSong(track, id); + S_StopMusic (true); + mus_playing.handle = ZMusic_OpenCDSong (track, id); } else { @@ -119,7 +333,7 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) } // shutdown old music - //S_StopMusic(true); + S_StopMusic (true); // Just record it if volume is 0 if (mus_volume <= 0) @@ -159,8 +373,8 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) { // play it try { - //S_StartMusicPlaying(mus_playing.handle, looping, S_GetMusicVolume(musicname), order); - //S_CreateStream(); + S_StartMusicPlaying(mus_playing.handle, looping, S_GetMusicVolume(musicname), order); + S_CreateStream(); mus_playing.baseorder = order; } catch (const std::runtime_error & err) @@ -172,11 +386,152 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) return false; } -void Mus_Play(const char* fn, bool loop) +//========================================================================== +// +// S_RestartMusic +// +// Must only be called from snd_reset in i_sound.cpp! +//========================================================================== + +void S_RestartMusic () { + if (!mus_playing.LastSong.IsEmpty()) + { + FString song = mus_playing.LastSong; + mus_playing.LastSong = ""; + S_ChangeMusic (song, mus_playing.baseorder, mus_playing.loop, true); + } +} + +//========================================================================== +// +// S_MIDIDeviceChanged +// +//========================================================================== + + +void S_MIDIDeviceChanged(int newdev) +{ + MusInfo* song = mus_playing.handle; + if (song != nullptr && ZMusic_IsMIDI(song) && ZMusic_IsPlaying(song)) + { + // Reload the song to change the device + auto mi = mus_playing; + S_StopMusic(true); + S_ChangeMusic(mi.name, mi.baseorder, mi.loop); + } +} + +//========================================================================== +// +// S_GetMusic +// +//========================================================================== + +int S_GetMusic (const char **name) +{ + int order; + + if (mus_playing.name.IsNotEmpty()) + { + *name = mus_playing.name; + order = mus_playing.baseorder; + } + else + { + *name = nullptr; + order = 0; + } + return order; +} + +//========================================================================== +// +// S_StopMusic +// +//========================================================================== + +void S_StopMusic (bool force) +{ + try + { + // [RH] Don't stop if a playlist is active. + if ((force || PlayList.GetNumSongs() == 0) && !mus_playing.name.IsEmpty()) + { + if (mus_playing.handle != nullptr) + { + S_ResumeMusic(); + S_StopStream(); + ZMusic_Stop(mus_playing.handle); + auto h = mus_playing.handle; + mus_playing.handle = nullptr; + ZMusic_Close(h); + } + mus_playing.LastSong = std::move(mus_playing.name); + } + } + catch (const std::runtime_error& ) + { + //Printf("Unable to stop %s: %s\n", mus_playing.name.GetChars(), err.what()); + if (mus_playing.handle != nullptr) + { + auto h = mus_playing.handle; + mus_playing.handle = nullptr; + ZMusic_Close(h); + } + mus_playing.name = ""; + } +} } -void Mus_Stop() + +//========================================================================== +// +// CCMD changemus +// +//========================================================================== + +CCMD (changemus) +{ + if (!nomusic) + { + if (argv.argc() > 1) + { + PlayList.Clear(); + S_ChangeMusic (argv[1], argv.argc() > 2 ? atoi (argv[2]) : 0); + } + else + { + const char *currentmus = mus_playing.name.GetChars(); + if(currentmus != nullptr && *currentmus != 0) + { + Printf ("currently playing %s\n", currentmus); + } + else + { + Printf ("no music playing\n"); + } + } + } + else + { + Printf("Music is disabled\n"); + } +} + +//========================================================================== +// +// CCMD stopmus +// +//========================================================================== + +CCMD (stopmus) +{ + 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 +} + { } diff --git a/source/common/music/music_config.cpp b/source/common/music/music_config.cpp new file mode 100644 index 000000000..e28875606 --- /dev/null +++ b/source/common/music/music_config.cpp @@ -0,0 +1,359 @@ +/* +** music_config.cpp +** This forwards all CVAR changes to the music system. +** +**--------------------------------------------------------------------------- +** Copyright 1999-2016 Randy Heit +** Copyright 2005-2019 Christoph Oelckers +** All rights reserved. +** +** 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 +#include +#include "c_cvars.h" +#include "s_music.h" +#include "zmusic/zmusic.h" + +//========================================================================== +// +// +// +//========================================================================== + +#define FORWARD_CVAR(key) \ + decltype(*self) newval; \ + auto ret = ChangeMusicSetting(ZMusic::key, mus_playing.handle, *self, &newval); \ + self = (decltype(*self))newval; \ + if (ret) S_MIDIDeviceChanged(-1); + +#define FORWARD_BOOL_CVAR(key) \ + int newval; \ + auto ret = ChangeMusicSetting(ZMusic::key, mus_playing.handle,*self, &newval); \ + self = !!newval; \ + if (ret) S_MIDIDeviceChanged(-1); + +#define FORWARD_STRING_CVAR(key) \ + auto ret = ChangeMusicSetting(ZMusic::key, mus_playing.handle,*self); \ + if (ret) S_MIDIDeviceChanged(-1); + + +//========================================================================== +// +// Fluidsynth MIDI device +// +//========================================================================== + +CUSTOM_CVAR(String, fluid_lib, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(fluid_lib); +} + +CUSTOM_CVAR(String, fluid_patchset, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(fluid_patchset); +} + +CUSTOM_CVAR(Float, fluid_gain, 0.5, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_gain); +} + +CUSTOM_CVAR(Bool, fluid_reverb, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(fluid_reverb); +} + +CUSTOM_CVAR(Bool, fluid_chorus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(fluid_chorus); +} + +CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_voices); +} + +CUSTOM_CVAR(Int, fluid_interp, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_interp); +} + +CUSTOM_CVAR(Int, fluid_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_samplerate); +} + +CUSTOM_CVAR(Int, fluid_threads, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_threads); +} + +CUSTOM_CVAR(Float, fluid_reverb_roomsize, 0.61f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_reverb_roomsize); +} + +CUSTOM_CVAR(Float, fluid_reverb_damping, 0.23f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_reverb_damping); +} + +CUSTOM_CVAR(Float, fluid_reverb_width, 0.76f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_reverb_width); +} + +CUSTOM_CVAR(Float, fluid_reverb_level, 0.57f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_reverb_level); +} + +CUSTOM_CVAR(Int, fluid_chorus_voices, 3, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_chorus_voices); +} + +CUSTOM_CVAR(Float, fluid_chorus_level, 1.2f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_chorus_level); +} + +CUSTOM_CVAR(Float, fluid_chorus_speed, 0.3f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_chorus_speed); +} + +// depth is in ms and actual maximum depends on the sample rate +CUSTOM_CVAR(Float, fluid_chorus_depth, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_chorus_depth); +} + +CUSTOM_CVAR(Int, fluid_chorus_type, 0/*FLUID_CHORUS_DEFAULT_TYPE*/, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(fluid_chorus_type); +} + + +//========================================================================== +// +// OPL MIDI device +// +//========================================================================== + +CUSTOM_CVAR(Int, opl_numchips, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(opl_numchips); +} + +CUSTOM_CVAR(Int, opl_core, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(opl_core); +} + +CUSTOM_CVAR(Bool, opl_fullpan, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(opl_fullpan); +} + +//========================================================================== +// +// 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, min_sustain_time, 5000, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(min_sustain_time); +} + +CUSTOM_CVAR(String, timidity_config, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_STRING_CVAR(timidity_config); +} + +//========================================================================== +// +// This one is for Win32 MMAPI. +// +//========================================================================== + +CUSTOM_CVAR(Bool, snd_midiprecache, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(snd_midiprecache); +} + +//========================================================================== +// +// GME +// +//========================================================================== + +CUSTOM_CVAR(Float, gme_stereodepth, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(gme_stereodepth); +} + +//========================================================================== +// +// sndfile +// +//========================================================================== + +CUSTOM_CVAR(Int, snd_streambuffersize, 64, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(snd_streambuffersize); +} + +//========================================================================== +// +// Dumb +// +//========================================================================== + +CUSTOM_CVAR(Int, mod_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_samplerate); +} + +CUSTOM_CVAR(Int, mod_volramp, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_volramp); +} + +CUSTOM_CVAR(Int, mod_interp, 2/*DUMB_LQ_CUBIC*/, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_interp); +} + +CUSTOM_CVAR(Bool, mod_autochip, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_BOOL_CVAR(mod_autochip); +} + +CUSTOM_CVAR(Int, mod_autochip_size_force, 100, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_autochip_size_force); +} + +CUSTOM_CVAR(Int, mod_autochip_size_scan, 500, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_autochip_size_scan); +} + +CUSTOM_CVAR(Int, mod_autochip_scan_threshold, 12, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_autochip_scan_threshold); +} + +CUSTOM_CVAR(Float, mod_dumb_mastervolume, 1.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) +{ + FORWARD_CVAR(mod_dumb_mastervolume); +} + diff --git a/source/common/music/music_midi_base.cpp b/source/common/music/music_midi_base.cpp new file mode 100644 index 000000000..c9090d7a7 --- /dev/null +++ b/source/common/music/music_midi_base.cpp @@ -0,0 +1,81 @@ +/* +** music_midi_base.cpp +** +**--------------------------------------------------------------------------- +** Copyright 1998-2010 Randy Heit +** All rights reserved. +** +** 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. +**--------------------------------------------------------------------------- +** +*/ + +#define DEF_MIDIDEV -5 + +EXTERN_CVAR(Int, snd_mididevice) +static uint32_t nummididevices; + +#define NUM_DEF_DEVICES 7 + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include + +void I_InitMusicWin32 () +{ + nummididevices = midiOutGetNumDevs (); +} + + +#endif + +#include "c_dispatch.h" + +#include "v_text.h" +#include "zmusic/zmusic.h" +#include "s_music.h" + + + +CUSTOM_CVAR (Int, snd_mididevice, DEF_MIDIDEV, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL) +{ + if ((self >= (signed)nummididevices) || (self < -5)) + { + // Don't do repeated message spam if there is no valid device. + if (self != 0) + { + Printf("ID out of range. Using default device.\n"); + } + self = DEF_MIDIDEV; + return; + } + else if (self == -1) + { + self = DEF_MIDIDEV; + return; + } + bool change = ChangeMusicSetting(ZMusic::snd_mididevice, nullptr, self); + if (change) S_MIDIDeviceChanged(self); +}