diff --git a/addons/acodec/CMakeLists.txt b/addons/acodec/CMakeLists.txt index 6b7a8b75fa..5b1922483f 100644 --- a/addons/acodec/CMakeLists.txt +++ b/addons/acodec/CMakeLists.txt @@ -8,6 +8,7 @@ option(WANT_VORBIS "Enable Ogg Vorbis support using libvorbis" on) option(WANT_TREMOR "Enable Ogg Vorbis support using Tremor" off) option(WANT_OPUS "Enable Opus support using libopus" on) option(WANT_MODAUDIO "Enable MOD Audio support" on) +option(WANT_OPENMPT "Enable OpenMPT Audio support" on) option(WANT_ACODEC_DYNAMIC_LOAD "Enable DLL loading in acodec (Windows)" off) option(WANT_MP3 "Enable MP3 support" on) @@ -204,6 +205,38 @@ endif() acodec_summary(" - DUMB" SUPPORT_MODAUDIO) +if(WANT_OPENMPT) + find_package(OpenMPT) + if(OPENMPT_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${OPENMPT_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENMPT_LIBRARIES}) + run_c_compile_test(" + #include + #include + int main(void) + { + openmpt_stream_get_file_callbacks(); + return 0; + }" + OPENMPT_COMPILES) + set(CMAKE_REQUIRED_INCLUDES) + set(CMAKE_REQUIRED_LIBRARIES) + if(OPENMPT_COMPILES) + set(SUPPORT_OPENMPT 1) + endif() + endif() +endif() + +if(SUPPORT_OPENMPT) + include_directories(SYSTEM ${OPENMPT_INCLUDE_DIR}) + set(ALLEGRO_CFG_ACODEC_OPENMPT 1) + list(APPEND ACODEC_SOURCES openmpt.c) + list(APPEND ACODEC_INCLUDE_DIRECTORIES ${OPENMPT_INCLUDE_DIR}) + list(APPEND ACODEC_LIBRARIES ${OPENMPT_LIBRARIES}) +endif() + +acodec_summary(" - OpenMPT" SUPPORT_OPENMPT) + # # Vorbis/Tremor # diff --git a/addons/acodec/acodec.c b/addons/acodec/acodec.c index 397e9c0f96..5352cd7101 100644 --- a/addons/acodec/acodec.c +++ b/addons/acodec/acodec.c @@ -63,6 +63,10 @@ bool al_init_acodec_addon(void) ret &= _al_register_dumb_loaders(); #endif +#ifdef ALLEGRO_CFG_ACODEC_OPENMPT + ret &= _al_register_openmpt_loaders(); +#endif + /* MP3 will mis-identify a lot of mod files, so put its identifier last */ #ifdef ALLEGRO_CFG_ACODEC_MP3 ret &= al_register_sample_loader(".mp3", _al_load_mp3); diff --git a/addons/acodec/acodec.h b/addons/acodec/acodec.h index 2a6ae8830a..cb2aaf99cb 100644 --- a/addons/acodec/acodec.h +++ b/addons/acodec/acodec.h @@ -57,6 +57,10 @@ ALLEGRO_AUDIO_STREAM *_al_load_ogg_opus_audio_stream_f(ALLEGRO_FILE* file, bool _al_identify_ogg_opus(ALLEGRO_FILE *f); #endif +#ifdef ALLEGRO_CFG_ACODEC_OPENMPT +bool _al_register_openmpt_loaders(void); +#endif + #ifdef ALLEGRO_CFG_ACODEC_MP3 ALLEGRO_SAMPLE *_al_load_mp3(const char *filename); ALLEGRO_SAMPLE *_al_load_mp3_f(ALLEGRO_FILE *f); diff --git a/addons/acodec/allegro5/internal/aintern_acodec_cfg.h.cmake b/addons/acodec/allegro5/internal/aintern_acodec_cfg.h.cmake index 56ac55f736..7620101255 100644 --- a/addons/acodec/allegro5/internal/aintern_acodec_cfg.h.cmake +++ b/addons/acodec/allegro5/internal/aintern_acodec_cfg.h.cmake @@ -3,6 +3,7 @@ #cmakedefine ALLEGRO_CFG_ACODEC_VORBIS #cmakedefine ALLEGRO_CFG_ACODEC_TREMOR #cmakedefine ALLEGRO_CFG_ACODEC_OPUS +#cmakedefine ALLEGRO_CFG_ACODEC_OPENMPT #cmakedefine ALLEGRO_CFG_ACODEC_MP3 diff --git a/addons/acodec/modaudio.c b/addons/acodec/modaudio.c index 6a0ba50d07..cddc1a254e 100644 --- a/addons/acodec/modaudio.c +++ b/addons/acodec/modaudio.c @@ -863,8 +863,8 @@ bool _al_register_dumb_loaders(void) ret &= al_register_audio_stream_loader(".stm", load_dumb_audio_stream); ret &= al_register_audio_stream_loader_f(".stm", load_dumb_audio_stream_f); ret &= al_register_sample_identifier(".stm", _al_identify_stm); - ret &= al_register_audio_stream_loader(".xm", load_dumb_audio_stream); - ret &= al_register_audio_stream_loader_f(".xm", load_dumb_audio_stream_f); + //ret &= al_register_audio_stream_loader(".xm", load_dumb_audio_stream); + //ret &= al_register_audio_stream_loader_f(".xm", load_dumb_audio_stream_f); ret &= al_register_sample_identifier(".xm", _al_identify_xm); #else /* diff --git a/addons/acodec/openmpt.c b/addons/acodec/openmpt.c new file mode 100644 index 0000000000..11f5e40958 --- /dev/null +++ b/addons/acodec/openmpt.c @@ -0,0 +1,331 @@ +/* + * Allegro OpenMPT wrapper + * author: Pavel Sountsov + */ + +#define _FILE_OFFSET_BITS 64 +#include + +#include "allegro5/allegro.h" +#include "allegro5/allegro_acodec.h" +#include "allegro5/allegro_audio.h" +#include "allegro5/internal/aintern_audio.h" +#include "allegro5/internal/aintern_exitfunc.h" +#include "allegro5/internal/aintern_system.h" +#include "acodec.h" +#include "helper.h" + +#ifndef ALLEGRO_CFG_ACODEC_OPENMPT + #error configuration problem, ALLEGRO_CFG_ACODEC_OPENMPT not set +#endif + +#include +#include + +ALLEGRO_DEBUG_CHANNEL("acodec") + + +typedef struct MOD_FILE +{ + openmpt_module *mod; + ALLEGRO_FILE *fh; + double length; + double loop_start, loop_end; +} MOD_FILE; + + +static size_t stream_read_func(void *stream, void *dst, size_t bytes) +{ + return al_fread((ALLEGRO_FILE*)stream, dst, bytes); +} + + +static int stream_seek_func(void *stream, int64_t offset, int whence) +{ + int allegro_whence; + switch (whence) + { + case OPENMPT_STREAM_SEEK_SET: + allegro_whence = ALLEGRO_SEEK_SET; + break; + case OPENMPT_STREAM_SEEK_CUR: + allegro_whence = ALLEGRO_SEEK_CUR; + break; + case OPENMPT_STREAM_SEEK_END: + allegro_whence = ALLEGRO_SEEK_END; + break; + default: + return -1; + } + + return al_fseek((ALLEGRO_FILE*)stream, offset, allegro_whence) ? 0 : -1; +} + + +static int64_t stream_tell_func(void *stream) +{ + return al_ftell((ALLEGRO_FILE*)stream); +} + + +static void log_func(const char* message, void *user) +{ + (void)user; + ALLEGRO_DEBUG("OpenMPT: %s", message); +} + + +static int error_func(int error, void *user) +{ + (void)user; + const char* error_str = openmpt_error_string(error); + if (error_str) { + ALLEGRO_ERROR("OpenMPT error: %s\n", error_str); + openmpt_free_string(error_str); + } + else + ALLEGRO_ERROR("OpenMPT error: %d\n", error); + return OPENMPT_ERROR_FUNC_RESULT_NONE; +} + +// /* Stream Functions */ +// +// static int loop_callback(void *data) +// { +// bool *internal_loop = (bool *)data; +// *internal_loop = true; +// return 0; +// } +#include +static size_t openmpt_stream_update(ALLEGRO_AUDIO_STREAM *stream, void *data, + size_t buf_size) +{ + MOD_FILE *const df = stream->extra; + + /* the mod files are stereo and 16-bit */ + const int frame_size = 4; + size_t written = 0; + size_t i; + bool internal_loop = false; + printf("Here\n"); + + // OpenMPT_IT_SIGRENDERER *it_sig = lib.duh_get_it_sigrenderer(df->sig); + // if (it_sig) { + // lib.openmpt_it_set_loop_callback(it_sig, + // stream->spl.loop == _ALLEGRO_PLAYMODE_STREAM_ONCE + // ? lib.openmpt_it_callback_terminate : loop_callback, &internal_loop); + // } + + while (written < buf_size) { + long frames_to_read = (buf_size - written * frame_size) / frame_size; + double position = openmpt_module_get_position_seconds(df->mod); + bool manual_loop = false; + internal_loop = false; + /* If manual looping is not enabled, then we need to implement + * short-stopping manually. */ + if (stream->spl.loop != _ALLEGRO_PLAYMODE_STREAM_ONCE && df->loop_end != -1 && + position + frames_to_read / 44100 >= df->loop_end) { + frames_to_read = (long)((df->loop_end - position) * 44100); + if (frames_to_read < 0) + frames_to_read = 0; + manual_loop = true; + } + written += openmpt_module_read_interleaved_stereo(df->mod, 44100, frames_to_read, + (int16_t*)&(((char *)data)[written])) * frame_size; + /* For internal loops, we don't rewind. */ + if (!internal_loop && + ((long)written < frames_to_read * frame_size || manual_loop)) { + break; + } + } + + /* Fill the remainder with silence */ + for (i = written; i < buf_size; ++i) + ((char *)data)[i] = 0; + + return written; +} + +static void openmpt_stream_close(ALLEGRO_AUDIO_STREAM *stream) +{ + MOD_FILE *const df = stream->extra; + _al_acodec_stop_feed_thread(stream); + + openmpt_module_destroy(df->mod); + + if (df->fh) + al_fclose(df->fh); +} + +static bool openmpt_stream_rewind(ALLEGRO_AUDIO_STREAM *stream) +{ + MOD_FILE *const df = stream->extra; + openmpt_module_set_position_seconds(df->mod, df->loop_start); + return true; +} + + +static bool openmpt_stream_seek(ALLEGRO_AUDIO_STREAM *stream, double time) +{ + MOD_FILE *const df = stream->extra; + + if (df->loop_end != -1 && time > df->loop_end) { + return false; + } + + openmpt_module_set_position_seconds(df->mod, time); + + return false; +} + + +static double openmpt_stream_get_position(ALLEGRO_AUDIO_STREAM *stream) +{ + MOD_FILE *const df = stream->extra; + return openmpt_module_get_position_seconds(df->mod); +} + + +static double openmpt_stream_get_length(ALLEGRO_AUDIO_STREAM *stream) +{ + MOD_FILE *const df = stream->extra; + return df->length; +} + + +static bool openmpt_stream_set_loop(ALLEGRO_AUDIO_STREAM *stream, + double start, double end) +{ + MOD_FILE *const df = stream->extra; + + df->loop_start = start; + df->loop_end = end; + + return true; +} + + +static ALLEGRO_AUDIO_STREAM *openmpt_stream_init(ALLEGRO_FILE* f, + size_t buffer_count, unsigned int samples +) +{ + int64_t start_pos = al_ftell(f); + ALLEGRO_AUDIO_STREAM *stream = NULL; + + openmpt_stream_callbacks callbacks = { + stream_read_func, + stream_seek_func, + stream_tell_func, + }; + + // TODO: Check if these are redundant with the callbacks. + int mod_err = OPENMPT_ERROR_OK; + const char *mod_err_str = NULL; + + openmpt_module *mod = openmpt_module_create2( + callbacks, f, + &log_func, NULL, + &error_func, NULL, + &mod_err, &mod_err_str, + NULL); + // TODO: Free the err_str. + + if (!mod) + return NULL; + + // TODO + // it_sig = lib.duh_get_it_sigrenderer(sig); + // if (it_sig) { + // /* Turn off freezing for XM files. Seems completely pointless. */ + // lib.openmpt_it_set_xm_speed_zero_callback(it_sig, lib.openmpt_it_callback_terminate, NULL); + // } + + // TODO: openmpt recommends 48000 and float + // TODO: openmpt supports quad channels too + stream = al_create_audio_stream(buffer_count, samples, 44100, + ALLEGRO_AUDIO_DEPTH_INT16, ALLEGRO_CHANNEL_CONF_2); + + if (stream) { + MOD_FILE *mf = al_malloc(sizeof(MOD_FILE)); + mf->mod = mod; + mf->fh = NULL; + mf->length = openmpt_module_get_duration_seconds(mod); + if (mf->length < 0) { + mf->length = 0; + } + /* + * Set these to -1, so that we can default to the internal loop + * points. + */ + mf->loop_start = -1; + mf->loop_end = -1; + + stream->extra = mf; + stream->feeder = openmpt_stream_update; + stream->unload_feeder = openmpt_stream_close; + stream->rewind_feeder = openmpt_stream_rewind; + stream->seek_feeder = openmpt_stream_seek; + stream->get_feeder_position = openmpt_stream_get_position; + stream->get_feeder_length = openmpt_stream_get_length; + stream->set_feeder_loop = openmpt_stream_set_loop; + _al_acodec_start_feed_thread(stream); + } + else { + ALLEGRO_ERROR("Failed to create stream.\n"); + goto Error; + } + + return stream; + +Error: + + openmpt_module_destroy(mod); + + /* try to return back to where we started to load */ + if (start_pos != -1) + al_fseek(f, start_pos, ALLEGRO_SEEK_SET); + + return NULL; +} + + +static ALLEGRO_AUDIO_STREAM *load_audio_stream_f(ALLEGRO_FILE *f, + size_t buffer_count, unsigned int samples) +{ + return openmpt_stream_init(f, buffer_count, samples); +} + +static ALLEGRO_AUDIO_STREAM *load_audio_stream(const char *filename, + size_t buffer_count, unsigned int samples) +{ + ALLEGRO_FILE *f; + ALLEGRO_AUDIO_STREAM *stream; + ASSERT(filename); + + f = al_fopen(filename, "rb"); + if (!f) { + ALLEGRO_ERROR("Unable to open %s for reading.\n", filename); + return NULL; + } + + stream = load_audio_stream_f(f, buffer_count, samples); + + if (!stream) { + al_fclose(f); + return NULL; + } + + ((MOD_FILE *)stream->extra)->fh = f; + + return stream; +} + +bool _al_register_openmpt_loaders(void) +{ + bool ret = true; + ret &= al_register_audio_stream_loader(".xm", load_audio_stream); + ret &= al_register_audio_stream_loader_f(".xm", load_audio_stream_f); + return ret; +} + +/* vim: set sts=3 sw=3 et: */ diff --git a/cmake/FindOpenMPT.cmake b/cmake/FindOpenMPT.cmake new file mode 100644 index 0000000000..b6542387f9 --- /dev/null +++ b/cmake/FindOpenMPT.cmake @@ -0,0 +1,30 @@ +# - Find OPENMPT +# Find the native OpenMPT includes and libraries +# +# OPENMPT_INCLUDE_DIR - where to find OpenMPT headers. +# OPENMPT_LIBRARIES - List of libraries when using OpenMPT. +# OPENMPT_FOUND - True if OpenMPT found. + +if(OPENMPT_INCLUDE_DIR) + # Already in cache, be silent + set(OPENMPT_FIND_QUIETLY TRUE) +endif(OPENMPT_INCLUDE_DIR) + +find_path(OPENMPT_INCLUDE_DIR libopenmpt/libopenmpt.h) + +find_library(OPENMPT_LIBRARY NAMES openmpt) + +# Handle the QUIETLY and REQUIRED arguments and set OPENMPT_FOUND to TRUE if +# all listed variables are TRUE. +include(FindPackageHandleStandardArgs) +set(FPHSA_NAME_MISMATCHED TRUE) +find_package_handle_standard_args(OPENMPT DEFAULT_MSG + OPENMPT_INCLUDE_DIR OGG_LIBRARY OPENMPT_LIBRARY) + +if(OPENMPT_FOUND) + set(OPENMPT_LIBRARIES ${OPENMPT_LIBRARY}) +else(OPENMPT_FOUND) + set(OPENMPT_LIBRARIES) +endif(OPENMPT_FOUND) + +mark_as_advanced(OPENMPT_INCLUDE_DIR OPENMPT_LIBRARY)