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