2014-06-01 17:55:23 +00:00
|
|
|
/*
|
2018-09-02 00:57:29 +00:00
|
|
|
* This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
|
2014-06-01 17:55:23 +00:00
|
|
|
* It is copyright by its individual contributors, as recorded in the
|
|
|
|
* project's Git history. See COPYING.txt at the top level for license
|
|
|
|
* terms and a link to the Git history.
|
|
|
|
*/
|
2006-10-17 20:52:09 +00:00
|
|
|
/*
|
|
|
|
* This is an alternate backend for the music system.
|
|
|
|
* It uses SDL_mixer to provide a more reliable playback,
|
|
|
|
* and allow processing of multiple audio formats.
|
|
|
|
*
|
|
|
|
* -- MD2211 (2006-04-24)
|
|
|
|
*/
|
|
|
|
|
2022-09-24 17:47:52 +00:00
|
|
|
#include <span>
|
2013-06-30 02:22:56 +00:00
|
|
|
#include <SDL.h>
|
|
|
|
#include <SDL_mixer.h>
|
2006-10-17 20:52:09 +00:00
|
|
|
#include <string.h>
|
2008-01-23 17:25:09 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
2006-10-17 20:52:09 +00:00
|
|
|
#include "args.h"
|
2010-07-18 20:28:15 +00:00
|
|
|
#include "hmp.h"
|
2018-10-08 03:02:02 +00:00
|
|
|
#include "adlmidi_dynamic.h"
|
2007-09-18 13:37:39 +00:00
|
|
|
#include "digi_mixer_music.h"
|
2013-12-26 04:18:28 +00:00
|
|
|
#include "strutil.h"
|
2008-01-23 17:25:09 +00:00
|
|
|
#include "u_mem.h"
|
2018-10-18 02:18:56 +00:00
|
|
|
#include "config.h"
|
2011-06-01 07:59:51 +00:00
|
|
|
#include "console.h"
|
2020-05-22 02:40:26 +00:00
|
|
|
#include "physfsrwops.h"
|
2006-10-17 20:52:09 +00:00
|
|
|
|
2015-12-13 18:00:49 +00:00
|
|
|
namespace dcx {
|
2015-12-05 22:57:24 +00:00
|
|
|
|
2015-01-23 03:55:06 +00:00
|
|
|
namespace {
|
|
|
|
|
2020-05-22 02:40:26 +00:00
|
|
|
struct Music_delete
|
2014-08-27 02:52:21 +00:00
|
|
|
{
|
2020-05-22 02:40:26 +00:00
|
|
|
void operator()(Mix_Music *m) const
|
2014-08-27 02:52:21 +00:00
|
|
|
{
|
2020-05-22 02:40:26 +00:00
|
|
|
Mix_FreeMusic(m);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class current_music_t : std::unique_ptr<Mix_Music, Music_delete>
|
|
|
|
{
|
2018-07-05 04:03:34 +00:00
|
|
|
using music_pointer = std::unique_ptr<Mix_Music, Music_delete>;
|
2014-08-27 02:52:21 +00:00
|
|
|
public:
|
2020-05-22 02:40:26 +00:00
|
|
|
using music_pointer::reset;
|
2022-03-19 22:55:58 +00:00
|
|
|
void reset(SDL_RWops *rw);
|
2020-05-22 02:40:26 +00:00
|
|
|
using music_pointer::operator bool;
|
|
|
|
using music_pointer::get;
|
2014-08-27 02:52:21 +00:00
|
|
|
};
|
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
void current_music_t::reset(SDL_RWops *const rw)
|
|
|
|
{
|
|
|
|
if (!rw)
|
|
|
|
{
|
|
|
|
/* As a special case, exit early if rw is nullptr. SDL is
|
|
|
|
* guaranteed to fail in this case, but will set an error message
|
|
|
|
* when it does so. The error message about a nullptr SDL_RWops
|
|
|
|
* will replace any prior error message, which might have been more
|
|
|
|
* useful.
|
|
|
|
*/
|
|
|
|
reset();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
reset(Mix_LoadMUSType_RW(rw, MUS_NONE, SDL_TRUE));
|
2022-09-24 17:47:53 +00:00
|
|
|
if constexpr (SDL_MIXER_MAJOR_VERSION == 1)
|
|
|
|
{
|
|
|
|
/* In SDL_mixer-1, setting freesrc==SDL_TRUE only transfers ownership
|
|
|
|
* of the RWops structure on success. On failure, the structure is
|
|
|
|
* still owned by the caller, and must be freed here.
|
|
|
|
*
|
|
|
|
* In SDL_mixer-2, setting freesrc==SDL_TRUE always transfers ownership
|
|
|
|
* of the RWops structure. On failure, SDL_mixer-2 will free the RWops
|
|
|
|
* before returning, so the structure must not be freed here.
|
2022-03-19 22:55:58 +00:00
|
|
|
*/
|
2022-09-24 17:47:53 +00:00
|
|
|
if (!*this)
|
|
|
|
SDL_RWclose(rw);
|
|
|
|
}
|
2022-03-19 22:55:58 +00:00
|
|
|
}
|
|
|
|
|
2014-08-27 02:52:21 +00:00
|
|
|
static current_music_t current_music;
|
2014-09-20 23:14:03 +00:00
|
|
|
static std::vector<uint8_t> current_music_hndlbuf;
|
2006-10-17 20:52:09 +00:00
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
static void mix_set_music_type_sdlmixer(int loop, void (*const hook_finished_track)())
|
|
|
|
{
|
|
|
|
Mix_PlayMusic(current_music.get(), (loop ? -1 : 1));
|
|
|
|
Mix_HookMusicFinished(hook_finished_track);
|
|
|
|
}
|
|
|
|
|
2018-10-15 00:51:53 +00:00
|
|
|
#if DXX_USE_ADLMIDI
|
|
|
|
static ADL_MIDIPlayer_t current_adlmidi;
|
2018-10-03 23:31:19 +00:00
|
|
|
static ADL_MIDIPlayer *get_adlmidi()
|
|
|
|
{
|
2018-10-18 02:18:56 +00:00
|
|
|
if (!CGameCfg.ADLMIDI_enabled)
|
|
|
|
return nullptr;
|
2018-10-03 23:31:19 +00:00
|
|
|
ADL_MIDIPlayer *adlmidi = current_adlmidi.get();
|
|
|
|
if (!adlmidi)
|
|
|
|
{
|
|
|
|
int sample_rate;
|
|
|
|
Mix_QuerySpec(&sample_rate, nullptr, nullptr);
|
|
|
|
adlmidi = adl_init(sample_rate);
|
2018-10-08 03:02:02 +00:00
|
|
|
if (adlmidi)
|
|
|
|
{
|
|
|
|
adl_switchEmulator(adlmidi, ADLMIDI_EMU_DOSBOX);
|
2018-10-18 02:18:56 +00:00
|
|
|
adl_setNumChips(adlmidi, CGameCfg.ADLMIDI_num_chips);
|
|
|
|
adl_setBank(adlmidi, CGameCfg.ADLMIDI_bank);
|
2018-10-08 03:02:02 +00:00
|
|
|
adl_setSoftPanEnabled(adlmidi, 1);
|
|
|
|
current_adlmidi.reset(adlmidi);
|
|
|
|
}
|
2018-10-03 23:31:19 +00:00
|
|
|
}
|
|
|
|
return adlmidi;
|
|
|
|
}
|
|
|
|
|
2018-10-15 00:51:53 +00:00
|
|
|
static void mix_adlmidi(void *udata, Uint8 *stream, int len);
|
2022-03-19 22:55:58 +00:00
|
|
|
|
|
|
|
static void mix_set_music_type_adl(int loop, void (*const hook_finished_track)())
|
|
|
|
{
|
|
|
|
ADL_MIDIPlayer *adlmidi = get_adlmidi();
|
|
|
|
adl_setLoopEnabled(adlmidi, loop);
|
|
|
|
Mix_HookMusic(&mix_adlmidi, nullptr);
|
|
|
|
Mix_HookMusicFinished(hook_finished_track);
|
|
|
|
}
|
2018-10-15 00:51:53 +00:00
|
|
|
#endif
|
|
|
|
|
2018-10-08 03:02:02 +00:00
|
|
|
enum class CurrentMusicType
|
|
|
|
{
|
2018-10-05 12:20:13 +00:00
|
|
|
None,
|
2018-10-15 00:51:53 +00:00
|
|
|
#if DXX_USE_ADLMIDI
|
2018-10-05 12:20:13 +00:00
|
|
|
ADLMIDI,
|
2018-10-15 00:51:53 +00:00
|
|
|
#endif
|
2018-10-05 12:20:13 +00:00
|
|
|
SDLMixer,
|
2018-10-03 23:31:19 +00:00
|
|
|
};
|
|
|
|
|
2018-10-05 12:20:13 +00:00
|
|
|
static CurrentMusicType current_music_type = CurrentMusicType::None;
|
2018-10-03 23:31:19 +00:00
|
|
|
|
2022-09-24 17:47:52 +00:00
|
|
|
static CurrentMusicType load_mus_data(std::span<const uint8_t> data, int loop, void (*const hook_finished_track)());
|
2022-03-19 22:55:58 +00:00
|
|
|
static CurrentMusicType load_mus_file(const char *filename, int loop, void (*const hook_finished_track)());
|
2018-10-03 23:31:19 +00:00
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
}
|
|
|
|
|
2008-01-23 17:25:09 +00:00
|
|
|
/*
|
2010-08-22 13:27:47 +00:00
|
|
|
* Plays a music file from an absolute path or a relative path
|
2008-01-23 17:25:09 +00:00
|
|
|
*/
|
2006-10-17 20:52:09 +00:00
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
int mix_play_file(const char *filename, int loop, void (*const entry_hook_finished_track)())
|
2010-06-14 08:13:16 +00:00
|
|
|
{
|
|
|
|
mix_free_music(); // stop and free what we're already playing, if anything
|
2007-03-12 21:56:41 +00:00
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
const auto hook_finished_track = entry_hook_finished_track ? entry_hook_finished_track : mix_free_music;
|
2010-11-28 15:49:32 +00:00
|
|
|
// It's a .hmp!
|
2022-03-19 22:55:58 +00:00
|
|
|
if (const auto fptr = strrchr(filename, '.'); fptr && !d_stricmp(fptr, ".hmp"))
|
2010-06-14 08:13:16 +00:00
|
|
|
{
|
2022-03-19 22:55:58 +00:00
|
|
|
if (auto &&[v, hoe] = hmp2mid(filename); hoe == hmp_open_error::None)
|
|
|
|
{
|
|
|
|
current_music_hndlbuf = std::move(v);
|
2022-09-24 17:47:52 +00:00
|
|
|
current_music_type = load_mus_data(current_music_hndlbuf, loop, hook_finished_track);
|
2022-03-19 22:55:58 +00:00
|
|
|
if (current_music_type != CurrentMusicType::None)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
/* hmp2mid printed an error message, so there is no need for another one here */
|
|
|
|
return 0;
|
2010-06-14 08:13:16 +00:00
|
|
|
}
|
2007-03-12 21:56:41 +00:00
|
|
|
|
2010-07-05 07:41:30 +00:00
|
|
|
// try loading music via given filename
|
2022-03-19 22:55:58 +00:00
|
|
|
{
|
|
|
|
current_music_type = load_mus_file(filename, loop, hook_finished_track);
|
|
|
|
if (current_music_type != CurrentMusicType::None)
|
|
|
|
return 1;
|
|
|
|
}
|
2010-07-05 07:41:30 +00:00
|
|
|
|
2011-04-18 12:30:46 +00:00
|
|
|
// allow the shell convention tilde character to mean the user's home folder
|
|
|
|
// chiefly used for default jukebox level song music referenced in 'descent.m3u' for Mac OS X
|
2022-03-19 22:55:58 +00:00
|
|
|
if (*filename == '~')
|
2011-04-18 12:30:46 +00:00
|
|
|
{
|
2016-08-06 19:55:25 +00:00
|
|
|
const auto sep = PHYSFS_getDirSeparator();
|
|
|
|
const auto lensep = strlen(sep);
|
2022-09-24 17:47:52 +00:00
|
|
|
std::array<char, PATH_MAX> full_path;
|
2016-08-06 19:55:25 +00:00
|
|
|
snprintf(full_path.data(), PATH_MAX, "%s%s", PHYSFS_getUserDir(),
|
|
|
|
&filename[1 + (!strncmp(&filename[1], sep, lensep)
|
|
|
|
? lensep
|
|
|
|
: 0)]);
|
2022-03-19 22:55:58 +00:00
|
|
|
current_music_type = load_mus_file(full_path.data(), loop, hook_finished_track);
|
2018-10-05 12:20:13 +00:00
|
|
|
if (current_music_type != CurrentMusicType::None)
|
2022-03-19 22:55:58 +00:00
|
|
|
return 1;
|
2011-04-18 12:30:46 +00:00
|
|
|
}
|
|
|
|
|
2010-10-29 15:40:21 +00:00
|
|
|
// still nothin'? Let's open via PhysFS in case it's located inside an archive
|
2010-06-14 08:13:16 +00:00
|
|
|
{
|
2015-01-17 18:31:42 +00:00
|
|
|
if (RAIIPHYSFS_File filehandle{PHYSFS_openRead(filename)})
|
2010-10-29 15:40:21 +00:00
|
|
|
{
|
2022-09-24 17:47:52 +00:00
|
|
|
const auto len = PHYSFS_fileLength(filehandle);
|
2014-09-20 23:14:03 +00:00
|
|
|
current_music_hndlbuf.resize(len);
|
2022-09-24 17:47:52 +00:00
|
|
|
const auto bufsize = PHYSFS_read(filehandle, ¤t_music_hndlbuf[0], sizeof(char), len);
|
|
|
|
current_music_type = load_mus_data(std::span(current_music_hndlbuf).first(bufsize), loop, hook_finished_track);
|
2022-03-19 22:55:58 +00:00
|
|
|
if (current_music_type != CurrentMusicType::None)
|
|
|
|
return 1;
|
2010-10-29 15:40:21 +00:00
|
|
|
}
|
2010-06-14 08:13:16 +00:00
|
|
|
}
|
2006-10-17 20:52:09 +00:00
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
con_printf(CON_CRITICAL, "Music %s could not be loaded: %s", filename, Mix_GetError());
|
|
|
|
mix_stop_music();
|
2018-10-03 23:31:19 +00:00
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2008-03-23 08:42:24 +00:00
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
// What to do when stopping song playback
|
|
|
|
void mix_free_music()
|
2008-03-23 08:42:24 +00:00
|
|
|
{
|
2010-06-14 08:13:16 +00:00
|
|
|
Mix_HaltMusic();
|
2018-10-15 00:51:53 +00:00
|
|
|
#if DXX_USE_ADLMIDI
|
|
|
|
/* Only ADLMIDI can set a hook, so if ADLMIDI is compiled out, there is no
|
|
|
|
* need to clear the hook.
|
|
|
|
*
|
|
|
|
* When ADLMIDI is supported, clear unconditionally, instead of checking
|
|
|
|
* whether the music type requires it.
|
|
|
|
*/
|
2018-10-03 23:31:19 +00:00
|
|
|
Mix_HookMusic(nullptr, nullptr);
|
2018-10-15 00:51:53 +00:00
|
|
|
#endif
|
2014-08-27 02:52:21 +00:00
|
|
|
current_music.reset();
|
2014-09-20 23:14:03 +00:00
|
|
|
current_music_hndlbuf.clear();
|
2018-10-05 12:20:13 +00:00
|
|
|
current_music_type = CurrentMusicType::None;
|
2010-06-14 08:13:16 +00:00
|
|
|
}
|
2008-03-23 08:42:24 +00:00
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
void mix_set_music_volume(int vol)
|
|
|
|
{
|
2010-08-19 15:54:19 +00:00
|
|
|
vol *= MIX_MAX_VOLUME/8;
|
2010-06-14 08:13:16 +00:00
|
|
|
Mix_VolumeMusic(vol);
|
2008-03-23 08:42:24 +00:00
|
|
|
}
|
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
void mix_stop_music()
|
|
|
|
{
|
|
|
|
Mix_HaltMusic();
|
2014-09-20 23:14:03 +00:00
|
|
|
current_music_hndlbuf.clear();
|
2008-01-10 20:35:59 +00:00
|
|
|
}
|
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
void mix_pause_music()
|
|
|
|
{
|
|
|
|
Mix_PauseMusic();
|
2006-10-17 20:52:09 +00:00
|
|
|
}
|
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
void mix_resume_music()
|
|
|
|
{
|
|
|
|
Mix_ResumeMusic();
|
|
|
|
}
|
|
|
|
|
|
|
|
void mix_pause_resume_music()
|
|
|
|
{
|
|
|
|
if (Mix_PausedMusic())
|
|
|
|
Mix_ResumeMusic();
|
|
|
|
else if (Mix_PlayingMusic())
|
|
|
|
Mix_PauseMusic();
|
2006-10-17 20:52:09 +00:00
|
|
|
}
|
2015-12-05 22:57:24 +00:00
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
namespace {
|
|
|
|
|
2022-09-24 17:47:52 +00:00
|
|
|
static CurrentMusicType load_mus_data(const std::span<const uint8_t> data, int loop, void (*const hook_finished_track)())
|
2018-10-03 23:31:19 +00:00
|
|
|
{
|
2018-10-15 00:51:53 +00:00
|
|
|
#if DXX_USE_ADLMIDI
|
2018-10-18 02:18:56 +00:00
|
|
|
const auto adlmidi = get_adlmidi();
|
2022-09-24 17:47:52 +00:00
|
|
|
if (adlmidi && adl_openData(adlmidi, data.data(), data.size()) == 0)
|
2022-03-19 22:55:58 +00:00
|
|
|
{
|
|
|
|
mix_set_music_type_adl(loop, hook_finished_track);
|
|
|
|
return CurrentMusicType::ADLMIDI;
|
|
|
|
}
|
2018-10-05 12:20:13 +00:00
|
|
|
else
|
2018-10-15 00:51:53 +00:00
|
|
|
#endif
|
2018-10-03 23:31:19 +00:00
|
|
|
{
|
2022-09-24 17:47:52 +00:00
|
|
|
const auto rw = SDL_RWFromConstMem(data.data(), data.size());
|
2022-03-19 22:55:58 +00:00
|
|
|
current_music.reset(rw);
|
2018-10-03 23:31:19 +00:00
|
|
|
if (current_music)
|
2022-03-19 22:55:58 +00:00
|
|
|
{
|
|
|
|
mix_set_music_type_sdlmixer(loop, hook_finished_track);
|
|
|
|
return CurrentMusicType::SDLMixer;
|
|
|
|
}
|
2018-10-03 23:31:19 +00:00
|
|
|
}
|
2022-03-19 22:55:58 +00:00
|
|
|
return CurrentMusicType::None;
|
2018-10-03 23:31:19 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 22:55:58 +00:00
|
|
|
static CurrentMusicType load_mus_file(const char *filename, int loop, void (*const hook_finished_track)())
|
2018-10-03 23:31:19 +00:00
|
|
|
{
|
2018-10-05 12:20:13 +00:00
|
|
|
CurrentMusicType type = CurrentMusicType::None;
|
2018-10-15 00:51:53 +00:00
|
|
|
#if DXX_USE_ADLMIDI
|
2018-10-18 02:18:56 +00:00
|
|
|
const auto adlmidi = get_adlmidi();
|
2018-10-08 03:02:02 +00:00
|
|
|
if (adlmidi && adl_openFile(adlmidi, filename) == 0)
|
2022-03-19 22:55:58 +00:00
|
|
|
{
|
|
|
|
mix_set_music_type_adl(loop, hook_finished_track);
|
|
|
|
return CurrentMusicType::ADLMIDI;
|
|
|
|
}
|
2018-10-05 12:20:13 +00:00
|
|
|
else
|
2018-10-15 00:51:53 +00:00
|
|
|
#endif
|
2018-10-03 23:31:19 +00:00
|
|
|
{
|
2022-03-19 22:55:58 +00:00
|
|
|
const auto rw = SDL_RWFromFile(filename, "rb");
|
|
|
|
current_music.reset(rw);
|
2018-10-03 23:31:19 +00:00
|
|
|
if (current_music)
|
2022-03-19 22:55:58 +00:00
|
|
|
{
|
|
|
|
mix_set_music_type_sdlmixer(loop, hook_finished_track);
|
|
|
|
return CurrentMusicType::SDLMixer;
|
|
|
|
}
|
2018-10-03 23:31:19 +00:00
|
|
|
}
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
2018-10-15 00:51:53 +00:00
|
|
|
#if DXX_USE_ADLMIDI
|
2018-10-03 23:31:19 +00:00
|
|
|
static int16_t sat16(int32_t x)
|
|
|
|
{
|
|
|
|
x = (x < INT16_MIN) ? INT16_MIN : x;
|
|
|
|
x = (x > INT16_MAX) ? INT16_MAX : x;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mix_adlmidi(void *, Uint8 *stream, int len)
|
|
|
|
{
|
|
|
|
ADLMIDI_AudioFormat format;
|
|
|
|
format.containerSize = sizeof(int16_t);
|
|
|
|
format.sampleOffset = 2 * format.containerSize;
|
|
|
|
format.type = ADLMIDI_SampleType_S16;
|
2018-10-05 12:20:13 +00:00
|
|
|
|
|
|
|
ADL_MIDIPlayer *adlmidi = get_adlmidi();
|
2018-10-03 23:31:19 +00:00
|
|
|
int sampleCount = len / format.containerSize;
|
|
|
|
adl_playFormat(adlmidi, sampleCount, stream, stream + format.containerSize, &format);
|
2018-10-05 12:20:13 +00:00
|
|
|
|
|
|
|
const auto samples = reinterpret_cast<int16_t *>(stream);
|
|
|
|
const auto amplify = [](int16_t i) { return sat16(2 * i); };
|
|
|
|
std::transform(samples, samples + sampleCount, samples, amplify);
|
2018-10-03 23:31:19 +00:00
|
|
|
}
|
2018-10-15 00:51:53 +00:00
|
|
|
#endif
|
2018-10-03 23:31:19 +00:00
|
|
|
|
2015-12-05 22:57:24 +00:00
|
|
|
}
|
2022-03-19 22:55:58 +00:00
|
|
|
|
|
|
|
}
|