GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/soundmanager.cpp Lines: 24 271 8.9 %
Date: 2017-11-29 Branches: 11 359 3.1 %

Line Branch Exec Source
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-2017  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
36
#include "resources/loaders/musicloader.h"
37
#include "resources/loaders/soundloader.h"
38
39
#include "resources/resourcemanager/resourcemanager.h"
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
54
2
SoundManager soundManager;
55
56
/**
57
 * This will be set to true, when a music can be freed after a fade out
58
 * Currently used by fadeOutCallBack()
59
 */
60
static bool sFadingOutEnded = false;
61
62
/**
63
 * Callback used at end of fadeout.
64
 * It is called by Mix_MusicFadeFinished().
65
 */
66
static void fadeOutCallBack()
67
{
68
    sFadingOutEnded = true;
69
}
70
71
2
SoundManager::SoundManager() :
72
    ConfigListener(),
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
8
    mCacheSounds(true)
85
{
86
    // This set up our callback function used to
87
    // handle fade outs endings.
88
2
    sFadingOutEnded = false;
89
2
    Mix_HookMusicFinished(&fadeOutCallBack);
90
2
}
91
92
6
SoundManager::~SoundManager()
93
{
94
2
}
95
96
384
void SoundManager::shutdown()
97
{
98
384
    config.removeListeners(this);
99
100
    // Unlink the callback function.
101
384
    Mix_HookMusicFinished(nullptr);
102
103
    CHECKLISTENERS
104
384
}
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
126
void SoundManager::init()
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)
193
        playMusic(mCurrentMusicFile, SkipError_true);
194
}
195
196
void SoundManager::testAudio()
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
259
void SoundManager::info()
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
360
    mCurrentMusicFile = fileName;
361
362
    haltMusic();
363
364
    if (!fileName.empty())
365
    {
366
        mMusic = loadMusic(fileName,
367
            skipError);
368
        if (mMusic != nullptr)
369
            mMusic->play();
370
    }
371
}
372
373
void SoundManager::stopMusic()
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
423
void SoundManager::fadeOutAndPlayMusic(const std::string &fileName,
424
                                       const int ms)
425
{
426
    if (!mPlayMusic)
427
        return;
428
429
    mNextMusicFile = fileName;
430
    fadeOutMusic(ms);
431
}
432
433
void SoundManager::logic()
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
        {
447
            playMusic(mNextMusicFile,
448
                SkipError_false);
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);
489
        if (!mCacheSounds)
490
            sample->decRef();
491
    }
492
}
493
#endif  // DYECMD
494
495
23
void SoundManager::playGuiSound(const std::string &name)
496
{
497
46
    const std::string sound = config.getStringValue(name);
498
23
    if (sound == "(no sound)")
499
        return;
500


138
    playGuiSfx(pathJoin(branding.getStringValue("systemsounds"),
501
46
        std::string(sound).append(".ogg")));
502
}
503
504
23
void SoundManager::playGuiSfx(const std::string &path)
505
{
506
23
    if (!mInstalled ||
507

23
        !mPlayGui ||
508
        path.empty())
509
    {
510
23
        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
530
384
void SoundManager::close()
531
{
532
384
    if (!mInstalled)
533
        return;
534
535
    if (mMusic != nullptr)
536
    {
537
        Mix_HaltMusic();
538
        ResourceManager::decRefDelete(mMusic);
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
549
void SoundManager::haltMusic()
550
{
551
    if (mMusic == nullptr)
552
        return;
553
554
    Mix_HaltMusic();
555
    mMusic->decRef();
556
    mMusic = nullptr;
557
    mCurrentMusicFile.clear();
558
}
559
560
void SoundManager::changeAudio()
561
{
562
    if (mInstalled)
563
        close();
564
    else
565
        init();
566
}
567
568
void SoundManager::volumeOff() const
569
{
570
    if (mInstalled)
571
    {
572
        Mix_VolumeMusic(0);
573
        Mix_Volume(-1, 0);
574
    }
575
}
576
577
void SoundManager::volumeRestore() const
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

6
}