The SDL_mixer library has already been instructed, via Mix_Volume, to scale the volume of sounds on all channels, by an amount based on digi_volume. There is no need to manipulate the effective distance of a particular sound to further scale it by digi_volume. Even if this second scale was needed, it was done incorrectly, because it was only applied when the sound was started, but not re-applied when the sound's volume was updated due to positional changes. As a result, any sound which was updated would switch to an unscaled version. Sounds which were never updated, such as those attached to the viewer object, would retain their original scaled volume. Update the implementation of digi_mixer_set_channel_volume to call Mix_SetDistance in the same way as digi_mixer_start_sound, for readability and consistency.
292 lines
6.9 KiB
C++
292 lines
6.9 KiB
C++
/*
|
|
* This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
|
|
* 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.
|
|
*/
|
|
/*
|
|
* This is an alternate backend for the sound effect system.
|
|
* It uses SDL_mixer to provide a more reliable playback,
|
|
* and allow processing of multiple audio formats.
|
|
*
|
|
* This file is based on the original D1X arch/sdl/digi.c
|
|
*
|
|
* -- MD2211 (2006-10-12)
|
|
*/
|
|
|
|
#include <bitset>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_audio.h>
|
|
#include <SDL_mixer.h>
|
|
|
|
#include "pstypes.h"
|
|
#include "dxxerror.h"
|
|
#include "sounds.h"
|
|
#include "digi.h"
|
|
#include "digi_mixer.h"
|
|
#include "digi_mixer_music.h"
|
|
#include "console.h"
|
|
#include "config.h"
|
|
#include "args.h"
|
|
|
|
#include "maths.h"
|
|
#include "piggy.h"
|
|
#include "u_mem.h"
|
|
#include <memory>
|
|
|
|
#define MIX_DIGI_DEBUG 0
|
|
#define MIX_OUTPUT_FORMAT AUDIO_S16
|
|
#define MIX_OUTPUT_CHANNELS 2
|
|
|
|
#if !((defined(__APPLE__) && defined(__MACH__)) || defined(macintosh))
|
|
#define SOUND_BUFFER_SIZE 2048
|
|
#else
|
|
#define SOUND_BUFFER_SIZE 1024
|
|
#endif
|
|
|
|
namespace dcx {
|
|
|
|
namespace {
|
|
|
|
/* channel management */
|
|
static unsigned digi_mixer_find_channel(const std::bitset<64> &channels, const unsigned max_channels)
|
|
{
|
|
unsigned i = 0;
|
|
for (; i < max_channels; ++i)
|
|
if (!channels[i])
|
|
break;
|
|
return i;
|
|
}
|
|
|
|
struct RAIIMix_Chunk : public Mix_Chunk
|
|
{
|
|
RAIIMix_Chunk() = default;
|
|
~RAIIMix_Chunk()
|
|
{
|
|
delete [] abuf;
|
|
}
|
|
RAIIMix_Chunk(const RAIIMix_Chunk &) = delete;
|
|
RAIIMix_Chunk &operator=(const RAIIMix_Chunk &) = delete;
|
|
};
|
|
|
|
static uint8_t fix2byte(const fix f)
|
|
{
|
|
if (f >= UINT8_MAX << 8)
|
|
/* Values greater than this would produce incorrect results if
|
|
* shifted and truncated. As a special case, coerce such values
|
|
* to the largest representable return value.
|
|
*/
|
|
return UINT8_MAX;
|
|
return f >> 8;
|
|
}
|
|
|
|
uint8_t digi_initialised;
|
|
std::bitset<64> channels;
|
|
unsigned digi_mixer_max_channels = channels.size();
|
|
|
|
void digi_mixer_free_channel(const int channel_num)
|
|
{
|
|
channels.reset(channel_num);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
|
|
static std::array<RAIIMix_Chunk, MAX_SOUNDS> SoundChunks;
|
|
|
|
/* Initialise audio */
|
|
int digi_mixer_init()
|
|
{
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
const unsigned
|
|
#endif
|
|
digi_sample_rate = SAMPLE_RATE_44K;
|
|
|
|
#if MIX_DIGI_DEBUG
|
|
con_printf(CON_DEBUG, "digi_init %u (SDL_Mixer)", MAX_SOUNDS.value);
|
|
#endif
|
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) Error("SDL audio initialisation failed: %s.", SDL_GetError());
|
|
|
|
if (Mix_OpenAudio(digi_sample_rate, MIX_OUTPUT_FORMAT, MIX_OUTPUT_CHANNELS, SOUND_BUFFER_SIZE))
|
|
{
|
|
//edited on 10/05/98 by Matt Mueller - should keep running, just with no sound.
|
|
con_printf(CON_URGENT,"\nError: Couldn't open audio: %s", SDL_GetError());
|
|
CGameArg.SndNoSound = 1;
|
|
return 1;
|
|
}
|
|
|
|
digi_mixer_max_channels = Mix_AllocateChannels(digi_mixer_max_channels);
|
|
channels.reset();
|
|
Mix_Pause(0);
|
|
Mix_ChannelFinished(digi_mixer_free_channel);
|
|
|
|
digi_initialised = 1;
|
|
|
|
digi_mixer_set_digi_volume( (GameCfg.DigiVolume*32768)/8 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
namespace dcx {
|
|
|
|
/* Shut down audio */
|
|
void digi_mixer_close() {
|
|
#if MIX_DIGI_DEBUG
|
|
con_printf(CON_DEBUG, "digi_close (SDL_Mixer)");
|
|
#endif
|
|
if (!digi_initialised) return;
|
|
digi_initialised = 0;
|
|
Mix_CloseAudio();
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
|
|
/*
|
|
* Play-time conversion. Performs output conversion only once per sound effect used.
|
|
* Once the sound sample has been converted, it is cached in SoundChunks[]
|
|
*/
|
|
static void mixdigi_convert_sound(const unsigned i)
|
|
{
|
|
if (SoundChunks[i].abuf)
|
|
//proceed only if not converted yet
|
|
return;
|
|
SDL_AudioCVT cvt;
|
|
Uint8 *data = GameSounds[i].data;
|
|
Uint32 dlen = GameSounds[i].length;
|
|
int freq;
|
|
int out_freq;
|
|
Uint16 out_format;
|
|
int out_channels;
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
out_freq = digi_sample_rate;
|
|
out_format = MIX_OUTPUT_FORMAT;
|
|
out_channels = MIX_OUTPUT_CHANNELS;
|
|
freq = GameSounds[i].freq;
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
Mix_QuerySpec(&out_freq, &out_format, &out_channels); // get current output settings
|
|
freq = GameArg.SndDigiSampleRate;
|
|
#endif
|
|
|
|
if (data)
|
|
{
|
|
if (SDL_BuildAudioCVT(&cvt, AUDIO_U8, 1, freq, out_format, out_channels, out_freq) == -1)
|
|
{
|
|
con_printf(CON_URGENT, "%s:%u: SDL_BuildAudioCVT failed: sound=%i dlen=%u freq=%i out_format=%i out_channels=%i out_freq=%i", __FILE__, __LINE__, i, dlen, freq, out_format, out_channels, out_freq);
|
|
return;
|
|
}
|
|
|
|
auto cvtbuf = std::make_unique<Uint8[]>(dlen * cvt.len_mult);
|
|
cvt.buf = cvtbuf.get();
|
|
cvt.len = dlen;
|
|
memcpy(cvt.buf, data, dlen);
|
|
if (SDL_ConvertAudio(&cvt))
|
|
{
|
|
con_printf(CON_URGENT, "%s:%u: SDL_ConvertAudio failed: sound=%i dlen=%u freq=%i out_format=%i out_channels=%i out_freq=%i", __FILE__, __LINE__, i, dlen, freq, out_format, out_channels, out_freq);
|
|
return;
|
|
}
|
|
|
|
SoundChunks[i].abuf = cvtbuf.release();
|
|
SoundChunks[i].alen = cvt.len_cvt;
|
|
SoundChunks[i].allocated = 1;
|
|
SoundChunks[i].volume = 128; // Max volume = 128
|
|
}
|
|
}
|
|
|
|
// Volume 0-F1_0
|
|
int digi_mixer_start_sound(short soundnum, const fix volume, const sound_pan pan, const int looping, const int loop_start, const int loop_end, sound_object *)
|
|
{
|
|
if (!digi_initialised) return -1;
|
|
|
|
if (soundnum < 0)
|
|
return -1;
|
|
|
|
const unsigned max_channels = digi_mixer_max_channels;
|
|
if (max_channels > channels.size())
|
|
return -1;
|
|
const auto channel = digi_mixer_find_channel(channels, max_channels);
|
|
if (channel >= max_channels)
|
|
return -1;
|
|
|
|
Assert(GameSounds[soundnum].data != reinterpret_cast<void *>(-1));
|
|
|
|
mixdigi_convert_sound(soundnum);
|
|
|
|
const int mix_pan = fix2byte(static_cast<fix>(pan));
|
|
#if MIX_DIGI_DEBUG
|
|
con_printf(CON_DEBUG, "digi_start_sound %d, volume=%d, pan %d (start=%d, end=%d)", soundnum, volume, mix_pan, loop_start, loop_end);
|
|
#else
|
|
(void)loop_start;
|
|
(void)loop_end;
|
|
#endif
|
|
|
|
const int mix_loop = looping * -1;
|
|
Mix_PlayChannel(channel, &(SoundChunks[soundnum]), mix_loop);
|
|
Mix_SetPanning(channel, 255-mix_pan, mix_pan);
|
|
Mix_SetDistance(channel, UINT8_MAX - fix2byte(volume));
|
|
channels.set(channel);
|
|
|
|
return channel;
|
|
}
|
|
|
|
}
|
|
|
|
namespace dcx {
|
|
|
|
void digi_mixer_set_channel_volume(int channel, int volume)
|
|
{
|
|
if (!digi_initialised) return;
|
|
Mix_SetDistance(channel, UINT8_MAX - fix2byte(volume));
|
|
}
|
|
|
|
void digi_mixer_set_channel_pan(int channel, const sound_pan pan)
|
|
{
|
|
int mix_pan = fix2byte(static_cast<fix>(pan));
|
|
Mix_SetPanning(channel, 255-mix_pan, mix_pan);
|
|
}
|
|
|
|
void digi_mixer_stop_sound(int channel) {
|
|
if (!digi_initialised) return;
|
|
#if MIX_DIGI_DEBUG
|
|
con_printf(CON_DEBUG, "digi_stop_sound %d", channel);
|
|
#endif
|
|
Mix_HaltChannel(channel);
|
|
channels.reset(channel);
|
|
}
|
|
|
|
void digi_mixer_end_sound(int channel)
|
|
{
|
|
digi_mixer_stop_sound(channel);
|
|
channels.reset(channel);
|
|
}
|
|
|
|
void digi_mixer_set_digi_volume( int dvolume )
|
|
{
|
|
digi_volume = dvolume;
|
|
if (!digi_initialised) return;
|
|
Mix_Volume(-1, fix2byte(dvolume));
|
|
}
|
|
|
|
int digi_mixer_is_channel_playing(const int c)
|
|
{
|
|
return channels[c];
|
|
}
|
|
|
|
void digi_mixer_stop_all_channels()
|
|
{
|
|
channels = {};
|
|
Mix_HaltChannel(-1);
|
|
}
|
|
|
|
}
|