introduce MetaTags api
authorVitaly Novichkov
Mon, 23 Dec 2019 14:01:02 +0300
changeset 111924ca9a03d51c
parent 1118 d711a86866aa
child 1120 57a746548d9e
introduce MetaTags api

In some games or players it would be possible to print a title and
some minor tags about playing music.

There are 5 added functions which can give meta-tags from music if
they are available or supported by a codec:
- Mix_GetMusicTitle() gives song title.
Unlike Mix_GetMusicTitleTag, returns filename if tag is blank.
- Mix_GetMusicTitleTag() gives song title
- Mix_GetMusicArtistTag() gives artist name
- Mix_GetMusicAlbumTag() gives album name
- Mix_GetMusicCopyrightTag() gives copyright tag
include/SDL_mixer.h
src/codecs/music_cmd.c
src/codecs/music_flac.c
src/codecs/music_fluidsynth.c
src/codecs/music_mad.c
src/codecs/music_mikmod.c
src/codecs/music_modplug.c
src/codecs/music_mpg123.c
src/codecs/music_nativemidi.c
src/codecs/music_ogg.c
src/codecs/music_opus.c
src/codecs/music_timidity.c
src/codecs/music_wav.c
src/music.c
src/music.h
     1.1 --- a/include/SDL_mixer.h	Mon Dec 23 14:01:02 2019 +0300
     1.2 +++ b/include/SDL_mixer.h	Mon Dec 23 14:01:02 2019 +0300
     1.3 @@ -213,6 +213,17 @@
     1.4  */
     1.5  extern DECLSPEC Mix_MusicType SDLCALL Mix_GetMusicType(const Mix_Music *music);
     1.6  
     1.7 +/* Get music title from meta-tag if possible. If title tag is empty, filename will be returned */
     1.8 +extern DECLSPEC const char *SDLCALL Mix_GetMusicTitle(const Mix_Music *music);
     1.9 +/* Get music title from meta-tag if possible */
    1.10 +extern DECLSPEC const char *SDLCALL Mix_GetMusicTitleTag(const Mix_Music *music);
    1.11 +/* Get music artist from meta-tag if possible */
    1.12 +extern DECLSPEC const char *SDLCALL Mix_GetMusicArtistTag(const Mix_Music *music);
    1.13 +/* Get music album from meta-tag if possible */
    1.14 +extern DECLSPEC const char *SDLCALL Mix_GetMusicAlbumTag(const Mix_Music *music);
    1.15 +/* Get music copyright from meta-tag if possible */
    1.16 +extern DECLSPEC const char *SDLCALL Mix_GetMusicCopyrightTag(const Mix_Music *music);
    1.17 +
    1.18  /* Set a function that is called after all mixing is performed.
    1.19     This can be used to provide real-time visual display of the audio stream
    1.20     or add a custom mixer filter for the stream data.
     2.1 --- a/src/codecs/music_cmd.c	Mon Dec 23 14:01:02 2019 +0300
     2.2 +++ b/src/codecs/music_cmd.c	Mon Dec 23 14:01:02 2019 +0300
     2.3 @@ -285,6 +285,7 @@
     2.4      NULL,   /* LoopStart */
     2.5      NULL,   /* LoopEnd */
     2.6      NULL,   /* LoopLength */
     2.7 +    NULL,   /* GetMetaTag */
     2.8      MusicCMD_Pause,
     2.9      MusicCMD_Resume,
    2.10      MusicCMD_Stop,
     3.1 --- a/src/codecs/music_flac.c	Mon Dec 23 14:01:02 2019 +0300
     3.2 +++ b/src/codecs/music_flac.c	Mon Dec 23 14:01:02 2019 +0300
     3.3 @@ -161,6 +161,7 @@
     3.4      FLAC__int64 loop_start;
     3.5      FLAC__int64 loop_end;
     3.6      FLAC__int64 loop_len;
     3.7 +    Mix_MusicMetaTags tags;
     3.8  } FLAC_Music;
     3.9  
    3.10  
    3.11 @@ -401,6 +402,13 @@
    3.12      return (result * 60 + val) * samplerate_hz;
    3.13  }
    3.14  
    3.15 +static SDL_bool is_loop_tag(const char *tag)
    3.16 +{
    3.17 +    char buf[5];
    3.18 +    SDL_strlcpy(buf, tag, 5);
    3.19 +    return SDL_strcasecmp(buf, "LOOP") == 0;
    3.20 +}
    3.21 +
    3.22  static void flac_metadata_music_cb(
    3.23                      const FLAC__StreamDecoder *decoder,
    3.24                      const FLAC__StreamMetadata *metadata,
    3.25 @@ -453,8 +461,9 @@
    3.26  
    3.27              /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
    3.28               * string if it is present at position 4. */
    3.29 -            if ((argument[4] == '_') || (argument[4] == '-')) {
    3.30 -                SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
    3.31 +            if (is_loop_tag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
    3.32 +                SDL_memmove(argument + 4, argument + 5,
    3.33 +                           SDL_strlen(argument) - 4);
    3.34              }
    3.35  
    3.36              if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
    3.37 @@ -465,13 +474,14 @@
    3.38              } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
    3.39                  music->loop_end = parse_time(value, rate);
    3.40                  is_loop_length = SDL_FALSE;
    3.41 -            }
    3.42 -            if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
    3.43 -                music->loop_start = 0;
    3.44 -                music->loop_len = 0;
    3.45 -                music->loop_end = 0;
    3.46 -                SDL_free(param);
    3.47 -                break;  /* ignore tag. */
    3.48 +            } else if (SDL_strcasecmp(argument, "TITLE") == 0) {
    3.49 +                meta_tags_set(&music->tags, MIX_META_TITLE, value);
    3.50 +            } else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
    3.51 +                meta_tags_set(&music->tags, MIX_META_ARTIST, value);
    3.52 +            } else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
    3.53 +                meta_tags_set(&music->tags, MIX_META_ALBUM, value);
    3.54 +            } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
    3.55 +                meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
    3.56              }
    3.57              SDL_free(param);
    3.58          }
    3.59 @@ -481,6 +491,13 @@
    3.60          } else {
    3.61              music->loop_len = music->loop_end - music->loop_start;
    3.62          }
    3.63 +
    3.64 +        /* Ignore invalid loop tag */
    3.65 +        if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
    3.66 +            music->loop_start = 0;
    3.67 +            music->loop_len = 0;
    3.68 +            music->loop_end = 0;
    3.69 +        }
    3.70      }
    3.71  }
    3.72  
    3.73 @@ -582,6 +599,13 @@
    3.74      return music;
    3.75  }
    3.76  
    3.77 +static const char* FLAC_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
    3.78 +{
    3.79 +    FLAC_Music *music = (FLAC_Music *)context;
    3.80 +    return meta_tags_get(&music->tags, tag_type);
    3.81 +}
    3.82 +
    3.83 +
    3.84  /* Set the volume for an FLAC stream */
    3.85  static void FLAC_SetVolume(void *context, int volume)
    3.86  {
    3.87 @@ -731,6 +755,7 @@
    3.88  {
    3.89      FLAC_Music *music = (FLAC_Music *)context;
    3.90      if (music) {
    3.91 +        meta_tags_clear(&music->tags);
    3.92          if (music->flac_decoder) {
    3.93              flac.FLAC__stream_decoder_finish(music->flac_decoder);
    3.94              flac.FLAC__stream_decoder_delete(music->flac_decoder);
    3.95 @@ -768,6 +793,7 @@
    3.96      FLAC_LoopStart,
    3.97      FLAC_LoopEnd,
    3.98      FLAC_LoopLength,
    3.99 +    FLAC_GetMetaTag,/* GetMetaTag */
   3.100      NULL,   /* Pause */
   3.101      NULL,   /* Resume */
   3.102      NULL,   /* Stop */
     4.1 --- a/src/codecs/music_fluidsynth.c	Mon Dec 23 14:01:02 2019 +0300
     4.2 +++ b/src/codecs/music_fluidsynth.c	Mon Dec 23 14:01:02 2019 +0300
     4.3 @@ -315,6 +315,7 @@
     4.4      NULL,   /* LoopStart */
     4.5      NULL,   /* LoopEnd */
     4.6      NULL,   /* LoopLength */
     4.7 +    NULL,   /* GetMetaTag */
     4.8      NULL,   /* Pause */
     4.9      NULL,   /* Resume */
    4.10      FLUIDSYNTH_Stop,
     5.1 --- a/src/codecs/music_mad.c	Mon Dec 23 14:01:02 2019 +0300
     5.2 +++ b/src/codecs/music_mad.c	Mon Dec 23 14:01:02 2019 +0300
     5.3 @@ -477,6 +477,7 @@
     5.4      NULL,   /* LoopStart */
     5.5      NULL,   /* LoopEnd */
     5.6      NULL,   /* LoopLength */
     5.7 +    NULL,   /* GetMetaTag */
     5.8      NULL,   /* Pause */
     5.9      NULL,   /* Resume */
    5.10      NULL,   /* Stop */
     6.1 --- a/src/codecs/music_mikmod.c	Mon Dec 23 14:01:02 2019 +0300
     6.2 +++ b/src/codecs/music_mikmod.c	Mon Dec 23 14:01:02 2019 +0300
     6.3 @@ -171,6 +171,7 @@
     6.4      SDL_AudioStream *stream;
     6.5      SBYTE *buffer;
     6.6      ULONG buffer_size;
     6.7 +    Mix_MusicMetaTags tags;
     6.8  } MIKMOD_Music;
     6.9  
    6.10  
    6.11 @@ -329,6 +330,9 @@
    6.12      music->module->loop    = 1;
    6.13      music->module->fadeout = 0;
    6.14  
    6.15 +    meta_tags_init(&music->tags);
    6.16 +    meta_tags_set(&music->tags, MIX_META_TITLE, music->module->songname);
    6.17 +
    6.18      if ((*mikmod.md_mode & DMODE_16BITS) == DMODE_16BITS) {
    6.19          format = AUDIO_S16SYS;
    6.20      } else {
    6.21 @@ -455,11 +459,18 @@
    6.22      mikmod.Player_Stop();
    6.23  }
    6.24  
    6.25 +static const char* MIKMOD_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
    6.26 +{
    6.27 +    MIKMOD_Music *music = (MIKMOD_Music *)context;
    6.28 +    return meta_tags_get(&music->tags, tag_type);
    6.29 +}
    6.30 +
    6.31  /* Close the given MOD stream */
    6.32  static void MIKMOD_Delete(void *context)
    6.33  {
    6.34      MIKMOD_Music *music = (MIKMOD_Music *)context;
    6.35  
    6.36 +    meta_tags_clear(&music->tags);
    6.37      if (music->module) {
    6.38          mikmod.Player_Free(music->module);
    6.39      }
    6.40 @@ -495,6 +506,7 @@
    6.41      NULL,   /* LoopStart */
    6.42      NULL,   /* LoopEnd */
    6.43      NULL,   /* LoopLength */
    6.44 +    MIKMOD_GetMetaTag,
    6.45      NULL,   /* Pause */
    6.46      NULL,   /* Resume */
    6.47      MIKMOD_Stop,
     7.1 --- a/src/codecs/music_modplug.c	Mon Dec 23 14:01:02 2019 +0300
     7.2 +++ b/src/codecs/music_modplug.c	Mon Dec 23 14:01:02 2019 +0300
     7.3 @@ -43,6 +43,7 @@
     7.4      void (*ModPlug_GetSettings)(ModPlug_Settings* settings);
     7.5      void (*ModPlug_SetSettings)(const ModPlug_Settings* settings);
     7.6      void (*ModPlug_SetMasterVolume)(ModPlugFile* file,unsigned int cvol);
     7.7 +    const char* (*ModPlug_GetName)(ModPlugFile* file);
     7.8  } modplug_loader;
     7.9  
    7.10  static modplug_loader modplug = {
    7.11 @@ -86,6 +87,7 @@
    7.12          FUNCTION_LOADER(ModPlug_GetSettings, void (*)(ModPlug_Settings* settings))
    7.13          FUNCTION_LOADER(ModPlug_SetSettings, void (*)(const ModPlug_Settings* settings))
    7.14          FUNCTION_LOADER(ModPlug_SetMasterVolume, void (*)(ModPlugFile* file,unsigned int cvol))
    7.15 +        FUNCTION_LOADER(ModPlug_GetName, const char* (*)(ModPlugFile* file))
    7.16      }
    7.17      ++modplug.loaded;
    7.18  
    7.19 @@ -114,6 +116,7 @@
    7.20      SDL_AudioStream *stream;
    7.21      void *buffer;
    7.22      int buffer_size;
    7.23 +    Mix_MusicMetaTags tags;
    7.24  } MODPLUG_Music;
    7.25  
    7.26  
    7.27 @@ -197,6 +200,9 @@
    7.28          return NULL;
    7.29      }
    7.30  
    7.31 +    meta_tags_init(&music->tags);
    7.32 +    meta_tags_set(&music->tags, MIX_META_TITLE, modplug.ModPlug_GetName(music->file));
    7.33 +
    7.34      if (freesrc) {
    7.35          SDL_RWclose(src);
    7.36      }
    7.37 @@ -284,10 +290,17 @@
    7.38      return modplug.ModPlug_GetLength(music->file) / 1000.0;
    7.39  }
    7.40  
    7.41 +static const char* MODPLUG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
    7.42 +{
    7.43 +    MODPLUG_Music *music = (MODPLUG_Music *)context;
    7.44 +    return meta_tags_get(&music->tags, tag_type);
    7.45 +}
    7.46 +
    7.47  /* Close the given modplug stream */
    7.48  static void MODPLUG_Delete(void *context)
    7.49  {
    7.50      MODPLUG_Music *music = (MODPLUG_Music *)context;
    7.51 +    meta_tags_clear(&music->tags);
    7.52      if (music->file) {
    7.53          modplug.ModPlug_Unload(music->file);
    7.54      }
    7.55 @@ -323,6 +336,7 @@
    7.56      NULL,   /* LoopStart */
    7.57      NULL,   /* LoopEnd */
    7.58      NULL,   /* LoopLength */
    7.59 +    MODPLUG_GetMetaTag,
    7.60      NULL,   /* Pause */
    7.61      NULL,   /* Resume */
    7.62      NULL,   /* Stop */
     8.1 --- a/src/codecs/music_mpg123.c	Mon Dec 23 14:01:02 2019 +0300
     8.2 +++ b/src/codecs/music_mpg123.c	Mon Dec 23 14:01:02 2019 +0300
     8.3 @@ -502,6 +502,7 @@
     8.4      NULL,   /* LoopStart */
     8.5      NULL,   /* LoopEnd */
     8.6      NULL,   /* LoopLength */
     8.7 +    NULL,   /* GetMetaTag */
     8.8      NULL,   /* Pause */
     8.9      NULL,   /* Resume */
    8.10      NULL,   /* Stop */
     9.1 --- a/src/codecs/music_nativemidi.c	Mon Dec 23 14:01:02 2019 +0300
     9.2 +++ b/src/codecs/music_nativemidi.c	Mon Dec 23 14:01:02 2019 +0300
     9.3 @@ -106,6 +106,7 @@
     9.4      NULL,   /* LoopStart */
     9.5      NULL,   /* LoopEnd */
     9.6      NULL,   /* LoopLength */
     9.7 +    NULL,   /* GetMetaTag */
     9.8      NATIVEMIDI_Pause,
     9.9      NATIVEMIDI_Resume,
    9.10      NATIVEMIDI_Stop,
    10.1 --- a/src/codecs/music_ogg.c	Mon Dec 23 14:01:02 2019 +0300
    10.2 +++ b/src/codecs/music_ogg.c	Mon Dec 23 14:01:02 2019 +0300
    10.3 @@ -143,6 +143,7 @@
    10.4      ogg_int64_t loop_start;
    10.5      ogg_int64_t loop_end;
    10.6      ogg_int64_t loop_len;
    10.7 +    Mix_MusicMetaTags tags;
    10.8  } OGG_music;
    10.9  
   10.10  
   10.11 @@ -264,6 +265,13 @@
   10.12      return (result * 60 + val) * samplerate_hz;
   10.13  }
   10.14  
   10.15 +static SDL_bool is_loop_tag(const char *tag)
   10.16 +{
   10.17 +    char buf[5];
   10.18 +    SDL_strlcpy(buf, tag, 5);
   10.19 +    return SDL_strcasecmp(buf, "LOOP") == 0;
   10.20 +}
   10.21 +
   10.22  /* Load an OGG stream from an SDL_RWops object */
   10.23  static void *OGG_CreateFromRW(SDL_RWops *src, int freesrc)
   10.24  {
   10.25 @@ -314,7 +322,7 @@
   10.26  
   10.27          /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
   10.28           * string if it is present at position 4. */
   10.29 -        if ((argument[4] == '_') || (argument[4] == '-')) {
   10.30 +        if (is_loop_tag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
   10.31              SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
   10.32          }
   10.33  
   10.34 @@ -326,13 +334,14 @@
   10.35          } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
   10.36              music->loop_end = parse_time(value, rate);
   10.37              is_loop_length = SDL_FALSE;
   10.38 -        }
   10.39 -        if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
   10.40 -            music->loop_start = 0;
   10.41 -            music->loop_len = 0;
   10.42 -            music->loop_end = 0;
   10.43 -            SDL_free(param);
   10.44 -            break;  /* ignore tag. */
   10.45 +        } else if (SDL_strcasecmp(argument, "TITLE") == 0) {
   10.46 +            meta_tags_set(&music->tags, MIX_META_TITLE, value);
   10.47 +        } else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
   10.48 +            meta_tags_set(&music->tags, MIX_META_ARTIST, value);
   10.49 +        } else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
   10.50 +            meta_tags_set(&music->tags, MIX_META_ALBUM, value);
   10.51 +        } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
   10.52 +            meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
   10.53          }
   10.54          SDL_free(param);
   10.55      }
   10.56 @@ -343,6 +352,13 @@
   10.57          music->loop_len = music->loop_end - music->loop_start;
   10.58      }
   10.59  
   10.60 +    /* Ignore invalid loop tag */
   10.61 +    if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
   10.62 +        music->loop_start = 0;
   10.63 +        music->loop_len = 0;
   10.64 +        music->loop_end = 0;
   10.65 +    }
   10.66 +
   10.67      full_length = vorbis.ov_pcm_total(&music->vf, -1);
   10.68      if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
   10.69          (music->loop_start < music->loop_end)) {
   10.70 @@ -353,6 +369,12 @@
   10.71      return music;
   10.72  }
   10.73  
   10.74 +static const char* OGG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
   10.75 +{
   10.76 +    OGG_music *music = (OGG_music *)context;
   10.77 +    return meta_tags_get(&music->tags, tag_type);
   10.78 +}
   10.79 +
   10.80  /* Set the volume for an OGG stream */
   10.81  static void OGG_SetVolume(void *context, int volume)
   10.82  {
   10.83 @@ -525,6 +547,7 @@
   10.84  static void OGG_Delete(void *context)
   10.85  {
   10.86      OGG_music *music = (OGG_music *)context;
   10.87 +    meta_tags_clear(&music->tags);
   10.88      vorbis.ov_clear(&music->vf);
   10.89      if (music->stream) {
   10.90          SDL_FreeAudioStream(music->stream);
   10.91 @@ -561,6 +584,7 @@
   10.92      OGG_LoopStart,
   10.93      OGG_LoopEnd,
   10.94      OGG_LoopLength,
   10.95 +    OGG_GetMetaTag,   /* GetMetaTag */
   10.96      NULL,   /* Pause */
   10.97      NULL,   /* Resume */
   10.98      NULL,   /* Stop */
    11.1 --- a/src/codecs/music_opus.c	Mon Dec 23 14:01:02 2019 +0300
    11.2 +++ b/src/codecs/music_opus.c	Mon Dec 23 14:01:02 2019 +0300
    11.3 @@ -121,6 +121,7 @@
    11.4      ogg_int64_t loop_end;
    11.5      ogg_int64_t loop_len;
    11.6      ogg_int64_t full_length;
    11.7 +    Mix_MusicMetaTags tags;
    11.8  } OPUS_music;
    11.9  
   11.10  
   11.11 @@ -320,13 +321,14 @@
   11.12          } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
   11.13              music->loop_end = parse_time(value);
   11.14              is_loop_length = SDL_FALSE;
   11.15 -        }
   11.16 -        if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
   11.17 -            music->loop_start = 0;
   11.18 -            music->loop_len = 0;
   11.19 -            music->loop_end = 0;
   11.20 -            SDL_free(param);
   11.21 -            break;  /* ignore tag. */
   11.22 +        } else if (SDL_strcasecmp(argument, "TITLE") == 0) {
   11.23 +            meta_tags_set(&music->tags, MIX_META_TITLE, value);
   11.24 +        } else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
   11.25 +            meta_tags_set(&music->tags, MIX_META_ARTIST, value);
   11.26 +        } else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
   11.27 +            meta_tags_set(&music->tags, MIX_META_ALBUM, value);
   11.28 +        } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
   11.29 +            meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
   11.30          }
   11.31          SDL_free(param);
   11.32      }
   11.33 @@ -337,6 +339,13 @@
   11.34          music->loop_len = music->loop_end - music->loop_start;
   11.35      }
   11.36  
   11.37 +    /* Ignore invalid loop tag */
   11.38 +    if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
   11.39 +        music->loop_start = 0;
   11.40 +        music->loop_len = 0;
   11.41 +        music->loop_end = 0;
   11.42 +    }
   11.43 +
   11.44      full_length = opus.op_pcm_total(music->of, -1);
   11.45      if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
   11.46          (music->loop_start < music->loop_end)) {
   11.47 @@ -348,6 +357,12 @@
   11.48      return music;
   11.49  }
   11.50  
   11.51 +static const char* OPUS_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
   11.52 +{
   11.53 +    OPUS_music *music = (OPUS_music *)context;
   11.54 +    return meta_tags_get(&music->tags, tag_type);
   11.55 +}
   11.56 +
   11.57  /* Set the volume for an Opus stream */
   11.58  static void OPUS_SetVolume(void *context, int volume)
   11.59  {
   11.60 @@ -540,6 +555,7 @@
   11.61      OPUS_LoopStart,
   11.62      OPUS_LoopEnd,
   11.63      OPUS_LoopLength,
   11.64 +    OPUS_GetMetaTag,
   11.65      NULL,   /* Pause */
   11.66      NULL,   /* Resume */
   11.67      NULL,   /* Stop */
    12.1 --- a/src/codecs/music_timidity.c	Mon Dec 23 14:01:02 2019 +0300
    12.2 +++ b/src/codecs/music_timidity.c	Mon Dec 23 14:01:02 2019 +0300
    12.3 @@ -261,6 +261,7 @@
    12.4      NULL,   /* LoopStart */
    12.5      NULL,   /* LoopEnd */
    12.6      NULL,   /* LoopLength */
    12.7 +    NULL,   /* GetMetaTag */
    12.8      NULL,   /* Pause */
    12.9      NULL,   /* Resume */
   12.10      NULL,   /* Stop */
    13.1 --- a/src/codecs/music_wav.c	Mon Dec 23 14:01:02 2019 +0300
    13.2 +++ b/src/codecs/music_wav.c	Mon Dec 23 14:01:02 2019 +0300
    13.3 @@ -47,6 +47,7 @@
    13.4      SDL_AudioStream *stream;
    13.5      unsigned int numloops;
    13.6      WAVLoopPoint *loops;
    13.7 +    Mix_MusicMetaTags tags;
    13.8      Uint16 encoding;
    13.9      int (*decode)(void *music, int length);
   13.10  } WAV_Music;
   13.11 @@ -600,12 +601,19 @@
   13.12      return (double)(music->stop - music->start) / sample_size;
   13.13  }
   13.14  
   13.15 +static const char* WAV_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
   13.16 +{
   13.17 +    WAV_Music *music = (WAV_Music *)context;
   13.18 +    return meta_tags_get(&music->tags, tag_type);
   13.19 +}
   13.20 +
   13.21  /* Close the given WAV stream */
   13.22  static void WAV_Delete(void *context)
   13.23  {
   13.24      WAV_Music *music = (WAV_Music *)context;
   13.25  
   13.26      /* Clean up associated data */
   13.27 +    meta_tags_clear(&music->tags);
   13.28      if (music->loops) {
   13.29          SDL_free(music->loops);
   13.30      }
   13.31 @@ -793,6 +801,70 @@
   13.32      return loaded;
   13.33  }
   13.34  
   13.35 +static void read_meta_field(Mix_MusicMetaTags *tags, Mix_MusicMetaTag tag_type, size_t *i, Uint32 chunk_length, Uint8 *data, size_t fieldOffset)
   13.36 +{
   13.37 +    Uint32 len = 0;
   13.38 +    int isID3 = fieldOffset == 7;
   13.39 +    char *field = NULL;
   13.40 +    *i += 4;
   13.41 +    len = isID3 ?
   13.42 +          SDL_SwapBE32(*((Uint32 *)(data + *i))) : /* ID3  */
   13.43 +          SDL_SwapLE32(*((Uint32 *)(data + *i))); /* LIST */
   13.44 +    if (len > chunk_length) {
   13.45 +        return; /* Do nothing due to broken lenght */
   13.46 +    }
   13.47 +    *i += fieldOffset;
   13.48 +    field = (char *)SDL_malloc(len + 1);
   13.49 +    SDL_memset(field, 0, (len + 1));
   13.50 +    SDL_strlcpy(field, (char *)(data + *i), isID3 ? len - 1 : len);
   13.51 +    *i += len;
   13.52 +    meta_tags_set(tags, tag_type, field);
   13.53 +    SDL_free(field);
   13.54 +}
   13.55 +
   13.56 +static SDL_bool ParseLIST(WAV_Music *wave, Uint32 chunk_length)
   13.57 +{
   13.58 +    SDL_bool loaded = SDL_FALSE;
   13.59 +
   13.60 +    Uint8 *data;
   13.61 +    data = (Uint8 *)SDL_malloc(chunk_length);
   13.62 +    if (!data) {
   13.63 +        Mix_SetError("Out of memory");
   13.64 +        return SDL_FALSE;
   13.65 +    }
   13.66 +
   13.67 +    if (!SDL_RWread(wave->src, data, chunk_length, 1)) {
   13.68 +        Mix_SetError("Couldn't read %d bytes from WAV file", chunk_length);
   13.69 +        return SDL_FALSE;
   13.70 +    }
   13.71 +
   13.72 +    if (SDL_strncmp((char *)data, "INFO", 4) == 0) {
   13.73 +        size_t i = 4;
   13.74 +        for (i = 4; i < chunk_length - 4;) {
   13.75 +            if(SDL_strncmp((char *)(data + i), "INAM", 4) == 0) {
   13.76 +                read_meta_field(&wave->tags, MIX_META_TITLE, &i, chunk_length, data, 4);
   13.77 +                continue;
   13.78 +            } else if(SDL_strncmp((char *)(data + i), "IART", 4) == 0) {
   13.79 +                read_meta_field(&wave->tags, MIX_META_ARTIST, &i, chunk_length, data, 4);
   13.80 +                continue;
   13.81 +            } else if(SDL_strncmp((char *)(data + i), "IALB", 4) == 0) {
   13.82 +                read_meta_field(&wave->tags, MIX_META_ALBUM, &i, chunk_length, data, 4);
   13.83 +                continue;
   13.84 +            } else if (SDL_strncmp((char *)(data + i), "BCPR", 4) == 0) {
   13.85 +                read_meta_field(&wave->tags, MIX_META_COPYRIGHT, &i, chunk_length, data, 4);
   13.86 +                continue;
   13.87 +            }
   13.88 +            i++;
   13.89 +        }
   13.90 +        loaded = SDL_TRUE;
   13.91 +    }
   13.92 +
   13.93 +    /* done: */
   13.94 +    SDL_free(data);
   13.95 +
   13.96 +    return loaded;
   13.97 +}
   13.98 +
   13.99  static SDL_bool LoadWAVMusic(WAV_Music *wave)
  13.100  {
  13.101      SDL_RWops *src = wave->src;
  13.102 @@ -804,6 +876,8 @@
  13.103      Uint32 wavelen;
  13.104      Uint32 WAVEmagic;
  13.105  
  13.106 +    meta_tags_init(&wave->tags);
  13.107 +
  13.108      /* Check the magic header */
  13.109      wavelen = SDL_ReadLE32(src);
  13.110      WAVEmagic = SDL_ReadLE32(src);
  13.111 @@ -835,6 +909,10 @@
  13.112              if (!ParseSMPL(wave, chunk_length))
  13.113                  return SDL_FALSE;
  13.114              break;
  13.115 +        case LIST:
  13.116 +            if (!ParseLIST(wave, chunk_length))
  13.117 +                return SDL_FALSE;
  13.118 +            break;
  13.119          default:
  13.120              SDL_RWseek(src, chunk_length, RW_SEEK_CUR);
  13.121              break;
  13.122 @@ -907,6 +985,7 @@
  13.123      Uint32 frequency = 0;
  13.124      Uint32 AIFCVersion1 = 0;
  13.125      Uint32 compressionType = 0;
  13.126 +    char *chunk_buffer;
  13.127  
  13.128      file_length = SDL_RWsize(src);
  13.129  
  13.130 @@ -959,7 +1038,17 @@
  13.131          case NAME:
  13.132          case AUTH:
  13.133          case _c__:
  13.134 -            /* Just skip those chunks */
  13.135 +            chunk_buffer = (char*)SDL_calloc(1, chunk_length + 1);
  13.136 +            if (SDL_RWread(src, chunk_buffer, 1, chunk_length) != chunk_length) {
  13.137 +                SDL_free(chunk_buffer);
  13.138 +                return SDL_FALSE;
  13.139 +            }
  13.140 +            meta_tags_set(&wave->tags,
  13.141 +                          chunk_type == NAME ? MIX_META_TITLE :
  13.142 +                          chunk_type == AUTH ? MIX_META_ARTIST :
  13.143 +                          chunk_type == _c__ ? MIX_META_COPYRIGHT : 0,
  13.144 +                          chunk_buffer);
  13.145 +            SDL_free(chunk_buffer);
  13.146              break;
  13.147  
  13.148          case COMM:
  13.149 @@ -1115,6 +1204,7 @@
  13.150      NULL,   /* LoopStart */
  13.151      NULL,   /* LoopEnd */
  13.152      NULL,   /* LoopLength */
  13.153 +    WAV_GetMetaTag,   /* GetMetaTag */
  13.154      NULL,   /* Pause */
  13.155      NULL,   /* Resume */
  13.156      NULL,   /* Stop */
    14.1 --- a/src/music.c	Mon Dec 23 14:01:02 2019 +0300
    14.2 +++ b/src/music.c	Mon Dec 23 14:01:02 2019 +0300
    14.3 @@ -65,6 +65,8 @@
    14.4      Mix_Fading fading;
    14.5      int fade_step;
    14.6      int fade_steps;
    14.7 +
    14.8 +    char music_filename[1024];
    14.9  };
   14.10  
   14.11  /* Used to calculate fading steps */
   14.12 @@ -80,6 +82,63 @@
   14.13  /* full path of timidity config file */
   14.14  static char* timidity_cfg = NULL;
   14.15  
   14.16 +/* Meta-Tags utiltiy */
   14.17 +void meta_tags_init(Mix_MusicMetaTags *tags)
   14.18 +{
   14.19 +    SDL_memset(tags, 0, sizeof(Mix_MusicMetaTags));
   14.20 +}
   14.21 +
   14.22 +void meta_tags_clear(Mix_MusicMetaTags *tags)
   14.23 +{
   14.24 +    size_t i = 0;
   14.25 +    for (i = 0; i < MIX_META_LAST; i++) {
   14.26 +        if (tags->tags[i]) {
   14.27 +            SDL_free(tags->tags[i]);
   14.28 +            tags->tags[i] = NULL;
   14.29 +        }
   14.30 +    }
   14.31 +}
   14.32 +
   14.33 +void meta_tags_set(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type, const char *value)
   14.34 +{
   14.35 +    char *out;
   14.36 +    size_t len;
   14.37 +
   14.38 +    if (!value) {
   14.39 +        return;
   14.40 +    }
   14.41 +    if (type >= MIX_META_LAST) {
   14.42 +        return;
   14.43 +    }
   14.44 +
   14.45 +    len = SDL_strlen(value);
   14.46 +    out = (char *)SDL_malloc(sizeof(char) * len + 1);
   14.47 +    SDL_memset(out, 0, len + 1);
   14.48 +    SDL_strlcpy(out, value, len +1);
   14.49 +
   14.50 +    if (tags->tags[type]) {
   14.51 +        SDL_free(tags->tags[type]);
   14.52 +    }
   14.53 +
   14.54 +    tags->tags[type] = out;
   14.55 +}
   14.56 +
   14.57 +const char *meta_tags_get(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type)
   14.58 +{
   14.59 +    switch (type) {
   14.60 +    case MIX_META_TITLE:
   14.61 +    case MIX_META_ARTIST:
   14.62 +    case MIX_META_ALBUM:
   14.63 +    case MIX_META_COPYRIGHT:
   14.64 +        return tags->tags[type] ? tags->tags[type] : "";
   14.65 +    case MIX_META_LAST:
   14.66 +    default:
   14.67 +        break;
   14.68 +    }
   14.69 +    return "";
   14.70 +}
   14.71 +
   14.72 +
   14.73  /* Interfaces for the various music interfaces, ordered by priority */
   14.74  static Mix_MusicInterface *s_music_interfaces[] =
   14.75  {
   14.76 @@ -492,6 +551,7 @@
   14.77              }
   14.78              music->interface = interface;
   14.79              music->context = context;
   14.80 +            SDL_strlcpy(music->music_filename, (SDL_strstr(file, "/")) ? (SDL_strrchr(file, '/') + 1) : file, 1024);
   14.81              return music;
   14.82          }
   14.83      }
   14.84 @@ -666,6 +726,58 @@
   14.85      return(type);
   14.86  }
   14.87  
   14.88 +static const char * get_music_tag_internal(const Mix_Music *music, Mix_MusicMetaTag tag_type)
   14.89 +{
   14.90 +    const char *tag = "";
   14.91 +
   14.92 +    Mix_LockAudio();
   14.93 +    if (music && music->interface->GetMetaTag) {
   14.94 +        tag = music->interface->GetMetaTag(music->context, tag_type);
   14.95 +    } else if (music_playing && music_playing->interface->GetMetaTag) {
   14.96 +        tag = music_playing->interface->GetMetaTag(music_playing->context, tag_type);
   14.97 +    } else {
   14.98 +        Mix_SetError("Music isn't playing");
   14.99 +    }
  14.100 +    Mix_UnlockAudio();
  14.101 +    return tag;
  14.102 +}
  14.103 +
  14.104 +const char *Mix_GetMusicTitleTag(const Mix_Music *music)
  14.105 +{
  14.106 +    return get_music_tag_internal(music, MIX_META_TITLE);
  14.107 +}
  14.108 +
  14.109 +/* Get music title from meta-tag if possible */
  14.110 +const char *Mix_GetMusicTitle(const Mix_Music *music)
  14.111 +{
  14.112 +    const char *tag = Mix_GetMusicTitleTag(music);
  14.113 +    if (SDL_strlen(tag) > 0) {
  14.114 +        return tag;
  14.115 +    }
  14.116 +    if (music) {
  14.117 +        return music->music_filename;
  14.118 +    }
  14.119 +    if (music_playing) {
  14.120 +        return music_playing->music_filename;
  14.121 +    }
  14.122 +    return "";
  14.123 +}
  14.124 +
  14.125 +const char *Mix_GetMusicArtistTag(const Mix_Music *music)
  14.126 +{
  14.127 +    return get_music_tag_internal(music, MIX_META_ARTIST);
  14.128 +}
  14.129 +
  14.130 +const char *Mix_GetMusicAlbumTag(const Mix_Music *music)
  14.131 +{
  14.132 +    return get_music_tag_internal(music, MIX_META_ALBUM);
  14.133 +}
  14.134 +
  14.135 +const char *Mix_GetMusicCopyrightTag(const Mix_Music *music)
  14.136 +{
  14.137 +    return get_music_tag_internal(music, MIX_META_COPYRIGHT);
  14.138 +}
  14.139 +
  14.140  /* Play a music chunk.  Returns 0, or -1 if there was an error.
  14.141   */
  14.142  static int music_internal_play(Mix_Music *music, int play_count, double position)
    15.1 --- a/src/music.h	Mon Dec 23 14:01:02 2019 +0300
    15.2 +++ b/src/music.h	Mon Dec 23 14:01:02 2019 +0300
    15.3 @@ -43,6 +43,31 @@
    15.4  } Mix_MusicAPI;
    15.5  
    15.6  
    15.7 +/* Supported meta-tags */
    15.8 +
    15.9 +typedef enum
   15.10 +{
   15.11 +    MIX_META_TITLE,
   15.12 +    MIX_META_ARTIST,
   15.13 +    MIX_META_ALBUM,
   15.14 +    MIX_META_COPYRIGHT,
   15.15 +    MIX_META_LAST
   15.16 +} Mix_MusicMetaTag;
   15.17 +
   15.18 +
   15.19 +/* MIXER-X: Meta-tags utility structure */
   15.20 +
   15.21 +typedef struct {
   15.22 +    char *tags[4];
   15.23 +} Mix_MusicMetaTags;
   15.24 +
   15.25 +
   15.26 +extern void meta_tags_init(Mix_MusicMetaTags *tags);
   15.27 +extern void meta_tags_clear(Mix_MusicMetaTags *tags);
   15.28 +extern void meta_tags_set(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type, const char *value);
   15.29 +extern const char* meta_tags_get(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type);
   15.30 +
   15.31 +
   15.32  /* Music API implementation */
   15.33  
   15.34  typedef struct
   15.35 @@ -100,6 +125,9 @@
   15.36      /* Tell a loop length position (in seconds) */
   15.37      double (*LoopLength)(void *music);
   15.38  
   15.39 +    /* Get a meta-tag string if available */
   15.40 +    const char* (*GetMetaTag)(void *music, Mix_MusicMetaTag tag_type);
   15.41 +
   15.42      /* Pause playing music */
   15.43      void (*Pause)(void *music);
   15.44