Freeciv21
Develop your civilization from humble roots to a global empire
audio.cpp
Go to the documentation of this file.
1 /*
2 Copyright (c) 1996-2023 Freeciv21 and Freeciv contributors. This file is
3  /\/\ part of Freeciv21. Freeciv21 is free software: you can
4  \_\ _..._ redistribute it and/or modify it under the terms of the
5  (" )(_..._) GNU General Public License as published by the Free
6  ^^ // \\ Software Foundation, either version 3 of the License,
7  or (at your option) any later version. You should have
8 received a copy of the GNU General Public License along with Freeciv21.
9  If not, see https://www.gnu.org/licenses/.
10  */
11 
12 #include <fc_config.h>
13 
14 #include <QString>
15 #include <QVector>
16 #include <cstdlib>
17 // utility
18 #include "capability.h"
19 #include "fcintl.h"
20 #include "log.h"
21 #include "rand.h"
22 #include "registry.h"
23 #include "registry_ini.h"
24 #include "shared.h"
25 
26 // client
27 #include "audio_none.h"
28 #ifdef AUDIO_SDL
29 #include "audio_sdl.h"
30 #endif
31 #include "client_main.h"
32 #include "options.h"
33 
34 #include "audio.h"
35 
36 #define MAX_NUM_PLUGINS 2
37 #define SNDSPEC_SUFFIX ".soundspec"
38 #define MUSICSPEC_SUFFIX ".musicspec"
39 
40 #define SOUNDSPEC_CAPSTR "+Freeciv-3.0-soundset"
41 #define MUSICSPEC_CAPSTR "+Freeciv-2.6-musicset"
42 
43 // keep it open throughout
44 static struct section_file *ss_tagfile = nullptr;
45 static struct section_file *ms_tagfile = nullptr;
46 
47 static struct audio_plugin plugins[MAX_NUM_PLUGINS];
48 static int num_plugins_used = 0;
49 static int selected_plugin = -1;
50 static int current_track = -1;
51 static enum music_usage current_usage;
52 static bool switching_usage = false;
53 static bool let_single_track_play = false;
54 
55 static struct mfcb_data {
57  QString tag;
58 } mfcb;
59 
61 
62 static int audio_play_tag(struct section_file *sfile, const QString &tag,
63  bool repeat, int exclude, bool keepstyle);
64 
70 const QVector<QString> *get_soundplugin_list(const struct option *poption)
71 {
72  if (plugin_list->isEmpty()) {
73  for (int i = 0; i < num_plugins_used; i++) {
74  plugin_list->append(plugins[i].name);
75  }
76  }
77 
78  return plugin_list;
79 }
80 
84 const QVector<QString> *get_soundset_list(const struct option *poption)
85 {
86  static QVector<QString> *sound_list = new QVector<QString>;
88  *sound_list = std::move(*list);
89  delete list;
90  return sound_list;
91 }
92 
96 const QVector<QString> *get_musicset_list(const struct option *poption)
97 {
98  static QVector<QString> *music_list = new QVector<QString>;
100  *music_list = std::move(*list);
101  delete list;
102  return music_list;
103 }
104 
109 {
121 }
122 
126 bool audio_select_plugin(const QString &name)
127 {
128  int i;
129  bool found = false;
130 
131  for (i = 0; i < num_plugins_used; i++) {
132  if (QString(plugins[i].name) == name) {
133  found = true;
134  break;
135  }
136  }
137 
138  if (found && i != selected_plugin) {
139  log_debug("Shutting down %s",
140  qUtf8Printable(plugins[selected_plugin].name));
144  }
145 
146  if (!found) {
147  qFatal(_("Plugin '%s' isn't available. Available are %s"),
148  qUtf8Printable(name),
149  qUtf8Printable(audio_get_all_plugin_names()));
150  exit(EXIT_FAILURE);
151  }
152 
153  if (!plugins[i].init()) {
154  qCritical("Plugin %s found, but can't be initialized.",
155  qUtf8Printable(name));
156  return false;
157  }
158 
159  selected_plugin = i;
160  qDebug("Plugin '%s' is now selected",
161  qUtf8Printable(plugins[selected_plugin].name));
162 
164  / 100.0);
165 
166  return true;
167 }
168 
174 {
175 #ifdef AUDIO_SDL
176  audio_sdl_init();
177 #endif
178 
179  /* Initialize dummy plugin last, as lowest priority plugin. This
180  * affects which plugin gets selected as default in new installations. */
181  audio_none_init();
182  selected_plugin = 0;
183 }
184 
189 static const QString audiospec_fullname(const QString &audioset_name,
190  bool music)
191 {
192  const QString suffix = music ? MUSICSPEC_SUFFIX : SNDSPEC_SUFFIX;
193  QString audioset_default =
194  music ? QStringLiteral("stdmusic") : QStringLiteral("stdsounds");
195  QString dname;
196 
197  QString fname = QStringLiteral("%1%2").arg(audioset_name, suffix);
198  dname = fileinfoname(get_data_dirs(), qUtf8Printable(fname));
199 
200  if (!dname.isEmpty()) {
201  return dname;
202  }
203 
204  if (audioset_name == audioset_default) {
205  // avoid endless recursion
206  return nullptr;
207  }
208 
209  qCritical("Couldn't find audioset \"%s\", trying \"%s\".",
210  qUtf8Printable(audioset_name), qUtf8Printable(audioset_default));
211 
212  return audiospec_fullname(audioset_default, music);
213 }
214 
218 static bool check_audiofile_capstr(struct section_file *sfile,
219  const QString *filename,
220  const QString *our_cap,
221  const QString *opt_path)
222 {
223  const char *file_capstr;
224 
225  file_capstr = secfile_lookup_str(sfile, "%s", qUtf8Printable(*opt_path));
226  if (nullptr == file_capstr) {
227  qFatal("Audio spec-file \"%s\" doesn't have capability string.",
228  qUtf8Printable(*filename));
229  exit(EXIT_FAILURE);
230  }
231  if (!has_capabilities(qUtf8Printable(*our_cap), file_capstr)) {
232  qFatal("Audio spec-file appears incompatible:");
233  qFatal(" file: \"%s\"", qUtf8Printable(*filename));
234  qFatal(" file options: %s", file_capstr);
235  qFatal(" supported options: %s", qUtf8Printable(*our_cap));
236  exit(EXIT_FAILURE);
237  }
238  if (!has_capabilities(file_capstr, qUtf8Printable(*our_cap))) {
239  qFatal("Audio spec-file claims required option(s) "
240  "which we don't support:");
241  qFatal(" file: \"%s\"", qUtf8Printable(*filename));
242  qFatal(" file options: %s", file_capstr);
243  qFatal(" supported options: %s", qUtf8Printable(*our_cap));
244  exit(EXIT_FAILURE);
245  }
246 
247  return true;
248 }
249 
253 void audio_real_init(const QString &soundset_name,
254  const QString &musicset_name,
255  const QString &preferred_plugin_name)
256 {
257  QString ss_filename;
258  QString ms_filename;
259  const QString us_ss_capstr = SOUNDSPEC_CAPSTR;
260  const QString us_ms_capstr = MUSICSPEC_CAPSTR;
261 
262  if (preferred_plugin_name == QLatin1String("none")) {
263  // We explicitly choose none plugin, silently skip the code below
264  qDebug("Proceeding with sound support disabled.");
265  ss_tagfile = nullptr;
266  ms_tagfile = nullptr;
267  return;
268  }
269  if (num_plugins_used == 1) {
270  // We only have the dummy plugin, skip the code but issue an advertise
271  qInfo(_("No real audio plugin present."));
272  qInfo(_("Proceeding with sound support disabled."));
273  qInfo(_("For sound support, install SDL2_mixer"));
274  qInfo("http://www.libsdl.org/projects/SDL_mixer/index.html");
275  ss_tagfile = nullptr;
276  ms_tagfile = nullptr;
277  return;
278  }
279  if (soundset_name.isEmpty()) {
280  qFatal("No sound spec-file given!");
281  exit(EXIT_FAILURE);
282  }
283  if (musicset_name.isEmpty()) {
284  qFatal("No music spec-file given!");
285  exit(EXIT_FAILURE);
286  }
287  qDebug("Initializing sound using %s and %s...",
288  qUtf8Printable(soundset_name), qUtf8Printable(musicset_name));
289  ss_filename = audiospec_fullname(soundset_name, false);
290  ms_filename = audiospec_fullname(musicset_name, true);
291  if (ss_filename.isEmpty() || ms_filename.isEmpty()) {
292  qCritical("Cannot find audio spec-file \"%s\" or \"%s\"",
293  qUtf8Printable(soundset_name), qUtf8Printable(musicset_name));
294  qInfo(_("To get sound you need to download a sound set!"));
295  qInfo(_("Get sound sets from the Modpack Installer "
296  "(freeciv21-modpack-qt) program."));
297  qInfo(_("Proceeding with sound support disabled."));
298  ss_tagfile = nullptr;
299  ms_tagfile = nullptr;
300  return;
301  }
302  ss_tagfile = secfile_load(ss_filename, true);
303  if (!ss_tagfile) {
304  qFatal(_("Could not load sound spec-file '%s':\n%s"),
305  qUtf8Printable(ss_filename), secfile_error());
306  exit(EXIT_FAILURE);
307  }
308  ms_tagfile = secfile_load(ms_filename, true);
309  if (!ms_tagfile) {
310  qFatal(_("Could not load music spec-file '%s':\n%s"),
311  qUtf8Printable(ms_filename), secfile_error());
312  exit(EXIT_FAILURE);
313  }
314  QString t0 = QStringLiteral("soundspec.options");
315  check_audiofile_capstr(ss_tagfile, &ss_filename, &us_ss_capstr, &t0);
316 
317  QString t1 = QStringLiteral("musicspec.options");
318  check_audiofile_capstr(ms_tagfile, &ms_filename, &us_ms_capstr, &t1);
319 
320  atexit(audio_shutdown);
321 
322  if (!preferred_plugin_name.isEmpty()) {
323  if (!audio_select_plugin(preferred_plugin_name)) {
324  qInfo(_("Proceeding with sound support disabled."));
325  }
326  return;
327  }
328 
329 #ifdef AUDIO_SDL
330  QString audio_str = QStringLiteral("sdl");
331  if (audio_select_plugin(audio_str)) {
332  return;
333  }
334 #endif
335  qInfo(_("No real audio subsystem managed to initialize!"));
336  qInfo(_("Perhaps there is some misconfiguration or bad permissions."));
337  qInfo(_("Proceeding with sound support disabled."));
338 }
339 
343 void audio_restart(const QString &soundset_name,
344  const QString &musicset_name)
345 {
346  audio_stop(); // Fade down old one
347 
348  sound_set_name = soundset_name;
349  music_set_name = musicset_name;
351 }
352 
357 {
358  if (let_single_track_play) {
359  /* This call is style music ending before single track plays.
360  * Do not restart style music now.
361  * Make sure style music restarts when single track itself finishes. */
362  let_single_track_play = false;
363  return;
364  }
365 
366  if (switching_usage) {
367  switching_usage = false;
368  return;
369  }
370 
371  bool usage_enabled = true;
372  switch (current_usage) {
373  case MU_MENU:
374  usage_enabled = gui_options->sound_enable_menu_music;
375  break;
376  case MU_INGAME:
377  usage_enabled = gui_options->sound_enable_game_music;
378  break;
379  }
380 
381  if (usage_enabled) {
382  current_track =
383  audio_play_tag(mfcb.sfile, mfcb.tag, true, current_track, false);
384  }
385 }
386 
391 static int audio_play_tag(struct section_file *sfile, const QString &tag,
392  bool repeat, int exclude, bool keepstyle)
393 {
394  QString soundfile;
395  QString fullpath;
396  audio_finished_callback cb = nullptr;
397  int ret = 0;
398 
399  if (tag.isEmpty() || (tag == QLatin1String("-"))) {
400  return -1;
401  }
402 
403  if (sfile) {
404  auto str = secfile_lookup_str(sfile, "files.%s", qUtf8Printable(tag));
405  if (str) {
406  soundfile = str;
407  } else {
408  std::vector<QString> files;
409  for (int i = 0; i < std::numeric_limits<int>::max(); i++) {
410  const char *ftmp =
411  secfile_lookup_str(sfile, "files.%s_%d", qUtf8Printable(tag), i);
412 
413  if (ftmp == nullptr) {
414  // Reached the end of the tracks vector
415  break;
416  }
417 
418  files.push_back(ftmp);
419  }
420 
421  if (files.size() > 1 && exclude >= 0) {
422  // Handle excluded track. Can only do it if we have more than one...
423  // There are only N-1 possible files to choose from since one is
424  // excluded.
425  ret = fc_rand(files.size() - 1);
426 
427  if (ret == exclude) {
428  // The excluded file was selected, select the last one instead.
429  // Note that the last file cannot be selected above.
430  ret = files.size() - 1;
431  }
432  } else {
433  // No excluded track, easy case.
434  ret = fc_rand(files.size());
435  }
436 
437  if (files.empty()) {
438  ret = -1;
439  } else {
440  soundfile = files.at(ret);
441  }
442  }
443 
444  if (repeat) {
445  if (!keepstyle) {
446  mfcb.sfile = sfile;
447  mfcb.tag = tag;
448  }
449 
450  /* Callback is needed even when there's no alternative tracks -
451  * we may be running single track now, and want to switch
452  * (by the callback) back to style music when it ends. */
454  }
455 
456  if (soundfile.isEmpty()) {
457  qDebug("No sound file for tag %s", qUtf8Printable(tag));
458  } else {
459  fullpath = fileinfoname(get_data_dirs(), qUtf8Printable(soundfile));
460  if (fullpath.isEmpty()) {
461  qCritical("Cannot find audio file %s for tag %s",
462  qUtf8Printable(soundfile), qUtf8Printable(tag));
463  }
464  }
465  }
466 
467  if (!plugins[selected_plugin].play(tag, fullpath, repeat, cb)) {
468  return -1;
469  }
470 
471  return ret;
472 }
473 
477 static bool audio_play_sound_tag(const QString &tag, bool repeat)
478 {
479  return (audio_play_tag(ss_tagfile, tag, repeat, -1, false) >= 0);
480 }
481 
485 static int audio_play_music_tag(const QString &tag, bool repeat,
486  bool keepstyle)
487 {
488  return audio_play_tag(ms_tagfile, tag, repeat, -1, keepstyle);
489 }
490 
494 void audio_play_sound(const QString &tag, const QString &alt_tag)
495 {
496  const QString pretty_alt_tag =
497  alt_tag.isEmpty() ? QStringLiteral("(null)") : alt_tag;
498 
500  fc_assert_ret(tag != nullptr);
501 
502  log_debug("audio_play_sound('%s', '%s')", qUtf8Printable(tag),
503  qUtf8Printable(pretty_alt_tag));
504 
505  // try playing primary tag first, if not go to alternative tag
506  if (!audio_play_sound_tag(tag, false)
507  && !audio_play_sound_tag(alt_tag, false)) {
508  qDebug("Neither of tags %s or %s found", qUtf8Printable(tag),
509  qUtf8Printable(pretty_alt_tag));
510  }
511  }
512 }
513 
518 static void real_audio_play_music(const QString &tag, const QString &alt_tag,
519  bool keepstyle)
520 {
521  QString pretty_alt_tag = alt_tag.isEmpty() ? ("(null)") : alt_tag;
522 
523  fc_assert_ret(tag != nullptr);
524 
525  log_debug("audio_play_music('%s', '%s')", qUtf8Printable(tag),
526  qUtf8Printable(pretty_alt_tag));
527 
528  // try playing primary tag first, if not go to alternative tag
529  current_track = audio_play_music_tag(tag, true, keepstyle);
530 
531  if (current_track < 0) {
532  current_track = audio_play_music_tag(alt_tag, true, keepstyle);
533 
534  if (current_track < 0) {
535  qDebug("Neither of tags %s or %s found", qUtf8Printable(tag),
536  qUtf8Printable(pretty_alt_tag));
537  }
538  }
539 }
540 
544 void audio_play_music(const QString &tag, const QString &alt_tag,
545  enum music_usage usage)
546 {
547  current_usage = usage;
548 
549  real_audio_play_music(tag, alt_tag, false);
550 }
551 
555 void audio_play_track(const QString &tag, const QString &alt_tag)
556 {
557  if (current_track >= 0) {
558  /* Only set let_single_track_play when there's music playing that will
559  * result in calling the music_finished_callback */
560  let_single_track_play = true;
561 
562  /* Stop old music. */
563  audio_stop();
564  }
565 
566  real_audio_play_music(tag, alt_tag, true);
567 }
568 
573 
578 {
579  switching_usage = true;
581 }
582 
587 
591 void audio_set_volume(double volume)
592 {
594 }
595 
600 {
601  // Already shut down
602  if (selected_plugin < 0) {
603  return;
604  }
605 
606  // avoid infinite loop at end of game
607  audio_stop();
608 
609  audio_play_sound(QStringLiteral("e_game_quit"), nullptr);
612 
613  if (nullptr != ss_tagfile) {
615  ss_tagfile = nullptr;
616  }
617  if (nullptr != ms_tagfile) {
619  ms_tagfile = nullptr;
620  }
621 
622  // Mark shutdown
623  selected_plugin = -1;
624 }
625 
631 {
632  QString buffer;
633  int i;
634 
635  buffer = QStringLiteral("[");
636 
637  for (i = 0; i < num_plugins_used; i++) {
638  buffer = buffer + plugins[i].name;
639  if (i != num_plugins_used - 1) {
640  buffer = buffer + ", ";
641  }
642  }
643  buffer += QLatin1String("]");
644  return buffer;
645 }
static int audio_play_music_tag(const QString &tag, bool repeat, bool keepstyle)
Play tag from music set.
Definition: audio.cpp:485
void audio_init()
Initialize base audio system.
Definition: audio.cpp:173
void audio_play_sound(const QString &tag, const QString &alt_tag)
Play an audio sample as suggested by sound tags.
Definition: audio.cpp:494
static struct audio_plugin plugins[MAX_NUM_PLUGINS]
Definition: audio.cpp:47
static void music_finished_callback()
Callback to start new track.
Definition: audio.cpp:356
static int audio_play_tag(struct section_file *sfile, const QString &tag, bool repeat, int exclude, bool keepstyle)
INTERNAL.
Definition: audio.cpp:391
void audio_play_track(const QString &tag, const QString &alt_tag)
Play single track as suggested by sound tags.
Definition: audio.cpp:555
#define SNDSPEC_SUFFIX
Definition: audio.cpp:37
static enum music_usage current_usage
Definition: audio.cpp:51
#define MUSICSPEC_CAPSTR
Definition: audio.cpp:41
static int selected_plugin
Definition: audio.cpp:49
void audio_play_music(const QString &tag, const QString &alt_tag, enum music_usage usage)
Loop music as suggested by sound tags.
Definition: audio.cpp:544
const QVector< QString > * get_musicset_list(const struct option *poption)
Returns a static string vector of musicsets available on the system.
Definition: audio.cpp:96
static int current_track
Definition: audio.cpp:50
#define SOUNDSPEC_CAPSTR
Definition: audio.cpp:40
static struct section_file * ss_tagfile
Definition: audio.cpp:44
static bool check_audiofile_capstr(struct section_file *sfile, const QString *filename, const QString *our_cap, const QString *opt_path)
Check capabilities of the audio specfile.
Definition: audio.cpp:218
void audio_stop_usage()
Stop looping sound.
Definition: audio.cpp:577
static void real_audio_play_music(const QString &tag, const QString &alt_tag, bool keepstyle)
Play music, either in loop or just one track in the middle of the style music.
Definition: audio.cpp:518
void audio_set_volume(double volume)
Set sound volume to use.
Definition: audio.cpp:591
void audio_stop()
Stop sound.
Definition: audio.cpp:572
void audio_real_init(const QString &soundset_name, const QString &musicset_name, const QString &preferred_plugin_name)
Initialize audio system and autoselect a plugin.
Definition: audio.cpp:253
void audio_restart(const QString &soundset_name, const QString &musicset_name)
Switch soundset.
Definition: audio.cpp:343
void audio_add_plugin(struct audio_plugin *p)
Add a plugin.
Definition: audio.cpp:108
const QVector< QString > * get_soundset_list(const struct option *poption)
Returns a static string vector of soundsets available on the system.
Definition: audio.cpp:84
static bool let_single_track_play
Definition: audio.cpp:53
static bool audio_play_sound_tag(const QString &tag, bool repeat)
Play tag from sound set.
Definition: audio.cpp:477
const QVector< QString > * get_soundplugin_list(const struct option *poption)
Returns a static string vector of all sound plugins available on the system.
Definition: audio.cpp:70
double audio_get_volume()
Get sound volume currently in use.
Definition: audio.cpp:586
static struct section_file * ms_tagfile
Definition: audio.cpp:45
static struct mfcb_data mfcb
const QString audio_get_all_plugin_names()
Returns a string which list all available plugins.
Definition: audio.cpp:630
bool audio_select_plugin(const QString &name)
Choose plugin.
Definition: audio.cpp:126
static int num_plugins_used
Definition: audio.cpp:48
static const QString audiospec_fullname(const QString &audioset_name, bool music)
Returns the filename for the given audio set.
Definition: audio.cpp:189
#define MUSICSPEC_SUFFIX
Definition: audio.cpp:38
#define MAX_NUM_PLUGINS
Definition: audio.cpp:36
static bool switching_usage
Definition: audio.cpp:52
void audio_shutdown()
Call this at end of program only.
Definition: audio.cpp:599
void(* audio_finished_callback)()
Definition: audio.h:17
music_usage
Definition: audio.h:33
@ MU_INGAME
Definition: audio.h:33
@ MU_MENU
Definition: audio.h:33
void audio_none_init()
Initialize.
Definition: audio_none.cpp:53
void audio_sdl_init()
Initialize.
Definition: audio_sdl.cpp:253
bool has_capabilities(const char *us, const char *them)
This routine returns true if all the mandatory capabilities in us appear in them.
Definition: capability.cpp:80
QString sound_plugin_name
QString music_set_name
QString sound_set_name
#define _(String)
Definition: fcintl.h:50
const char * name
Definition: inputfile.cpp:118
#define fc_assert_ret(condition)
Definition: log.h:112
#define log_debug(message,...)
Definition: log.h:65
bool init
Definition: mapimg.cpp:328
client_options * gui_options
Definition: options.cpp:74
#define fc_rand(_size)
Definition: rand.h:16
struct section_file * secfile_load(const QString &filename, bool allow_duplicates)
Create a section file from a file.
Definition: registry.cpp:21
const char * secfile_error()
Returns the last error which occurred in a string.
void secfile_destroy(struct section_file *secfile)
Free a section file.
const char * secfile_lookup_str(const struct section_file *secfile, const char *path,...)
Lookup a string value in the secfile.
Q_GLOBAL_STATIC(QVector< QString >, future_name_translation)
const QStringList & get_data_dirs()
Returns a list of data directory paths, in the order in which they should be searched.
Definition: shared.cpp:533
QString fileinfoname(const QStringList &dirs, const QString &filename)
Returns a filename to access the specified file from a directory by searching all specified directori...
Definition: shared.cpp:661
QVector< QString > * fileinfolist(const QStringList &dirs, const char *suffix)
Returns a string vector storing the filenames in the data directories matching the given suffix.
Definition: shared.cpp:623
bool(* play)(const QString &tag, const QString &path, bool repeat, audio_finished_callback cb)
Definition: audio.h:29
void(* wait)()
Definition: audio.h:26
void(* stop)()
Definition: audio.h:25
void(* set_volume)(double volume)
Definition: audio.h:28
QString descr
Definition: audio.h:22
void(* shutdown)()
Definition: audio.h:24
QString name
Definition: audio.h:21
double(* get_volume)()
Definition: audio.h:27
bool(* init)()
Definition: audio.h:23
bool sound_enable_menu_music
Definition: options.h:116
int sound_effects_volume
Definition: options.h:118
bool sound_enable_effects
Definition: options.h:115
bool sound_enable_game_music
Definition: options.h:117
struct section_file * sfile
Definition: audio.cpp:56
QString tag
Definition: audio.cpp:57
The base class for options.
Definition: options.cpp:209