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 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. }
  290.