1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
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 "engines/stark/resources/sound.h"
24 
25 #include "audio/decoders/vorbis.h"
26 
27 #include "common/system.h"
28 
29 #include "engines/stark/formats/iss.h"
30 #include "engines/stark/formats/xrc.h"
31 #include "engines/stark/services/archiveloader.h"
32 #include "engines/stark/services/global.h"
33 #include "engines/stark/services/services.h"
34 #include "engines/stark/services/stateprovider.h"
35 
36 namespace Stark {
37 namespace Resources {
38 
~Sound()39 Sound::~Sound() {
40 }
41 
Sound(Object * parent,byte subType,uint16 index,const Common::String & name)42 Sound::Sound(Object *parent, byte subType, uint16 index, const Common::String &name) :
43 		Object(parent, subType, index, name),
44 		_enabled(0),
45 		_looping(false),
46 		_field_64(0),
47 		_loopIndefinitely(false),
48 		_loadFromFile(true),
49 		_maxDuration(0),
50 		_stockSoundType(0),
51 		_field_6C(0),
52 		_soundType(0),
53 		_pan(0),
54 		_volume(0),
55 		_fadeDurationRemaining(0),
56 		_fadeTargetVolume(0.0),
57 		_fadeTargetPan(0.0) {
58 	_type = TYPE;
59 }
60 
makeAudioStream()61 Audio::RewindableAudioStream *Sound::makeAudioStream() {
62 	Common::SeekableReadStream *stream = nullptr;
63 	Audio::RewindableAudioStream *audioStream = nullptr;
64 
65 	// First try the .iss / isn files
66 	if (_loadFromFile) {
67 		stream = StarkArchiveLoader->getExternalFile(_filename, _archiveName);
68 	} else {
69 		stream = StarkArchiveLoader->getFile(_filename, _archiveName);
70 	}
71 
72 	if (stream) {
73 		audioStream = Formats::makeISSStream(stream, DisposeAfterUse::YES);
74 	}
75 
76 	if (!audioStream) {
77 		// The 2 CD version uses Ogg Vorbis
78 		Common::String filename = _filename;
79 		if (_filename.hasSuffix(".iss") || _filename.hasSuffix(".isn")) {
80 			filename = Common::String(_filename.c_str(), _filename.size() - 4) + ".ovs";
81 		}
82 
83 		stream = StarkArchiveLoader->getExternalFile(filename, _archiveName);
84 		if (stream) {
85 #ifdef USE_VORBIS
86 			audioStream = Audio::makeVorbisStream(stream, DisposeAfterUse::YES);
87 #else
88 			warning("Cannot decode sound '%s', Vorbis support is not compiled in", filename.c_str());
89 			delete stream;
90 #endif
91 		}
92 	}
93 
94 	if (!audioStream) {
95 		warning("Unable to load sound '%s'", _filename.c_str());
96 	}
97 
98 	return audioStream;
99 }
100 
getMixerSoundType()101 Audio::Mixer::SoundType Sound::getMixerSoundType() {
102 	switch (_soundType) {
103 	case kSoundTypeVoice:
104 		return Audio::Mixer::kSpeechSoundType;
105 	case kSoundTypeEffect:
106 		return Audio::Mixer::kSFXSoundType;
107 	case kSoundTypeMusic:
108 		return Audio::Mixer::kMusicSoundType;
109 	default:
110 		error("Unknown sound type '%d'", _soundType);
111 	}
112 }
113 
play()114 void Sound::play() {
115 	Audio::RewindableAudioStream *rewindableStream = makeAudioStream();
116 
117 	if (!rewindableStream) {
118 		return;
119 	}
120 
121 	Audio::AudioStream *playStream;
122 	if (_looping) {
123 		playStream = Audio::makeLoopingAudioStream(rewindableStream, 0);
124 	} else {
125 		playStream = rewindableStream;
126 	}
127 
128 	g_system->getMixer()->playStream(getMixerSoundType(), &_handle, playStream, -1,
129 	                                 _volume * Audio::Mixer::kMaxChannelVolume, _pan * 127);
130 }
131 
isPlaying()132 bool Sound::isPlaying() {
133 	return g_system->getMixer()->isSoundHandleActive(_handle);
134 }
135 
stop()136 void Sound::stop() {
137 	g_system->getMixer()->stopHandle(_handle);
138 	_handle = Audio::SoundHandle();
139 }
140 
onPreDestroy()141 void Sound::onPreDestroy() {
142 	Object::onPreDestroy();
143 	stop();
144 }
145 
readData(Formats::XRCReadStream * stream)146 void Sound::readData(Formats::XRCReadStream *stream) {
147 	_filename = stream->readString();
148 	_enabled = stream->readUint32LE();
149 	_looping = stream->readBool();
150 	_field_64 = stream->readUint32LE();
151 	_loopIndefinitely = stream->readBool();
152 	_maxDuration = stream->readUint32LE();
153 	_loadFromFile = stream->readBool(); // Used only in the 4CD version
154 	_stockSoundType = stream->readUint32LE();
155 	_soundName = stream->readString();
156 	_field_6C = stream->readUint32LE();
157 	_soundType = stream->readUint32LE();
158 	_pan = stream->readFloat();
159 	_volume = stream->readFloat();
160 	_archiveName = stream->getArchiveName();
161 }
162 
printData()163 void Sound::printData() {
164 	debug("filename: %s", _filename.c_str());
165 	debug("enabled: %d", _enabled);
166 	debug("looping: %d", _looping);
167 	debug("field_64: %d", _field_64);
168 	debug("loopIndefinitely: %d", _loopIndefinitely);
169 	debug("maxDuration: %d", _maxDuration);
170 	debug("loadFromFile: %d", _loadFromFile);
171 	debug("stockSoundType: %d", _stockSoundType);
172 	debug("soundName: %s", _soundName.c_str());
173 	debug("field_6C: %d", _field_6C);
174 	debug("soundType: %d", _soundType);
175 	debug("pan: %f", _pan);
176 	debug("volume: %f", _volume);
177 }
178 
onGameLoop()179 void Sound::onGameLoop() {
180 	Object::onGameLoop();
181 
182 	if (_looping && !_loopIndefinitely) {
183 		// Automatically stop after the maximum run time has been reached
184 		uint32 elapsedTime = g_system->getMixer()->getSoundElapsedTime(_handle);
185 		if (elapsedTime > _maxDuration) {
186 			stop();
187 		}
188 	}
189 
190 	if (_fadeDurationRemaining > 0 && isPlaying()) {
191 		_volume += (_fadeTargetVolume - _volume) * StarkGlobal->getMillisecondsPerGameloop() / (float) _fadeDurationRemaining;
192 		_pan += (_fadeTargetPan - _pan) * StarkGlobal->getMillisecondsPerGameloop() / (float) _fadeDurationRemaining;
193 
194 		_fadeDurationRemaining -= StarkGlobal->getMillisecondsPerGameloop();
195 
196 		if (_fadeDurationRemaining <= 0) {
197 			_fadeDurationRemaining = 0;
198 
199 			_volume = _fadeTargetVolume;
200 			_pan = _fadeTargetPan;
201 		}
202 
203 		g_system->getMixer()->setChannelVolume(_handle, _volume * Audio::Mixer::kMaxChannelVolume);
204 		g_system->getMixer()->setChannelBalance(_handle, _pan * 127);
205 	}
206 }
207 
getStockSoundType() const208 uint32 Sound::getStockSoundType() const {
209 	return _stockSoundType;
210 }
211 
changeVolumePan(int32 volume,int32 pan,int32 duration)212 void Sound::changeVolumePan(int32 volume, int32 pan, int32 duration) {
213 	if (isPlaying()) {
214 		_fadeDurationRemaining = duration;
215 
216 		if (_fadeDurationRemaining > 0) {
217 			_fadeTargetVolume = volume / 100.0f;
218 			_fadeTargetPan = pan / 100.0f;
219 		} else {
220 			_volume = volume / 100.0f;
221 			_pan = pan / 100.0f;
222 
223 			g_system->getMixer()->setChannelVolume(_handle, _volume * Audio::Mixer::kMaxChannelVolume);
224 			g_system->getMixer()->setChannelBalance(_handle, _pan * 127);
225 		}
226 	} else {
227 		if (_fadeDurationRemaining == 0) {
228 			_volume = volume / 100.0f;
229 			_pan = pan / 100.0f;
230 		}
231 	}
232 }
233 
saveLoadCurrent(ResourceSerializer * serializer)234 void Sound::saveLoadCurrent(ResourceSerializer *serializer) {
235 	bool playing = isPlaying();
236 	serializer->syncAsUint32LE(playing);
237 
238 	if (_subType != kSoundSub3 && playing) {
239 		uint32 elapsed = g_system->getMixer()->getSoundElapsedTime(_handle);
240 		serializer->syncAsUint32LE(elapsed);
241 		serializer->syncAsFloat(_volume);
242 		serializer->syncAsFloat(_pan);
243 		serializer->syncAsUint32LE(_fadeDurationRemaining);
244 		serializer->syncAsFloat(_fadeTargetVolume);
245 		serializer->syncAsFloat(_fadeTargetPan);
246 
247 		if (serializer->isLoading()) {
248 			play();
249 			// TODO: Seek to the "elapsed" position
250 		}
251 	}
252 }
253 
254 } // End of namespace Resources
255 } // End of namespace Stark
256