1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "ultima/ultima4/sound/sound.h"
24 #include "ultima/ultima4/core/config.h"
25 #include "ultima/ultima4/sound/music.h"
26 #include "ultima/ultima4/core/settings.h"
27 #include "ultima/ultima4/core/utils.h"
28 #include "audio/audiostream.h"
29 #include "audio/decoders/flac.h"
30 #include "audio/decoders/mp3.h"
31 #include "audio/decoders/voc.h"
32 #include "audio/decoders/vorbis.h"
33 #include "audio/decoders/wave.h"
34 #include "common/file.h"
35 
36 namespace Ultima {
37 namespace Ultima4 {
38 
39 SoundManager *g_sound;
40 
soundPlay(Sound sound,bool onlyOnce,int specificDurationInTicks)41 void soundPlay(Sound sound, bool onlyOnce, int specificDurationInTicks) {
42 	g_sound->play(sound, onlyOnce, specificDurationInTicks);
43 }
44 
soundStop(int channel)45 void soundStop(int channel) {
46 	g_sound->stop(channel);
47 }
48 
49 /*-------------------------------------------------------------------*/
50 
SoundManager(Audio::Mixer * mixer)51 SoundManager::SoundManager(Audio::Mixer *mixer) : _mixer(mixer) {
52 	g_sound = this;
53 
54 	// Load sound track filenames from xml config file
55 	const Config *config = Config::getInstance();
56 	_soundFilenames.reserve(SOUND_MAX);
57 	_sounds.resize(SOUND_MAX);
58 
59 	Std::vector<ConfigElement> soundConfs = config->getElement("sound").getChildren();
60 	Std::vector<ConfigElement>::const_iterator i = soundConfs.begin();
61 	Std::vector<ConfigElement>::const_iterator theEnd = soundConfs.end();
62 	for (; i != theEnd; ++i) {
63 		if (i->getName() != "track")
64 			continue;
65 
66 		_soundFilenames.push_back(i->getString("file"));
67 	}
68 }
69 
~SoundManager()70 SoundManager::~SoundManager() {
71 	g_sound = nullptr;
72 	_mixer->stopHandle(_soundHandle);
73 
74 	for (uint idx = 0; idx < _sounds.size(); ++idx)
75 		delete _sounds[idx];
76 }
77 
load(Sound sound)78 bool SoundManager::load(Sound sound) {
79 	assertMsg(sound < SOUND_MAX, "Attempted to load an invalid sound");
80 
81 	if (_sounds[sound] == nullptr) {
82 		Common::String pathname("data/sound/" + _soundFilenames[sound]);
83 		Common::String basename = pathname.substr(pathname.findLastOf("/") + 1);
84 		if (!basename.empty())
85 			return load_sys(sound, pathname);
86 	}
87 
88 	return true;
89 }
90 
play(Sound sound,bool onlyOnce,int specificDurationInTicks)91 void SoundManager::play(Sound sound, bool onlyOnce, int specificDurationInTicks) {
92 	assertMsg(sound < SOUND_MAX, "Attempted to play an invalid sound");
93 
94 	if (_sounds[sound] == nullptr) {
95 		if (!load(sound)) {
96 			return;
97 		}
98 	}
99 
100 	play_sys(sound, onlyOnce, specificDurationInTicks);
101 }
102 
stop(int channel)103 void SoundManager::stop(int channel) {
104 	stop_sys(channel);
105 }
106 
load_sys(Sound sound,const Common::String & filename)107 bool SoundManager::load_sys(Sound sound, const Common::String &filename) {
108 	Common::File f;
109 	if (!f.open(filename))
110 		return false;
111 
112 	Audio::SeekableAudioStream *audioStream = nullptr;
113 
114 #ifdef USE_FLAC
115 	if (filename.hasSuffixIgnoreCase(".fla"))
116 		audioStream = Audio::makeFLACStream(f.readStream(f.size()), DisposeAfterUse::YES);
117 #endif
118 #ifdef USE_VORBIS
119 	if (filename.hasSuffixIgnoreCase(".ogg"))
120 		audioStream = Audio::makeVorbisStream(f.readStream(f.size()), DisposeAfterUse::YES);
121 #endif
122 #ifdef USE_MAD
123 	if (filename.hasSuffixIgnoreCase(".mp3"))
124 		audioStream = Audio::makeMP3Stream(f.readStream(f.size()), DisposeAfterUse::YES);
125 #endif
126 	if (filename.hasSuffixIgnoreCase(".wav"))
127 		audioStream = Audio::makeWAVStream(f.readStream(f.size()), DisposeAfterUse::YES);
128 	if (filename.hasSuffixIgnoreCase(".voc"))
129 		audioStream = Audio::makeVOCStream(f.readStream(f.size()), DisposeAfterUse::YES);
130 
131 	_sounds[sound] = audioStream;
132 	return audioStream != nullptr;
133 }
134 
play_sys(Sound sound,bool onlyOnce,int specificDurationMilli)135 void SoundManager::play_sys(Sound sound, bool onlyOnce, int specificDurationMilli) {
136 	// Don't allow once only sounds if another sound is already playing
137 	if (onlyOnce && _mixer->isSoundHandleActive(_soundHandle))
138 		return;
139 
140 	// Ensure sound is stopped, and rewinded
141 	_mixer->stopHandle(_soundHandle);
142 	_sounds[sound]->rewind();
143 
144 	if (specificDurationMilli == -1) {
145 		// Play a single sound effect
146 		_mixer->playStream(Audio::Mixer::kSFXSoundType,
147 			&_soundHandle, _sounds[sound], -1, Audio::Mixer::kMaxChannelVolume,
148 			0, DisposeAfterUse::NO);
149 	} else {
150 		// Play a sound effect, looping if necessary, for a given duration
151 		// TODO: Better handle cases where a number of loops won't fit
152 		// exactly to give a desired duration
153 		int duration = _sounds[sound]->getLength().msecs();
154 		int loops = (specificDurationMilli + duration - 1) / duration;
155 		assert(loops >= 0);
156 
157 		Audio::AudioStream *audioStream = new Audio::LoopingAudioStream(
158 			_sounds[sound], loops, DisposeAfterUse::NO);
159 
160 		_mixer->playStream(Audio::Mixer::kSFXSoundType,
161 			&_soundHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume,
162 			0, DisposeAfterUse::NO);
163 	}
164 }
165 
stop_sys(int channel)166 void SoundManager::stop_sys(int channel) {
167 	// Channel 1 is the dummy channel number used for sound effects
168 	if (channel == 1)
169 		_mixer->stopHandle(_soundHandle);
170 }
171 
172 } // End of namespace Ultima4
173 } // End of namespace Ultima
174