scummvm/engines/groovie/music.cpp

315 lines
7.9 KiB
C++
Raw Normal View History

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "groovie/music.h"
#include "groovie/resource.h"
#include "sound/audiocd.h"
namespace Groovie {
MusicPlayer::MusicPlayer(GroovieEngine *vm) :
_vm(vm), _midiParser(NULL), _data(NULL), _driver(NULL),
_backgroundFileRef(0), _gameVolume(100), _prevCDtrack(0) {
// Create the parser
_midiParser = MidiParser::createParser_XMIDI();
// Create the driver
int driver = detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
_driver = createMidi(driver);
this->open();
// Initialize the channel volumes
for (int i = 0; i < 0x10; i++) {
_chanVolumes[i] = 0x7F;
}
// Set the parser's driver
_midiParser->setMidiDriver(this);
// Set the timer rate
_midiParser->setTimerRate(_driver->getBaseTempo());
}
MusicPlayer::~MusicPlayer() {
_driver->setTimerCallback(NULL, NULL);
Common::StackLock lock(_mutex);
// Unload the parser
unload();
delete _midiParser;
// Unload the MIDI Driver
_driver->close();
delete _driver;
}
void MusicPlayer::playSong(uint16 fileref) {
Common::StackLock lock(_mutex);
// Play the referenced file once
play(fileref, false);
}
void MusicPlayer::setBackgroundSong(uint16 fileref) {
Common::StackLock lock(_mutex);
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Changing the background song: %04X", fileref);
_backgroundFileRef = fileref;
}
void MusicPlayer::playCD(uint8 track) {
int startms = 0;
// Stop the MIDI playback
unload();
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Playing CD track %d", track);
if (track == 3) {
// This is the credits song, start at 23:20
startms = 1400000;
// TODO: If we want to play it directly from the CD, we should decrement
// the song number (it's track 2 on the 2nd CD)
} else if ((track == 98) && (_prevCDtrack == 3)) {
// Track 98 is used as a hack to stop the credits song
AudioCD.stop();
return;
}
// Save the playing track in order to be able to stop the credits song
_prevCDtrack = track;
// Wait until the CD stops playing the current song
AudioCD.updateCD();
while (AudioCD.isPlaying()) {
// Wait a bit and try again
_vm->_system->delayMillis(100);
AudioCD.updateCD();
}
// Play the track starting at the requested offset (1000ms = 75 frames)
AudioCD.play(track - 1, 1, startms * 75 / 1000, 0);
}
void MusicPlayer::setUserVolume(uint16 volume) {
Common::StackLock lock(_mutex);
// Save the new user volume
_userVolume = volume;
if (_userVolume > 0x100)
_userVolume = 0x100;
// Apply it to all the channels
for (int i = 0; i < 0x10; i++) {
updateChanVolume(i);
}
//FIXME: AdlibPercussionChannel::controlChange() is empty
//(can't set the volume for the percusion channel)
}
void MusicPlayer::setGameVolume(uint16 volume, uint16 time) {
Common::StackLock lock(_mutex);
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Setting game volume from %d to %d in %dms", _gameVolume, volume, time);
// Save the start parameters of the fade
_fadingStartTime = _vm->_system->getMillis();
_fadingStartVolume = _gameVolume;
_fadingDuration = time;
// Save the new game volume
_fadingEndVolume = volume;
if (_fadingEndVolume > 100)
_fadingEndVolume = 100;
}
void MusicPlayer::applyFading() {
Common::StackLock lock(_mutex);
// Calculate the passed time
uint32 time = _vm->_system->getMillis() - _fadingStartTime;
if (time >= _fadingDuration) {
// If we were fading to 0, stop the playback and restore the volume
if (_fadingEndVolume == 0) {
unload();
_fadingEndVolume = 100;
}
// Set the end volume
_gameVolume = _fadingEndVolume;
} else {
// Calculate the interpolated volume for the current time
_gameVolume = (_fadingStartVolume * (_fadingDuration - time) +
_fadingEndVolume * time) / _fadingDuration;
}
// Apply the new volume to all the channels
for (int i = 0; i < 0x10; i++) {
updateChanVolume(i);
}
}
void MusicPlayer::updateChanVolume(byte channel) {
// Generate a MIDI Control change message for the volume
uint32 b = 0x7B0;
// Specify the channel
b |= (channel & 0xF);
// Scale by the user and game volumes
uint32 val = (_chanVolumes[channel] * _userVolume * _gameVolume) / 0x100 / 100;
val &= 0x7F;
// Send it to the driver
_driver->send(b | (val << 16));
}
bool MusicPlayer::play(uint16 fileref, bool loop) {
// Unload the previous song
unload();
// Set the looping option
_midiParser->property(MidiParser::mpAutoLoop, loop);
// Load the new file
return load(fileref);
}
bool MusicPlayer::load(uint16 fileref) {
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);
// Open the song resource
Common::SeekableReadStream *xmidiFile = _vm->_resMan->open(fileref);
if (!xmidiFile) {
error("Groovie::Music: Couldn't resource 0x%04X", fileref);
return false;
}
// Read the whole file to memory
int length = xmidiFile->size();
_data = new byte[length];
xmidiFile->read(_data, length);
delete xmidiFile;
// Start parsing the data
if (!_midiParser->loadMusic(_data, length)) {
error("Groovie::Music: Invalid XMI file");
return false;
}
// Activate the timer source
_driver->setTimerCallback(this, &onTimer);
return true;
}
void MusicPlayer::unload() {
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Stopping the playback");
// Unload the parser
_midiParser->unloadMusic();
// Unload the xmi file
delete[] _data;
_data = NULL;
}
int MusicPlayer::open() {
// Don't ever call open without first setting the output driver!
if (!_driver)
return 255;
int ret = _driver->open();
if (ret)
return ret;
return 0;
}
void MusicPlayer::close() {}
void MusicPlayer::send(uint32 b) {
if ((b & 0xFFF0) == 0x07B0) { // Volume change
// Save the specific channel volume
byte chan = b & 0xF;
_chanVolumes[chan] = (b >> 16) & 0x7F;
// Send the updated value
updateChanVolume(chan);
return;
}
_driver->send(b);
}
void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
switch (type) {
case 0x2F:
// End of Track, play the background song
debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: End of song");
if (_backgroundFileRef) {
play(_backgroundFileRef, true);
}
break;
default:
_driver->metaEvent(type, data, length);
break;
}
}
void MusicPlayer::onTimer(void *refCon) {
MusicPlayer *music = (MusicPlayer *)refCon;
Common::StackLock lock(music->_mutex);
// Apply the game volume fading
if (music->_gameVolume != music->_fadingEndVolume) {
// Apply the next step of the fading
music->applyFading();
}
// TODO: We really only need to call this while music is playing.
music->_midiParser->onTimer();
}
void MusicPlayer::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
_driver->setTimerCallback(timer_param, timer_proc);
}
uint32 MusicPlayer::getBaseTempo(void) {
return _driver->getBaseTempo();
}
MidiChannel *MusicPlayer::allocateChannel() {
return _driver->allocateChannel();
}
MidiChannel *MusicPlayer::getPercussionChannel() {
return _driver->getPercussionChannel();
}
} // End of Groovie namespace