text: /*
    SDL_mixer:  An audio mixer library based on the SDL library
    Copyright (C) 1997-2021 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_MOD_MODPLUG
  
  #include "SDL_loadso.h"
  
  #include "music_modplug.h"
  
  #ifdef MODPLUG_HEADER
  #include MODPLUG_HEADER
  #else
  #include <libmodplug/modplug.h>
  #endif
  
  typedef struct {
      int loaded;
      void *handle;
  
      ModPlugFile* (*ModPlug_Load)(const void* data, int size);
      void (*ModPlug_Unload)(ModPlugFile* file);
      int  (*ModPlug_Read)(ModPlugFile* file, void* buffer, int size);
      void (*ModPlug_Seek)(ModPlugFile* file, int millisecond);
      void (*ModPlug_SeekOrder)(ModPlugFile* file, int order);
      int  (*ModPlug_GetLength)(ModPlugFile* file);
      void (*ModPlug_GetSettings)(ModPlug_Settings* settings);
      void (*ModPlug_SetSettings)(const ModPlug_Settings* settings);
      void (*ModPlug_SetMasterVolume)(ModPlugFile* file,unsigned int cvol);
      const char* (*ModPlug_GetName)(ModPlugFile* file);
  } modplug_loader;
  
  static modplug_loader modplug = {
      0, NULL
  };
  
  
  static ModPlug_Settings settings;
  
  #ifdef MODPLUG_DYNAMIC
  #define FUNCTION_LOADER(FUNC, SIG) \
      modplug.FUNC = (SIG) SDL_LoadFunction(modplug.handle, #FUNC); \
      if (modplug.FUNC == NULL) { SDL_UnloadObject(modplug.handle); return -1; }
  #else
  #define FUNCTION_LOADER(FUNC, SIG) \
      modplug.FUNC = FUNC;
  #endif
  
  static int MODPLUG_Load(void)
  {
      if (modplug.loaded == 0) {
  #ifdef MODPLUG_DYNAMIC
          modplug.handle = SDL_LoadObject(MODPLUG_DYNAMIC);
          if (modplug.handle == NULL) {
              return -1;
          }
  #elif defined(__MACOSX__)
          extern ModPlugFile* ModPlug_Load(const void* data, int size) __attribute__((weak_import));
          if (ModPlug_Load == NULL) {
              /* Missing weakly linked framework */
              Mix_SetError("Missing modplug.framework");
              return -1;
          }
  #endif
          FUNCTION_LOADER(ModPlug_Load, ModPlugFile* (*)(const void* data, int size))
          FUNCTION_LOADER(ModPlug_Unload, void (*)(ModPlugFile* file))
          FUNCTION_LOADER(ModPlug_Read, int  (*)(ModPlugFile* file, void* buffer, int size))
          FUNCTION_LOADER(ModPlug_Seek, void (*)(ModPlugFile* file, int millisecond))
          FUNCTION_LOADER(ModPlug_SeekOrder, void (*)(ModPlugFile* file, int order))
          FUNCTION_LOADER(ModPlug_GetLength, int (*)(ModPlugFile* file))
          FUNCTION_LOADER(ModPlug_GetSettings, void (*)(ModPlug_Settings* settings))
          FUNCTION_LOADER(ModPlug_SetSettings, void (*)(const ModPlug_Settings* settings))
          FUNCTION_LOADER(ModPlug_SetMasterVolume, void (*)(ModPlugFile* file,unsigned int cvol))
          FUNCTION_LOADER(ModPlug_GetName, const char* (*)(ModPlugFile* file))
      }
      ++modplug.loaded;
  
      return 0;
  }
  
  static void MODPLUG_Unload(void)
  {
      if (modplug.loaded == 0) {
          return;
      }
      if (modplug.loaded == 1) {
  #ifdef MODPLUG_DYNAMIC
          SDL_UnloadObject(modplug.handle);
  #endif
      }
      --modplug.loaded;
  }
  
  
  typedef struct
  {
      int volume;
      int play_count;
      ModPlugFile *file;
      SDL_AudioStream *stream;
      void *buffer;
      int buffer_size;
      Mix_MusicMetaTags tags;
  } MODPLUG_Music;
  
  
  static int MODPLUG_Seek(void *context, double position);
  static void MODPLUG_Delete(void *context);
  
  static int MODPLUG_Open(const SDL_AudioSpec *spec)
  {
      /* ModPlug supports U8 or S16 audio output */
      modplug.ModPlug_GetSettings(&settings);
      settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING;
      if (spec->channels == 1) {
          settings.mChannels = 1;
      } else {
          settings.mChannels = 2;
      }
      if (SDL_AUDIO_BITSIZE(spec->format) == 8) {
          settings.mBits = 8;
      } else {
          settings.mBits = 16;
      }
      if (spec->freq >= 44100) {
          settings.mFrequency = 44100;
      } else if (spec->freq >= 22050) {
          settings.mFrequency = 22050;
      } else {
          settings.mFrequency = 11025;
      }
      settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
      settings.mReverbDepth = 0;
      settings.mReverbDelay = 100;
      settings.mBassAmount = 0;
      settings.mBassRange = 50;
      settings.mSurroundDepth = 0;
      settings.mSurroundDelay = 10;
      settings.mLoopCount = -1;
      modplug.ModPlug_SetSettings(&settings);
      return 0;
  }
  
  /* Load a modplug stream from an SDL_RWops object */
  void *MODPLUG_CreateFromRW(SDL_RWops *src, int freesrc)
  {
      MODPLUG_Music *music;
      void *buffer;
      size_t size;
  
      music = (MODPLUG_Music *)SDL_calloc(1, sizeof(*music));
      if (!music) {
          SDL_OutOfMemory();
          return NULL;
      }
  
      music->volume = MIX_MAX_VOLUME;
  
      music->stream = SDL_NewAudioStream((settings.mBits == 8) ? AUDIO_U8 : AUDIO_S16SYS, (Uint8)settings.mChannels, settings.mFrequency,
                                         music_spec.format, music_spec.channels, music_spec.freq);
      if (!music->stream) {
          MODPLUG_Delete(music);
          return NULL;
      }
  
      music->buffer_size = music_spec.samples * (settings.mBits / 8) * settings.mChannels;
      music->buffer = SDL_malloc((size_t)music->buffer_size);
      if (!music->buffer) {
          MODPLUG_Delete(music);
          return NULL;
      }
  
      buffer = SDL_LoadFile_RW(src, &size, SDL_FALSE);
      if (buffer) {
          music->file = modplug.ModPlug_Load(buffer, (int)size);
          if (!music->file) {
              Mix_SetError("ModPlug_Load failed");
          }
          SDL_free(buffer);
      }
  
      if (!music->file) {
          MODPLUG_Delete(music);
          return NULL;
      }
  
      meta_tags_init(&music->tags);
      meta_tags_set(&music->tags, MIX_META_TITLE, modplug.ModPlug_GetName(music->file));
  
      if (freesrc) {
          SDL_RWclose(src);
      }
      return music;
  }
  
  /* Set the volume for a modplug stream */
  static void MODPLUG_SetVolume(void *context, int volume)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      music->volume = volume;
      modplug.ModPlug_SetMasterVolume(music->file, (unsigned int)volume * 2); /* 0-512, reduced to 0-256 to prevent clipping */
  }
  
  /* Get the volume for a modplug stream */
  static int MODPLUG_GetVolume(void *context)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      return music->volume;
  }
  
  /* Start playback of a given modplug stream */
  static int MODPLUG_Play(void *context, int play_count)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      music->play_count = play_count;
      return MODPLUG_Seek(music, 0.0);
  }
  
  /* Play some of a stream previously started with modplug_play() */
  static int MODPLUG_GetSome(void *context, void *data, int bytes, SDL_bool *done)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      int filled, amount;
  
      filled = SDL_AudioStreamGet(music->stream, data, bytes);
      if (filled != 0) {
          return filled;
      }
  
      if (!music->play_count) {
          /* All done */
          *done = SDL_TRUE;
          return 0;
      }
  
      amount = modplug.ModPlug_Read(music->file, music->buffer, music->buffer_size);
      if (amount > 0) {
          if (SDL_AudioStreamPut(music->stream, music->buffer, amount) < 0) {
              return -1;
          }
      } else {
          if (music->play_count == 1) {
              music->play_count = 0;
              SDL_AudioStreamFlush(music->stream);
          } else {
              int play_count = -1;
              if (music->play_count > 0) {
                  play_count = (music->play_count - 1);
              }
              if (MODPLUG_Play(music, play_count) < 0) {
                  return -1;
              }
          }
      }
      return 0;
  }
  
  static int MODPLUG_GetAudio(void *context, void *data, int bytes)
  {
      return music_pcm_getaudio(context, data, bytes, MIX_MAX_VOLUME, MODPLUG_GetSome);
  }
  
  /* Jump to a given order */
  static int MODPLUG_Jump(void *context, int order)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      modplug.ModPlug_SeekOrder(music->file, order);
      return 0;
  }
  
  /* Jump (seek) to a given position */
  static int MODPLUG_Seek(void *context, double position)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      modplug.ModPlug_Seek(music->file, (int)(position*1000));
      return 0;
  }
  
  /* Return music duration in seconds */
  static double MODPLUG_Duration(void *context)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      return modplug.ModPlug_GetLength(music->file) / 1000.0;
  }
  
  static const char* MODPLUG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      return meta_tags_get(&music->tags, tag_type);
  }
  
  /* Close the given modplug stream */
  static void MODPLUG_Delete(void *context)
  {
      MODPLUG_Music *music = (MODPLUG_Music *)context;
      meta_tags_clear(&music->tags);
      if (music->file) {
          modplug.ModPlug_Unload(music->file);
      }
      if (music->stream) {
          SDL_FreeAudioStream(music->stream);
      }
      if (music->buffer) {
          SDL_free(music->buffer);
      }
      SDL_free(music);
  }
  
  Mix_MusicInterface Mix_MusicInterface_MODPLUG =
  {
      "MODPLUG",
      MIX_MUSIC_MODPLUG,
      MUS_MOD,
      SDL_FALSE,
      SDL_FALSE,
  
      MODPLUG_Load,
      MODPLUG_Open,
      MODPLUG_CreateFromRW,
      NULL,   /* CreateFromFile */
      MODPLUG_SetVolume,
      MODPLUG_GetVolume,
      MODPLUG_Play,
      NULL,   /* IsPlaying */
      MODPLUG_GetAudio,
      MODPLUG_Jump,
      MODPLUG_Seek,
      NULL,   /* Tell */
      MODPLUG_Duration,
      NULL,   /* LoopStart */
      NULL,   /* LoopEnd */
      NULL,   /* LoopLength */
      MODPLUG_GetMetaTag,
      NULL,   /* Pause */
      NULL,   /* Resume */
      NULL,   /* Stop */
      MODPLUG_Delete,
      NULL,   /* Close */
      MODPLUG_Unload
  };
  
  #endif /* MUSIC_MOD_MODPLUG */
  
  /* vi: set ts=4 sw=4 expandtab: */
  