Freeciv21
Develop your civilization from humble roots to a global empire
audio_sdl.cpp
Go to the documentation of this file.
1 /*__ ___ ***************************************
2 / \ / \ Copyright (c) 1996-2023 Freeciv21 and Freeciv
3 \_ \ / __/ contributors. This file is part of Freeciv21.
4  _\ \ / /__ Freeciv21 is free software: you can redistribute it
5  \___ \____/ __/ and/or modify it under the terms of the GNU General
6  \_ _/ Public License as published by the Free Software
7  | @ @ \_ Foundation, either version 3 of the License,
8  | or (at your option) any later version.
9  _/ /\ You should have received a copy of the GNU
10  /o) (o/\ \_ General Public License along with Freeciv21.
11  \_____/ / If not, see https://www.gnu.org/licenses/.
12  \____/ ********************************************************/
13 
14 #include <fc_config.h>
15 
16 #include <cstring>
17 
18 #ifdef AUDIO_SDL
19 #include <SDL2/SDL.h>
20 #include <SDL2/SDL_mixer.h>
21 #endif
22 // utility
23 #include "log.h"
24 #include "support.h"
25 
26 // client
27 #include "audio.h"
28 
29 #include "audio_sdl.h"
30 
31 #include <array>
32 
33 struct sample {
34  Mix_Chunk *wave = nullptr;
35  QString tag;
36 };
37 
38 /* Sounds don't sound good on Windows unless the buffer size is 4k,
39  * but this seems to cause strange behaviour on other systems,
40  * such as a delay before playing the sound. */
41 #ifdef FREECIV_MSWINDOWS
42 const size_t buf_size = 4096;
43 #else
44 const size_t buf_size = 1024;
45 #endif
46 
47 static Mix_Music *mus = nullptr;
48 static std::array<sample, MIX_CHANNELS> samples;
49 static double sdl_audio_volume;
50 
54 static void sdl_audio_set_volume(double volume)
55 {
56  Mix_VolumeMusic(volume * MIX_MAX_VOLUME);
57  Mix_Volume(-1, volume * MIX_MAX_VOLUME);
58  sdl_audio_volume = volume;
59 }
60 
64 static double sdl_audio_get_volume() { return sdl_audio_volume; }
65 
69 static bool sdl_audio_play(const QString &tag, const QString &fullpath,
70  bool repeat, audio_finished_callback cb)
71 {
72  Mix_Chunk *wave = nullptr;
73 
74  if (fullpath.isEmpty()) {
75  return false;
76  }
77 
78  if (repeat) {
79  // unload previous
80  Mix_HaltMusic();
81  Mix_FreeMusic(mus);
82 
83  // load music file
84  mus = Mix_LoadMUS(qUtf8Printable(fullpath));
85  if (mus == nullptr) {
86  qCritical("Can't open file \"%s\"", qUtf8Printable(fullpath));
87  return false;
88  }
89 
90  if (cb == nullptr) {
91  Mix_PlayMusic(mus, -1); // -1 means loop forever
92  } else {
93  Mix_PlayMusic(mus, 0);
94  Mix_HookMusicFinished(cb);
95  }
96  qDebug("Playing file \"%s\" on music channel", qUtf8Printable(fullpath));
97  /* in case we did a sdl_audio_stop() recently; add volume controls later
98  */
99  Mix_VolumeMusic(sdl_audio_volume * MIX_MAX_VOLUME);
100 
101  } else {
102  // see if we can cache on this one
103  for (auto sample : samples) {
104  if (sample.wave != nullptr && sample.tag == tag) {
105  log_debug("Playing file \"%s\" from cache",
106  qUtf8Printable(fullpath));
107  Mix_PlayChannel(-1, sample.wave, 0);
108  return true;
109  }
110  } // guess not
111 
112  // load wave
113  wave = Mix_LoadWAV(qUtf8Printable(fullpath));
114  if (wave == nullptr) {
115  qCritical("Can't open file \"%s\"", qUtf8Printable(fullpath));
116  return false;
117  }
118 
119  /* play sound sample on first available channel, returns -1 if no
120  channel found */
121  int i = Mix_PlayChannel(-1, wave, 0);
122  if (i < 0) {
123  qDebug("No available sound channel to play %s.", qUtf8Printable(tag));
124  Mix_FreeChunk(wave);
125  return false;
126  }
127  qDebug("Playing file \"%s\" on channel %d", qUtf8Printable(fullpath), i);
128  /* free previous sample on this channel. it will by definition no
129  longer be playing by the time we get here */
130  if (samples[i].wave) {
131  Mix_FreeChunk(samples[i].wave);
132  samples[i].wave = nullptr;
133  }
134  // remember for cacheing
135  samples[i].wave = wave;
136  samples[i].tag = tag;
137  }
138  return true;
139 }
140 
144 static void sdl_audio_stop()
145 {
146  // fade out over 2 sec
147  Mix_FadeOutMusic(2000);
148 }
149 
155 static void sdl_audio_wait()
156 {
157  while (Mix_Playing(-1) != 0) {
158  SDL_Delay(100);
159  }
160 }
161 
168 static void quit_sdl_audio()
169 {
170  if (SDL_WasInit(SDL_INIT_VIDEO)) {
171  SDL_QuitSubSystem(SDL_INIT_AUDIO);
172  } else {
173  SDL_Quit();
174  }
175 }
176 
183 static int init_sdl_audio()
184 {
185  if (SDL_WasInit(SDL_INIT_VIDEO)) {
186  return SDL_InitSubSystem(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
187  } else {
188  return SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
189  }
190 }
191 
195 static void sdl_audio_shutdown()
196 {
197  int i;
198 
199  sdl_audio_stop();
200  sdl_audio_wait();
201 
202  // remove all buffers
203  for (i = 0; i < MIX_CHANNELS; i++) {
204  if (samples[i].wave) {
205  Mix_FreeChunk(samples[i].wave);
206  }
207  }
208  Mix_HaltMusic();
209  if (mus != nullptr) {
210  Mix_FreeMusic(mus);
211  }
212 
213  Mix_CloseAudio();
214  quit_sdl_audio();
215 }
216 
220 static bool sdl_audio_init()
221 {
222  // Initialize variables
223  const int audio_rate = MIX_DEFAULT_FREQUENCY;
224  const int audio_format = MIX_DEFAULT_FORMAT;
225  const int audio_channels = 2;
226  int i;
227 
228  if (init_sdl_audio() < 0) {
229  return false;
230  }
231 
232  if (Mix_OpenAudio(audio_rate, audio_format, audio_channels, buf_size)
233  < 0) {
234  qCritical("Error calling Mix_OpenAudio");
235  // try something else
236  quit_sdl_audio();
237  return false;
238  }
239 
240  Mix_AllocateChannels(MIX_CHANNELS);
241  for (i = 0; i < MIX_CHANNELS; i++) {
242  samples[i].wave = nullptr;
243  }
244  // sanity check, for now; add volume controls later
246  return true;
247 }
248 
254 {
255  struct audio_plugin self;
256 
257  self.name = QStringLiteral("sdl");
258  self.descr =
259  QStringLiteral("Simple DirectMedia Library (SDL) mixer plugin");
260  self.init = sdl_audio_init;
261  self.shutdown = sdl_audio_shutdown;
262  self.stop = sdl_audio_stop;
263  self.wait = sdl_audio_wait;
264  self.play = sdl_audio_play;
265  self.set_volume = sdl_audio_set_volume;
266  self.get_volume = sdl_audio_get_volume;
267  audio_add_plugin(&self);
268  sdl_audio_volume = 1.0;
269 }
void audio_add_plugin(struct audio_plugin *p)
Add a plugin.
Definition: audio.cpp:108
void(* audio_finished_callback)()
Definition: audio.h:17
static void sdl_audio_wait()
Wait for audio to die on all channels.
Definition: audio_sdl.cpp:155
static void sdl_audio_set_volume(double volume)
Set the volume.
Definition: audio_sdl.cpp:54
static int init_sdl_audio()
Init SDL.
Definition: audio_sdl.cpp:183
static void sdl_audio_stop()
Stop music.
Definition: audio_sdl.cpp:144
static std::array< sample, MIX_CHANNELS > samples
Definition: audio_sdl.cpp:48
static bool sdl_audio_init()
Initialize.
Definition: audio_sdl.cpp:220
static double sdl_audio_get_volume()
Get the volume.
Definition: audio_sdl.cpp:64
void audio_sdl_init()
Initialize.
Definition: audio_sdl.cpp:253
static double sdl_audio_volume
Definition: audio_sdl.cpp:49
static void sdl_audio_shutdown()
Clean up.
Definition: audio_sdl.cpp:195
static Mix_Music * mus
Definition: audio_sdl.cpp:47
const size_t buf_size
Definition: audio_sdl.cpp:44
static void quit_sdl_audio()
Quit SDL.
Definition: audio_sdl.cpp:168
static bool sdl_audio_play(const QString &tag, const QString &fullpath, bool repeat, audio_finished_callback cb)
Play sound.
Definition: audio_sdl.cpp:69
static struct ai_type * self
Definition: classicai.cpp:40
#define log_debug(message,...)
Definition: log.h:65
char name[MAX_LEN_NAME]
Definition: ai.h:43
Mix_Chunk * wave
Definition: audio_sdl.cpp:34
QString tag
Definition: audio_sdl.cpp:35