Subversion Repositories Games.Descent

Rev

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
}