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 
24 #include "kyra/sound/sound.h"
25 #include "kyra/resource/resource.h"
26 
27 #include "audio/mixer.h"
28 #include "audio/audiostream.h"
29 
30 #include "audio/decoders/flac.h"
31 #include "audio/decoders/mp3.h"
32 #include "audio/decoders/raw.h"
33 #include "audio/decoders/voc.h"
34 #include "audio/decoders/vorbis.h"
35 
36 namespace Kyra {
37 
Sound(KyraEngine_v1 * vm,Audio::Mixer * mixer)38 Sound::Sound(KyraEngine_v1 *vm, Audio::Mixer *mixer)
39 	: _vm(vm), _mixer(mixer), _soundChannels(), _musicEnabled(1),
40 	  _sfxEnabled(true) {
41 }
42 
~Sound()43 Sound::~Sound() {
44 }
45 
getSfxType() const46 Sound::kType Sound::getSfxType() const {
47 	return getMusicType();
48 }
49 
isPlaying() const50 bool Sound::isPlaying() const {
51 	return false;
52 }
53 
isVoicePresent(const char * file) const54 bool Sound::isVoicePresent(const char *file) const {
55 	Common::String filename;
56 
57 	for (int i = 0; _supportedCodecs[i].fileext; ++i) {
58 		filename = file;
59 		filename += _supportedCodecs[i].fileext;
60 
61 		if (_vm->resource()->exists(filename.c_str()))
62 			return true;
63 	}
64 
65 	return false;
66 }
67 
voicePlay(const char * file,Audio::SoundHandle * handle,uint8 volume,uint8 priority,bool isSfx)68 int32 Sound::voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) {
69 	Audio::SeekableAudioStream *audioStream = getVoiceStream(file);
70 
71 	if (!audioStream) {
72 		return 0;
73 	}
74 
75 	int playTime = audioStream->getLength().msecs();
76 	playVoiceStream(audioStream, handle, volume, priority, isSfx);
77 	return playTime;
78 }
79 
getVoiceStream(const char * file) const80 Audio::SeekableAudioStream *Sound::getVoiceStream(const char *file) const {
81 	Common::String filename;
82 
83 	Audio::SeekableAudioStream *audioStream = 0;
84 	for (int i = 0; _supportedCodecs[i].fileext; ++i) {
85 		filename = file;
86 		filename += _supportedCodecs[i].fileext;
87 
88 		Common::SeekableReadStream *stream = _vm->resource()->createReadStream(filename);
89 		if (!stream)
90 			continue;
91 
92 		audioStream = _supportedCodecs[i].streamFunc(stream, DisposeAfterUse::YES);
93 		break;
94 	}
95 
96 	if (!audioStream) {
97 		warning("Couldn't load sound file '%s'", file);
98 		return 0;
99 	} else {
100 		return audioStream;
101 	}
102 }
103 
playVoiceStream(Audio::AudioStream * stream,Audio::SoundHandle * handle,uint8 volume,uint8 priority,bool isSfx)104 bool Sound::playVoiceStream(Audio::AudioStream *stream, Audio::SoundHandle *handle, uint8 volume, uint8 priority, bool isSfx) {
105 	int h = 0;
106 	while (h < kNumChannelHandles && _mixer->isSoundHandleActive(_soundChannels[h].handle))
107 		++h;
108 
109 	if (h >= kNumChannelHandles) {
110 		h = 0;
111 		while (h < kNumChannelHandles && _soundChannels[h].priority > priority)
112 			++h;
113 		if (h < kNumChannelHandles)
114 			voiceStop(&_soundChannels[h].handle);
115 	}
116 
117 	if (h >= kNumChannelHandles) {
118 		// When we run out of handles we need to destroy the stream object,
119 		// this is to avoid memory leaks in some scenes where too many sfx
120 		// are started.
121 		// See bug #5886 "LOL-CD: Memory leak in caves level 3".
122 		delete stream;
123 		return false;
124 	}
125 
126 	_mixer->playStream(isSfx ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType, &_soundChannels[h].handle, stream, -1, volume);
127 	_soundChannels[h].priority = priority;
128 	if (handle)
129 		*handle = _soundChannels[h].handle;
130 
131 	return true;
132 }
133 
voiceStop(const Audio::SoundHandle * handle)134 void Sound::voiceStop(const Audio::SoundHandle *handle) {
135 	if (!handle) {
136 		for (int h = 0; h < kNumChannelHandles; ++h) {
137 			if (_mixer->isSoundHandleActive(_soundChannels[h].handle))
138 				_mixer->stopHandle(_soundChannels[h].handle);
139 		}
140 	} else {
141 		_mixer->stopHandle(*handle);
142 	}
143 }
144 
voiceIsPlaying(const Audio::SoundHandle * handle) const145 bool Sound::voiceIsPlaying(const Audio::SoundHandle *handle) const {
146 	if (!handle) {
147 		for (int h = 0; h < kNumChannelHandles; ++h) {
148 			if (_mixer->isSoundHandleActive(_soundChannels[h].handle))
149 				return true;
150 		}
151 	} else {
152 		return _mixer->isSoundHandleActive(*handle);
153 	}
154 
155 	return false;
156 }
157 
allVoiceChannelsPlaying() const158 bool Sound::allVoiceChannelsPlaying() const {
159 	for (int i = 0; i < kNumChannelHandles; ++i)
160 		if (!_mixer->isSoundHandleActive(_soundChannels[i].handle))
161 			return false;
162 	return true;
163 }
164 
165 #pragma mark -
166 
MixedSoundDriver(KyraEngine_v1 * vm,Audio::Mixer * mixer,Sound * music,Sound * sfx)167 MixedSoundDriver::MixedSoundDriver(KyraEngine_v1 *vm, Audio::Mixer *mixer, Sound *music, Sound *sfx)
168 	: Sound(vm, mixer), _music(music), _sfx(sfx) {
169 }
170 
~MixedSoundDriver()171 MixedSoundDriver::~MixedSoundDriver() {
172 	delete _music;
173 	delete _sfx;
174 }
175 
getMusicType() const176 Sound::kType MixedSoundDriver::getMusicType() const {
177 	return _music->getMusicType();
178 }
179 
getSfxType() const180 Sound::kType MixedSoundDriver::getSfxType() const {
181 	return _sfx->getSfxType();
182 }
183 
init()184 bool MixedSoundDriver::init() {
185 	return (_music->init() && _sfx->init());
186 }
187 
process()188 void MixedSoundDriver::process() {
189 	_music->process();
190 	_sfx->process();
191 }
192 
updateVolumeSettings()193 void MixedSoundDriver::updateVolumeSettings() {
194 	_music->updateVolumeSettings();
195 	_sfx->updateVolumeSettings();
196 }
197 
initAudioResourceInfo(int set,void * info)198 void MixedSoundDriver::initAudioResourceInfo(int set, void *info) {
199 	_music->initAudioResourceInfo(set, info);
200 	_sfx->initAudioResourceInfo(set, info);
201 }
202 
selectAudioResourceSet(int set)203 void MixedSoundDriver::selectAudioResourceSet(int set) {
204 	_music->selectAudioResourceSet(set);
205 	_sfx->selectAudioResourceSet(set);
206 }
207 
hasSoundFile(uint file) const208 bool MixedSoundDriver::hasSoundFile(uint file) const {
209 	return _music->hasSoundFile(file) && _sfx->hasSoundFile(file);
210 }
211 
loadSoundFile(uint file)212 void MixedSoundDriver::loadSoundFile(uint file) {
213 	_music->loadSoundFile(file);
214 	_sfx->loadSoundFile(file);
215 }
216 
loadSoundFile(Common::String file)217 void MixedSoundDriver::loadSoundFile(Common::String file) {
218 	_music->loadSoundFile(file);
219 	_sfx->loadSoundFile(file);
220 }
221 
loadSfxFile(Common::String file)222 void MixedSoundDriver::loadSfxFile(Common::String file) {
223 	_sfx->loadSoundFile(file);
224 }
225 
playTrack(uint8 track)226 void MixedSoundDriver::playTrack(uint8 track) {
227 	_music->playTrack(track);
228 }
229 
haltTrack()230 void MixedSoundDriver::haltTrack() {
231 	_music->haltTrack();
232 }
233 
isPlaying() const234 bool MixedSoundDriver::isPlaying() const {
235 	return _music->isPlaying() | _sfx->isPlaying();
236 }
237 
playSoundEffect(uint16 track,uint8 volume)238 void MixedSoundDriver::playSoundEffect(uint16 track, uint8 volume) {
239 	_sfx->playSoundEffect(track, volume);
240 }
241 
stopAllSoundEffects()242 void MixedSoundDriver::stopAllSoundEffects() {
243 	_sfx->stopAllSoundEffects();
244 }
245 
beginFadeOut()246 void MixedSoundDriver::beginFadeOut() {
247 	_music->beginFadeOut();
248 }
249 
pause(bool paused)250 void MixedSoundDriver::pause(bool paused) {
251 	_music->pause(paused);
252 	_sfx->pause(paused);
253 }
254 
255 #pragma mark -
256 
snd_playTheme(int file,int track)257 void KyraEngine_v1::snd_playTheme(int file, int track) {
258 	if (_curMusicTheme == file)
259 		return;
260 
261 	_curSfxFile = _curMusicTheme = file;
262 	_sound->loadSoundFile(_curMusicTheme);
263 
264 	// Kyrandia 2 uses a special file for
265 	// MIDI sound effects, so we load
266 	// this here
267 	if (_flags.gameID == GI_KYRA2)
268 		_sound->loadSfxFile("K2SFX");
269 
270 	if (track != -1)
271 		_sound->playTrack(track);
272 }
273 
snd_playSoundEffect(int track,int volume)274 void KyraEngine_v1::snd_playSoundEffect(int track, int volume) {
275 	_sound->playSoundEffect(track, volume);
276 }
277 
snd_playWanderScoreViaMap(int command,int restart)278 void KyraEngine_v1::snd_playWanderScoreViaMap(int command, int restart) {
279 	if (restart)
280 		_lastMusicCommand = -1;
281 
282 	// no track mapping given
283 	// so don't do anything here
284 	if (!_trackMap || !_trackMapSize)
285 		return;
286 
287 	//if (!_disableSound) {
288 	//	XXX
289 	//}
290 
291 	if (_flags.platform == Common::kPlatformDOS || _flags.platform == Common::kPlatformMacintosh) {
292 		assert(command * 2 + 1 < _trackMapSize);
293 		if (_curMusicTheme != _trackMap[command * 2]) {
294 			if (_trackMap[command * 2] != -1 && _trackMap[command * 2] != -2)
295 				snd_playTheme(_trackMap[command * 2], -1);
296 		}
297 
298 		if (command != 1) {
299 			if (_lastMusicCommand != command) {
300 				_sound->haltTrack();
301 				_sound->playTrack(_trackMap[command * 2 + 1]);
302 			}
303 		} else {
304 			_sound->beginFadeOut();
305 		}
306 	} else if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
307 		if (command == -1) {
308 			_sound->haltTrack();
309 		} else {
310 			assert(command * 2 + 1 < _trackMapSize);
311 			if (_trackMap[command * 2] != -2 && command != _lastMusicCommand) {
312 				_sound->haltTrack();
313 				_sound->playTrack(command);
314 			}
315 		}
316 	} else if (_flags.platform == Common::kPlatformAmiga) {
317 		if (_curMusicTheme != 1)
318 			snd_playTheme(1, -1);
319 
320 		assert(command < _trackMapSize);
321 		if (_trackMap[_lastMusicCommand] != _trackMap[command])
322 			_sound->playTrack(_trackMap[command]);
323 	}
324 
325 	_lastMusicCommand = command;
326 }
327 
snd_stopVoice()328 void KyraEngine_v1::snd_stopVoice() {
329 	_sound->voiceStop(&_speechHandle);
330 }
331 
snd_voiceIsPlaying()332 bool KyraEngine_v1::snd_voiceIsPlaying() {
333 	return _sound->voiceIsPlaying(&_speechHandle);
334 }
335 
336 // static res
337 
338 namespace {
339 
340 // A simple wrapper to create VOC streams the way like creating MP3, OGG/Vorbis and FLAC streams.
341 // Possible TODO: Think of making this complete and moving it to sound/voc.cpp ?
makeVOCStream(Common::SeekableReadStream * stream,DisposeAfterUse::Flag disposeAfterUse)342 Audio::SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
343 	Audio::SeekableAudioStream *as = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, disposeAfterUse);
344 	return as;
345 }
346 
347 } // end of anonymous namespace
348 
349 const Sound::SpeechCodecs Sound::_supportedCodecs[] = {
350 	{ ".VOC", makeVOCStream },
351 	{ "", makeVOCStream },
352 
353 #ifdef USE_MAD
354 	{ ".VO3", Audio::makeMP3Stream },
355 	{ ".MP3", Audio::makeMP3Stream },
356 #endif // USE_MAD
357 
358 #ifdef USE_VORBIS
359 	{ ".VOG", Audio::makeVorbisStream },
360 	{ ".OGG", Audio::makeVorbisStream },
361 #endif // USE_VORBIS
362 
363 #ifdef USE_FLAC
364 	{ ".VOF", Audio::makeFLACStream },
365 	{ ".FLA", Audio::makeFLACStream },
366 #endif // USE_FLAC
367 
368 	{ 0, 0 }
369 };
370 
371 } // End of namespace Kyra
372