AUDIO: Add libopenmpt support

This commit is contained in:
polyesterswing 2024-01-31 17:43:21 +05:30 committed by Eugene Sandulenko
parent ea03d64b62
commit 15a9c63565
12 changed files with 299 additions and 36 deletions

View file

@ -33,12 +33,6 @@
#define FORBIDDEN_SYMBOL_EXCEPTION_longjmp
#endif
#include "audio/mods/impulsetracker.h"
#ifdef USE_MIKMOD
#include <mikmod.h>
#include "common/inttypes.h"
#include "common/ptr.h"
#include "common/stream.h"
@ -48,8 +42,61 @@
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
// Start memory wrapper
// Extras, mikmod mreader struct to wrap readstream
#ifdef USE_OPENMPT
#include <libopenmpt/libopenmpt.h>
#elif defined(USE_MIKMOD)
#include <mikmod.h>
#endif
#ifdef USE_OPENMPT
static size_t memoryReaderRead(void *stream, void *dst, size_t bytes) {
Common::SeekableReadStream *reader = (Common::SeekableReadStream *)stream;
if (!reader) {
return 0;
}
uint32 receivedBytes = reader->read(dst, bytes);
if (receivedBytes < bytes) {
return 0;
}
return receivedBytes;
}
static int memoryReaderSeek(void *stream, int64_t offset, int whence) {
Common::SeekableReadStream *reader = (Common::SeekableReadStream *)stream;
if (!reader) {
return -1;
}
bool ret = reader->seek(offset, whence);
if (ret)
return 0;
return -1;
}
static int64_t memoryReaderTell(void *stream) {
Common::SeekableReadStream *reader = (Common::SeekableReadStream *)stream;
if (reader) {
return reader->pos();
}
return -1;
}
#elif defined(USE_MIKMOD)
typedef struct MikMemoryReader {
MREADER core;
Common::SeekableReadStream *stream;
@ -131,11 +178,106 @@ static long memoryReaderTell(MREADER *reader) {
}
// End memory wrappper
#endif // USE_MIKMOD
#ifdef USE_OPENMPT
namespace Audio {
class ImpulseTrackerMod : public RewindableAudioStream {
class UniversalTrackerMod : public RewindableAudioStream {
public:
ImpulseTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
~ImpulseTrackerMod();
UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate);
~UniversalTrackerMod();
// ImpulseTrackerMod functions
bool isLoaded() {
return _mpt_load_successful;
}
int readBuffer(int16 *buffer, const int numSamples) override {
openmpt_module_read_interleaved_stereo(_mod, getRate(), numSamples / 2, buffer);
return numSamples;
}
bool isStereo() const override {
return true;
}
int getRate() const override {
return _sampleRate;
}
bool endOfData() const override {
return _stream->eos();
}
bool endOfStream() const override {
return endOfData();
}
// RewindableAudioStream API
bool rewind() override {
openmpt_module_set_position_seconds(_mod, 0.0);
return true;
}
private:
DisposeAfterUse::Flag _dispose;
bool _mpt_load_successful = false;
Common::SeekableReadStream *_stream;
openmpt_module *_mod = nullptr;
int _sampleRate;
};
UniversalTrackerMod::UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate) {
if (!stream) {
warning("UniversalTrackerMod::UniversalTrackerMod(): Input file/stream is invalid.");
return;
}
int mod_err;
const char *mod_err_str = NULL;
_stream = stream;
_dispose = disposeAfterUse;
_sampleRate = rate;
openmpt_stream_callbacks stream_callbacks;
stream_callbacks.read = &memoryReaderRead;
stream_callbacks.seek = &memoryReaderSeek;
stream_callbacks.tell = &memoryReaderTell;
_mod = openmpt_module_create2(stream_callbacks, _stream, NULL, NULL, NULL, NULL, &mod_err, &mod_err_str, NULL);
if (!_mod) {
mod_err_str = openmpt_error_string(mod_err);
warning("UniversalTrackerMod::UniversalTrackerMod(): Parsing mod error: %s ", mod_err_str);
openmpt_free_string(mod_err_str);
return;
}
_mpt_load_successful = true;
}
UniversalTrackerMod::~UniversalTrackerMod() {
if (_mod)
openmpt_module_destroy(_mod);
if (_dispose == DisposeAfterUse::Flag::YES)
delete _stream;
}
} // End of namespace Audio
#endif // #ifdef USE_OPENMPT
#ifdef USE_MIKMOD
namespace Audio {
class UniversalTrackerMod : public RewindableAudioStream {
public:
UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate);
~UniversalTrackerMod();
// ImpulseTrackerMod functions
bool isLoaded() {
@ -154,7 +296,7 @@ public:
return true;
}
int getRate() const override {
return 44100;
return _sampleRate;
}
bool endOfData() const override {
return !Player_Active();
@ -175,14 +317,17 @@ private:
Common::SeekableReadStream *_stream;
MREADER *_reader = nullptr;
MODULE *_mod = nullptr;
int _sampleRate;
};
ImpulseTrackerMod::ImpulseTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
UniversalTrackerMod::UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate) {
if (!stream) {
warning("ImpulseTrackerMod::ImpulseTrackerMod(): Input file/stream is invalid.");
warning("UniversalTrackerMod::UniversalTrackerMod(): Input file/stream is invalid.");
return;
}
_sampleRate = rate;
MikMod_InitThreads();
MikMod_RegisterDriver(&drv_nos);
@ -191,7 +336,7 @@ ImpulseTrackerMod::ImpulseTrackerMod(Common::SeekableReadStream *stream, Dispose
md_mixfreq = getRate();
if (MikMod_Init("")) {
warning("ImpulseTrackerMod::ImpulseTrackerMod(): Could not initialize sound, reason: %s",
warning("UniversalTrackerMod::UniversalTrackerMod(): Could not initialize sound, reason: %s",
MikMod_strerror(MikMod_errno));
return;
}
@ -206,7 +351,7 @@ ImpulseTrackerMod::ImpulseTrackerMod(Common::SeekableReadStream *stream, Dispose
_reader = createMikMemoryReader(_stream);
_mod = Player_LoadGeneric(_reader, 64, 0);
if (!_mod) {
warning("ImpulseTrackerMod::ImpulseTrackerMod(): Parsing mod error: %s", MikMod_strerror(MikMod_errno));
warning("UniversalTrackerMod::UniversalTrackerMod(): Parsing mod error: %s", MikMod_strerror(MikMod_errno));
return;
}
@ -215,7 +360,7 @@ ImpulseTrackerMod::ImpulseTrackerMod(Common::SeekableReadStream *stream, Dispose
_mikmod_load_successful = true;
}
ImpulseTrackerMod::~ImpulseTrackerMod() {
UniversalTrackerMod::~UniversalTrackerMod() {
Player_Stop();
if (_mod)
@ -230,12 +375,16 @@ ImpulseTrackerMod::~ImpulseTrackerMod() {
MikMod_Exit();
}
RewindableAudioStream *makeImpulseTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
ImpulseTrackerMod *impulseTrackerMod = new ImpulseTrackerMod(stream, disposeAfterUse);
return impulseTrackerMod;
}
bool probeImpulseTracker(Common::SeekableReadStream *st) {
} // End of namespace Audio
#endif // #ifdef USE_MIKMOD
namespace Audio {
bool probeUniversalTracker(Common::SeekableReadStream *st) {
#ifdef USE_MIKMOD
int32 setPos = st->pos();
// xm file
@ -246,9 +395,45 @@ bool probeImpulseTracker(Common::SeekableReadStream *st) {
return true;
}
#elif defined(USE_OPENMPT)
openmpt_stream_callbacks stream_callbacks;
stream_callbacks.read = &memoryReaderRead;
stream_callbacks.seek = &memoryReaderSeek;
stream_callbacks.tell = &memoryReaderTell;
uint result = openmpt_probe_file_header_from_stream(OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT, stream_callbacks, st, NULL, NULL, NULL, NULL, NULL, NULL);
if (result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS) {
return true;
}
#endif
return false;
}
} // End of namespace Audio
RewindableAudioStream *makeUniversalTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate) {
#endif // #ifdef USE_MIKMOD
#if !defined(USE_OPENMPT) && !defined(USE_MIKMOD)
warning("Modplayer Support not compiled in");
return nullptr;
#else
if (!probeUniversalTracker(stream)) {
return nullptr;
}
UniversalTrackerMod *impulseTrackerMod = new UniversalTrackerMod(stream, disposeAfterUse, rate);
if (!impulseTrackerMod->isLoaded()) {
delete impulseTrackerMod;
return nullptr;
}
return impulseTrackerMod;
#endif
}
}

View file

@ -25,13 +25,15 @@
* - sludge
*/
#ifndef AUDIO_IMPULSETRACKER_H
#define AUDIO_IMPULSETRACKER_H
#ifndef AUDIO_UNIVERSALTRACKER_H
#define AUDIO_UNIVERSALTRACKER_H
#include "common/scummsys.h"
#include "common/types.h"
#ifdef USE_MIKMOD
#if defined(USE_MIKMOD) && defined(USE_OPENMPT)
#error "MikMod and OpenMPT are mutually exclusive"
#endif
namespace Common {
class SeekableReadStream;
@ -47,14 +49,13 @@ class RewindableAudioStream;
* @param disposeAfterUse whether to delete the stream after use
* @return a new AudioStream, or NULL, if an error occurred
*/
RewindableAudioStream *makeImpulseTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
RewindableAudioStream *makeUniversalTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate = 48000);
/**
* Check if the stream is one of the supported formats
*/
bool probeImpulseTracker(Common::SeekableReadStream *stream);
bool probeUniversalTracker(Common::SeekableReadStream *stream);
} // End of namespace Audio
#endif // #ifdef USE_MIKMOD
#endif // #ifndef AUDIO_IMPULSETRACKER_H
#endif // #ifndef AUDIO_UNIVERSALTRACKER_H

View file

@ -43,7 +43,7 @@ MODULE_OBJS := \
decoders/wma.o \
decoders/xa.o \
decoders/xan_dpcm.o \
mods/impulsetracker.o \
mods/universaltracker.o \
mods/infogrames.o \
mods/maxtrax.o \
mods/mod_xm_s3m.o \

View file

@ -145,6 +145,10 @@ const char gScummVMFeatures[] = ""
"MikMod "
#endif
#ifdef USE_OPENMPT
"OpenMPT "
#endif
#ifdef USE_THEORADEC
"Theora "
#endif

55
configure vendored
View file

@ -170,6 +170,7 @@ _tinygl=yes
_readline=auto
_freetype2=auto
_libmikmod=auto
_libopenmpt=auto
_taskbar=auto
_updates=no
_libunity=auto
@ -254,6 +255,7 @@ _sdlpath="$PATH"
_freetypepath="$PATH"
_libcurlpath="$PATH"
_libmikmodpath="$PATH"
_libopenmptpath="$PATH"
_nasmpath="$PATH"
NASMFLAGS=""
NASM=""
@ -1026,6 +1028,9 @@ Optional Libraries:
--with-mikmod-prefix=DIR Prefix where libmikmod is installed (optional)
--enable-mikmod enable mikmod module for playing various sound formats like impulsetracker [autodetect]
--with-openmpt-prefix=DIR Prefix where libopenmpt is installed (optional)
--enable-openmpt enable openmpt module for playing various sound formats like impulsetracker [autodetect]
--with-a52-prefix=DIR Prefix where liba52 is installed (optional)
--enable-a52 enable a52 codec for MPEG decoder [autodetect]
@ -1221,6 +1226,8 @@ for ac_option in $@; do
--disable-mpeg2) _mpeg2=no ;;
--enable-mikmod) _libmikmod=yes ;;
--disable-mikmod) _libmikmod=no ;;
--enable-openmpt) _libopenmpt=yes ;;
--disable-openmpt) _libopenmpt=no ;;
--enable-a52) _a52=yes ;;
--disable-a52) _a52=no ;;
--disable-jpeg) _jpeg=no ;;
@ -1535,6 +1542,10 @@ for ac_option in $@; do
arg=`echo $ac_option | cut -d '=' -f 2`
_libmikmodpath="$arg:$arg/bin"
;;
--with-openmpt-prefix=*)
arg=`echo $ac_option | cur -d '=' -f 2`
_libopenmptpath="$arg:$arg/bin"
;;
--with-nasm-prefix=*)
arg=`echo $ac_option | cut -d '=' -f 2`
_nasmpath="$arg:$arg/bin"
@ -5809,6 +5820,50 @@ echo "$_libcurl"
define_in_config_if_yes "$_libcurl" "USE_LIBCURL"
#
# Check for libopenmpt to be present
#
if test "$_libopenmpt" != "no"; then
echocheck "libopenmpt"
if test "$_pkg_config" = "yes" && $_pkgconfig --exists libopenmpt; then
append_var LIBOPENMPT_LIBS "`$_pkgconfig --libs libopenmpt`"
append_var LIBOPENMPT_CFLAGS "`$_pkgconfig --cflags libopenmpt`"
else
append_var LIBOPENMPT_LIBS "-lopenmpt"
fi
if test "$_libopenmpt" = "auto"; then
_libopenmpt=no
cat > $TMPC << EOF
#include <libopenmpt/libopenmpt.h>
#include <libopenmpt/libopenmpt_stream_callbacks_file.h>
int main(void) {
openmpt_module *mod = 0;
FILE *file = 0;
file = fopen("abc", "rb");
mod = openmpt_module_create2( openmpt_stream_get_file_callbacks2(), file, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
fclose(file);
return 0;
}
EOF
cc_check $LIBOPENMPT_CFLAGS $LIBOPENMPT_LIBS && _libopenmpt=yes
fi
if test "$_libopenmpt" = "yes"; then
append_var LIBS "$LIBOPENMPT_LIBS"
append_var INCLUDES "$LIBOPENMPT_CFLAGS"
# Skip mikmod detection if openmpt is detected
_libmikmod=no
fi
define_in_config_if_yes "$_libopenmpt" "USE_OPENMPT"
echo "$_libopenmpt"
fi
#
# Check for libmikmod to be present
#

View file

@ -49,6 +49,7 @@ const CMakeProvider::Library *CMakeProvider::getLibraryFromFeature(const char *f
LibraryProps("flac", "flac").Libraries("FLAC"),
LibraryProps("mad", "mad").Libraries("mad"),
LibraryProps("mikmod", "mikmod").Libraries("mikmod"),
LibraryProps("openmpt", "openmpt").Libraries("openmpt"),
LibraryProps("ogg", "ogg").Libraries("ogg"),
LibraryProps("vorbis", "vorbisfile vorbis").Libraries("vorbisfile vorbis"),
LibraryProps("tremor", "vorbisidec").Libraries("vorbisidec"),

View file

@ -1088,6 +1088,7 @@ const Feature s_features[] = {
{ "gif", "USE_GIF", true, false, "libgif support" },
{ "faad", "USE_FAAD", true, false, "AAC support" },
{ "mikmod", "USE_MIKMOD", true, false, "libmikmod support" },
{ "openmpt", "USE_OPENMPT", true, false, "libopenmpt support" },
{ "mpeg2", "USE_MPEG2", true, true, "MPEG-2 support" },
{ "theoradec", "USE_THEORADEC", true, true, "Theora decoding support" },
{ "vpx", "USE_VPX", true, false, "VP8/VP9 decoding support" },

View file

@ -64,6 +64,7 @@ std::string MSVCProvider::getLibraryFromFeature(const char *feature, const Build
{ "gif", "gif.lib", nullptr, nullptr },
{ "faad", "faad.lib", nullptr, nullptr },
{ "mikmod", "mikmod.lib", nullptr, nullptr },
{ "openmpt", "openmpt.lib", nullptr, nullptr },
{ "mpeg2", "mpeg2.lib", nullptr, nullptr },
{ "theoradec", "theora.lib", nullptr, nullptr },
{ "vpx", "vpx.lib", nullptr, nullptr },

View file

@ -532,6 +532,10 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
DEF_LOCALLIB_STATIC("libmikmod");
DEF_LOCALXCFRAMEWORK("mikmod", projectOutputDirectory);
}
if (CONTAINS_DEFINE(setup.defines, "USE_OPENMPT")) {
DEF_LOCALLIB_STATIC("libopenmpt");
DEF_LOCALXCFRAMEWORK("openmpt", projectOutputDirectory);
}
if (CONTAINS_DEFINE(setup.defines, "USE_MPEG2")) {
DEF_LOCALLIB_STATIC("libmpeg2");
DEF_LOCALXCFRAMEWORK("mpeg2", projectOutputDirectory);
@ -667,6 +671,9 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
if (CONTAINS_DEFINE(setup.defines, "USE_MIKMOD")) {
frameworks_iOS.push_back(getLibString("mikmod", setup.useXCFramework));
}
if (CONTAINS_DEFINE(setup.defines, "USE_OPENMPT")) {
frameworks_iOS.push_back(getLibString("openmpt", setup.useXCFramework));
}
if (CONTAINS_DEFINE(setup.defines, "USE_MPEG2")) {
frameworks_iOS.push_back(getLibString("mpeg2", setup.useXCFramework));
}
@ -767,6 +774,9 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
if (CONTAINS_DEFINE(setup.defines, "USE_MIKMOD")) {
frameworks_osx.push_back("libmikmod.a");
}
if (CONTAINS_DEFINE(setup.defines, "USE_OPENMPT")) {
frameworks_osx.push_back("libopenmpt.a");
}
if (CONTAINS_DEFINE(setup.defines, "USE_MPEG2")) {
frameworks_osx.push_back(getLibString("mpeg2", setup.useXCFramework));
}
@ -895,6 +905,9 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
if (CONTAINS_DEFINE(setup.defines, "USE_MIKMOD")) {
frameworks_tvOS.push_back(getLibString("mikmod", setup.useXCFramework));
}
if (CONTAINS_DEFINE(setup.defines, "USE_OPENMPT")) {
frameworks_tvOS.push_back(getLibString("openmpt", setup.useXCFramework));
}
if (CONTAINS_DEFINE(setup.defines, "USE_MPEG2")) {
frameworks_tvOS.push_back(getLibString("mpeg2", setup.useXCFramework));
}

View file

@ -214,12 +214,9 @@ TestExitStatus SoundSubsystem::modPlayback() {
if (Audio::probeModXmS3m(&f))
mod = Audio::makeModXmS3mStream(&f, DisposeAfterUse::NO);
#ifdef USE_MIKMOD
if (!mod) {
if (Audio::probeImpulseTracker(&f))
mod = Audio::makeImpulseTrackerStream(&f, DisposeAfterUse::NO);
mod = Audio::makeUniversalTrackerStream(&f, DisposeAfterUse::NO);
}
#endif
if (!mod) {
Testsuite::displayMessage(Common::String::format("Could not load MOD file '%s'", music[i]));

View file

@ -513,6 +513,10 @@ ifdef USE_MIKMOD
OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libmikmod.a
endif
ifdef USE_OPENMPT
OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libopenmpt.a
endif
ifdef USE_MPEG2
OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libmpeg2.a
endif

View file

@ -13,6 +13,7 @@
"libjpeg-turbo",
"libmad",
"libmikmod",
"libopenmpt",
"libmpeg2",
"libogg",
"libpng",