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