From 02507effe89aa0e21c6357ff8d106497d4f7ff27 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 30 Sep 2019 02:35:47 +0200 Subject: [PATCH] - moved the music loader code to ZMusic. This was the final piece of code reorganization. What's left is cleaning up the interface. --- libraries/zmusic/CMakeLists.txt | 3 +- libraries/zmusic/zmusic/zmusic.cpp | 334 +++++++++++++++++++++++++++++ libraries/zmusic/zmusic/zmusic.h | 6 + src/sound/music/i_music.cpp | 261 ---------------------- src/sound/music/i_musicinterns.h | 32 --- src/sound/music/i_soundfont.cpp | 1 - src/sound/s_music.cpp | 3 +- 7 files changed, 343 insertions(+), 297 deletions(-) create mode 100644 libraries/zmusic/zmusic/zmusic.cpp delete mode 100644 src/sound/music/i_musicinterns.h diff --git a/libraries/zmusic/CMakeLists.txt b/libraries/zmusic/CMakeLists.txt index 0350202a3..879afd6af 100644 --- a/libraries/zmusic/CMakeLists.txt +++ b/libraries/zmusic/CMakeLists.txt @@ -38,7 +38,7 @@ elseif( FLUIDSYNTH_FOUND ) endif() -include_directories( "../libraries/dumb/include" "${ADL_INCLUDE_DIR}" "${OPN_INCLUDE_DIR}" "${TIMIDITYPP_INCLUDE_DIR}" "${TIMIDITY_INCLUDE_DIR}" "${WILDMIDI_INCLUDE_DIR}" "${OPLSYNTH_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" ) +include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/../libraries/dumb/include" "${ADL_INCLUDE_DIR}" "${OPN_INCLUDE_DIR}" "${TIMIDITYPP_INCLUDE_DIR}" "${TIMIDITY_INCLUDE_DIR}" "${WILDMIDI_INCLUDE_DIR}" "${OPLSYNTH_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" ) if (WIN32) set( PLAT_SOURCES @@ -87,6 +87,7 @@ add_library( zmusic STATIC decoder/sndfile_decoder.cpp decoder/mpg123_decoder.cpp zmusic/configuration.cpp + zmusic/zmusic.cpp ${PLAT_SOURCES} ) target_link_libraries( zmusic ) diff --git a/libraries/zmusic/zmusic/zmusic.cpp b/libraries/zmusic/zmusic/zmusic.cpp new file mode 100644 index 000000000..f5a0e384e --- /dev/null +++ b/libraries/zmusic/zmusic/zmusic.cpp @@ -0,0 +1,334 @@ +/* + ** i_music.cpp + ** Plays music + ** + **--------------------------------------------------------------------------- + ** Copyright 1998-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 +#include "m_swap.h" +#include "zmusic.h" +#include "midiconfig.h" +#include "musinfo.h" +#include "streamsources/streamsource.h" +#include "midisources/midisource.h" + +#define GZIP_ID1 31 +#define GZIP_ID2 139 +#define GZIP_CM 8 +#define GZIP_ID MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0) + +#define GZIP_FTEXT 1 +#define GZIP_FHCRC 2 +#define GZIP_FEXTRA 4 +#define GZIP_FNAME 8 +#define GZIP_FCOMMENT 16 + +class MIDIDevice; +class OPLmusicFile; +class StreamSource; +class MusInfo; + +MusInfo *OpenStreamSong(StreamSource *source); +const char *GME_CheckFormat(uint32_t header); +MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader); +MusInfo* CD_OpenSong(int track, int id); +MusInfo* CreateMIDIStreamer(MIDISource *source, EMidiDevice devtype, const char* args); +//========================================================================== +// +// ungzip +// +// VGZ files are compressed with gzip, so we need to uncompress them before +// handing them to GME. +// +//========================================================================== + +static bool ungzip(uint8_t *data, int complen, std::vector &newdata) +{ + const uint8_t *max = data + complen - 8; + const uint8_t *compstart = data + 10; + uint8_t flags = data[3]; + unsigned isize; + z_stream stream; + int err; + + // Find start of compressed data stream + if (flags & GZIP_FEXTRA) + { + compstart += 2 + LittleShort(*(uint16_t *)(data + 10)); + } + if (flags & GZIP_FNAME) + { + while (compstart < max && *compstart != 0) + { + compstart++; + } + } + if (flags & GZIP_FCOMMENT) + { + while (compstart < max && *compstart != 0) + { + compstart++; + } + } + if (flags & GZIP_FHCRC) + { + compstart += 2; + } + if (compstart >= max - 1) + { + return false; + } + + // Decompress + isize = LittleLong(*(uint32_t *)(data + complen - 4)); + newdata.resize(isize); + + stream.next_in = (Bytef *)compstart; + stream.avail_in = (uInt)(max - compstart); + stream.next_out = &newdata[0]; + stream.avail_out = isize; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) + { + return false; + } + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) + { + inflateEnd(&stream); + return false; + } + err = inflateEnd(&stream); + if (err != Z_OK) + { + return false; + } + return true; +} + + +//========================================================================== +// +// identify a music lump's type and set up a player for it +// +//========================================================================== + +MusInfo *I_RegisterSong (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args) +{ + MusInfo *info = nullptr; + StreamSource *streamsource = nullptr; + const char *fmt; + uint32_t id[32/4]; + + if(reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0) + { + reader->close(); + return nullptr; + } + try + { + // Check for gzip compression. Some formats are expected to have players + // that can handle it, so it simplifies things if we make all songs + // gzippable. + if ((id[0] & MAKE_ID(255, 255, 255, 0)) == GZIP_ID) + { + // swap out the reader with one that reads the decompressed content. + auto zreader = new MusicIO::VectorReader([reader](std::vector& array) + { + bool res = false; + auto len = reader->filelength(); + uint8_t* gzipped = new uint8_t[len]; + if (reader->read(gzipped, len) == len) + { + res = ungzip(gzipped, (int)len, array); + } + delete[] gzipped; + }); + reader->close(); + reader = zreader; + + + if (reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0) + { + reader->close(); + return nullptr; + } + } + + EMIDIType miditype = IdentifyMIDIType(id, sizeof(id)); + if (miditype != MIDI_NOTMIDI) + { + std::vector data(reader->filelength()); + if (reader->read(data.data(), (long)data.size()) != (long)data.size()) + { + reader->close(); + return nullptr; + } + auto source = CreateMIDISource(data.data(), data.size(), miditype); + if (source == nullptr) + { + reader->close(); + return nullptr; + } + if (!source->isValid()) + { + delete source; + return nullptr; + } + +#ifndef _WIN32 + // non-Windows platforms don't support MDEV_MMAPI so map to MDEV_SNDSYS + if (device == MDEV_MMAPI) + device = MDEV_SNDSYS; +#endif + + info = CreateMIDIStreamer(source, device, Args? Args : ""); + } + + // Check for CDDA "format" + else if (id[0] == (('R') | (('I') << 8) | (('F') << 16) | (('F') << 24))) + { + uint32_t subid; + + reader->seek(8, SEEK_CUR); + if (reader->read(&subid, 4) == 4) + { + reader->seek(-12, SEEK_CUR); + + if (subid == (('C') | (('D') << 8) | (('D') << 16) | (('A') << 24))) + { + // This is a CDDA file + info = CDDA_OpenSong(reader); + } + } + } + + // Check for various raw OPL formats + else + { + if ( + (id[0] == MAKE_ID('R', 'A', 'W', 'A') && id[1] == MAKE_ID('D', 'A', 'T', 'A')) || // Rdos Raw OPL + (id[0] == MAKE_ID('D', 'B', 'R', 'A') && id[1] == MAKE_ID('W', 'O', 'P', 'L')) || // DosBox Raw OPL + (id[0] == MAKE_ID('A', 'D', 'L', 'I') && *((uint8_t*)id + 4) == 'B')) // Martin Fernandez's modified IMF + { + streamsource = OPL_OpenSong(reader, &oplConfig); + } + else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A'))) + { + streamsource = XA_OpenSong(reader); // this takes over the reader. + reader = nullptr; // We do not own this anymore. + } + // Check for game music + else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0') + { + streamsource = GME_OpenSong(reader, fmt, miscConfig.snd_outputrate); + } + // Check for module formats + else + { + streamsource = MOD_OpenSong(reader, miscConfig.snd_outputrate); + } + if (streamsource == nullptr) + { + streamsource = SndFile_OpenSong(reader); // this only takes over the reader if it succeeds. We need to look out for this. + if (streamsource != nullptr) reader = nullptr; + } + + if (streamsource) + { + info = OpenStreamSong(streamsource); + } + } + + if (!info) + { + // File could not be identified as music. + if (reader) reader->close(); + return nullptr; + } + + if (info && !info->IsValid()) + { + delete info; + info = nullptr; + } + } + catch (...) + { + // Make sure the reader is closed if this function abnormally terminates + if (reader) reader->close(); + throw; + } + if (reader) reader->close(); + return info; +} + +//========================================================================== +// +// play CD music +// +//========================================================================== + +MusInfo *I_RegisterCDSong (int track, int id) +{ + MusInfo *info = CD_OpenSong (track, id); + + if (info && !info->IsValid ()) + { + delete info; + info = nullptr; + } + + return info; +} + +//========================================================================== +// +// +// +//========================================================================== + +void TimidityPP_Shutdown(); +extern "C" void dumb_exit(); + +void ZMusic_Shutdown() +{ + // free static data in the backends. + TimidityPP_Shutdown(); + dumb_exit(); +} diff --git a/libraries/zmusic/zmusic/zmusic.h b/libraries/zmusic/zmusic/zmusic.h index 4847ae81f..ac2580cb2 100644 --- a/libraries/zmusic/zmusic/zmusic.h +++ b/libraries/zmusic/zmusic/zmusic.h @@ -151,10 +151,16 @@ void SetDmxGus(const void* data, unsigned len); // These exports are needed by the MIDI dumpers which need to remain on the client side. class MIDISource; // abstract for the client +class MusInfo; EMIDIType IdentifyMIDIType(uint32_t *id, int size); MIDISource *CreateMIDISource(const uint8_t *data, size_t length, EMIDIType miditype); void MIDIDumpWave(MIDISource* source, EMidiDevice devtype, const char* devarg, const char* outname, int subsong, int samplerate); +MusInfo *I_RegisterSong (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args); +MusInfo *I_RegisterCDSong (int track, int cdid = 0); + +void ZMusic_Shutdown(); + class MusInfo; // Configuration interface. The return value specifies if a music restart is needed. // RealValue should be written back to the CVAR or whatever other method the client uses to store configuration state. diff --git a/src/sound/music/i_music.cpp b/src/sound/music/i_music.cpp index dd7f02d32..d8b01ac35 100644 --- a/src/sound/music/i_music.cpp +++ b/src/sound/music/i_music.cpp @@ -39,7 +39,6 @@ #include -#include "i_musicinterns.h" #include "m_argv.h" #include "w_wad.h" #include "c_dispatch.h" @@ -59,16 +58,7 @@ #include "filereadermusicinterface.h" #include "../libraries/zmusic/midisources/midisource.h" -#define GZIP_ID1 31 -#define GZIP_ID2 139 -#define GZIP_CM 8 -#define GZIP_ID MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0) -#define GZIP_FTEXT 1 -#define GZIP_FHCRC 2 -#define GZIP_FEXTRA 4 -#define GZIP_FNAME 8 -#define GZIP_FCOMMENT 16 void I_InitSoundFonts(); @@ -287,257 +277,6 @@ void I_ShutdownMusic(bool onexit) } } -//========================================================================== -// -// identify a music lump's type and set up a player for it -// -//========================================================================== - -MusInfo *I_RegisterSong (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args) -{ - MusInfo *info = nullptr; - StreamSource *streamsource = nullptr; - const char *fmt; - uint32_t id[32/4]; - - if(reader->read(id, 32) != 32 || reader->seek(-32, FileReader::SeekCur) != 0) - { - reader->close(); - return nullptr; - } - try - { - // Check for gzip compression. Some formats are expected to have players - // that can handle it, so it simplifies things if we make all songs - // gzippable. - if ((id[0] & MAKE_ID(255, 255, 255, 0)) == GZIP_ID) - { - // swap out the reader with one that reads the decompressed content. - auto zreader = new MusicIO::VectorReader([reader](std::vector& array) - { - bool res = false; - auto len = reader->filelength(); - uint8_t* gzipped = new uint8_t[len]; - if (reader->read(gzipped, len) == len) - { - res = ungzip(gzipped, (int)len, array); - } - delete[] gzipped; - }); - reader->close(); - reader = zreader; - - - if (reader->read(id, 32) != 32 || reader->seek(-32, FileReader::SeekCur) != 0) - { - reader->close(); - return nullptr; - } - } - - EMIDIType miditype = IdentifyMIDIType(id, sizeof(id)); - if (miditype != MIDI_NOTMIDI) - { - std::vector data(reader->filelength()); - if (reader->read(data.data(), (long)data.size()) != (long)data.size()) - { - reader->close(); - return nullptr; - } - auto source = CreateMIDISource(data.data(), data.size(), miditype); - if (source == nullptr) - { - reader->close(); - return nullptr; - } - if (!source->isValid()) - { - delete source; - return nullptr; - } - -#ifndef _WIN32 - // non-Windows platforms don't support MDEV_MMAPI so map to MDEV_SNDSYS - if (device == MDEV_MMAPI) - device = MDEV_SNDSYS; -#endif - - info = CreateMIDIStreamer(source, device, Args? Args : ""); - } - - // Check for CDDA "format" - else if (id[0] == (('R') | (('I') << 8) | (('F') << 16) | (('F') << 24))) - { - uint32_t subid; - - reader->seek(8, SEEK_CUR); - if (reader->read(&subid, 4) == 4) - { - reader->seek(-12, SEEK_CUR); - - if (subid == (('C') | (('D') << 8) | (('D') << 16) | (('A') << 24))) - { - // This is a CDDA file - info = CDDA_OpenSong(reader); - } - } - } - - // Check for various raw OPL formats - else - { - if ( - (id[0] == MAKE_ID('R', 'A', 'W', 'A') && id[1] == MAKE_ID('D', 'A', 'T', 'A')) || // Rdos Raw OPL - (id[0] == MAKE_ID('D', 'B', 'R', 'A') && id[1] == MAKE_ID('W', 'O', 'P', 'L')) || // DosBox Raw OPL - (id[0] == MAKE_ID('A', 'D', 'L', 'I') && *((uint8_t*)id + 4) == 'B')) // Martin Fernandez's modified IMF - { - streamsource = OPL_OpenSong(reader, &oplConfig); - } - else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A'))) - { - streamsource = XA_OpenSong(reader); // this takes over the reader. - reader = nullptr; // We do not own this anymore. - } - // Check for game music - else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0') - { - streamsource = GME_OpenSong(reader, fmt, (int)GSnd->GetOutputRate()); - } - // Check for module formats - else - { - streamsource = MOD_OpenSong(reader, (int)GSnd->GetOutputRate()); - } - if (streamsource == nullptr) - { - streamsource = SndFile_OpenSong(reader); // this only takes over the reader if it succeeds. We need to look out for this. - if (streamsource != nullptr) reader = nullptr; - } - - if (streamsource) - { - info = OpenStreamSong(streamsource); - } - } - - if (!info) - { - // File could not be identified as music. - if (reader) reader->close(); - return nullptr; - } - - if (info && !info->IsValid()) - { - delete info; - info = nullptr; - } - } - catch (...) - { - // Make sure the reader is closed if this function abnormally terminates - if (reader) reader->close(); - throw; - } - if (reader) reader->close(); - return info; -} - -//========================================================================== -// -// play CD music -// -//========================================================================== - -MusInfo *I_RegisterCDSong (int track, int id) -{ - MusInfo *info = CD_OpenSong (track, id); - - if (info && !info->IsValid ()) - { - delete info; - info = nullptr; - } - - return info; -} - -//========================================================================== -// -// ungzip -// -// VGZ files are compressed with gzip, so we need to uncompress them before -// handing them to GME. -// -//========================================================================== - -static bool ungzip(uint8_t *data, int complen, std::vector &newdata) -{ - const uint8_t *max = data + complen - 8; - const uint8_t *compstart = data + 10; - uint8_t flags = data[3]; - unsigned isize; - z_stream stream; - int err; - - // Find start of compressed data stream - if (flags & GZIP_FEXTRA) - { - compstart += 2 + LittleShort(*(uint16_t *)(data + 10)); - } - if (flags & GZIP_FNAME) - { - while (compstart < max && *compstart != 0) - { - compstart++; - } - } - if (flags & GZIP_FCOMMENT) - { - while (compstart < max && *compstart != 0) - { - compstart++; - } - } - if (flags & GZIP_FHCRC) - { - compstart += 2; - } - if (compstart >= max - 1) - { - return false; - } - - // Decompress - isize = LittleLong(*(uint32_t *)(data + complen - 4)); - newdata.resize(isize); - - stream.next_in = (Bytef *)compstart; - stream.avail_in = (uInt)(max - compstart); - stream.next_out = &newdata[0]; - stream.avail_out = isize; - stream.zalloc = (alloc_func)0; - stream.zfree = (free_func)0; - - err = inflateInit2(&stream, -MAX_WBITS); - if (err != Z_OK) - { - return false; - } - err = inflate(&stream, Z_FINISH); - if (err != Z_STREAM_END) - { - inflateEnd(&stream); - return false; - } - err = inflateEnd(&stream); - if (err != Z_OK) - { - return false; - } - return true; -} - //========================================================================== // // diff --git a/src/sound/music/i_musicinterns.h b/src/sound/music/i_musicinterns.h deleted file mode 100644 index c316ba27d..000000000 --- a/src/sound/music/i_musicinterns.h +++ /dev/null @@ -1,32 +0,0 @@ - -#include -#include -#include -#include "zmusic/zmusic.h" - -class MIDISource; -class MIDIDevice; -class OPLmusicFile; -class StreamSource; -class MusInfo; - -MusInfo *OpenStreamSong(StreamSource *source); -const char *GME_CheckFormat(uint32_t header); -MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader); -MusInfo* CD_OpenSong(int track, int id); -MusInfo* CreateMIDIStreamer(MIDISource *source, EMidiDevice devtype, const char* args); - -// Registers a song handle to song data. - -MusInfo *I_RegisterSong (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args); -MusInfo *I_RegisterCDSong (int track, int cdid = 0); - -void TimidityPP_Shutdown(); -extern "C" void dumb_exit(); - -inline void ZMusic_Shutdown() -{ - // free static data in the backends. - TimidityPP_Shutdown(); - dumb_exit(); -} diff --git a/src/sound/music/i_soundfont.cpp b/src/sound/music/i_soundfont.cpp index 4f4bb1110..f14e53842 100644 --- a/src/sound/music/i_soundfont.cpp +++ b/src/sound/music/i_soundfont.cpp @@ -42,7 +42,6 @@ #include "filereadermusicinterface.h" #include "zmusic/zmusic.h" #include "resourcefiles/resourcefile.h" -#include "../libraries/timidityplus/timiditypp/common.h" //========================================================================== // diff --git a/src/sound/s_music.cpp b/src/sound/s_music.cpp index d0fc7a800..fd344dcdd 100644 --- a/src/sound/s_music.cpp +++ b/src/sound/s_music.cpp @@ -88,8 +88,7 @@ #include "s_music.h" #include "filereadermusicinterface.h" #include "zmusic/musinfo.h" - -#include "i_musicinterns.h" +#include "zmusic/zmusic.h" // MACROS ------------------------------------------------------------------