GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/soundmanager.cpp Lines: 24 270 8.9 %
Date: 2021-03-17 Branches: 12 293 4.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-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
37
#include "resources/loaders/musicloader.h"
38
#include "resources/loaders/soundloader.h"
39
40
#include "resources/resourcemanager/resourcemanager.h"
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
55
1
SoundManager soundManager;
56
57
/**
58
 * This will be set to true, when a music can be freed after a fade out
59
 * Currently used by fadeOutCallBack()
60
 */
61
static bool sFadingOutEnded = false;
62
63
/**
64
 * Callback used at end of fadeout.
65
 * It is called by Mix_MusicFadeFinished().
66
 */
67
static void fadeOutCallBack()
68
{
69
    sFadingOutEnded = true;
70
}
71
72
1
SoundManager::SoundManager() :
73
    ConfigListener(),
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
4
    mCacheSounds(true)
86
{
87
    // This set up our callback function used to
88
    // handle fade outs endings.
89
1
    sFadingOutEnded = false;
90
1
    Mix_HookMusicFinished(&fadeOutCallBack);
91
1
}
92
93
3
SoundManager::~SoundManager()
94
{
95
1
}
96
97
215
void SoundManager::shutdown()
98
{
99
215
    config.removeListeners(this);
100
101
    // Unlink the callback function.
102
215
    Mix_HookMusicFinished(nullptr);
103
104
    CHECKLISTENERS
105
215
}
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
127
void SoundManager::init()
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)
194
        playMusic(mCurrentMusicFile, SkipError_true);
195
}
196
197
void SoundManager::testAudio()
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
260
void SoundManager::info()
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
358
    if (mCurrentMusicFile == fileName)
359
        return;
360
361
    mCurrentMusicFile = fileName;
362
363
    haltMusic();
364
365
    if (!fileName.empty())
366
    {
367
        mMusic = loadMusic(fileName,
368
            skipError);
369
        if (mMusic != nullptr)
370
            mMusic->play(-1, 0);
371
    }
372
}
373
374
void SoundManager::stopMusic()
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
424
void SoundManager::fadeOutAndPlayMusic(const std::string &fileName,
425
                                       const int ms)
426
{
427
    if (!mPlayMusic)
428
        return;
429
430
    mNextMusicFile = fileName;
431
    fadeOutMusic(ms);
432
}
433
434
void SoundManager::logic()
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
        {
448
            playMusic(mNextMusicFile,
449
                SkipError_false);
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
12
void SoundManager::playGuiSound(const std::string &name)
497
{
498
24
    const std::string sound = config.getStringValue(name);
499
12
    if (sound == "(no sound)")
500
        return;
501

72
    playGuiSfx(pathJoin(branding.getStringValue("systemsounds"),
502

36
        std::string(sound).append(".ogg")));
503
}
504
505
12
void SoundManager::playGuiSfx(const std::string &path)
506
{
507

24
    if (!mInstalled ||
508

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

3
}