From a6fa9067640eb9679d5a094236a70a4cc7070d11 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 22 Feb 2018 12:51:45 +0100 Subject: [PATCH] Framework for context independent sounffont management Not tested yet! --- src/CMakeLists.txt | 1 + src/cmdlib.cpp | 17 + src/cmdlib.h | 1 + src/sound/i_music.cpp | 39 +-- src/sound/i_soundfont.cpp | 572 ++++++++++++++++++++++++++++++++ src/sound/timiditypp/common.cpp | 16 +- src/sound/timiditypp/common.h | 1 - src/w_wad.cpp | 16 + src/w_wad.h | 1 + 9 files changed, 612 insertions(+), 52 deletions(-) create mode 100644 src/sound/i_soundfont.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b1a46f54..c85c0a586 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1137,6 +1137,7 @@ set (PCH_SOURCES sfmt/SFMT.cpp sound/i_music.cpp sound/i_sound.cpp + sound/i_soundfont.cpp sound/mididevices/music_opldumper_mididevice.cpp sound/mididevices/music_opl_mididevice.cpp sound/mididevices/music_pseudo_mididevice.cpp diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp index e58b3569f..af89cb352 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -1061,3 +1061,20 @@ void ScanDirectory(TArray &list, const char *dirpath) delete[] argv[0]; } #endif + + +//========================================================================== +// +// +// +//========================================================================== + +bool IsAbsPath(const char *name) +{ + if (IsSeperator(name[0])) return true; +#ifdef _WIN32 + /* [A-Za-z]: (for Windows) */ + if (isalpha(name[0]) && name[1] == ':') return true; +#endif /* _WIN32 */ + return 0; +} diff --git a/src/cmdlib.h b/src/cmdlib.h index 76a2798da..7a5fa21a6 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -62,6 +62,7 @@ struct FFileList }; void ScanDirectory(TArray &list, const char *dirpath); +bool IsAbsPath(const char*); inline int Tics2Seconds(int tics) diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 4bf77fa7c..43762fab9 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -65,6 +65,8 @@ extern void ChildSigHandler (int signum); #include "timidity/timidity.h" #include "vm.h" + + #define GZIP_ID1 31 #define GZIP_ID2 139 #define GZIP_CM 8 @@ -664,43 +666,6 @@ void I_SetMusicVolume (float factor) snd_musicvolume.Callback(); } -//========================================================================== -// -// -// -//========================================================================== - -void I_CollectSoundfonts() -{ - FString path = M_GetDocumentsPath(); - TArray sffiles; - const char *match; - findstate_t c_file; - void *file; - - path << "/soundfonts/*"; - - if ((file = I_FindFirst(path, &c_file)) == ((void *)(-1))) - { - return; - } - path.StripRight("*"); - do - { - if (!I_FindAttr(&c_file) & FA_DIREC) - { - FStringf name("%s%s", path.GetChars(), I_FindName(&c_file)); - sffiles.Push(name); - } - } while (I_FindNext(file, &c_file) == 0); - I_FindClose(file); - - auto soundfonts = new FWadCollection; - soundfonts->InitMultipleFiles(sffiles); - - -} - DEFINE_ACTION_FUNCTION(DObject, SetMusicVolume) { PARAM_PROLOGUE; diff --git a/src/sound/i_soundfont.cpp b/src/sound/i_soundfont.cpp new file mode 100644 index 000000000..50714e9a9 --- /dev/null +++ b/src/sound/i_soundfont.cpp @@ -0,0 +1,572 @@ +/* +** 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 + +#include "i_musicinterns.h" +#include "doomtype.h" +#include "m_argv.h" +#include "i_music.h" +#include "w_wad.h" +#include "cmdlib.h" +#include "files.h" + +#include "gameconfigfile.h" +#include "resourcefiles/resourcefile.h" +#include "pathexpander.h" + +enum +{ + SF_SF2 = 1, + SF_GUS = 2 +}; + +struct FSoundFontInfo +{ + FString mName; // This is what the sounfont is identified with. It's the extension-less base file name + FString mFilename; // Full path to the backing file - this is needed by FluidSynth to load the sound font. + int type; +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FSoundFontReader +{ +protected: + FWadCollection *collection; + FString mMainConfigForSF2; + int mFindInfile; + // 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 (since it repurposes the same implementation.) + bool mCaseSensitivePaths = false; + TArray mPaths; + + + FSoundFontReader() + { + collection = nullptr; + mFindInfile = -1; + } + + int pathcmp(const char *p1, const char *p2); + + +public: + + FSoundFontReader(FWadCollection *coll, const FSoundFontInfo *sfi); + virtual FileReader *OpenMainConfigFile(); // this is special because it needs to be synthesized for .sf files and set some restrictions for patch sets + virtual FileReader *OpenFile(const char *name); + std::pair LookupFile(const char *name); + void AddPath(const char *str); +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FSF2Reader : FSoundFontReader +{ + FString mFilename; +public: + FSF2Reader(const char *filename); + virtual FileReader *OpenFile(const char *name) override; +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FZipPatReader : FSoundFontReader +{ + FResourceFile *resf; +public: + FZipPatReader(const char *filename); + ~FZipPatReader(); + virtual FileReader *OpenFile(const char *name) override; +}; + +//========================================================================== +// +// This one gets ugly... +// +//========================================================================== + +class FPatchSetReader : FSoundFontReader +{ + FString mBasePath; + FString mFullPathToConfig; + +public: + FPatchSetReader(const char *filename); + ~FPatchSetReader(); + virtual FileReader *OpenMainConfigFile() override; + virtual FileReader *OpenFile(const char *name) override; +}; + +//========================================================================== +// +// +// +//========================================================================== + +class FSoundFontManager +{ + TArray soundfonts; + FWadCollection soundfontcollection; + + + void ProcessOneFile(const FString & fn, TArray &sffiles); + +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 + +}; + +//========================================================================== +// +// +// +//========================================================================== + +FSoundFontReader::FSoundFontReader(FWadCollection *coll, const FSoundFontInfo *sfi) +{ + collection = coll; + if (sfi->type == SF_SF2) + { + mMainConfigForSF2.Format("soundfont %s", sfi->mFilename.GetChars()); + } + if (coll != nullptr) + { + auto num = coll->GetNumWads(); + for(int i = 0; i < num; i++) + { + auto wadname = ExtractFileBase(coll->GetWadFullName(i), false); + if (sfi->mName.CompareNoCase(wadname) == 0) + { + // For the given sound font we may only read from this file and no other. + // This is to avoid conflicts with duplicate patches between patch sets. + // For SF2 fonts it's just an added precaution in cause some zipped patch set + // contains extraneous data that gets in the way. + mFindInfile = i; + break; + } + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +FileReader *FSoundFontReader::OpenMainConfigFile() +{ + if (mMainConfigForSF2.IsNotEmpty()) + { + return new MemoryReader(mMainConfigForSF2.GetChars(), mMainConfigForSF2.Len()); + } + else + { + auto lump = collection->CheckNumForFullName("timidity.cfg", mFindInfile); + return lump < 0? nullptr : collection->ReopenLumpNum(lump); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +FileReader *FSoundFontReader::OpenFile(const char *name) +{ + auto lump = collection->CheckNumForFullName(name, mFindInfile); + if (lump >= 0) + { + // For SF2 files return the backing file reader to avoid reopening the file. + // Note that there is no possibility of a non-SF2 resource file having only one lump due to the check in the init code. + if (collection->GetLumpCount(mFindInfile) == 1) + { + auto fr = collection->GetFileReader(mFindInfile); + fr->Seek(0, SEEK_SET); + return fr; + } + return collection->ReopenLumpNum(lump); + } + return nullptr; +} + +//========================================================================== +// +// returns both a file reader and the full name of the looked up file +// +//========================================================================== + +std::pair FSoundFontReader::LookupFile(const char *name) +{ + if (IsAbsPath(name)) + { + auto fr = OpenFile(name); + if (fr != nullptr) return std::make_pair(fr, name); + } + else + { + for(int i = mPaths.Size()-1; i>=0; i--) + { + FString fullname = mPaths[i] + name; + auto fr = OpenFile(fullname); + if (fr != nullptr) return std::make_pair(fr, fullname); + } + } + return std::make_pair(nullptr, FString()); +} + +//========================================================================== +// +// 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; + FixPathSeperator(str); + 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); +} + +//========================================================================== +// +// Note that the file type has already been checked +// +//========================================================================== + +FSF2Reader::FSF2Reader(const char *fn) +{ + mMainConfigForSF2.Format("soundfont %s", fn); + mFilename = fn; +} + +FileReader *FSF2Reader::OpenFile(const char *name) +{ + if (mFilename.CompareNoCase(name) == 0) + { + auto fr = new FileReader; + if (fr->Open(name)) return fr; + delete fr; + } + return nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FZipPatReader::FZipPatReader(const char *filename) +{ + resf = FResourceFile::OpenResourceFile(filename, nullptr); +} + +FZipPatReader::~FZipPatReader() +{ + if (resf != nullptr) delete resf; +} + +FileReader *FZipPatReader::OpenFile(const char *name) +{ + if (resf != nullptr) + { + auto lump = resf->FindLump(name); + if (lump != nullptr) + { + return lump->NewReader(); + } + } + return nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +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 = new FileReader; + if (fr->Open(filename)) + { + mFullPathToConfig = filename; + } + else if (!IsAbsPath(filename)) + { + for(auto c : paths) + { + FStringf fullname("%s/%s", c, filename); + if (fr->Open(fullname)) + { + mFullPathToConfig = fullname; + } + } + } + if (mFullPathToConfig.Len() > 0) + { + FixPathSeperator(mFullPathToConfig); + mBasePath = ExtractFilePath(mFullPathToConfig); + if (mBasePath.Len() > 0 && mBasePath.Back() != '/') mBasePath += '/'; + } +} + +FileReader *FPatchSetReader::OpenMainConfigFile() +{ + auto fr = new FileReader; + if (fr->Open(mBasePath)) return fr; + delete fr; + return nullptr; +} + +FileReader *FPatchSetReader::OpenFile(const char *name) +{ + FString path; + if (IsAbsPath(name)) path = name; + else path = mBasePath + name; + auto fr = new FileReader; + if (fr->Open(path)) return fr; + delete fr; + return nullptr; +} + +//========================================================================== +// +// 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, TArray &sffiles) +{ + auto fb = ExtractFileBase(fn, false); + 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.Open(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)) + { + sffiles.Push(fn); + FSoundFontInfo sft = { fb, fn, SF_SF2 }; + soundfonts.Push(sft); + } + else if (!memcmp(head, "PK", 2)) + { + auto zip = FResourceFile::OpenResourceFile(fn, &fr); + if (zip != nullptr && 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 + sffiles.Push(fn); + FSoundFontInfo sft = { fb, fn, SF_GUS }; + soundfonts.Push(sft); + } + delete zip; + } + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FSoundFontManager::CollectSoundfonts() +{ + findstate_t c_file; + void *file; + TArray sffiles; + + + if (GameConfig != NULL && GameConfig->SetSection ("FileSearch.Directories")) + { + const char *key; + const char *value; + + while (GameConfig->NextInSection (key, value)) + { + if (stricmp (key, "Path") == 0) + { + FString dir; + + dir = NicePath(value); + if (dir.IsNotEmpty()) + { + if (dir.Back() != '/') dir += '/'; + FString path = dir + '*'; + if ((file = I_FindFirst(path, &c_file)) != ((void *)(-1))) + { + do + { + if (!(I_FindAttr(&c_file) & FA_DIREC)) + { + FStringf name("%s%s", path.GetChars(), I_FindName(&c_file)); + ProcessOneFile(name, sffiles); + } + } while (I_FindNext(file, &c_file) == 0); + I_FindClose(file); + } + } + } + } + } + soundfontcollection.InitMultipleFiles(sffiles); +} + +//========================================================================== +// +// +// +//========================================================================== + +const FSoundFontInfo *FSoundFontManager::FindSoundFont(const char *name, int allowed) const +{ + for(auto &sfi : soundfonts) + { + if (allowed & sfi.type && !sfi.mFilename.CompareNoCase(name)) + { + return &sfi; + } + } + return nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSoundFontReader *FSoundFontManager::OpenSoundFont(const char *name, int allowed) +{ + auto sfi = FindSoundFont(name, allowed); + if (sfi != nullptr) + { + return new FSoundFontReader(&soundfontcollection, sfi); + } + // 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.Open(name)) + { + char head[16] = { 0}; + fr.Read(head, 16); + if (!memcmp(head, "RIFF", 4) && !memcmp(head+8, "sfbkLIST", 8)) + { + FSoundFontInfo sft = { name, name, SF_SF2 }; + soundfonts.Push(sft); + } + + } + } + return nullptr; + +} + + diff --git a/src/sound/timiditypp/common.cpp b/src/sound/timiditypp/common.cpp index dc51ae98e..8ea7eb05d 100644 --- a/src/sound/timiditypp/common.cpp +++ b/src/sound/timiditypp/common.cpp @@ -222,7 +222,7 @@ std::pair PathList::openFile(const char *name, bool i { /* First try the given name */ - if (!isAbsPath(name)) + if (!IsAbsPath(name)) { for (int i = (int)paths.size() - 1; i >= 0; i--) { @@ -253,18 +253,6 @@ std::pair PathList::openFile(const char *name, bool i return std::make_pair(nullptr, std::string()); } -int PathList::isAbsPath(const char *name) -{ - if (name[0] == '/') return 1; - -#ifdef _WIN32 - /* [A-Za-z]: (for Windows) */ - if (isalpha(name[0]) && name[1] == ':') return 1; -#endif /* _WIN32 */ - return 0; -} - - struct timidity_file *open_file(const char *name, bool ismainfile, PathList &pathList) { auto file = pathList.openFile(name, ismainfile); @@ -320,4 +308,4 @@ long tf_tell(struct timidity_file *tf) return tf->url->Tell(); } -} \ No newline at end of file +} diff --git a/src/sound/timiditypp/common.h b/src/sound/timiditypp/common.h index 61b289ae4..5120d2203 100644 --- a/src/sound/timiditypp/common.h +++ b/src/sound/timiditypp/common.h @@ -36,7 +36,6 @@ class PathList { std::vector paths; - int isAbsPath(const char *name); FileReader *tryOpenPath(const char *name, bool ismain); public: diff --git a/src/w_wad.cpp b/src/w_wad.cpp index 1e1de76f0..21c35e669 100644 --- a/src/w_wad.cpp +++ b/src/w_wad.cpp @@ -1423,6 +1423,22 @@ int FWadCollection::GetLastLump (int wadnum) const return Files[wadnum]->GetFirstLump() + Files[wadnum]->LumpCount() - 1; } +//========================================================================== +// +// +//========================================================================== + +int FWadCollection::GetLumpCount (int wadnum) const +{ + if ((uint32_t)wadnum >= Files.Size()) + { + return 0; + } + + return Files[wadnum]->LumpCount(); +} + + //========================================================================== // // W_GetWadFullName diff --git a/src/w_wad.h b/src/w_wad.h index ddca55ed1..11e4a954d 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -146,6 +146,7 @@ public: int GetFirstLump(int wadnum) const; int GetLastLump(int wadnum) const; + int GetLumpCount(int wadnum) const; int CheckNumForName (const char *name, int namespc); int CheckNumForName (const char *name, int namespc, int wadfile, bool exact = true);