/* The common sound font reader interface. Used by GUS, Timidity++ and WildMidi backends for reading sound font configurations. The FileInterface is also used by streaming sound formats. Copyright (C) 2019 Christoph Oelckers This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include #include #if defined _WIN32 && !defined _WINDOWS_ // only define this if windows.h is not included. // I'd rather not include Windows.h for just this. This header is not supposed to pollute everything it touches. extern "C" __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned CodePage, unsigned long dwFlags, const char* lpMultiByteStr, int cbMultiByte, const wchar_t* lpWideCharStr, int cchWideChar); enum { CP_UTF8 = 65001 }; #endif namespace MusicIO { //========================================================================== // // This class defines a common file wrapper interface which allows these // libraries to work with any kind of file access API, e.g. stdio (provided below), // Win32, POSIX, iostream or custom implementations (like GZDoom's FileReader.) // //========================================================================== struct FileInterface { std::string filename; long length = -1; // It's really too bad that the using code requires long instead of size_t. // Fortunately 2GB files are unlikely to come by here. protected: // virtual ~FileInterface() {} public: virtual char* gets(char* buff, int n) = 0; virtual long read(void* buff, int32_t size, int32_t nitems) = 0; long read(void* buff, int32_t size) { return read(buff, 1, size); } virtual long seek(long offset, int whence) = 0; virtual long tell() = 0; virtual void close() { delete this; } long filelength() { if (length == -1) { long pos = tell(); seek(0, SEEK_END); length = tell(); seek(pos, SEEK_SET); } return length; } }; //========================================================================== // // Inplementation of the FileInterface for stdio's FILE*. // //========================================================================== struct StdioFileReader : public FileInterface { FILE* f = nullptr; ~StdioFileReader() { if (f) fclose(f); } char* gets(char* buff, int n) override { if (!f) return nullptr; return fgets(buff, n, f); } long read(void* buff, int32_t size, int32_t nitems) override { if (!f) return 0; return (long)fread(buff, size, nitems, f); } long seek(long offset, int whence) override { if (!f) return 0; return fseek(f, offset, whence); } long tell() override { if (!f) return 0; return ftell(f); } }; //========================================================================== // // Inplementation of the FileInterface for a block of memory // //========================================================================== struct MemoryReader : public FileInterface { const uint8_t *mData; long mLength; long mPos; MemoryReader(const uint8_t *data, long length) : mData(data), mLength(length), mPos(0) { } char* gets(char* strbuf, int len) override { if (len > mLength - mPos) len = mLength - mPos; if (len <= 0) return NULL; char *p = strbuf; while (len > 1) { if (mData[mPos] == 0) { mPos++; break; } if (mData[mPos] != '\r') { *p++ = mData[mPos]; len--; if (mData[mPos] == '\n') { mPos++; break; } } mPos++; } if (p == strbuf) return nullptr; *p++ = 0; return strbuf; } long read(void* buff, int32_t size, int32_t nitems) override { long len = long(size) * nitems; if (len > mLength - mPos) len = mLength - mPos; if (len < 0) len = 0; memcpy(buff, mData + mPos, len); mPos += len; return len / size; } long seek(long offset, int whence) override { switch (whence) { case SEEK_CUR: offset += mPos; break; case SEEK_END: offset += mLength; break; } if (offset < 0 || offset > mLength) return -1; mPos = offset; return 0; } long tell() override { return mPos; } protected: MemoryReader() {} }; //========================================================================== // // Inplementation of the FileInterface for an std::vector owned by the reader // //========================================================================== struct VectorReader : public MemoryReader { std::vector mVector; template VectorReader(getFunc getter) // read contents to a buffer and return a reader to it { getter(mVector); mData = mVector.data(); mLength = (long)mVector.size(); mPos = 0; } }; //========================================================================== // // The follpwing two functions are needed to allow using UTF-8 in the file interface. // fopen on Windows is only safe for ASCII, // //========================================================================== #ifdef _WIN32 inline std::wstring wideString(const char *filename) { std::wstring filePathW; auto len = strlen(filename); filePathW.resize(len); int newSize = MultiByteToWideChar(CP_UTF8, 0, filename, (int)len, const_cast(filePathW.c_str()), (int)len); filePathW.resize(newSize); return filePathW; } #endif inline FILE* utf8_fopen(const char* filename, const char *mode) { #ifndef _WIN32 return fopen(filename, mode); #else auto fn = wideString(filename); auto mo = wideString(mode); return _wfopen(fn.c_str(), mo.c_str()); #endif } inline bool fileExists(const char *fn) { FILE *f = utf8_fopen(fn, "rb"); if (!f) return false; fclose(f); return true; } //========================================================================== // // This class providea a framework for reading sound fonts. // This is needed when the sound font data is not read from // the file system. e.g. zipped GUS patch sets. // //========================================================================== class SoundFontReaderInterface { public: virtual ~SoundFontReaderInterface() {} virtual struct FileInterface* open_file(const char* fn) = 0; virtual void add_search_path(const char* path) = 0; }; //========================================================================== // // A basic sound font reader for reading data from the file system. // //========================================================================== class FileSystemSoundFontReader : public SoundFontReaderInterface { protected: std::vector mPaths; std::string mBaseFile; bool mAllowAbsolutePaths; bool IsAbsPath(const char *name) { if (name[0] == '/' || name[0] == '\\') return true; #ifdef _WIN32 /* [A-Za-z]: (for Windows) */ if (isalpha(name[0]) && name[1] == ':') return true; #endif /* _WIN32 */ return 0; } public: FileSystemSoundFontReader(const char *configfilename, bool allowabs = false) { // Note that this does not add the directory the base file is in to the search path! // The caller of this has to do it themselves! mBaseFile = configfilename; mAllowAbsolutePaths = allowabs; } struct FileInterface* open_file(const char* fn) override { FILE *f = nullptr; std::string fullname; if (!fn) { f = utf8_fopen(mBaseFile.c_str(), "rt"); fullname = mBaseFile; } else { if (!IsAbsPath(fn)) { for(int i = (int)mPaths.size()-1; i>=0; i--) { fullname = mPaths[i] + fn; f = utf8_fopen(fullname.c_str(), "rt"); break; } } if (!f) f = fopen(fn, "rt"); } if (!f) return nullptr; auto tf = new StdioFileReader; tf->f = f; tf->filename = fullname; return tf; } void add_search_path(const char* path) override { std::string p = path; if (p.back() != '/' && p.back() != '\\') p += '/'; // always let it end with a slash. mPaths.push_back(p); } }; //========================================================================== // // This reader exists to trick Timidity config readers into accepting // a loose SF2 file by providing a fake config pointing to the given file. // //========================================================================== class SF2Reader : public FileSystemSoundFontReader { std::string mMainConfigForSF2; public: SF2Reader(const char *filename) : FileSystemSoundFontReader(filename) { mMainConfigForSF2 = "soundfont \"" + mBaseFile + "\"\n"; } struct FileInterface* open_file(const char* fn) override { if (fn == nullptr) { return new MemoryReader((uint8_t*)mMainConfigForSF2.c_str(), (long)mMainConfigForSF2.length()); } else return FileSystemSoundFontReader::open_file(fn); } }; }