Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 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 sound effect system. |
||
| 9 | * It uses SDL_mixer to provide a more reliable playback, |
||
| 10 | * and allow processing of multiple audio formats. |
||
| 11 | * |
||
| 12 | * This file is based on the original D1X arch/sdl/digi.c |
||
| 13 | * |
||
| 14 | * -- MD2211 (2006-10-12) |
||
| 15 | */ |
||
| 16 | |||
| 17 | #include <bitset> |
||
| 18 | #include <stdlib.h> |
||
| 19 | #include <stdio.h> |
||
| 20 | #include <string.h> |
||
| 21 | |||
| 22 | #include <SDL.h> |
||
| 23 | #include <SDL_audio.h> |
||
| 24 | #include <SDL_mixer.h> |
||
| 25 | |||
| 26 | #include "pstypes.h" |
||
| 27 | #include "dxxerror.h" |
||
| 28 | #include "sounds.h" |
||
| 29 | #include "digi.h" |
||
| 30 | #include "digi_mixer.h" |
||
| 31 | #include "digi_mixer_music.h" |
||
| 32 | #include "console.h" |
||
| 33 | #include "config.h" |
||
| 34 | #include "args.h" |
||
| 35 | |||
| 36 | #include "maths.h" |
||
| 37 | #include "piggy.h" |
||
| 38 | #include "u_mem.h" |
||
| 39 | #include <memory> |
||
| 40 | |||
| 41 | #define MIX_DIGI_DEBUG 0 |
||
| 42 | #define MIX_OUTPUT_FORMAT AUDIO_S16 |
||
| 43 | #define MIX_OUTPUT_CHANNELS 2 |
||
| 44 | |||
| 45 | #if !((defined(__APPLE__) && defined(__MACH__)) || defined(macintosh)) |
||
| 46 | #define SOUND_BUFFER_SIZE 2048 |
||
| 47 | #else |
||
| 48 | #define SOUND_BUFFER_SIZE 1024 |
||
| 49 | #endif |
||
| 50 | |||
| 51 | namespace dcx { |
||
| 52 | |||
| 53 | namespace { |
||
| 54 | |||
| 55 | /* channel management */ |
||
| 56 | static unsigned digi_mixer_find_channel(const std::bitset<64> &channels, const unsigned max_channels) |
||
| 57 | { |
||
| 58 | unsigned i = 0; |
||
| 59 | for (; i < max_channels; ++i) |
||
| 60 | if (!channels[i]) |
||
| 61 | break; |
||
| 62 | return i; |
||
| 63 | } |
||
| 64 | |||
| 65 | struct RAIIMix_Chunk : public Mix_Chunk |
||
| 66 | { |
||
| 67 | RAIIMix_Chunk() = default; |
||
| 68 | ~RAIIMix_Chunk() |
||
| 69 | { |
||
| 70 | delete [] abuf; |
||
| 71 | } |
||
| 72 | RAIIMix_Chunk(const RAIIMix_Chunk &) = delete; |
||
| 73 | RAIIMix_Chunk &operator=(const RAIIMix_Chunk &) = delete; |
||
| 74 | }; |
||
| 75 | |||
| 76 | static int fix2byte(const fix f) |
||
| 77 | { |
||
| 78 | return (f / 256) % 256; |
||
| 79 | } |
||
| 80 | |||
| 81 | uint8_t digi_initialised; |
||
| 82 | std::bitset<64> channels; |
||
| 83 | unsigned digi_mixer_max_channels = channels.size(); |
||
| 84 | |||
| 85 | void digi_mixer_free_channel(const int channel_num) |
||
| 86 | { |
||
| 87 | channels.reset(channel_num); |
||
| 88 | } |
||
| 89 | |||
| 90 | } |
||
| 91 | |||
| 92 | } |
||
| 93 | |||
| 94 | namespace dsx { |
||
| 95 | |||
| 96 | static std::array<RAIIMix_Chunk, MAX_SOUNDS> SoundChunks; |
||
| 97 | |||
| 98 | /* Initialise audio */ |
||
| 99 | int digi_mixer_init() |
||
| 100 | { |
||
| 101 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 102 | const unsigned |
||
| 103 | #endif |
||
| 104 | digi_sample_rate = SAMPLE_RATE_44K; |
||
| 105 | |||
| 106 | #if MIX_DIGI_DEBUG |
||
| 107 | con_printf(CON_DEBUG, "digi_init %u (SDL_Mixer)", MAX_SOUNDS.value); |
||
| 108 | #endif |
||
| 109 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) Error("SDL audio initialisation failed: %s.", SDL_GetError()); |
||
| 110 | |||
| 111 | if (Mix_OpenAudio(digi_sample_rate, MIX_OUTPUT_FORMAT, MIX_OUTPUT_CHANNELS, SOUND_BUFFER_SIZE)) |
||
| 112 | { |
||
| 113 | //edited on 10/05/98 by Matt Mueller - should keep running, just with no sound. |
||
| 114 | con_printf(CON_URGENT,"\nError: Couldn't open audio: %s", SDL_GetError()); |
||
| 115 | CGameArg.SndNoSound = 1; |
||
| 116 | return 1; |
||
| 117 | } |
||
| 118 | |||
| 119 | digi_mixer_max_channels = Mix_AllocateChannels(digi_mixer_max_channels); |
||
| 120 | channels.reset(); |
||
| 121 | Mix_Pause(0); |
||
| 122 | Mix_ChannelFinished(digi_mixer_free_channel); |
||
| 123 | |||
| 124 | digi_initialised = 1; |
||
| 125 | |||
| 126 | digi_mixer_set_digi_volume( (GameCfg.DigiVolume*32768)/8 ); |
||
| 127 | |||
| 128 | return 0; |
||
| 129 | } |
||
| 130 | |||
| 131 | } |
||
| 132 | |||
| 133 | namespace dcx { |
||
| 134 | |||
| 135 | /* Shut down audio */ |
||
| 136 | void digi_mixer_close() { |
||
| 137 | #if MIX_DIGI_DEBUG |
||
| 138 | con_printf(CON_DEBUG, "digi_close (SDL_Mixer)"); |
||
| 139 | #endif |
||
| 140 | if (!digi_initialised) return; |
||
| 141 | digi_initialised = 0; |
||
| 142 | Mix_CloseAudio(); |
||
| 143 | } |
||
| 144 | |||
| 145 | } |
||
| 146 | |||
| 147 | namespace dsx { |
||
| 148 | |||
| 149 | /* |
||
| 150 | * Play-time conversion. Performs output conversion only once per sound effect used. |
||
| 151 | * Once the sound sample has been converted, it is cached in SoundChunks[] |
||
| 152 | */ |
||
| 153 | static void mixdigi_convert_sound(const unsigned i) |
||
| 154 | { |
||
| 155 | if (SoundChunks[i].abuf) |
||
| 156 | //proceed only if not converted yet |
||
| 157 | return; |
||
| 158 | SDL_AudioCVT cvt; |
||
| 159 | Uint8 *data = GameSounds[i].data; |
||
| 160 | Uint32 dlen = GameSounds[i].length; |
||
| 161 | int freq; |
||
| 162 | int out_freq; |
||
| 163 | Uint16 out_format; |
||
| 164 | int out_channels; |
||
| 165 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 166 | out_freq = digi_sample_rate; |
||
| 167 | out_format = MIX_OUTPUT_FORMAT; |
||
| 168 | out_channels = MIX_OUTPUT_CHANNELS; |
||
| 169 | freq = GameSounds[i].freq; |
||
| 170 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 171 | Mix_QuerySpec(&out_freq, &out_format, &out_channels); // get current output settings |
||
| 172 | freq = GameArg.SndDigiSampleRate; |
||
| 173 | #endif |
||
| 174 | |||
| 175 | if (data) |
||
| 176 | { |
||
| 177 | if (SDL_BuildAudioCVT(&cvt, AUDIO_U8, 1, freq, out_format, out_channels, out_freq) == -1) |
||
| 178 | { |
||
| 179 | 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); |
||
| 180 | return; |
||
| 181 | } |
||
| 182 | |||
| 183 | auto cvtbuf = std::make_unique<Uint8[]>(dlen * cvt.len_mult); |
||
| 184 | cvt.buf = cvtbuf.get(); |
||
| 185 | cvt.len = dlen; |
||
| 186 | memcpy(cvt.buf, data, dlen); |
||
| 187 | if (SDL_ConvertAudio(&cvt)) |
||
| 188 | { |
||
| 189 | 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); |
||
| 190 | return; |
||
| 191 | } |
||
| 192 | |||
| 193 | SoundChunks[i].abuf = cvtbuf.release(); |
||
| 194 | SoundChunks[i].alen = cvt.len_cvt; |
||
| 195 | SoundChunks[i].allocated = 1; |
||
| 196 | SoundChunks[i].volume = 128; // Max volume = 128 |
||
| 197 | } |
||
| 198 | } |
||
| 199 | |||
| 200 | // Volume 0-F1_0 |
||
| 201 | int digi_mixer_start_sound(short soundnum, fix volume, int pan, int looping, int loop_start, int loop_end, sound_object *) |
||
| 202 | { |
||
| 203 | if (!digi_initialised) return -1; |
||
| 204 | |||
| 205 | if (soundnum < 0) |
||
| 206 | return -1; |
||
| 207 | |||
| 208 | const unsigned max_channels = digi_mixer_max_channels; |
||
| 209 | if (max_channels > channels.size()) |
||
| 210 | return -1; |
||
| 211 | const auto channel = digi_mixer_find_channel(channels, max_channels); |
||
| 212 | if (channel >= max_channels) |
||
| 213 | return -1; |
||
| 214 | |||
| 215 | Assert(GameSounds[soundnum].data != reinterpret_cast<void *>(-1)); |
||
| 216 | |||
| 217 | mixdigi_convert_sound(soundnum); |
||
| 218 | |||
| 219 | const int mix_vol = fix2byte(fixmul(digi_volume, volume)); |
||
| 220 | const uint8_t mix_distance = (volume > F1_0) ? 0 : UINT8_MAX - mix_vol; |
||
| 221 | const int mix_pan = fix2byte(pan); |
||
| 222 | #if MIX_DIGI_DEBUG |
||
| 223 | con_printf(CON_DEBUG, "digi_start_sound %d, volume %d, pan %d (start=%d, end=%d)", soundnum, mix_vol, mix_pan, loop_start, loop_end); |
||
| 224 | #else |
||
| 225 | (void)loop_start; |
||
| 226 | (void)loop_end; |
||
| 227 | #endif |
||
| 228 | |||
| 229 | const int mix_loop = looping * -1; |
||
| 230 | Mix_PlayChannel(channel, &(SoundChunks[soundnum]), mix_loop); |
||
| 231 | Mix_SetPanning(channel, 255-mix_pan, mix_pan); |
||
| 232 | Mix_SetDistance(channel, mix_distance); |
||
| 233 | channels.set(channel); |
||
| 234 | |||
| 235 | return channel; |
||
| 236 | } |
||
| 237 | |||
| 238 | } |
||
| 239 | |||
| 240 | namespace dcx { |
||
| 241 | |||
| 242 | void digi_mixer_set_channel_volume(int channel, int volume) |
||
| 243 | { |
||
| 244 | int mix_vol = fix2byte(volume); |
||
| 245 | if (!digi_initialised) return; |
||
| 246 | Mix_SetDistance(channel, 255-mix_vol); |
||
| 247 | } |
||
| 248 | |||
| 249 | void digi_mixer_set_channel_pan(int channel, int pan) |
||
| 250 | { |
||
| 251 | int mix_pan = fix2byte(pan); |
||
| 252 | Mix_SetPanning(channel, 255-mix_pan, mix_pan); |
||
| 253 | } |
||
| 254 | |||
| 255 | void digi_mixer_stop_sound(int channel) { |
||
| 256 | if (!digi_initialised) return; |
||
| 257 | #if MIX_DIGI_DEBUG |
||
| 258 | con_printf(CON_DEBUG, "digi_stop_sound %d", channel); |
||
| 259 | #endif |
||
| 260 | Mix_HaltChannel(channel); |
||
| 261 | channels.reset(channel); |
||
| 262 | } |
||
| 263 | |||
| 264 | void digi_mixer_end_sound(int channel) |
||
| 265 | { |
||
| 266 | digi_mixer_stop_sound(channel); |
||
| 267 | channels.reset(channel); |
||
| 268 | } |
||
| 269 | |||
| 270 | void digi_mixer_set_digi_volume( int dvolume ) |
||
| 271 | { |
||
| 272 | digi_volume = dvolume; |
||
| 273 | if (!digi_initialised) return; |
||
| 274 | Mix_Volume(-1, fix2byte(dvolume)); |
||
| 275 | } |
||
| 276 | |||
| 277 | int digi_mixer_is_channel_playing(const int c) |
||
| 278 | { |
||
| 279 | return channels[c]; |
||
| 280 | } |
||
| 281 | |||
| 282 | void digi_mixer_reset() {} |
||
| 283 | void digi_mixer_stop_all_channels() |
||
| 284 | { |
||
| 285 | channels = {}; |
||
| 286 | Mix_HaltChannel(-1); |
||
| 287 | } |
||
| 288 | |||
| 289 | } |