ManaPlus
soundmanager.cpp
Go to the documentation of this file.
1 /*
2  * The ManaPlus Client
3  * Copyright (C) 2004-2009 The Mana World Development Team
4  * Copyright (C) 2009-2010 The Mana Developers
5  * Copyright (C) 2011-2019 The ManaPlus Developers
6  * Copyright (C) 2019-2021 Andrei Karas
7  *
8  * This file is part of The ManaPlus Client.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program. If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "soundmanager.h"
25 
26 #include "configuration.h"
27 
28 #ifndef DYECMD
29 #include "being/localplayer.h"
30 #endif // DYECMD
31 
32 #include "fs/virtfs/fs.h"
33 
34 #include "resources/sdlmusic.h"
35 #include "resources/soundeffect.h"
36 
39 
41 
42 #ifdef DYECMD
43 #include "utils/cast.h"
44 #endif
45 #include "utils/checkutils.h"
46 #include "utils/sdlmusichelper.h"
47 
48 PRAGMA48(GCC diagnostic push)
49 PRAGMA48(GCC diagnostic ignored "-Wshadow")
50 #include <SDL.h>
51 PRAGMA48(GCC diagnostic pop)
52 
53 #include "debug.h"
54 
56 
61 static bool sFadingOutEnded = false;
62 
67 static void fadeOutCallBack()
68 {
69  sFadingOutEnded = true;
70 }
71 
74  mNextMusicFile(),
75  mInstalled(false),
76  mSfxVolume(100),
77  mMusicVolume(60),
78  mCurrentMusicFile(),
79  mMusic(nullptr),
80  mGuiChannel(-1),
81  mPlayBattle(false),
82  mPlayGui(false),
83  mPlayMusic(false),
84  mFadeoutMusic(true),
85  mCacheSounds(true)
86 {
87  // This set up our callback function used to
88  // handle fade outs endings.
89  sFadingOutEnded = false;
90  Mix_HookMusicFinished(&fadeOutCallBack);
91 }
92 
94 {
95 }
96 
98 {
99  config.removeListeners(this);
100 
101  // Unlink the callback function.
102  Mix_HookMusicFinished(nullptr);
103 
105 }
106 
107 void SoundManager::optionChanged(const std::string &value)
108 {
109  if (value == "playBattleSound")
110  mPlayBattle = config.getBoolValue("playBattleSound");
111  else if (value == "playGuiSound")
112  mPlayGui = config.getBoolValue("playGuiSound");
113  else if (value == "playMusic")
114  mPlayMusic = config.getBoolValue("playMusic");
115  else if (value == "sfxVolume")
116  setSfxVolume(config.getIntValue("sfxVolume"));
117  else if (value == "musicVolume")
118  setMusicVolume(config.getIntValue("musicVolume"));
119  else if (value == "fadeoutmusic")
120  mFadeoutMusic = (config.getIntValue("fadeoutmusic") != 0);
121  else if (value == "uselonglivesounds")
122  mCacheSounds = (config.getIntValue("uselonglivesounds") != 0);
123  else if (value == "parallelAudioChannels")
124  setChannels(config.getIntValue("parallelAudioChannels"));
125 }
126 
128 {
129  // Don't initialize sound engine twice
130  if (mInstalled)
131  return;
132 
133  logger->log1("SoundManager::init() Initializing sound...");
134 
135  mPlayBattle = config.getBoolValue("playBattleSound");
136  mPlayGui = config.getBoolValue("playGuiSound");
137  mPlayMusic = config.getBoolValue("playMusic");
138  mFadeoutMusic = config.getBoolValue("fadeoutmusic");
139  mMusicVolume = config.getIntValue("musicVolume");
140  mSfxVolume = config.getIntValue("sfxVolume");
141  mCacheSounds = (config.getIntValue("uselonglivesounds") != 0);
142 
143  config.addListener("playBattleSound", this);
144  config.addListener("playGuiSound", this);
145  config.addListener("playMusic", this);
146  config.addListener("sfxVolume", this);
147  config.addListener("musicVolume", this);
148  config.addListener("fadeoutmusic", this);
149  config.addListener("uselonglivesounds", this);
150  config.addListener("parallelAudioChannels", this);
151 
152  if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1)
153  {
154  logger->log1("SoundManager::init() Failed to "
155  "initialize audio subsystem");
156  return;
157  }
158 
159  const size_t audioBuffer = 4096;
160  int channels = config.getIntValue("audioChannels");
161  switch (channels)
162  {
163  case 3:
164  channels = 4;
165  break;
166  case 4:
167  channels = 6;
168  break;
169  default:
170  break;
171  }
172 
173  const int res = SDL::MixOpenAudio(config.getIntValue("audioFrequency"),
174  MIX_DEFAULT_FORMAT, channels, audioBuffer);
175 
176  if (res < 0)
177  {
178  logger->log("SoundManager::init Could not initialize audio: %s",
179  SDL_GetError());
180  if (SDL::MixOpenAudio(22010, MIX_DEFAULT_FORMAT, 2, audioBuffer) < 0)
181  return;
182  logger->log("Fallback to stereo audio");
183  }
184 
185  Mix_AllocateChannels(config.getIntValue("parallelAudioChannels"));
186  Mix_VolumeMusic(mMusicVolume);
187  Mix_Volume(-1, mSfxVolume);
188 
189  info();
190 
191  mInstalled = true;
192 
193  if (!mCurrentMusicFile.empty() && mPlayMusic)
195 }
196 
198 {
199  mPlayBattle = config.getBoolValue("playBattleSound");
200  mPlayGui = config.getBoolValue("playGuiSound");
201  mPlayMusic = config.getBoolValue("playMusic");
202  mFadeoutMusic = config.getBoolValue("fadeoutmusic");
203  mMusicVolume = config.getIntValue("musicVolume");
204  mSfxVolume = config.getIntValue("sfxVolume");
205  mCacheSounds = (config.getIntValue("uselonglivesounds") != 0);
206 
207  const size_t audioBuffer = 4096;
208  int channels = config.getIntValue("audioChannels");
209  switch (channels)
210  {
211  case 3:
212  channels = 4;
213  break;
214  case 4:
215  channels = 6;
216  break;
217  default:
218  break;
219  }
220 
221  SDL_AudioSpec desired;
222  SDL_AudioSpec actual;
223 
224  desired.freq = config.getIntValue("audioFrequency");
225  desired.format = MIX_DEFAULT_FORMAT;
226  desired.channels = CAST_U8(channels);
227  desired.samples = audioBuffer;
228  desired.callback = nullptr;
229  desired.userdata = nullptr;
230 
231  if (SDL_OpenAudio(&desired, &actual) < 0)
232  {
233  logger->log("SoundManager::testAudio error: %s",
234  SDL_GetError());
235  return;
236  }
237  if (desired.freq != actual.freq)
238  {
239  logger->log("SoundManager::testAudio frequence: %d -> %d",
240  actual.freq, desired.freq);
241  }
242  if (desired.format != actual.format)
243  {
244  logger->log("SoundManager::testAudio format: %d -> %d",
245  actual.format, desired.format);
246  }
247  if (desired.channels != actual.channels)
248  {
249  logger->log("SoundManager::testAudio channels: %d -> %d",
250  actual.channels, desired.channels);
251  }
252  if (desired.samples != actual.samples)
253  {
254  logger->log("SoundManager::testAudio samples: %d -> %d",
255  actual.samples, desired.samples);
256  }
257  SDL_CloseAudio();
258 }
259 
261 {
262  SDL_version compiledVersion;
263  const char *format = "Unknown";
264  int rate = 0;
265  uint16_t audioFormat = 0;
266  int channels = 0;
267 
268  MIX_VERSION(&compiledVersion)
269  const SDL_version *const linkedVersion = Mix_Linked_Version();
270 
271 #ifdef USE_SDL2
272  const char *driver = SDL_GetCurrentAudioDriver();
273 #else // USE_SDL2
274  char driver[40] = "Unknown";
275  SDL_AudioDriverName(driver, 40);
276 #endif // USE_SDL2
277 
278  Mix_QuerySpec(&rate, &audioFormat, &channels);
279  switch (audioFormat)
280  {
281  case AUDIO_U8:
282  format = "U8";
283  break;
284  case AUDIO_S8:
285  format = "S8"; break;
286  case AUDIO_U16LSB:
287  format = "U16LSB";
288  break;
289  case AUDIO_S16LSB:
290  format = "S16LSB";
291  break;
292  case AUDIO_U16MSB:
293  format = "U16MSB";
294  break;
295  case AUDIO_S16MSB:
296  format = "S16MSB";
297  break;
298  default: break;
299  }
300 
301  logger->log("SoundManager::info() SDL_mixer: %i.%i.%i (compiled)",
302  compiledVersion.major,
303  compiledVersion.minor,
304  compiledVersion.patch);
305  if (linkedVersion != nullptr)
306  {
307  logger->log("SoundManager::info() SDL_mixer: %i.%i.%i (linked)",
308  linkedVersion->major,
309  linkedVersion->minor,
310  linkedVersion->patch);
311  }
312  else
313  {
314  logger->log1("SoundManager::info() SDL_mixer: unknown");
315  }
316  logger->log("SoundManager::info() Driver: %s", driver);
317  logger->log("SoundManager::info() Format: %s", format);
318  logger->log("SoundManager::info() Rate: %i", rate);
319  logger->log("SoundManager::info() Channels: %i", channels);
320 }
321 
322 void SoundManager::setMusicVolume(const int volume)
323 {
324  mMusicVolume = volume;
325 
326  if (mInstalled)
327  Mix_VolumeMusic(mMusicVolume);
328 }
329 
330 void SoundManager::setSfxVolume(const int volume)
331 {
332  mSfxVolume = volume;
333 
334  if (mInstalled)
335  Mix_Volume(-1, mSfxVolume);
336 }
337 
338 static SDLMusic *loadMusic(const std::string &fileName,
339  const SkipError skipError)
340 {
341  const std::string path = pathJoin(paths.getStringValue("music"),
342  fileName);
343  if (!VirtFs::exists(path))
344  {
345  if (skipError == SkipError_false)
346  reportAlways("Music file not found: %s", fileName.c_str())
347  return nullptr;
348  }
349  return Loader::getMusic(path);
350 }
351 
352 void SoundManager::playMusic(const std::string &fileName,
353  const SkipError skipError)
354 {
355  if (!mInstalled || !mPlayMusic)
356  return;
357 
359  return;
360 
362 
363  haltMusic();
364 
365  if (!fileName.empty())
366  {
368  skipError);
369  if (mMusic != nullptr)
370  mMusic->play(-1, 0);
371  }
372 }
373 
375 {
376  haltMusic();
377 }
378 
379 /*
380 void SoundManager::fadeInMusic(const std::string &fileName, const int ms)
381 {
382  mCurrentMusicFile = fileName;
383 
384  if (!mInstalled || !mPlayMusic)
385  return;
386 
387  haltMusic();
388 
389  if (!fileName.empty())
390  {
391  mMusic = loadMusic(fileName);
392  if (mMusic)
393  mMusic->play(-1, ms);
394  }
395 }
396 */
397 
398 void SoundManager::fadeOutMusic(const int ms)
399 {
400  if (!mPlayMusic)
401  return;
402 
403  mCurrentMusicFile.clear();
404 
405  if (!mInstalled)
406  return;
407 
408  logger->log("SoundManager::fadeOutMusic() Fading-out (%i ms)", ms);
409 
410  if ((mMusic != nullptr) && mFadeoutMusic)
411  {
412  Mix_FadeOutMusic(ms);
413  // Note: The fadeOutCallBack handler will take care about freeing
414  // the music file at fade out ending.
415  }
416  else
417  {
418  sFadingOutEnded = true;
419  if (!mFadeoutMusic)
420  haltMusic();
421  }
422 }
423 
425  const int ms)
426 {
427  if (!mPlayMusic)
428  return;
429 
431  fadeOutMusic(ms);
432 }
433 
435 {
436  BLOCK_START("SoundManager::logic")
437  if (sFadingOutEnded)
438  {
439  if (mMusic != nullptr)
440  {
441  mMusic->decRef();
442  mMusic = nullptr;
443  }
444  sFadingOutEnded = false;
445 
446  if (!mNextMusicFile.empty())
447  {
450  mNextMusicFile.clear();
451  }
452  }
453  BLOCK_END("SoundManager::logic")
454 }
455 
456 #ifdef DYECMD
457 void SoundManager::playSfx(const std::string &path A_UNUSED,
458  const int x A_UNUSED,
459  const int y A_UNUSED) const
460 {
461 }
462 #else // DYECMD
463 void SoundManager::playSfx(const std::string &path,
464  const int x, const int y) const
465 {
466  if (!mInstalled || path.empty() || !mPlayBattle)
467  return;
468 
469  std::string tmpPath = pathJoin(paths.getStringValue("sfx"), path);
470  SoundEffect *const sample = Loader::getSoundEffect(tmpPath);
471  if (sample != nullptr)
472  {
473  logger->log("SoundManager::playSfx() Playing: %s", path.c_str());
474  int vol = 120;
475  if ((localPlayer != nullptr) && (x > 0 || y > 0))
476  {
477  int dx = localPlayer->getTileX() - x;
478  int dy = localPlayer->getTileY() - y;
479  if (dx < 0)
480  dx = -dx;
481  if (dy < 0)
482  dy = -dy;
483  const int dist = dx > dy ? dx : dy;
484  if (dist * 8 > vol)
485  return;
486 
487  vol -= dist * 8;
488  }
489  sample->play(0, vol, -1);
490  if (!mCacheSounds)
491  sample->decRef();
492  }
493 }
494 #endif // DYECMD
495 
496 void SoundManager::playGuiSound(const std::string &name)
497 {
498  const std::string sound = config.getStringValue(name);
499  if (sound == "(no sound)")
500  return;
501  playGuiSfx(pathJoin(branding.getStringValue("systemsounds"),
502  std::string(sound).append(".ogg")));
503 }
504 
505 void SoundManager::playGuiSfx(const std::string &path)
506 {
507  if (!mInstalled ||
508  !mPlayGui ||
509  path.empty())
510  {
511  return;
512  }
513 
514  std::string tmpPath;
515  if (path.compare(0, 4, "sfx/") == 0)
516  tmpPath = path;
517  else
518  tmpPath = pathJoin(paths.getValue("sfx", "sfx"), path);
519  SoundEffect *const sample = Loader::getSoundEffect(tmpPath);
520  if (sample != nullptr)
521  {
522  logger->log("SoundManager::playGuiSfx() Playing: %s", path.c_str());
523  const int ret = static_cast<int>(sample->play(0, 120, mGuiChannel));
524  if (ret != -1)
525  mGuiChannel = ret;
526  if (!mCacheSounds)
527  sample->decRef();
528  }
529 }
530 
532 {
533  if (!mInstalled)
534  return;
535 
536  if (mMusic != nullptr)
537  {
538  Mix_HaltMusic();
540  mMusic = nullptr;
541  mCurrentMusicFile.clear();
542  }
543 
544  logger->log1("SoundManager::close() Shutting down sound...");
545  Mix_CloseAudio();
546 
547  mInstalled = false;
548 }
549 
551 {
552  if (mMusic == nullptr)
553  return;
554 
555  Mix_HaltMusic();
556  mMusic->decRef();
557  mMusic = nullptr;
558  mCurrentMusicFile.clear();
559 }
560 
562 {
563  if (mInstalled)
564  close();
565  else
566  init();
567 }
568 
570 {
571  if (mInstalled)
572  {
573  Mix_VolumeMusic(0);
574  Mix_Volume(-1, 0);
575  }
576 }
577 
579 {
580  if (mInstalled)
581  {
582  Mix_VolumeMusic(mMusicVolume);
583  Mix_Volume(-1, mSfxVolume);
584  }
585 }
586 
587 void SoundManager::setChannels(const int channels) const
588 {
589  if (mInstalled)
590  Mix_AllocateChannels(channels);
591 }
#define CAST_U8
Definition: cast.h:27
#define reportAlways(...)
Definition: checkutils.h:253
int getTileX() const
Definition: being.h:168
int getTileY() const
Definition: being.h:174
std::string getValue(const std::string &key, const std::string &deflt) const
bool getBoolValue(const std::string &key) const
std::string getStringValue(const std::string &key) const
void addListener(const std::string &key, ConfigListener *const listener)
void removeListeners(ConfigListener *const listener)
int getIntValue(const std::string &key) const
void log(const char *const log_text,...)
Definition: logger.cpp:269
void log1(const char *const log_text)
Definition: logger.cpp:238
virtual void decRef()
Definition: resource.cpp:50
bool play(const int loops, const int fadeIn) const
Definition: sdlmusic.cpp:64
bool play(const int loops, const int volume, const int channel) const
Definition: soundeffect.cpp:33
SDLMusic * mMusic
Definition: soundmanager.h:168
bool mFadeoutMusic
Definition: soundmanager.h:173
std::string mCurrentMusicFile
Definition: soundmanager.h:167
void optionChanged(const std::string &value)
void fadeOutAndPlayMusic(const std::string &fileName, const int ms)
void playGuiSound(const std::string &name)
void playGuiSfx(const std::string &path)
void setChannels(const int channels) const
void volumeOff() const
void playSfx(const std::string &path, const int x, const int y) const
void playMusic(const std::string &fileName, const SkipError skipError)
void volumeRestore() const
void changeAudio()
std::string mNextMusicFile
Definition: soundmanager.h:160
static void info()
void fadeOutMusic(const int ms)
void setSfxVolume(const int volume)
void setMusicVolume(const int volume)
Configuration config
Configuration paths
Configuration branding
#define PRAGMA48(str)
Definition: localconsts.h:199
#define nullptr
Definition: localconsts.h:45
#define A_UNUSED
Definition: localconsts.h:160
#define CHECKLISTENERS
Definition: localconsts.h:277
LocalPlayer * localPlayer
Logger * logger
Definition: logger.cpp:89
SoundEffect * getSoundEffect(const std::string &idPath)
Definition: soundloader.cpp:71
SDLMusic * getMusic(const std::string &idPath)
Definition: musicloader.cpp:74
void decRefDelete(Resource *const res)
int MixOpenAudio(const int frequency, const uint16_t format, const int nchannels, const int chunksize)
bool exists(std::string name)
Definition: fs.cpp:124
#define BLOCK_END(name)
Definition: perfomance.h:80
#define BLOCK_START(name)
Definition: perfomance.h:79
const bool SkipError_false
Definition: skiperror.h:30
const bool SkipError_true
Definition: skiperror.h:30
bool SkipError
Definition: skiperror.h:30
SoundManager soundManager
static void fadeOutCallBack()
static SDLMusic * loadMusic(const std::string &fileName, const SkipError skipError)
static bool sFadingOutEnded
std::string pathJoin(std::string str1, const std::string &str2)
std::string fileName
Definition: testmain.cpp:39