Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
  3.  * It is copyright by its individual contributors, as recorded in the
  4.  * project's Git history.  See COPYING.txt at the top level for license
  5.  * terms and a link to the Git history.
  6.  */
  7. /*
  8.  * This is an alternate backend for the music system.
  9.  * It uses SDL_mixer to provide a more reliable playback,
  10.  * and allow processing of multiple audio formats.
  11.  *
  12.  *  -- MD2211 (2006-04-24)
  13.  */
  14.  
  15. #include <SDL.h>
  16. #include <SDL_mixer.h>
  17. #include <string.h>
  18. #include <stdlib.h>
  19. #include <unistd.h> // Pierre-Marie Baty -- for getuid()
  20. #include <pwd.h> // Pierre-Marie Baty -- for getpwuid()
  21.  
  22. #include "args.h"
  23. #include "hmp.h"
  24. #include "adlmidi_dynamic.h"
  25. #include "digi_mixer_music.h"
  26. #include "strutil.h"
  27. #include "u_mem.h"
  28. #include "config.h"
  29. #include "console.h"
  30. #include "physfsrwops.h"
  31.  
  32. namespace dcx {
  33.  
  34. namespace {
  35.  
  36. #if SDL_MIXER_MAJOR_VERSION == 2
  37. #define DXX_SDL_MIXER_MANAGES_RWOPS     1
  38. #else
  39. #define DXX_SDL_MIXER_MANAGES_RWOPS     0
  40. #endif
  41.  
  42. #if DXX_SDL_MIXER_MANAGES_RWOPS
  43. #define DXX_SDL_MIXER_Mix_LoadMUS_MANAGE_RWOPS  , SDL_TRUE
  44. #define DXX_SDL_MIXER_Mix_LoadMUS_PASS_RWOPS(rw)
  45. #else
  46. #define DXX_SDL_MIXER_Mix_LoadMUS_MANAGE_RWOPS
  47. #define DXX_SDL_MIXER_Mix_LoadMUS_PASS_RWOPS(rw)        , rw
  48. #endif
  49.  
  50. struct Music_delete
  51. {
  52.         void operator()(Mix_Music *m) const
  53.         {
  54.                 Mix_FreeMusic(m);
  55.         }
  56. };
  57.  
  58. class current_music_t : std::unique_ptr<Mix_Music, Music_delete>
  59. {
  60. #if !DXX_SDL_MIXER_MANAGES_RWOPS
  61.         using rwops_pointer = std::unique_ptr<SDL_RWops, RWops_delete>;
  62.         rwops_pointer m_ops;
  63. #endif
  64.         using music_pointer = std::unique_ptr<Mix_Music, Music_delete>;
  65. public:
  66. #if DXX_SDL_MIXER_MANAGES_RWOPS
  67.         using music_pointer::reset;
  68. #else
  69.         void reset(
  70.                 Mix_Music *const music = nullptr
  71.                 , SDL_RWops *const rwops = nullptr
  72.                 ) noexcept
  73.         {
  74.                 /* Clear music first in case it needs the old ops
  75.                  * Clear old ops
  76.                  * If no new music, clear new ops immediately.  This only
  77.                  * happens if the new music fails to load.
  78.                  */
  79.                 this->music_pointer::reset(music);
  80.                 m_ops.reset(rwops);
  81.                 if (!music)
  82.                         m_ops.reset();
  83.         }
  84. #endif
  85.         using music_pointer::operator bool;
  86.         using music_pointer::get;
  87. };
  88.  
  89. }
  90.  
  91. static current_music_t current_music;
  92. static std::vector<uint8_t> current_music_hndlbuf;
  93.  
  94. #if DXX_USE_ADLMIDI
  95. static ADL_MIDIPlayer_t current_adlmidi;
  96. static ADL_MIDIPlayer *get_adlmidi()
  97. {
  98.         if (!CGameCfg.ADLMIDI_enabled)
  99.                 return nullptr;
  100.         ADL_MIDIPlayer *adlmidi = current_adlmidi.get();
  101.         if (!adlmidi)
  102.         {
  103.                 int sample_rate;
  104.                 Mix_QuerySpec(&sample_rate, nullptr, nullptr);
  105.                 adlmidi = adl_init(sample_rate);
  106.                 if (adlmidi)
  107.                 {
  108.                         adl_switchEmulator(adlmidi, ADLMIDI_EMU_DOSBOX);
  109.                         adl_setNumChips(adlmidi, CGameCfg.ADLMIDI_num_chips);
  110.                         adl_setBank(adlmidi, CGameCfg.ADLMIDI_bank);
  111.                         adl_setSoftPanEnabled(adlmidi, 1);
  112.                         current_adlmidi.reset(adlmidi);
  113.                 }
  114.         }
  115.         return adlmidi;
  116. }
  117.  
  118. static void mix_adlmidi(void *udata, Uint8 *stream, int len);
  119. #endif
  120.  
  121. enum class CurrentMusicType
  122. {
  123.         None,
  124. #if DXX_USE_ADLMIDI
  125.         ADLMIDI,
  126. #endif
  127.         SDLMixer,
  128. };
  129.  
  130. static CurrentMusicType current_music_type = CurrentMusicType::None;
  131.  
  132. static CurrentMusicType load_mus_data(const uint8_t *data, size_t size);
  133. static CurrentMusicType load_mus_file(const char *filename);
  134.  
  135. /*
  136.  *  Plays a music file from an absolute path or a relative path
  137.  */
  138.  
  139. int mix_play_file(const char *filename, int loop, void (*hook_finished_track)())
  140. {
  141.         std::array<char, PATH_MAX> full_path;
  142.         const char *fptr;
  143.         unsigned int bufsize = 0;
  144.  
  145.         mix_free_music();       // stop and free what we're already playing, if anything
  146.  
  147.         fptr = strrchr(filename, '.');
  148.  
  149.         if (fptr == NULL)
  150.                 return 0;
  151.  
  152.         // It's a .hmp!
  153.         if (!d_stricmp(fptr, ".hmp"))
  154.         {
  155.                 hmp2mid(filename, current_music_hndlbuf);
  156.                 current_music_type = load_mus_data(current_music_hndlbuf.data(), current_music_hndlbuf.size());
  157.         }
  158.  
  159.         // try loading music via given filename
  160.         if (current_music_type == CurrentMusicType::None)
  161.                 current_music_type = load_mus_file(filename);
  162.  
  163.         // allow the shell convention tilde character to mean the user's home folder
  164.         // chiefly used for default jukebox level song music referenced in 'descent.m3u' for Mac OS X
  165.         if (current_music_type == CurrentMusicType::None && *filename == '~')
  166.         {
  167.                 const auto sep = PHYSFS_getDirSeparator();
  168.                 const auto lensep = strlen(sep);
  169.                 snprintf(full_path.data(), PATH_MAX, "%s/%s", /*PHYSFS_getUserDir()*/getpwuid(getuid())->pw_dir, // Pierre-Marie Baty -- work around PHYSFS_getUserDir() deprecation
  170.                                  &filename[1 + (!strncmp(&filename[1], sep, lensep)
  171.                         ? lensep
  172.                         : 0)]);
  173.                 current_music_type = load_mus_file(full_path.data());
  174.                 if (current_music_type != CurrentMusicType::None)
  175.                         filename = full_path.data();    // used later for possible error reporting
  176.         }
  177.  
  178.         // no luck. so it might be in Searchpath. So try to build absolute path
  179.         if (current_music_type == CurrentMusicType::None)
  180.         {
  181.                 PHYSFSX_getRealPath(filename, full_path);
  182.                 current_music_type = load_mus_file(full_path.data());
  183.                 if (current_music_type != CurrentMusicType::None)
  184.                         filename = full_path.data();    // used later for possible error reporting
  185.         }
  186.  
  187.         // still nothin'? Let's open via PhysFS in case it's located inside an archive
  188.         if (current_music_type == CurrentMusicType::None)
  189.         {
  190.                 if (RAIIPHYSFS_File filehandle{PHYSFS_openRead(filename)})
  191.                 {
  192.                         unsigned len = PHYSFS_fileLength(filehandle);
  193.                         current_music_hndlbuf.resize(len);
  194.                         bufsize = PHYSFS_read(filehandle, &current_music_hndlbuf[0], sizeof(char), len);
  195.                         current_music_type = load_mus_data(current_music_hndlbuf.data(), bufsize*sizeof(char));
  196.                 }
  197.         }
  198.  
  199.         switch (current_music_type)
  200.         {
  201.  
  202. #if DXX_USE_ADLMIDI
  203.         case CurrentMusicType::ADLMIDI:
  204.         {
  205.                 ADL_MIDIPlayer *adlmidi = get_adlmidi();
  206.                 adl_setLoopEnabled(adlmidi, loop);
  207.                 Mix_HookMusic(&mix_adlmidi, nullptr);
  208.                 Mix_HookMusicFinished(hook_finished_track ? hook_finished_track : mix_free_music);
  209.                 return 1;
  210.         }
  211. #endif
  212.  
  213.         case CurrentMusicType::SDLMixer:
  214.         {
  215.                 Mix_PlayMusic(current_music.get(), (loop ? -1 : 1));
  216.                 Mix_HookMusicFinished(hook_finished_track ? hook_finished_track : mix_free_music);
  217.                 return 1;
  218.         }
  219.  
  220.         default:
  221.         {
  222.                 con_printf(CON_CRITICAL,"Music %s could not be loaded: %s", filename, Mix_GetError());
  223.                 mix_stop_music();
  224.         }
  225.  
  226.         }
  227.  
  228.         return 0;
  229. }
  230.  
  231. // What to do when stopping song playback
  232. void mix_free_music()
  233. {
  234.         Mix_HaltMusic();
  235. #if DXX_USE_ADLMIDI
  236.         /* Only ADLMIDI can set a hook, so if ADLMIDI is compiled out, there is no
  237.          * need to clear the hook.
  238.          *
  239.          * When ADLMIDI is supported, clear unconditionally, instead of checking
  240.          * whether the music type requires it.
  241.          */
  242.         Mix_HookMusic(nullptr, nullptr);
  243. #endif
  244.         current_music.reset();
  245.         current_music_hndlbuf.clear();
  246.         current_music_type = CurrentMusicType::None;
  247. }
  248.  
  249. void mix_set_music_volume(int vol)
  250. {
  251.         vol *= MIX_MAX_VOLUME/8;
  252.         Mix_VolumeMusic(vol);
  253. }
  254.  
  255. void mix_stop_music()
  256. {
  257.         Mix_HaltMusic();
  258.         current_music_hndlbuf.clear();
  259. }
  260.  
  261. void mix_pause_music()
  262. {
  263.         Mix_PauseMusic();
  264. }
  265.  
  266. void mix_resume_music()
  267. {
  268.         Mix_ResumeMusic();
  269. }
  270.  
  271. void mix_pause_resume_music()
  272. {
  273.         if (Mix_PausedMusic())
  274.                 Mix_ResumeMusic();
  275.         else if (Mix_PlayingMusic())
  276.                 Mix_PauseMusic();
  277. }
  278.  
  279. static CurrentMusicType load_mus_data(const uint8_t *data, size_t size)
  280. {
  281.         CurrentMusicType type = CurrentMusicType::None;
  282. #if DXX_USE_ADLMIDI
  283.         const auto adlmidi = get_adlmidi();
  284.         if (adlmidi && adl_openData(adlmidi, data, size) == 0)
  285.                 type = CurrentMusicType::ADLMIDI;
  286.         else
  287. #endif
  288.         {
  289.                 const auto rw = SDL_RWFromConstMem(data, size);
  290.                 current_music.reset(Mix_LoadMUS_RW(rw DXX_SDL_MIXER_Mix_LoadMUS_MANAGE_RWOPS) DXX_SDL_MIXER_Mix_LoadMUS_PASS_RWOPS(rw));
  291.                 if (current_music)
  292.                         type = CurrentMusicType::SDLMixer;
  293.         }
  294.  
  295.         return type;
  296. }
  297.  
  298. static CurrentMusicType load_mus_file(const char *filename)
  299. {
  300.         CurrentMusicType type = CurrentMusicType::None;
  301. #if DXX_USE_ADLMIDI
  302.         const auto adlmidi = get_adlmidi();
  303.         if (adlmidi && adl_openFile(adlmidi, filename) == 0)
  304.                 type = CurrentMusicType::ADLMIDI;
  305.         else
  306. #endif
  307.         {
  308.                 current_music.reset(Mix_LoadMUS(filename));
  309.                 if (current_music)
  310.                         type = CurrentMusicType::SDLMixer;
  311.         }
  312.  
  313.         return type;
  314. }
  315.  
  316. #if DXX_USE_ADLMIDI
  317. static int16_t sat16(int32_t x)
  318. {
  319.         x = (x < INT16_MIN) ? INT16_MIN : x;
  320.         x = (x > INT16_MAX) ? INT16_MAX : x;
  321.         return x;
  322. }
  323.  
  324. static void mix_adlmidi(void *, Uint8 *stream, int len)
  325. {
  326.         ADLMIDI_AudioFormat format;
  327.         format.containerSize = sizeof(int16_t);
  328.         format.sampleOffset = 2 * format.containerSize;
  329.         format.type = ADLMIDI_SampleType_S16;
  330.  
  331.         ADL_MIDIPlayer *adlmidi = get_adlmidi();
  332.         int sampleCount = len / format.containerSize;
  333.         adl_playFormat(adlmidi, sampleCount, stream, stream + format.containerSize, &format);
  334.  
  335.         const auto samples = reinterpret_cast<int16_t *>(stream);
  336.         const auto amplify = [](int16_t i) { return sat16(2 * i); };
  337.         std::transform(samples, samples + sampleCount, samples, amplify);
  338. }
  339. #endif
  340.  
  341. }
  342.