From 19a88666a4c973c5a97d2a50a20b01c37338588b Mon Sep 17 00:00:00 2001
From: Connor Clark <[EMAIL REDACTED]>
Date: Thu, 22 Dec 2022 14:38:37 -0800
Subject: [PATCH] Add support for GME (Game Music Emulator)
---
configure.ac | 46 ++++
include/SDL_mixer.h | 7 +-
src/codecs/music_cmd.c | 2 +
src/codecs/music_drflac.c | 2 +
src/codecs/music_drmp3.c | 2 +
src/codecs/music_flac.c | 4 +-
src/codecs/music_fluidsynth.c | 2 +
src/codecs/music_gme.c | 452 ++++++++++++++++++++++++++++++++++
src/codecs/music_gme.h | 26 ++
src/codecs/music_modplug.c | 2 +
src/codecs/music_mpg123.c | 2 +
src/codecs/music_nativemidi.c | 2 +
src/codecs/music_ogg.c | 2 +
src/codecs/music_ogg_stb.c | 2 +
src/codecs/music_opus.c | 2 +
src/codecs/music_timidity.c | 2 +
src/codecs/music_wav.c | 6 +-
src/codecs/music_wavpack.c | 2 +
src/codecs/music_xmp.c | 2 +
src/music.c | 68 +++++
src/music.h | 8 +-
21 files changed, 637 insertions(+), 6 deletions(-)
create mode 100644 src/codecs/music_gme.c
create mode 100644 src/codecs/music_gme.h
diff --git a/configure.ac b/configure.ac
index 64d68e39..e8bd5116 100644
--- a/configure.ac
+++ b/configure.ac
@@ -571,6 +571,52 @@ if test x$enable_music_midi_timidity != xyes -a \
AC_MSG_WARN([MIDI support disabled])
fi
+AC_ARG_ENABLE([music-gme],
+[AS_HELP_STRING([--enable-music-gme], [enable Game Music Emu support [default=no]])],
+ [], [enable_music_gme=no])
+AC_ARG_ENABLE([music-gme-shared],
+[AS_HELP_STRING([--enable-music-gme-shared], [dynamically load libgme library [default=yes]])],
+ [], [enable_music_gme_shared=yes])
+if test x$enable_music_gme = xyes; then
+ PKG_CHECK_MODULES([GME], [libgme], [dnl
+ have_gme_hdr=yes
+ have_gme_lib=yes
+ have_gme_pc=yes
+ ], [dnl
+ AC_CHECK_HEADER([gme/gme.h], [have_gme_hdr=yes])
+ AC_CHECK_LIB([gme], [gme_open_data], [have_gme_lib=yes;GME_LIBS="-lgme"])
+ ])
+ if test x$have_gme_hdr = xyes -a x$have_gme_lib = xyes; then
+ have_gme=yes
+ case "$host" in
+ *-*-darwin*)
+ gme_lib=[`find_lib libgme.dylib`]
+ ;;
+ *-*-cygwin* | *-*-mingw*)
+ gme_lib=[`find_lib "libgme*.dll"`]
+ ;;
+ *)
+ gme_lib=[`find_lib "libgme.so.*"`]
+ ;;
+ esac
+ EXTRA_CFLAGS="$EXTRA_CFLAGS -DMUSIC_GME $GME_CFLAGS"
+ if test x$enable_music_gme_shared = xyes && test x$gme_lib != x; then
+ echo "-- dynamic libgme -> $gme_lib"
+ EXTRA_CFLAGS="$EXTRA_CFLAGS -DGME_DYNAMIC=\\\"$gme_lib\\\""
+ else
+ EXTRA_LDFLAGS="$EXTRA_LDFLAGS $GME_LIBS"
+ if test x$have_gme_pc = xyes; then
+ PC_REQUIRES="$PC_REQUIRES libgme"
+ else
+ PC_LIBS="$PC_LIBS $GME_LIBS"
+ fi
+ fi
+ else
+ AC_MSG_WARN([*** Unable to find GME library (https://bitbucket.org/mpyne/game-music-emu)])
+ AC_MSG_WARN([Game Music Emu support disabled.])
+ fi
+fi
+
AC_ARG_ENABLE([music-ogg],
[AS_HELP_STRING([--enable-music-ogg], [enable Ogg Vorbis music [default=yes]])],
[], [enable_music_ogg=yes])
diff --git a/include/SDL_mixer.h b/include/SDL_mixer.h
index 4231e2ae..2e8f1ab5 100644
--- a/include/SDL_mixer.h
+++ b/include/SDL_mixer.h
@@ -262,7 +262,8 @@ typedef enum {
MUS_FLAC,
MUS_MODPLUG_UNUSED,
MUS_OPUS,
- MUS_WAVPACK
+ MUS_WAVPACK,
+ MUS_GME
} Mix_MusicType;
/**
@@ -2425,6 +2426,10 @@ extern DECLSPEC int SDLCALL Mix_PausedMusic(void);
*/
extern DECLSPEC int SDLCALL Mix_ModMusicJumpToOrder(int order);
+/* Tracks */
+extern DECLSPEC int SDLCALL Mix_StartTrack(Mix_Music *music, int track);
+extern DECLSPEC int SDLCALL Mix_GetNumTracks(Mix_Music *music);
+
/**
* Set the current position in the music stream, in seconds.
*
diff --git a/src/codecs/music_cmd.c b/src/codecs/music_cmd.c
index a293f92a..5d129f07 100644
--- a/src/codecs/music_cmd.c
+++ b/src/codecs/music_cmd.c
@@ -293,6 +293,8 @@ Mix_MusicInterface Mix_MusicInterface_CMD =
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
MusicCMD_Pause,
MusicCMD_Resume,
MusicCMD_Stop,
diff --git a/src/codecs/music_drflac.c b/src/codecs/music_drflac.c
index 635e9757..e1216ba3 100644
--- a/src/codecs/music_drflac.c
+++ b/src/codecs/music_drflac.c
@@ -409,6 +409,8 @@ Mix_MusicInterface Mix_MusicInterface_DRFLAC =
DRFLAC_LoopEnd,
DRFLAC_LoopLength,
DRFLAC_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
DRFLAC_Stop,
diff --git a/src/codecs/music_drmp3.c b/src/codecs/music_drmp3.c
index 7af022d0..9e3087b2 100644
--- a/src/codecs/music_drmp3.c
+++ b/src/codecs/music_drmp3.c
@@ -282,6 +282,8 @@ Mix_MusicInterface Mix_MusicInterface_DRMP3 =
NULL, /* LoopEnd */
NULL, /* LoopLength */
DRMP3_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
DRMP3_Stop,
diff --git a/src/codecs/music_flac.c b/src/codecs/music_flac.c
index 2e19d807..b45789fc 100644
--- a/src/codecs/music_flac.c
+++ b/src/codecs/music_flac.c
@@ -748,7 +748,9 @@ Mix_MusicInterface Mix_MusicInterface_FLAC =
FLAC_LoopStart,
FLAC_LoopEnd,
FLAC_LoopLength,
- FLAC_GetMetaTag,/* GetMetaTag */
+ FLAC_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
FLAC_Stop, /* Stop */
diff --git a/src/codecs/music_fluidsynth.c b/src/codecs/music_fluidsynth.c
index 49d9699a..9b366fe0 100644
--- a/src/codecs/music_fluidsynth.c
+++ b/src/codecs/music_fluidsynth.c
@@ -363,6 +363,8 @@ Mix_MusicInterface Mix_MusicInterface_FLUIDSYNTH =
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
FLUIDSYNTH_Stop,
diff --git a/src/codecs/music_gme.c b/src/codecs/music_gme.c
new file mode 100644
index 00000000..67569e28
--- /dev/null
+++ b/src/codecs/music_gme.c
@@ -0,0 +1,452 @@
+/*
+ SDL_mixer: An audio mixer library based on the SDL library
+ Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifdef MUSIC_GME
+
+#include "SDL_loadso.h"
+
+#include "music_gme.h"
+
+#include <gme/gme.h>
+
+typedef struct {
+ int loaded;
+ void *handle;
+
+ gme_err_t (*gme_open_data)(void const* data, long size, Music_Emu** out, int sample_rate);
+ int (*gme_track_count)(Music_Emu const*);
+ gme_err_t (*gme_start_track)(Music_Emu*, int index);
+ int (*gme_track_ended)(Music_Emu const*);
+ void (*gme_set_tempo)(Music_Emu*, double tempo);
+ int (*gme_voice_count)(Music_Emu const*);
+ void (*gme_mute_voice)(Music_Emu*, int index, int mute);
+#if GME_VERSION >= 0x000700
+ void (*gme_set_fade)(Music_Emu*, int start_msec, int fade_msec);
+#else
+ void (*gme_set_fade)(Music_Emu*, int start_msec);
+#endif
+ void (*gme_set_autoload_playback_limit)(Music_Emu*, int do_autoload_limit);
+ gme_err_t (*gme_track_info)(Music_Emu const*, gme_info_t** out, int track);
+ void (*gme_free_info)(gme_info_t*);
+ gme_err_t (*gme_seek)(Music_Emu*, int msec);
+ int (*gme_tell)(Music_Emu const*);
+ gme_err_t (*gme_play)(Music_Emu*, int count, short out[]);
+ void (*gme_delete)(Music_Emu*);
+} gme_loader;
+
+static gme_loader gme;
+
+#ifdef GME_DYNAMIC
+#define FUNCTION_LOADER(FUNC, SIG) \
+ gme.FUNC = (SIG) SDL_LoadFunction(gme.handle, #FUNC); \
+ if (gme.FUNC == NULL) { SDL_UnloadObject(gme.handle); return -1; }
+#else
+#define FUNCTION_LOADER(FUNC, SIG) \
+ gme.FUNC = FUNC; \
+ if (gme.FUNC == NULL) { Mix_SetError("Missing GME.framework"); return -1; }
+#endif
+
+static int GME_Load(void)
+{
+ if (gme.loaded == 0) {
+#ifdef GME_DYNAMIC
+ gme.handle = SDL_LoadObject(GME_DYNAMIC);
+ if (gme.handle == NULL) {
+ return -1;
+ }
+#endif
+ FUNCTION_LOADER(gme_open_data, gme_err_t (*)(void const*,long,Music_Emu**,int))
+ FUNCTION_LOADER(gme_track_count, int (*)(Music_Emu const*))
+ FUNCTION_LOADER(gme_start_track, gme_err_t (*)( Music_Emu*,int))
+ FUNCTION_LOADER(gme_track_ended, int (*)( Music_Emu const*))
+ FUNCTION_LOADER(gme_set_tempo, void (*)(Music_Emu*,double))
+ FUNCTION_LOADER(gme_voice_count, int (*)(Music_Emu const*))
+ FUNCTION_LOADER(gme_mute_voice, void (*)(Music_Emu*,int,int))
+#if GME_VERSION >= 0x000700
+ FUNCTION_LOADER(gme_set_fade, void (*)(Music_Emu*,int,int))
+#else
+ FUNCTION_LOADER(gme_set_fade, void (*)(Music_Emu*,int))
+#endif
+ FUNCTION_LOADER(gme_track_info, gme_err_t (*)(Music_Emu const*, gme_info_t**, int))
+ FUNCTION_LOADER(gme_free_info, void (*)(gme_info_t*))
+ FUNCTION_LOADER(gme_seek, gme_err_t (*)(Music_Emu*,int))
+ FUNCTION_LOADER(gme_tell, int (*)(Music_Emu const*))
+ FUNCTION_LOADER(gme_play, gme_err_t (*)(Music_Emu*, int, short[]))
+ FUNCTION_LOADER(gme_delete, void (*)(Music_Emu*))
+#if defined(GME_DYNAMIC)
+ gme.gme_set_autoload_playback_limit = (void (*)(Music_Emu*,int)) SDL_LoadFunction(gme.handle, "gme_set_autoload_playback_limit");
+#elif (GME_VERSION >= 0x000603)
+ gme.gme_set_autoload_playback_limit = gme_set_autoload_playback_limit;
+#else
+ gme.gme_set_autoload_playback_limit = NULL;
+#endif
+ }
+ ++gme.loaded;
+
+ return 0;
+}
+
+static void GME_Unload(void)
+{
+ if (gme.loaded == 0) {
+ return;
+ }
+ if (gme.loaded == 1) {
+#ifdef GME_DYNAMIC
+ SDL_UnloadObject(gme.handle);
+#endif
+ }
+ --gme.loaded;
+}
+
+/* This file supports Game Music Emulator music streams */
+typedef struct
+{
+ int play_count;
+ Music_Emu* game_emu;
+ int freesrc;
+ SDL_bool has_track_length;
+ int track_length;
+ int intro_length;
+ int loop_length;
+ int volume;
+ double tempo;
+ double gain;
+ SDL_AudioStream *stream;
+ void *buffer;
+ size_t buffer_size;
+ Mix_MusicMetaTags tags;
+} GME_Music;
+
+static void GME_Delete(void *context);
+
+/* Set the volume for a GME stream */
+static void GME_SetVolume(void *music_p, int volume)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ double v = SDL_floor(((double)(volume) * music->gain) + 0.5);
+ music->volume = (int)v;
+}
+
+/* Get the volume for a GME stream */
+static int GME_GetVolume(void *music_p)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ double v = SDL_floor(((double)(music->volume) / music->gain) + 0.5);
+ return (int)v;
+}
+
+static int initialize_from_track_info(GME_Music *music, int track)
+{
+ gme_info_t *musInfo;
+ SDL_bool has_loop_length = SDL_TRUE;
+ const char *err;
+
+ err = gme.gme_track_info(music->game_emu, &musInfo, track);
+ if (err != 0) {
+ Mix_SetError("GME: %s", err);
+ return -1;
+ }
+
+ music->track_length = musInfo->length;
+ music->intro_length = musInfo->intro_length;
+ music->loop_length = musInfo->loop_length;
+
+ music->has_track_length = SDL_TRUE;
+ if (music->track_length <= 0 ) {
+ music->track_length = (int)(2.5 * 60 * 1000);
+ music->has_track_length = SDL_FALSE;
+ }
+
+ if (music->intro_length < 0 ) {
+ music->intro_length = 0;
+ }
+ if (music->loop_length <= 0 ) {
+ if (music->track_length > 0) {
+ music->loop_length = music->track_length;
+ } else {
+ music->loop_length = (int)(2.5 * 60 * 1000);
+ }
+ has_loop_length = SDL_FALSE;
+ }
+
+ if (!music->has_track_length && has_loop_length) {
+ music->track_length = music->intro_length + music->loop_length;
+ music->has_track_length = SDL_TRUE;
+ }
+
+ meta_tags_set(&music->tags, MIX_META_TITLE, musInfo->song);
+ meta_tags_set(&music->tags, MIX_META_ARTIST, musInfo->author);
+ meta_tags_set(&music->tags, MIX_META_ALBUM, musInfo->game);
+ meta_tags_set(&music->tags, MIX_META_COPYRIGHT, musInfo->copyright);
+ gme.gme_free_info(musInfo);
+
+ return 0;
+}
+
+static void *GME_CreateFromRW(struct SDL_RWops *src, int freesrc)
+{
+ void *mem = 0;
+ size_t size;
+ GME_Music *music;
+ const char *err;
+
+ if (src == NULL) {
+ Mix_SetError("GME: Empty source given");
+ return NULL;
+ }
+
+ music = (GME_Music *)SDL_calloc(1, sizeof(GME_Music));
+
+ music->tempo = 1.0;
+ music->gain = 1.0;
+
+ music->stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, music_spec.freq,
+ music_spec.format, music_spec.channels, music_spec.freq);
+ if (!music->stream) {
+ GME_Delete(music);
+ return NULL;
+ }
+
+ music->buffer_size = music_spec.samples * sizeof(Sint16) * 2/*channels*/ * music_spec.channels;
+ music->buffer = SDL_malloc(music->buffer_size);
+ if (!music->buffer) {
+ SDL_OutOfMemory();
+ GME_Delete(music);
+ return NULL;
+ }
+
+ SDL_RWseek(src, 0, RW_SEEK_SET);
+ mem = SDL_LoadFile_RW(src, &size, SDL_FALSE);
+ if (mem) {
+ err = gme.gme_open_data(mem, size, &music->game_emu, music_spec.freq);
+ SDL_free(mem);
+ if (err != 0) {
+ GME_Delete(music);
+ Mix_SetError("GME: %s", err);
+ return NULL;
+ }
+ } else {
+ SDL_OutOfMemory();
+ GME_Delete(music);
+ return NULL;
+ }
+
+ /* Set this flag BEFORE calling the gme_start_track() to fix an inability to loop forever */
+ if (gme.gme_set_autoload_playback_limit) {
+ gme.gme_set_autoload_playback_limit(music->game_emu, 0);
+ }
+
+ err = gme.gme_start_track(music->game_emu, 0);
+ if (err != 0) {
+ GME_Delete(music);
+ Mix_SetError("GME: %s", err);
+ return NULL;
+ }
+
+ gme.gme_set_tempo(music->game_emu, music->tempo);
+
+ music->volume = MIX_MAX_VOLUME;
+
+ meta_tags_init(&music->tags);
+ if (initialize_from_track_info(music, 0) == -1) {
+ GME_Delete(music);
+ return NULL;
+ }
+
+ music->freesrc = freesrc;
+ return music;
+}
+
+/* Start playback of a given Game Music Emulators stream */
+static int GME_Play(void *music_p, int play_count)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ int fade_start;
+ if (music) {
+ SDL_AudioStreamClear(music->stream);
+ music->play_count = play_count;
+ fade_start = play_count > 0 ? music->intro_length + (music->loop_length * play_count) : -1;
+#if GME_VERSION >= 0x000700
+ gme.gme_set_fade(music->game_emu, fade_start, 8000);
+#else
+ gme.gme_set_fade(music->game_emu, fade_start);
+#endif
+ gme.gme_seek(music->game_emu, 0);
+ }
+ return 0;
+}
+
+static int GME_GetSome(void *context, void *data, int bytes, SDL_bool *done)
+{
+ GME_Music *music = (GME_Music*)context;
+ int filled;
+ const char *err = NULL;
+
+ filled = SDL_AudioStreamGet(music->stream, data, bytes);
+ if (filled != 0) {
+ return filled;
+ }
+
+ if (gme.gme_track_ended(music->game_emu)) {
+ /* All done */
+ *done = SDL_TRUE;
+ return 0;
+ }
+
+ err = gme.gme_play(music->game_emu, (music->buffer_size / 2), (short*)music->buffer);
+ if (err != NULL) {
+ Mix_SetError("GME: %s", err);
+ return 0;
+ }
+
+ if (SDL_AudioStreamPut(music->stream, music->buffer, music->buffer_size) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+/* Play some of a stream previously started with GME_Play() */
+static int GME_PlayAudio(void *music_p, void *data, int bytes)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ return music_pcm_getaudio(music_p, data, bytes, music->volume, GME_GetSome);
+}
+
+/* Close the given Game Music Emulators stream */
+static void GME_Delete(void *context)
+{
+ GME_Music *music = (GME_Music*)context;
+ if (music) {
+ meta_tags_clear(&music->tags);
+ if (music->game_emu && music->freesrc) {
+ gme.gme_delete(music->game_emu);
+ music->game_emu = NULL;
+ }
+ if (music->stream) {
+ SDL_FreeAudioStream(music->stream);
+ }
+ if (music->buffer) {
+ SDL_free(music->buffer);
+ }
+ SDL_free(music);
+ }
+}
+
+// TODO: this should accept a track number, not assume the current track!
+static const char* GME_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
+{
+ GME_Music *music = (GME_Music *)context;
+ return meta_tags_get(&music->tags, tag_type);
+}
+
+/* Jump (seek) to a given position (time is in seconds) */
+static int GME_Seek(void *music_p, double time)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ gme.gme_seek(music->game_emu, (int)(SDL_floor((time * 1000.0) + 0.5)));
+ return 0;
+}
+
+static double GME_Tell(void *music_p)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ return (double)(gme.gme_tell(music->game_emu)) / 1000.0;
+}
+
+static double GME_Duration(void *music_p)
+{
+ GME_Music *music = (GME_Music*)music_p;
+ if (music->has_track_length) {
+ return (double)(music->track_length) / 1000.0;
+ } else {
+
+ return -1.0;
+ }
+}
+
+static int GME_GetNumTracks(void *music_p)
+{
+ GME_Music *music = (GME_Music *)music_p;
+ return gme.gme_track_count(music->game_emu);
+}
+
+static int GME_StartTrack(void *music_p, int track)
+{
+ GME_Music *music = (GME_Music *)music_p;
+ const char *err;
+
+ if ((track < 0) || (track >= gme.gme_track_count(music->game_emu))) {
+ track = gme.gme_track_count(music->game_emu) - 1;
+ }
+
+ err = gme.gme_start_track(music->game_emu, track);
+ if (err != 0) {
+ Mix_SetError("GME: %s", err);
+ return -1;
+ }
+
+ GME_Play(music, music->play_count);
+
+ if (initialize_from_track_info(music, track) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+Mix_MusicInterface Mix_MusicInterface_GME =
+{
+ "GME",
+ MIX_MUSIC_GME,
+ MUS_GME,
+ SDL_FALSE,
+ SDL_FALSE,
+
+ GME_Load,
+ NULL, /* Open */
+ GME_CreateFromRW,
+ NULL, /* CreateFromFile */
+ GME_SetVolume,
+ GME_GetVolume,
+ GME_Play,
+ NULL, /* IsPlaying */
+ GME_PlayAudio,
+ NULL, /* Jump */
+ GME_Seek,
+ GME_Tell,
+ GME_Duration,
+ NULL,
+ NULL,
+ NULL,
+ GME_GetMetaTag,
+ GME_GetNumTracks,
+ GME_StartTrack,
+ NULL, /* Pause */
+ NULL, /* Resume */
+ NULL, /* Stop */
+ GME_Delete,
+ NULL, /* Close */
+ GME_Unload
+};
+
+#endif /* MUSIC_GME */
diff --git a/src/codecs/music_gme.h b/src/codecs/music_gme.h
new file mode 100644
index 00000000..e0c85b7b
--- /dev/null
+++ b/src/codecs/music_gme.h
@@ -0,0 +1,26 @@
+/*
+ SDL_mixer: An audio mixer library based on the SDL library
+ Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/* This file supports playing chiptune files with libGME */
+
+#include "music.h"
+
+extern Mix_MusicInterface Mix_MusicInterface_GME;
diff --git a/src/codecs/music_modplug.c b/src/codecs/music_modplug.c
index b8d2936e..d6985d5f 100644
--- a/src/codecs/music_modplug.c
+++ b/src/codecs/music_modplug.c
@@ -366,6 +366,8 @@ Mix_MusicInterface Mix_MusicInterface_MODPLUG =
NULL, /* LoopEnd */
NULL, /* LoopLength */
MODPLUG_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
MODPLUG_Stop,
diff --git a/src/codecs/music_mpg123.c b/src/codecs/music_mpg123.c
index a8198d8a..49cd090e 100644
--- a/src/codecs/music_mpg123.c
+++ b/src/codecs/music_mpg123.c
@@ -534,6 +534,8 @@ Mix_MusicInterface Mix_MusicInterface_MPG123 =
NULL, /* LoopEnd */
NULL, /* LoopLength */
MPG123_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
MPG123_Stop,
diff --git a/src/codecs/music_nativemidi.c b/src/codecs/music_nativemidi.c
index 22fbcc05..308fda15 100644
--- a/src/codecs/music_nativemidi.c
+++ b/src/codecs/music_nativemidi.c
@@ -108,6 +108,8 @@ Mix_MusicInterface Mix_MusicInterface_NATIVEMIDI =
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NATIVEMIDI_Pause,
NATIVEMIDI_Resume,
NATIVEMIDI_Stop,
diff --git a/src/codecs/music_ogg.c b/src/codecs/music_ogg.c
index 16af47b9..09f60a83 100644
--- a/src/codecs/music_ogg.c
+++ b/src/codecs/music_ogg.c
@@ -543,6 +543,8 @@ Mix_MusicInterface Mix_MusicInterface_OGG =
OGG_LoopEnd,
OGG_LoopLength,
OGG_GetMetaTag, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
OGG_Stop,
diff --git a/src/codecs/music_ogg_stb.c b/src/codecs/music_ogg_stb.c
index b33f38e0..84db5b54 100644
--- a/src/codecs/music_ogg_stb.c
+++ b/src/codecs/music_ogg_stb.c
@@ -472,6 +472,8 @@ Mix_MusicInterface Mix_MusicInterface_OGG =
OGG_LoopEnd,
OGG_LoopLength,
OGG_GetMetaTag, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
OGG_Stop,
diff --git a/src/codecs/music_opus.c b/src/codecs/music_opus.c
index 1afdc9d0..b5488eb7 100644
--- a/src/codecs/music_opus.c
+++ b/src/codecs/music_opus.c
@@ -515,6 +515,8 @@ Mix_MusicInterface Mix_MusicInterface_Opus =
OPUS_LoopEnd,
OPUS_LoopLength,
OPUS_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
OPUS_Stop,
diff --git a/src/codecs/music_timidity.c b/src/codecs/music_timidity.c
index 5822c4bf..6e6baa04 100644
--- a/src/codecs/music_timidity.c
+++ b/src/codecs/music_timidity.c
@@ -284,6 +284,8 @@ Mix_MusicInterface Mix_MusicInterface_TIMIDITY =
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
TIMIDITY_Stop,
diff --git a/src/codecs/music_wav.c b/src/codecs/music_wav.c
index e4140c2f..1d826cf8 100644
--- a/src/codecs/music_wav.c
+++ b/src/codecs/music_wav.c
@@ -931,8 +931,7 @@ static SDL_bool LoadWAVMusic(WAV_Music *wave)
if (chunk_length == 0)
break;
- switch (chunk_type)
- {
+ switch (chunk_type) {
case FMT:
found_FMT = SDL_TRUE;
if (!ParseFMT(wave, chunk_length))
@@ -1135,7 +1134,6 @@ static SDL_bool LoadAIFFMusic(WAV_Music *wave)
return SDL_FALSE;
}
-
wave->samplesize = channels * (samplesize / 8);
wave->stop = wave->start + channels * numsamples * (samplesize / 8);
@@ -1254,6 +1252,8 @@ Mix_MusicInterface Mix_MusicInterface_WAV =
NULL, /* LoopEnd */
NULL, /* LoopLength */
WAV_GetMetaTag, /* GetMetaTag */
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
WAV_Stop, /* Stop */
diff --git a/src/codecs/music_wavpack.c b/src/codecs/music_wavpack.c
index 4444498f..4e318a96 100644
--- a/src/codecs/music_wavpack.c
+++ b/src/codecs/music_wavpack.c
@@ -726,6 +726,8 @@ Mix_MusicInterface Mix_MusicInterface_WAVPACK =
NULL, /* LoopEnd */
NULL, /* LoopLength */
WAVPACK_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
WAVPACK_Stop,
diff --git a/src/codecs/music_xmp.c b/src/codecs/music_xmp.c
index cce2e6dd..9e3d2430 100644
--- a/src/codecs/music_xmp.c
+++ b/src/codecs/music_xmp.c
@@ -429,6 +429,8 @@ Mix_MusicInterface Mix_MusicInterface_XMP =
NULL, /* LoopEnd */
NULL, /* LoopLength */
XMP_GetMetaTag,
+ NULL, /* GetNumTracks */
+ NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
XMP_Stop,
diff --git a/src/music.c b/src/music.c
index c1cba2bc..416bdbeb 100644
--- a/src/music.c
+++ b/src/music.c
@@ -40,6 +40,7 @@
#include "music_drflac.h"
#include "music_flac.h"
#include "music_wavpack.h"
+#include "music_gme.h"
#include "native_midi/native_midi.h"
#include "utils.h"
@@ -201,6 +202,9 @@ static Mix_MusicInterface *s_music_interfaces[] =
#ifdef MUSIC_MID_NATIVE
&Mix_MusicInterface_NATIVEMIDI,
#endif
+#ifdef MUSIC_GME
+ &Mix_MusicInterface_GME,
+#endif
};
int get_num_music_interfaces(void)
@@ -593,6 +597,32 @@ Mix_MusicType detect_music_type(SDL_RWops *src)
return MUS_MP3;
}
+ /* GME Specific files */
+ if (SDL_memcmp(magic, "ZXAY", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "GBS\x01", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "GYMX", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "HESM", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "KSCC", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "KSSX", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "NESM", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "NSFE", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "SAP\x0D", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "SNES", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "Vgm ", 4) == 0)
+ return MUS_GME;
+ if (SDL_memcmp(magic, "\x1f\x8b", 2) == 0)
+ return MUS_GME;
+
/* Assume MOD format.
*
* Apparently there is no way to check if the file is really a MOD,
@@ -686,6 +716,12 @@ Mix_Music *Mix_LoadMUS(const char *file)
SDL_strcasecmp(ext, "WOW") == 0 ||
SDL_strcasecmp(ext, "XM") == 0) {
type = MUS_MOD;
+ } else if (SDL_strcasecmp(ext, "GBS") == 0 ||
+ SDL_strcasecmp(ext, "M3U") == 0 ||
+ SDL_strcasecmp(ext, "NSF") == 0 ||
+ SDL_strcasecmp(ext, "SPC") == 0 ||
+ SDL_strcasecmp(ext, "VGM") == 0) {
+ type = MUS_GME;
}
}
return Mix_LoadMUSType_RW(src, type, SDL_TRUE);
@@ -1291,6 +1327,38 @@ int Mix_PausedMusic(void)
return (music_active == SDL_FALSE);
}
+int Mix_StartTrack(Mix_Music *music, int track)
+{
+ int result;
+
+ Mix_LockAudio();
+ if (music && music->interface->StartTrack) {
+ if (music->interface->Pause) {
+ music->interface->Pause(music->context);
+ }
+ result = music->interface->StartTrack(music->context, track);
+ } else {
+ result = Mix_SetError("That operation is not supported");
+ }
+ Mix_UnlockAudio();
+
+ return result;
+}
+
+int Mix_GetNumTracks(Mix_Music *music)
+{
+ int result;
+
+ Mix_LockAudio();
+ if (music && music->interface->GetNumTracks) {
+ result = music->interface->GetNumTracks(music->context);
+ } else {
+ result = Mix_SetError("That operation is not supported");
+ }
+ Mix_UnlockAudio();
+ return result;
+}
+
/* Check the status of the music */
static SDL_bool music_internal_playing(void)
{
diff --git a/src/music.h b/src/music.h
index b193e0ec..8c3afccc 100644
--- a/src/music.h
+++ b/src/music.h
@@ -41,6 +41,7 @@ typedef enum
MIX_MUSIC_OPUS,
MIX_MUSIC_LIBXMP,
MIX_MUSIC_WAVPACK,
+ MIX_MUSIC_GME,
MIX_MUSIC_LAST
} Mix_MusicAPI;
@@ -133,6 +134,12 @@ typedef struct
/* Get a meta-tag string if available */
const char* (*GetMetaTag)(void *music, Mix_MusicMetaTag tag_type);
+ /* Get number of tracks. Returns -1 if not applicable */
+ int (*GetNumTracks)(void *music);
+
+ /* Start a specific track */
+ int (*StartTrack)(void *music, int track);
+
/* Pause playing music */
void (*Pause)(void *music);
@@ -150,7 +157,6 @@ typedef struct
/* Unload the library */
void (*Unload)(void);
-
} Mix_MusicInterface;