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 "bladerunner/music.h"
24 
25 #include "bladerunner/audio_mixer.h"
26 #include "bladerunner/aud_stream.h"
27 #include "bladerunner/bladerunner.h"
28 #include "bladerunner/game_info.h"
29 #include "bladerunner/savefile.h"
30 #include "bladerunner/game_constants.h"
31 
32 #include "common/timer.h"
33 namespace BladeRunner {
34 
Music(BladeRunnerEngine * vm)35 Music::Music(BladeRunnerEngine *vm) {
36 	_vm = vm;
37 	// _musicVolume here sets a percentage to be appied on the specified track volume
38 	// before sending it to the audio player
39 	// (setting _musicVolume to 100 renders it indifferent)
40 	_musicVolume = BLADERUNNER_ORIGINAL_SETTINGS ? 65 : 100;
41 	reset();
42 }
43 
~Music()44 Music::~Music() {
45 	stop(0u);
46 	while (isPlaying()) {
47 		// wait for the mixer to finish
48 	}
49 
50 #if BLADERUNNER_ORIGINAL_BUGS
51 	_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
52 	_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
53 #else
54 	// probably not really needed, but tidy up anyway
55 	reset();
56 	_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
57 	_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicNext);
58 #endif
59 }
60 
reset()61 void Music::reset() {
62 	_current.name = "";
63 	_next.name = "";
64 	_channel = -1;
65 	_isPlaying = false;
66 	_isPaused = false;
67 	_current.loop = 0;
68 	_isNextPresent = false;
69 	_data = nullptr;
70 	_stream = nullptr;
71 }
72 
play(const Common::String & trackName,int volume,int pan,int32 timeFadeInSeconds,int32 timePlaySeconds,int loop,int32 timeFadeOutSeconds)73 bool Music::play(const Common::String &trackName, int volume, int pan, int32 timeFadeInSeconds, int32 timePlaySeconds, int loop, int32 timeFadeOutSeconds) {
74 	//Common::StackLock lock(_mutex);
75 
76 	if (_musicVolume <= 0) {
77 		return false;
78 	}
79 
80 	int volumeAdjusted = volume * _musicVolume / 100;
81 	int volumeStart = volumeAdjusted;
82 	if (timeFadeInSeconds > 0) {
83 		volumeStart = 1;
84 	}
85 
86 	// Queuing mechanism:
87 	// if a music track is already playing, then:
88 	//    if the requested track is a different track
89 	//       queue it as "next", to play after the current one.
90 	//       However, if a "next" track already exists,
91 	//       then stop the _current track after 2 seconds (force stop current playing track)
92 	//       Also the previous "next" track still gets replaced by the new requested one.
93 	// 	     This can be best test at Animoid Row, Hawker's Circle moving from Izo's Pawn Shop to the Bar.
94 	//    if the requested track is the same as the currently playing,
95 	//       update the loop int value of the _current to the new one
96 	//       and adjust its fadeIn and balance/pan
97 	//    In these both cases above, the _current track is not (yet) changed.
98 	if (isPlaying()) {
99 		if (!_current.name.equalsIgnoreCase(trackName)) {
100 			_next.name = trackName;
101 			_next.volume = volume; // Don't store the adjustedVolume - This is a "target" value for the volume
102 			_next.pan = pan;       // This is a "target" value for the pan (balance)
103 			_next.timeFadeInSeconds = timeFadeInSeconds;
104 			_next.timePlaySeconds = timePlaySeconds;
105 			_next.loop = loop;
106 			_next.timeFadeOutSeconds = timeFadeOutSeconds;
107 			if (_isNextPresent) {
108 				stop(2u);
109 			}
110 			_isNextPresent = true;
111 		} else {
112 			_current.loop = loop;
113 			if (timeFadeInSeconds < 0) {
114 				timeFadeInSeconds = 0;
115 			}
116 			adjustVolume(volumeAdjusted, timeFadeInSeconds);
117 			adjustPan(pan, timeFadeInSeconds);
118 		}
119 		return true;
120 	}
121 
122 	// If we reach here, there is no music track currently playing
123 	// So we load it from the game's resources
124 	_data = getData(trackName);
125 	if (_data == nullptr) {
126 		return false;
127 	}
128 	_stream = new AudStream(_data);
129 
130 	_isNextPresent = false;
131 	uint32 trackLengthInMillis = _stream->getLength();
132 
133 	uint32 secondToStart = 0;
134 	// loop > 1 can only happen in restored content, so no need to check for _vm->_cutContent explicitly here
135 	if (loop > 1 && trackLengthInMillis > 0) {
136 		// start at some point within the first half of the track
137 		if (timePlaySeconds > 0 && trackLengthInMillis/1000 > (uint32)timePlaySeconds) {
138 			secondToStart = _vm->_rnd.getRandomNumberRng(0, MIN(trackLengthInMillis/2000, (trackLengthInMillis/1000 - (uint32)timePlaySeconds)));
139 		} else if (timeFadeOutSeconds >= 0 && trackLengthInMillis/1000 > (uint32)timeFadeOutSeconds) {
140 			secondToStart = _vm->_rnd.getRandomNumberRng(0, MIN(trackLengthInMillis/2000, (trackLengthInMillis/1000 - (uint32)timeFadeOutSeconds)));
141 		}
142 	}
143 	if (secondToStart > 0) {
144 		 _stream->startAtSecond(secondToStart);
145 	}
146 
147 	_channel = _vm->_audioMixer->playMusic(_stream, volumeStart, mixerChannelEnded, this, trackLengthInMillis);
148 	if (_channel < 0) {
149 		delete _stream;
150 		_stream = nullptr;
151 		delete[] _data;
152 		_data = nullptr;
153 
154 		return false;
155 	}
156 	if (timeFadeInSeconds > 0) {
157 		adjustVolume(volumeAdjusted, timeFadeInSeconds);
158 	}
159 	_current.name = trackName;
160 
161 	if (timePlaySeconds > 0) {
162 		// Removes any previous fadeout timer and installs a new one.
163 		// Uses the timeFadeOutSeconds value (see Music::fadeOut())
164 #if BLADERUNNER_ORIGINAL_BUGS
165 		_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
166 		_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, timePlaySeconds * 1000 * 1000, this, "BladeRunnerMusicFadeoutTimer");
167 #else
168 		_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
169 		_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicFadeOut, timePlaySeconds * 1000u);
170 #endif //BLADERUNNER_ORIGINAL_BUGS
171 	} else if (timeFadeOutSeconds > 0) {
172 #if BLADERUNNER_ORIGINAL_BUGS
173 		_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
174 		_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, (trackLengthInMillis - timeFadeOutSeconds * 1000) * 1000, this, "BladeRunnerMusicFadeoutTimer");
175 #else
176 		_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
177 		_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicFadeOut, (trackLengthInMillis - timeFadeOutSeconds * 1000u));
178 #endif //BLADERUNNER_ORIGINAL_BUGS
179 	}
180 	_isPlaying = true;
181 	_current.volume = volume; // Don't store the adjustedVolume - This is a "target" value for the volume
182 	_current.pan = pan;       // This is a "target" value for the pan (balance)
183 	_current.timeFadeInSeconds = timeFadeInSeconds;
184 	_current.timePlaySeconds = timePlaySeconds;
185 	_current.loop = loop;
186 	// loop == kMusicLoopPlayOnceRandomStart can only happen in restored content, so no need to check for _vm->_cutContent explicitly here
187 	if (_current.loop == kMusicLoopRepeatRandomStart) {
188 		// loop value to store (and use in next loop) should be kMusicLoopRepeat
189 		_current.loop = kMusicLoopRepeat;
190 	}
191 	_current.timeFadeOutSeconds = timeFadeOutSeconds;
192 	return true;
193 }
194 
stop(uint32 delaySeconds)195 void Music::stop(uint32 delaySeconds) {
196 	Common::StackLock lock(_mutex);
197 
198 	if (_channel < 0) {
199 		return;
200 	}
201 
202 #if !BLADERUNNER_ORIGINAL_BUGS
203 	// In original game, on queued music was not removed and it started playing after actor left the scene
204 	_isNextPresent = false;
205 #endif
206 
207 	_current.loop = 0;
208 	_vm->_audioMixer->stop(_channel, 60u * delaySeconds);
209 }
210 
adjust(int volume,int pan,uint32 delaySeconds)211 void Music::adjust(int volume, int pan, uint32 delaySeconds) {
212 	if (volume != -1) {
213 		adjustVolume(_musicVolume * volume/ 100, delaySeconds);
214 	}
215 	// -101 is used as a special value to skip adjusting pan
216 	if (pan != -101) {
217 		adjustPan(pan, delaySeconds);
218 	}
219 }
220 
isPlaying()221 bool Music::isPlaying() {
222 	return _channel >= 0 && _isPlaying;
223 }
224 
setVolume(int volume)225 void Music::setVolume(int volume) {
226 	_musicVolume = volume;
227 	if (volume <= 0) {
228 		stop(2u);
229 	} else if (isPlaying()) {
230 		// delay is 2 seconds (multiplied by 60u as expected by AudioMixer::adjustVolume())
231 		_vm->_audioMixer->adjustVolume(_channel, _musicVolume * _current.volume / 100, 120u);
232 	}
233 }
234 
getVolume()235 int Music::getVolume() {
236 	return _musicVolume;
237 }
238 
playSample()239 void Music::playSample() {
240 	if (!isPlaying()) {
241 		play(_vm->_gameInfo->getSfxTrack(kSfxMUSVOL8), 100, 0, 2, -1, kMusicLoopPlayOnce, 3);
242 	}
243 }
244 
save(SaveFileWriteStream & f)245 void Music::save(SaveFileWriteStream &f) {
246 	f.writeBool(_isNextPresent);
247 	f.writeBool(_isPlaying);
248 	f.writeBool(_isPaused);
249 	f.writeStringSz(_current.name, 13);
250 	f.writeInt(_current.volume);
251 	f.writeInt(_current.pan);
252 	f.writeInt(_current.timeFadeInSeconds);
253 	f.writeInt(_current.timePlaySeconds);
254 	f.writeInt(_current.loop);
255 	f.writeInt(_current.timeFadeOutSeconds);
256 	f.writeStringSz(_next.name, 13);
257 	f.writeInt(_next.volume);
258 	f.writeInt(_next.pan);
259 	f.writeInt(_next.timeFadeInSeconds);
260 	f.writeInt(_next.timePlaySeconds);
261 	f.writeInt(_next.loop);
262 	f.writeInt(_next.timeFadeOutSeconds);
263 }
264 
load(SaveFileReadStream & f)265 void Music::load(SaveFileReadStream &f) {
266 	_isNextPresent = f.readBool();
267 	_isPlaying = f.readBool();
268 	_isPaused = f.readBool();
269 	_current.name = f.readStringSz(13);
270 	_current.volume = f.readInt();
271 	_current.pan = f.readInt();
272 	_current.timeFadeInSeconds = f.readInt();
273 	_current.timePlaySeconds = f.readInt();
274 	_current.loop = f.readInt();
275 	_current.timeFadeOutSeconds = f.readInt();
276 	_next.name = f.readStringSz(13);
277 	_next.volume = f.readInt();
278 	_next.pan = f.readInt();
279 	_next.timeFadeInSeconds = f.readInt();
280 	_next.timePlaySeconds = f.readInt();
281 	_next.loop = f.readInt();
282 	_next.timeFadeOutSeconds = f.readInt();
283 
284 	stop(2u);
285 	if (_isPlaying) {
286 		if (_channel == -1) {
287 			play(_current.name,
288 				_current.volume,
289 				_current.pan,
290 				_current.timeFadeInSeconds,
291 				_current.timePlaySeconds,
292 				_current.loop,
293 				_current.timeFadeOutSeconds);
294 		} else {
295 			_isNextPresent = true;
296 			_next.name = _current.name;
297 			_next.volume = _current.volume;
298 			_next.pan = _current.pan;
299 			_next.timeFadeInSeconds = _current.timeFadeInSeconds;
300 			_next.timePlaySeconds = _current.timePlaySeconds;
301 			_next.loop = _current.loop;
302 			_next.timeFadeOutSeconds = _current.timeFadeOutSeconds;
303 		}
304 	}
305 }
306 
adjustVolume(int volume,uint32 delaySeconds)307 void Music::adjustVolume(int volume, uint32 delaySeconds) {
308 	// adjustVolume takes an "adjusted volume" value as an argument
309 	// We don't store that as target _current.volume - play() stores the proper value
310 	if (_channel >= 0) {
311 		_vm->_audioMixer->adjustVolume(_channel, volume, 60u * delaySeconds);
312 	}
313 }
314 
adjustPan(int pan,uint32 delaySeconds)315 void Music::adjustPan(int pan, uint32 delaySeconds) {
316 	_current.pan = pan;
317 	if (_channel >= 0) {
318 		_vm->_audioMixer->adjustPan(_channel, pan, 60u * delaySeconds);
319 	}
320 }
321 
ended()322 void Music::ended() {
323 	Common::StackLock lock(_mutex);
324 
325 	_isPlaying = false;
326 	_channel = -1;
327 
328 	delete[] _data;
329 	_data = nullptr;
330 
331 	// The timer that checks for a next track is started here.
332 	// When it expires, it should check for queued music (_isNextPresent) or looping music (_current.loop)
333 #if BLADERUNNER_ORIGINAL_BUGS
334 	_vm->getTimerManager()->installTimerProc(timerCallbackNext, 100 * 1000, this, "BladeRunnerMusicNextTimer");
335 #else
336 	_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicNext, 100u);
337 #endif // BLADERUNNER_ORIGINAL_BUGS
338 }
339 
fadeOut()340 void Music::fadeOut() {
341 #if BLADERUNNER_ORIGINAL_BUGS
342 	_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
343 #else
344 	_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
345 #endif // BLADERUNNER_ORIGINAL_BUGS
346 	if (_channel >= 0) {
347 		if (_current.timeFadeOutSeconds < 0) {
348 			_current.timeFadeOutSeconds = 0;
349 		}
350 		_vm->_audioMixer->stop(_channel, 60u * _current.timeFadeOutSeconds);
351 	}
352 }
353 
354 #if BLADERUNNER_ORIGINAL_BUGS
timerCallbackFadeOut(void * refCon)355 void Music::timerCallbackFadeOut(void *refCon) {
356 	((Music *)refCon)->fadeOut();
357 }
358 
timerCallbackNext(void * refCon)359 void Music::timerCallbackNext(void *refCon) {
360 	((Music *)refCon)->next();
361 }
362 
next()363 void Music::next() {
364 	_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
365 	if (_isNextPresent) {
366 		if (_isPaused) {
367 			// postpone loading the next track (re-arm the BladeRunnerMusicNextTimer timer)
368 			_vm->getTimerManager()->installTimerProc(timerCallbackNext, 2000 * 1000, this, "BladeRunnerMusicNextTimer");
369 		} else {
370 			play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeInSeconds, _next.timePlaySeconds, _next.loop, _next.timeFadeOutSeconds);
371 		}
372 		// This should not come after a possible call to play() which could swap the "_current" for the new (_next) track
373 		// Setting the loop to 0 here, would then make the new track non-looping, even if it is supposed to be looping
374 		_current.loop = 0;
375 	} else if (_current.loop) {
376 		play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeInSeconds, _current.timePlaySeconds, _current.loop, _current.timeFadeOutSeconds);
377 	}
378 }
379 #else
next()380 void Music::next() {
381 	_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicNext);
382 	if (_isNextPresent) {
383 		if (_isPaused) {
384 			// postpone loading the next track (re-arm the BladeRunnerMusicNextTimer timer)
385 			_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicNext, 2000u);
386 			_current.loop = 0;
387 		} else {
388 			_current.loop = 0;
389 			play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeInSeconds, _next.timePlaySeconds, _next.loop, _next.timeFadeOutSeconds);
390 		}
391 	} else if (_current.loop) {
392 		play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeInSeconds, _current.timePlaySeconds, _current.loop, _current.timeFadeOutSeconds);
393 	}
394 }
395 #endif // BLADERUNNER_ORIGINAL_BUGS
396 
mixerChannelEnded(int channel,void * data)397 void Music::mixerChannelEnded(int channel, void *data) {
398 	if (data != nullptr) {
399 		((Music *)data)->ended();
400 	}
401 }
402 
getData(const Common::String & name)403 byte *Music::getData(const Common::String &name) {
404 	// NOTE: This is not part original game, loading data is done in the mixer and its using buffering to limit memory usage
405 	Common::SeekableReadStream *stream = _vm->getResourceStream(name);
406 
407 	if (stream == nullptr) {
408 		return nullptr;
409 	}
410 
411 	uint32 size = stream->size();
412 	byte *data = new byte[size];
413 	stream->read(data, size);
414 
415 	delete stream;
416 
417 	return data;
418 }
419 
420 } // End of namespace BladeRunner
421