dxx-rebirth/similar/arch/sdl/digi_mixer.cpp
Kp 20a0166cf4 Avoid double-scaling sounds at start time
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.
2021-06-28 03:37:51 +00:00

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);
}
}