scummvm/engines/lure/sound.cpp
2010-01-25 00:39:46 +00:00

747 lines
20 KiB
C++

/* 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 "lure/sound.h"
#include "lure/game.h"
#include "lure/memory.h"
#include "lure/res.h"
#include "lure/room.h"
#include "common/algorithm.h"
#include "common/config-manager.h"
#include "common/endian.h"
#include "sound/midiparser.h"
DECLARE_SINGLETON(Lure::SoundManager);
namespace Lure {
//#define SOUND_CROP_CHANNELS
SoundManager::SoundManager() {
Disk &disk = Disk::getReference();
_soundMutex = g_system->createMutex();
int index;
_descs = disk.getEntry(SOUND_DESC_RESOURCE_ID);
_numDescs = _descs->size() / sizeof(SoundDescResource);
_soundData = NULL;
_paused = false;
MidiDriverType midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
_isRoland = midiDriver != MD_ADLIB;
_nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
Common::set_to(_channelsInUse, _channelsInUse + NUM_CHANNELS, false);
_driver = MidiDriver::createMidi(midiDriver);
int statusCode = _driver->open();
if (statusCode) {
warning("Sound driver returned error code %d", statusCode);
_driver = NULL;
} else {
if (_nativeMT32)
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
for (index = 0; index < NUM_CHANNELS; ++index) {
_channelsInner[index].midiChannel = _driver->allocateChannel();
/* 90 is power on default for midi compliant devices */
_channelsInner[index].volume = 90;
}
}
}
SoundManager::~SoundManager() {
if (_driver)
_driver->setTimerCallback(this, NULL);
removeSounds();
_activeSounds.clear();
g_system->lockMutex(_soundMutex);
_playingSounds.clear();
g_system->unlockMutex(_soundMutex);
delete _descs;
delete _soundData;
if (_driver) {
_driver->close();
delete _driver;
}
_driver = NULL;
g_system->deleteMutex(_soundMutex);
}
void SoundManager::saveToStream(Common::WriteStream *stream) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::saveToStream");
SoundListIterator i;
for (i = _activeSounds.begin(); i != _activeSounds.end(); ++i) {
SoundDescResource *rec = (*i).get();
stream->writeByte(rec->soundNumber);
}
stream->writeByte(0xff);
}
void SoundManager::loadFromStream(Common::ReadStream *stream) {
// Stop any existing sounds playing
killSounds();
// Load any playing sounds
uint8 soundNumber;
while ((soundNumber = stream->readByte()) != 0xff) {
uint8 soundIndex = descIndexOf(soundNumber);
if (soundIndex != 0xff) {
// Make sure that the sound is allowed to be restored
SoundDescResource &rec = soundDescs()[soundIndex];
if ((rec.flags & SF_RESTORE) != 0)
// Requeue the sound for playing
addSound(soundIndex, false);
}
}
}
void SoundManager::loadSection(uint16 sectionId) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::loadSection = %xh", sectionId);
killSounds();
if (_soundData) {
delete _soundData;
_driver->setTimerCallback(this, NULL);
}
_soundData = Disk::getReference().getEntry(sectionId);
_soundsTotal = *_soundData->data();
_driver->setTimerCallback(this, &onTimer);
}
void SoundManager::bellsBodge() {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::bellsBodge");
Resources &res = Resources::getReference();
Room &room = Room::getReference();
RoomData *roomData = res.getRoom(room.roomNumber());
if (roomData && roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {
res.fieldList().setField(AREA_FLAG, roomData->areaFlag);
switch (roomData->areaFlag) {
case 0:
killSound(1);
break;
case 1:
addSound(2);
killSound(33);
break;
case 2:
setVolume(0, 15);
// Deliberate fall through
default:
killSound(1);
break;
}
}
}
void SoundManager::killSounds() {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::killSounds");
// Stop the player playing all sounds
musicInterface_KillAll();
// Clear the active sounds
_activeSounds.clear();
Common::set_to(_channelsInUse, _channelsInUse + NUM_CHANNELS, false);
}
void SoundManager::addSound(uint8 soundIndex, bool tidyFlag) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound index=%d", soundIndex);
Game &game = Game::getReference();
if (tidyFlag)
tidySounds();
if (game.preloadFlag())
// Don't add a sound if in room preloading
return;
SoundDescResource &rec = soundDescs()[soundIndex];
int numChannels;
if (_isRoland)
numChannels = (rec.numChannels & 3) + 1;
else
numChannels = ((rec.numChannels >> 2) & 3) + 1;
int channelCtr, channelCtr2;
for (channelCtr = 0; channelCtr <= (NUM_CHANNELS - numChannels); ++channelCtr) {
for (channelCtr2 = 0; channelCtr2 < numChannels; ++channelCtr2)
if (_channelsInUse[channelCtr + channelCtr2])
break;
if (channelCtr2 == numChannels)
break;
}
if (channelCtr > (NUM_CHANNELS - numChannels)) {
// No channels free
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound - no channels free");
return;
}
// Mark the found channels as in use
Common::set_to(_channelsInUse+channelCtr, _channelsInUse+channelCtr + numChannels, true);
SoundDescResource *newEntry = new SoundDescResource();
newEntry->soundNumber = rec.soundNumber;
newEntry->channel = channelCtr;
newEntry->numChannels = numChannels;
newEntry->flags = rec.flags;
if (_isRoland)
newEntry->volume = rec.volume;
else /* resource volumes do not seem to work well with our AdLib emu */
newEntry->volume = 240; /* 255 causes clipping with AdLib */
_activeSounds.push_back(SoundList::value_type(newEntry));
musicInterface_Play(rec.soundNumber, channelCtr, numChannels);
musicInterface_SetVolume(channelCtr, newEntry->volume);
}
void SoundManager::addSound2(uint8 soundIndex) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound2 index=%d", soundIndex);
tidySounds();
if (soundIndex == 6) {
// Chinese torture
stopSound(6); // sometimes its still playing when restarted
addSound(6, false);
} else {
SoundDescResource &descEntry = soundDescs()[soundIndex];
SoundDescResource *rec = findSound(descEntry.soundNumber);
if (rec == NULL)
// Sound isn't active, so go and add it
addSound(soundIndex, false);
}
}
void SoundManager::stopSound(uint8 soundIndex) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::stopSound index=%d", soundIndex);
SoundDescResource &rec = soundDescs()[soundIndex];
musicInterface_Stop(rec.soundNumber);
}
void SoundManager::killSound(uint8 soundNumber) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::stopSound soundNumber=%d", soundNumber);
musicInterface_Stop(soundNumber);
}
void SoundManager::setVolume(uint8 soundNumber, uint8 volume) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::setVolume soundNumber=%d, volume=%d",
soundNumber, volume);
musicInterface_TidySounds();
SoundDescResource *entry = findSound(soundNumber);
if (entry)
musicInterface_SetVolume(entry->channel, volume);
}
uint8 SoundManager::descIndexOf(uint8 soundNumber) {
SoundDescResource *rec = soundDescs();
for (uint8 index = 0; index < _numDescs; ++index, ++rec) {
if (rec->soundNumber == soundNumber)
return index;
}
return 0xff; // Couldn't find entry
}
// Used to sync the volume for all channels with the Config Manager
//
void SoundManager::syncSounds() {
Game &game = Game::getReference();
musicInterface_TidySounds();
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
if ((*i)->isMusic())
(*i)->setVolume(game.musicVolume());
else
(*i)->setVolume(game.sfxVolume());
}
g_system->unlockMutex(_soundMutex);
}
SoundDescResource *SoundManager::findSound(uint8 soundNumber) {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::findSound soundNumber=%d", soundNumber);
SoundListIterator i;
for (i = _activeSounds.begin(); i != _activeSounds.end(); ++i) {
SoundDescResource *rec = (*i).get();
if (rec->soundNumber == soundNumber) {
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "SoundManager::findSound - sound found");
return rec;
}
}
// Signal that sound wasn't found
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "SoundManager::findSound - sound not found");
return NULL;
}
void SoundManager::tidySounds() {
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "SoundManager::tidySounds");
SoundListIterator i = _activeSounds.begin();
while (i != _activeSounds.end()) {
SoundDescResource *rec = (*i).get();
if (musicInterface_CheckPlaying(rec->soundNumber))
// Still playing, so move to next entry
++i;
else {
// Mark the channels that it used as now being free
Common::set_to(_channelsInUse+rec->channel, _channelsInUse+rec->channel+rec->numChannels, false);
i = _activeSounds.erase(i);
}
}
}
void SoundManager::removeSounds() {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::removeSounds");
bellsBodge();
SoundListIterator i = _activeSounds.begin();
while (i != _activeSounds.end()) {
SoundDescResource *rec = (*i).get();
if ((rec->flags & SF_IN_USE) != 0)
musicInterface_Stop(rec->soundNumber);
++i;
}
}
void SoundManager::restoreSounds() {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::restoreSounds");
SoundListIterator i = _activeSounds.begin();
while (i != _activeSounds.end()) {
SoundDescResource *rec = (*i).get();
if ((rec->numChannels != 0) && ((rec->flags & SF_RESTORE) != 0)) {
Common::set_to(_channelsInUse+rec->channel, _channelsInUse+rec->channel+rec->numChannels, true);
musicInterface_Play(rec->soundNumber, rec->channel, rec->numChannels);
musicInterface_SetVolume(rec->channel, rec->volume);
}
++i;
}
}
void SoundManager::fadeOut() {
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::fadeOut");
// Fade out all the active sounds
musicInterface_TidySounds();
bool inProgress = true;
while (inProgress)
{
inProgress = false;
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
MidiMusic *music = (*i).get();
if (music->getVolume() > 0) {
inProgress = true;
music->setVolume(music->getVolume() >= 10 ? (music->getVolume() - 10) : 0);
}
}
g_system->unlockMutex(_soundMutex);
g_system->delayMillis(10);
}
// Kill all the sounds
musicInterface_KillAll();
}
/*------------------------------------------------------------------------*/
// musicInterface_Play
// Play the specified sound
void SoundManager::musicInterface_Play(uint8 soundNumber, uint8 channelNumber, uint8 numChannels) {
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_Play soundNumber=%d, channel=%d",
soundNumber, channelNumber);
Game &game = Game::getReference();
if (!_soundData)
error("Sound section has not been specified");
uint8 soundNum = soundNumber & 0x7f;
if (soundNum > _soundsTotal)
error("Invalid sound index %d requested", soundNum);
if (_driver == NULL)
// Only play sounds if a sound driver is active
return;
bool isMusic = (soundNumber & 0x80) != 0;
if (!game.soundFlag())
// Don't play sounds if sound is turned off
return;
uint32 dataOfs = READ_LE_UINT32(_soundData->data() + soundNum * 4 + 2);
uint8 *soundStart = _soundData->data() + dataOfs;
uint32 dataSize;
if (soundNum == _soundsTotal - 1)
dataSize = _soundData->size() - dataOfs;
else {
uint32 nextDataOfs = READ_LE_UINT32(_soundData->data() + (soundNum + 1) * 4 + 2);
dataSize = nextDataOfs - dataOfs;
}
g_system->lockMutex(_soundMutex);
MidiMusic *sound = new MidiMusic(_driver, _channelsInner, channelNumber, soundNum,
isMusic, numChannels, soundStart, dataSize);
_playingSounds.push_back(MusicList::value_type(sound));
g_system->unlockMutex(_soundMutex);
}
// musicInterface_Stop
// Stops the specified sound from playing
void SoundManager::musicInterface_Stop(uint8 soundNumber) {
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_Stop soundNumber=%d", soundNumber);
musicInterface_TidySounds();
uint8 soundNum = soundNumber & 0x7f;
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
MidiMusic *music = (*i).get();
if (music->soundNumber() == soundNum) {
_playingSounds.erase(i);
break;
}
}
g_system->unlockMutex(_soundMutex);
}
// musicInterface_CheckPlaying
// Returns true if a sound is still playing
bool SoundManager::musicInterface_CheckPlaying(uint8 soundNumber) {
debugC(ERROR_DETAILED, kLureDebugSounds, "musicInterface_CheckPlaying soundNumber=%d", soundNumber);
musicInterface_TidySounds();
uint8 soundNum = soundNumber & 0x7f;
bool result = false;
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
MidiMusic *music = (*i).get();
if (music->soundNumber() == soundNum) {
result = true;
break;
}
}
g_system->unlockMutex(_soundMutex);
return result;
}
// musicInterface_SetVolume
// Sets the volume of the specified channel
void SoundManager::musicInterface_SetVolume(uint8 channelNum, uint8 volume) {
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_SetVolume channel=%d, volume=%d",
channelNum, volume);
musicInterface_TidySounds();
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
MidiMusic *music = (*i).get();
if (music->channelNumber() == channelNum)
music->setVolume(volume);
}
g_system->unlockMutex(_soundMutex);
}
// musicInterface_KillAll
// Stops all currently active sounds playing
void SoundManager::musicInterface_KillAll() {
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_KillAll");
musicInterface_TidySounds();
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
MidiMusic *music = (*i).get();
music->stopMusic();
}
_playingSounds.clear();
_activeSounds.clear();
g_system->unlockMutex(_soundMutex);
}
// musicInterface_ContinuePlaying
// The original player used this method for any sound managers needing continual calls
void SoundManager::musicInterface_ContinuePlaying() {
// No implementation needed
}
// musicInterface_TrashReverb
// Trashes reverb on actively playing sounds
void SoundManager::musicInterface_TrashReverb() {
// TODO: Handle support for trashing reverb
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_TrashReverb");
}
// musicInterface_KillAll
// Scans all the active sounds and deallocates any objects that have finished playing
void SoundManager::musicInterface_TidySounds() {
debugC(ERROR_DETAILED, kLureDebugSounds, "musicInterface_TidySounds");
g_system->lockMutex(_soundMutex);
MusicListIterator i = _playingSounds.begin();
while (i != _playingSounds.end()) {
MidiMusic *music = (*i).get();
if (!music->isPlaying())
i = _playingSounds.erase(i);
else
++i;
}
g_system->unlockMutex(_soundMutex);
}
void SoundManager::onTimer(void *data) {
SoundManager *snd = (SoundManager *) data;
snd->doTimer();
}
void SoundManager::doTimer() {
if (_paused)
return;
g_system->lockMutex(_soundMutex);
MusicListIterator i;
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
MidiMusic *music = (*i).get();
if (music->isPlaying())
music->onTimer();
}
g_system->unlockMutex(_soundMutex);
}
/*------------------------------------------------------------------------*/
MidiMusic::MidiMusic(MidiDriver *driver, ChannelEntry channels[NUM_CHANNELS],
uint8 channelNum, uint8 soundNum, bool isMus, uint8 numChannels, void *soundData, uint32 size) {
_driver = driver;
_channels = channels;
_soundNumber = soundNum;
_channelNumber = channelNum;
_isMusic = isMus;
_numChannels = numChannels;
_volume = 0;
for (int i = 0; i < _numChannels; ++i) {
/* 90 is power on default for midi compliant devices */
_channels[_channelNumber + i].volume = 90;
}
if (_isMusic)
setVolume(ConfMan.getInt("music_volume"));
else
setVolume(ConfMan.getInt("sfx_volume"));
_passThrough = false;
_parser = MidiParser::createParser_SMF();
_parser->setMidiDriver(this);
_parser->setTimerRate(_driver->getBaseTempo());
this->open();
_soundData = (uint8 *) soundData;
_soundSize = size;
// Check whether the music data is compressed - if so, decompress it for the duration
// of playing the sound
_decompressedSound = NULL;
if ((*_soundData == 'C') || (*_soundData == 'c')) {
uint32 packedSize = size - 0x201;
_decompressedSound = Memory::allocate(packedSize * 2);
uint16 *data = (uint16 *)(_soundData + 1);
uint16 *dataDest = (uint16 *) _decompressedSound->data();
byte *idx = ((byte *)data) + 0x200;
for (uint i = 0; i < packedSize; i++)
#if defined(SCUMM_NEED_ALIGNMENT)
memcpy(dataDest++, (byte*)((byte*)data + *(idx + i) * sizeof(uint16)), sizeof(uint16));
#else
*dataDest++ = data[*(idx + i)];
#endif
_soundData = _decompressedSound->data() + ((*_soundData == 'c') ? 1 : 0);
_soundSize = _decompressedSound->size();
}
playMusic();
}
MidiMusic::~MidiMusic() {
_parser->unloadMusic();
delete _parser;
this->close();
delete _decompressedSound;
}
void MidiMusic::setVolume(int volume) {
if (volume < 0)
volume = 0;
else if (volume > 255)
volume = 255;
if (_volume == volume)
return;
_volume = volume;
Game &game = Game::getReference();
volume *= _isMusic ? game.musicVolume() : game.sfxVolume();
for (int i = 0; i < _numChannels; ++i) {
if (_channels[_channelNumber + i].midiChannel != NULL)
_channels[_channelNumber + i].midiChannel->volume(
_channels[_channelNumber + i].volume *
volume / 65025);
}
}
void MidiMusic::playMusic() {
debugC(ERROR_DETAILED, kLureDebugSounds, "MidiMusic::PlayMusic playing sound %d", _soundNumber);
_parser->loadMusic(_soundData, _soundSize);
_parser->setTrack(0);
_isPlaying = true;
}
int MidiMusic::open() {
// Don't ever call open without first setting the output driver!
if (!_driver)
return 255;
return 0;
}
void MidiMusic::close() {
}
void MidiMusic::send(uint32 b) {
if (_passThrough) {
_driver->send(b);
return;
}
#ifdef SOUND_CROP_CHANNELS
if ((b & 0xF) >= _numChannels) return;
byte channel = _channelNumber + (byte)(b & 0x0F);
#else
byte channel = _channelNumber + ((byte)(b & 0x0F) % _numChannels);
#endif
if ((channel >= NUM_CHANNELS) || (_channels[channel].midiChannel == NULL))
return;
if ((b & 0xFFF0) == 0x07B0) {
// Adjust volume changes by song and master volume
byte volume = (byte)((b >> 16) & 0x7F);
_channels[channel].volume = volume;
Game &game = Game::getReference();
int master_volume = _isMusic ? game.musicVolume() : game.sfxVolume();
volume = volume * _volume * master_volume / 65025;
b = (b & 0xFF00FFFF) | (volume << 16);
} else if ((b & 0xF0) == 0xC0) {
if (Sound.isRoland() && !Sound.hasNativeMT32()) {
b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
}
}
else if ((b & 0xFFF0) == 0x007BB0) {
// No implementation
}
_channels[channel].midiChannel->send(b);
}
void MidiMusic::metaEvent(byte type, byte *data, uint16 length) {
//Only thing we care about is End of Track.
if (type != 0x2F)
return;
stopMusic();
}
void MidiMusic::onTimer() {
if (_isPlaying)
_parser->onTimer();
}
void MidiMusic::stopMusic() {
debugC(ERROR_DETAILED, kLureDebugSounds, "MidiMusic::stopMusic sound %d", _soundNumber);
_isPlaying = false;
_parser->unloadMusic();
close();
}
} // End of namespace Lure